Merge remote-tracking branch 'goog/upstream-master'.

The following commits were reverted:
840703a Fix update over cellular network on guest account
eaad5d0 Do not merge to AOSP: Fixes the link to brillo-clang-format in CrOS
740efad Reboot even if a system update is not available.

Fixed a few sign compare warnings.
Had to ifdef out 2 SquashfsFilesystemTest because it depends on unsquashfs -m.

Test: update_engine_unittests
Change-Id: I6f4ca5003e78c76064ec60d0797505d8c18d00bf
Merged-In: I6f4ca5003e78c76064ec60d0797505d8c18d00bf
diff --git a/.clang-format b/.clang-format
index 73f786c..f412743 120000
--- a/.clang-format
+++ b/.clang-format
@@ -1 +1 @@
-../../../platform2/common-mk/brillo-clang-format
\ No newline at end of file
+../../build/tools/brillo-clang-format
\ No newline at end of file
diff --git a/Android.bp b/Android.bp
new file mode 100644
index 0000000..c3d164b
--- /dev/null
+++ b/Android.bp
@@ -0,0 +1,8 @@
+// AIDL interface between libupdate_engine and framework.jar
+filegroup {
+    name: "libupdate_engine_aidl",
+    srcs: [
+        "binder_bindings/android/os/IUpdateEngine.aidl",
+        "binder_bindings/android/os/IUpdateEngineCallback.aidl",
+    ],
+}
diff --git a/Android.mk b/Android.mk
index 523ff6f..ddf633d 100644
--- a/Android.mk
+++ b/Android.mk
@@ -14,6 +14,8 @@
 # limitations under the License.
 #
 
+ifneq ($(TARGET_BUILD_PDK),true)
+
 LOCAL_PATH := $(my-dir)
 
 # Default values for the USE flags. Override these USE flags from your product
@@ -82,6 +84,7 @@
 generated_sources_dir := $(call local-generated-sources-dir)
 LOCAL_EXPORT_C_INCLUDE_DIRS := $(generated_sources_dir)/proto/system
 LOCAL_SRC_FILES := $(ue_update_metadata_protos_src_files)
+LOCAL_CFLAGS := -Wall -Werror
 include $(BUILD_HOST_STATIC_LIBRARY)
 
 # Build for the target.
@@ -91,6 +94,7 @@
 generated_sources_dir := $(call local-generated-sources-dir)
 LOCAL_EXPORT_C_INCLUDE_DIRS := $(generated_sources_dir)/proto/system
 LOCAL_SRC_FILES := $(ue_update_metadata_protos_src_files)
+LOCAL_CFLAGS := -Wall -Werror
 include $(BUILD_STATIC_LIBRARY)
 
 # libpayload_consumer (type: static_library)
@@ -101,6 +105,7 @@
     libxz \
     libbz \
     libbspatch \
+    libbrotli \
     libpuffpatch \
     $(ue_update_metadata_protos_exported_static_libraries)
 ue_libpayload_consumer_exported_shared_libraries := \
@@ -147,7 +152,6 @@
 LOCAL_MODULE := libpayload_consumer
 LOCAL_MODULE_CLASS := STATIC_LIBRARIES
 LOCAL_CPP_EXTENSION := .cc
-LOCAL_CLANG := true
 LOCAL_CFLAGS := $(ue_common_cflags)
 LOCAL_CPPFLAGS := $(ue_common_cppflags)
 LOCAL_LDFLAGS := $(ue_common_ldflags)
@@ -171,7 +175,6 @@
 LOCAL_MODULE := libpayload_consumer
 LOCAL_MODULE_CLASS := STATIC_LIBRARIES
 LOCAL_CPP_EXTENSION := .cc
-LOCAL_CLANG := true
 LOCAL_CFLAGS := $(ue_common_cflags)
 LOCAL_CPPFLAGS := $(ue_common_cppflags)
 LOCAL_LDFLAGS := $(ue_common_ldflags)
@@ -207,7 +210,6 @@
 LOCAL_MODULE := libupdate_engine_boot_control
 LOCAL_MODULE_CLASS := STATIC_LIBRARIES
 LOCAL_CPP_EXTENSION := .cc
-LOCAL_CLANG := true
 LOCAL_CFLAGS := $(ue_common_cflags)
 LOCAL_CPPFLAGS := $(ue_common_cppflags)
 LOCAL_LDFLAGS := $(ue_common_ldflags)
@@ -265,7 +267,6 @@
 LOCAL_MODULE := libupdate_engine
 LOCAL_MODULE_CLASS := STATIC_LIBRARIES
 LOCAL_CPP_EXTENSION := .cc
-LOCAL_CLANG := true
 LOCAL_EXPORT_C_INCLUDE_DIRS := $(ue_libupdate_engine_exported_c_includes)
 LOCAL_CFLAGS := $(ue_common_cflags)
 LOCAL_CPPFLAGS := $(ue_common_cppflags)
@@ -295,7 +296,7 @@
     hardware_android.cc \
     image_properties_android.cc \
     libcurl_http_fetcher.cc \
-    metrics.cc \
+    metrics_reporter_omaha.cc \
     metrics_utils.cc \
     omaha_request_action.cc \
     omaha_request_params.cc \
@@ -307,10 +308,15 @@
     proxy_resolver.cc \
     real_system_state.cc \
     update_attempter.cc \
+    update_manager/android_things_policy.cc \
+    update_manager/api_restricted_downloads_policy_impl.cc \
     update_manager/boxed_value.cc \
-    update_manager/chromeos_policy.cc \
     update_manager/default_policy.cc \
+    update_manager/enough_slots_ab_updates_policy_impl.cc \
     update_manager/evaluation_context.cc \
+    update_manager/interactive_update_policy_impl.cc \
+    update_manager/next_update_check_policy_impl.cc \
+    update_manager/official_build_check_policy_impl.cc \
     update_manager/policy.cc \
     update_manager/real_config_provider.cc \
     update_manager/real_device_policy_provider.cc \
@@ -363,6 +369,7 @@
     libbrillo-binder \
     libcutils \
     libcurl \
+    libmetricslogger \
     libssl \
     libutils
 
@@ -370,7 +377,6 @@
 LOCAL_MODULE := libupdate_engine_android
 LOCAL_MODULE_CLASS := STATIC_LIBRARIES
 LOCAL_CPP_EXTENSION := .cc
-LOCAL_CLANG := true
 LOCAL_CFLAGS := $(ue_common_cflags)
 LOCAL_CPPFLAGS := $(ue_common_cppflags)
 LOCAL_LDFLAGS := $(ue_common_ldflags)
@@ -397,6 +403,8 @@
     daemon_state_android.cc \
     hardware_android.cc \
     libcurl_http_fetcher.cc \
+    metrics_reporter_android.cc \
+    metrics_utils.cc \
     network_selector_android.cc \
     proxy_resolver.cc \
     update_attempter_android.cc \
@@ -415,7 +423,6 @@
 LOCAL_REQUIRED_MODULES := \
     cacerts_google
 LOCAL_CPP_EXTENSION := .cc
-LOCAL_CLANG := true
 LOCAL_CFLAGS := $(ue_common_cflags)
 LOCAL_CPPFLAGS := $(ue_common_cppflags)
 LOCAL_LDFLAGS := $(ue_common_ldflags)
@@ -457,7 +464,6 @@
 LOCAL_MODULE_PATH := $(TARGET_RECOVERY_ROOT_OUT)/sbin
 LOCAL_MODULE_CLASS := EXECUTABLES
 LOCAL_CPP_EXTENSION := .cc
-LOCAL_CLANG := true
 LOCAL_CFLAGS := \
     $(ue_common_cflags) \
     -D_UE_SIDELOAD
@@ -473,6 +479,8 @@
 LOCAL_SRC_FILES := \
     boot_control_recovery.cc \
     hardware_android.cc \
+    metrics_reporter_stub.cc \
+    metrics_utils.cc \
     network_selector_stub.cc \
     proxy_resolver.cc \
     sideload_main.cc \
@@ -493,21 +501,13 @@
 # library dependencies of these static libraries.
 LOCAL_STATIC_LIBRARIES += \
     $(ue_common_shared_libraries) \
-    libcutils \
+    libbase \
     liblog \
     $(ue_libpayload_consumer_exported_shared_libraries:-host=) \
     $(ue_update_metadata_protos_exported_shared_libraries) \
     libevent \
     libmodpb64 \
     libgtest_prod
-# libchrome requires these extra LDFLAGS which are not propagated through the
-# build system.
-LOCAL_LDFLAGS += \
-    -Wl,-wrap,calloc \
-    -Wl,-wrap,free \
-    -Wl,-wrap,malloc \
-    -Wl,-wrap,memalign \
-    -Wl,-wrap,realloc
 
 ifeq ($(strip $(PRODUCT_STATIC_BOOT_CONTROL_HAL)),)
 # No static boot_control HAL defined, so no sideload support. We use a fake
@@ -534,7 +534,6 @@
     -Werror \
     -Wno-unused-parameter \
     -DUSE_BINDER=$(local_use_binder)
-LOCAL_CLANG := true
 LOCAL_CPP_EXTENSION := .cc
 # TODO(deymo): Remove "external/cros/system_api/dbus" when dbus is not used.
 LOCAL_C_INCLUDES := \
@@ -572,7 +571,6 @@
 LOCAL_MODULE := update_engine_client
 LOCAL_MODULE_CLASS := EXECUTABLES
 LOCAL_CPP_EXTENSION := .cc
-LOCAL_CLANG := true
 LOCAL_CFLAGS := $(ue_common_cflags)
 LOCAL_CPPFLAGS := $(ue_common_cppflags)
 LOCAL_LDFLAGS := $(ue_common_ldflags)
@@ -612,6 +610,9 @@
 # for any client code.
 ue_libpayload_generator_exported_static_libraries := \
     libbsdiff \
+    libdivsufsort \
+    libdivsufsort64 \
+    libbrotli \
     liblzma \
     libpayload_consumer \
     libpuffdiff \
@@ -656,13 +657,14 @@
 LOCAL_MODULE := libpayload_generator
 LOCAL_MODULE_CLASS := STATIC_LIBRARIES
 LOCAL_CPP_EXTENSION := .cc
-LOCAL_CLANG := true
 LOCAL_CFLAGS := $(ue_common_cflags)
 LOCAL_CPPFLAGS := $(ue_common_cppflags)
 LOCAL_LDFLAGS := $(ue_common_ldflags)
 LOCAL_C_INCLUDES := $(ue_common_c_includes)
 LOCAL_STATIC_LIBRARIES := \
     libbsdiff \
+    libdivsufsort \
+    libdivsufsort64 \
     liblzma \
     libpayload_consumer \
     libpuffdiff \
@@ -684,12 +686,14 @@
 LOCAL_MODULE := libpayload_generator
 LOCAL_MODULE_CLASS := STATIC_LIBRARIES
 LOCAL_CPP_EXTENSION := .cc
-LOCAL_CLANG := true
 LOCAL_CFLAGS := $(ue_common_cflags)
 LOCAL_CPPFLAGS := $(ue_common_cppflags)
 LOCAL_LDFLAGS := $(ue_common_ldflags)
 LOCAL_C_INCLUDES := $(ue_common_c_includes)
 LOCAL_STATIC_LIBRARIES := \
+    libbsdiff \
+    libdivsufsort \
+    libdivsufsort64 \
     libpayload_consumer \
     update_metadata-protos \
     liblzma \
@@ -716,7 +720,6 @@
 LOCAL_MODULE := delta_generator
 LOCAL_MODULE_CLASS := EXECUTABLES
 LOCAL_CPP_EXTENSION := .cc
-LOCAL_CLANG := true
 LOCAL_CFLAGS := $(ue_common_cflags)
 LOCAL_CPPFLAGS := $(ue_common_cppflags)
 LOCAL_LDFLAGS := $(ue_common_ldflags)
@@ -742,7 +745,6 @@
 LOCAL_MODULE_STEM := delta_generator
 LOCAL_MODULE_CLASS := EXECUTABLES
 LOCAL_CPP_EXTENSION := .cc
-LOCAL_CLANG := true
 LOCAL_CFLAGS := $(ue_common_cflags)
 LOCAL_CPPFLAGS := $(ue_common_cppflags)
 LOCAL_LDFLAGS := $(ue_common_ldflags)
@@ -817,15 +819,6 @@
 $(call ue-unittest-sample-image,disk_ext2_4k_empty.img)
 $(call ue-unittest-sample-image,disk_ext2_unittest.img)
 
-# Zlib Fingerprint
-# ========================================================
-include $(CLEAR_VARS)
-LOCAL_MODULE := zlib_fingerprint
-LOCAL_MODULE_CLASS := ETC
-LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_NATIVE_TESTS)/update_engine_unittests
-LOCAL_PREBUILT_MODULE_FILE := $(TARGET_OUT_COMMON_GEN)/zlib_fingerprint
-include $(BUILD_PREBUILT)
-
 # update_engine.conf
 # ========================================================
 include $(CLEAR_VARS)
@@ -844,7 +837,6 @@
 LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_NATIVE_TESTS)/update_engine_unittests
 LOCAL_MODULE_CLASS := EXECUTABLES
 LOCAL_CPP_EXTENSION := .cc
-LOCAL_CLANG := true
 LOCAL_CFLAGS := $(ue_common_cflags)
 LOCAL_CPPFLAGS := $(ue_common_cppflags)
 LOCAL_LDFLAGS := $(ue_common_ldflags)
@@ -864,7 +856,6 @@
 LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_NATIVE_TESTS)/update_engine_unittests
 LOCAL_MODULE_CLASS := EXECUTABLES
 LOCAL_CPP_EXTENSION := .cc
-LOCAL_CLANG := true
 LOCAL_CFLAGS := $(ue_common_cflags)
 LOCAL_CPPFLAGS := $(ue_common_cppflags)
 LOCAL_LDFLAGS := $(ue_common_ldflags)
@@ -891,10 +882,8 @@
     ue_unittest_key.pub.pem \
     ue_unittest_key2.pem \
     ue_unittest_key2.pub.pem \
-    ue_unittest_update_engine.conf \
-    zlib_fingerprint
+    ue_unittest_update_engine.conf
 LOCAL_CPP_EXTENSION := .cc
-LOCAL_CLANG := true
 LOCAL_CFLAGS := $(ue_common_cflags)
 LOCAL_CPPFLAGS := $(ue_common_cppflags)
 LOCAL_LDFLAGS := $(ue_common_ldflags)
@@ -974,6 +963,8 @@
 LOCAL_SRC_FILES += \
     common_service_unittest.cc \
     fake_system_state.cc \
+    image_properties_android_unittest.cc \
+    metrics_reporter_omaha_unittest.cc \
     metrics_utils_unittest.cc \
     omaha_request_action_unittest.cc \
     omaha_request_params_unittest.cc \
@@ -982,12 +973,16 @@
     p2p_manager_unittest.cc \
     payload_consumer/download_action_unittest.cc \
     payload_state_unittest.cc \
-    proxy_resolver_unittest.cc \
+    parcelable_update_engine_status_unittest.cc \
     update_attempter_unittest.cc \
+    update_manager/android_things_policy_unittest.cc \
     update_manager/boxed_value_unittest.cc \
+    update_manager/chromeos_policy.cc \
     update_manager/chromeos_policy_unittest.cc \
     update_manager/evaluation_context_unittest.cc \
     update_manager/generic_variables_unittest.cc \
+    update_manager/next_update_check_policy_impl_unittest.cc \
+    update_manager/policy_test_utils.cc \
     update_manager/prng_unittest.cc \
     update_manager/real_device_policy_provider_unittest.cc \
     update_manager/real_random_provider_unittest.cc \
@@ -1003,12 +998,14 @@
     $(ue_libupdate_engine_android_exported_static_libraries:-host=)
 LOCAL_SHARED_LIBRARIES += \
     $(ue_libupdate_engine_android_exported_shared_libraries:-host=)
+LOCAL_SRC_FILES += \
+    update_attempter_android_unittest.cc
 endif  # local_use_omaha == 1
 include $(BUILD_NATIVE_TEST)
 
 # Update payload signing public key.
 # ========================================================
-ifdef BRILLO
+ifeq ($(PRODUCT_IOT),true)
 include $(CLEAR_VARS)
 LOCAL_MODULE := brillo-update-payload-key
 LOCAL_MODULE_CLASS := ETC
@@ -1017,7 +1014,7 @@
 LOCAL_SRC_FILES := update_payload_key/brillo-update-payload-key.pub.pem
 LOCAL_BUILT_MODULE_STEM := update_payload_key/brillo-update-payload-key.pub.pem
 include $(BUILD_PREBUILT)
-endif  # BRILLO
+endif  # PRODUCT_IOT
 
 # Brillo update payload generation script
 # ========================================================
@@ -1030,6 +1027,9 @@
 LOCAL_MODULE_TAGS := optional
 LOCAL_REQUIRED_MODULES := \
     delta_generator \
-    shflags
+    shflags \
+    simg2img
 include $(BUILD_PREBUILT)
 endif  # HOST_OS == linux
+
+endif  # ifneq ($(TARGET_BUILD_PDK),true)
diff --git a/PREUPLOAD.cfg b/PREUPLOAD.cfg
index 706dfa8..40ddcd1 100644
--- a/PREUPLOAD.cfg
+++ b/PREUPLOAD.cfg
@@ -1,3 +1,4 @@
 [Builtin Hooks]
 clang_format = true
+cpplint = true
 pylint = true
diff --git a/UpdateEngine.conf b/UpdateEngine.conf
index 192e6ab..58cca09 100644
--- a/UpdateEngine.conf
+++ b/UpdateEngine.conf
@@ -53,9 +53,6 @@
            send_member="SetUpdateOverCellularPermission"/>
     <allow send_destination="org.chromium.UpdateEngine"
            send_interface="org.chromium.UpdateEngineInterface"
-           send_member="SetUpdateOverCellularTarget"/>
-    <allow send_destination="org.chromium.UpdateEngine"
-           send_interface="org.chromium.UpdateEngineInterface"
            send_member="GetUpdateOverCellularPermission"/>
     <allow send_destination="org.chromium.UpdateEngine"
            send_interface="org.chromium.UpdateEngineInterface"
diff --git a/binder_bindings/android/brillo/IUpdateEngine.aidl b/binder_bindings/android/brillo/IUpdateEngine.aidl
index 7893548..e549a4d 100644
--- a/binder_bindings/android/brillo/IUpdateEngine.aidl
+++ b/binder_bindings/android/brillo/IUpdateEngine.aidl
@@ -20,7 +20,8 @@
 import android.brillo.ParcelableUpdateEngineStatus;
 
 interface IUpdateEngine {
-  void AttemptUpdate(in String app_version, in String omaha_url, in int flags);
+  void SetUpdateAttemptFlags(in int flags);
+  boolean AttemptUpdate(in String app_version, in String omaha_url, in int flags);
   void AttemptRollback(in boolean powerwash);
   boolean CanRollback();
   void ResetStatus();
@@ -33,8 +34,6 @@
   void SetP2PUpdatePermission(in boolean enabled);
   boolean GetP2PUpdatePermission();
   void SetUpdateOverCellularPermission(in boolean enabled);
-  void SetUpdateOverCellularTarget(in String target_version,
-                                   in long target_size);
   boolean GetUpdateOverCellularPermission();
   long GetDurationSinceUpdate();
   String GetPrevVersion();
diff --git a/binder_bindings/android/brillo/IUpdateEngineStatusCallback.aidl b/binder_bindings/android/brillo/IUpdateEngineStatusCallback.aidl
index 42b6438..837d44d 100644
--- a/binder_bindings/android/brillo/IUpdateEngineStatusCallback.aidl
+++ b/binder_bindings/android/brillo/IUpdateEngineStatusCallback.aidl
@@ -16,8 +16,9 @@
 
 package android.brillo;
 
+import android.brillo.ParcelableUpdateEngineStatus;
+
 interface IUpdateEngineStatusCallback {
   oneway
-  void HandleStatusUpdate(in long last_checked_time, in double progress,
-      in String current_operation, in String new_version, in long new_size);
+  void HandleStatusUpdate(in ParcelableUpdateEngineStatus status);
 }
diff --git a/binder_bindings/android/os/IUpdateEngine.aidl b/binder_bindings/android/os/IUpdateEngine.aidl
index 67f828a..7e26752 100644
--- a/binder_bindings/android/os/IUpdateEngine.aidl
+++ b/binder_bindings/android/os/IUpdateEngine.aidl
@@ -28,6 +28,8 @@
   /** @hide */
   boolean bind(IUpdateEngineCallback callback);
   /** @hide */
+  boolean unbind(IUpdateEngineCallback callback);
+  /** @hide */
   void suspend();
   /** @hide */
   void resume();
diff --git a/binder_service_android.cc b/binder_service_android.cc
index 872f64c..0305727 100644
--- a/binder_service_android.cc
+++ b/binder_service_android.cc
@@ -24,6 +24,7 @@
 
 using android::binder::Status;
 using android::os::IUpdateEngineCallback;
+using update_engine::UpdateEngineStatus;
 
 namespace {
 Status ErrorPtrToStatus(const brillo::ErrorPtr& error) {
@@ -40,13 +41,9 @@
 }
 
 void BinderUpdateEngineAndroidService::SendStatusUpdate(
-    int64_t /* last_checked_time */,
-    double progress,
-    update_engine::UpdateStatus status,
-    const std::string& /* new_version  */,
-    int64_t /* new_size */) {
-  last_status_ = static_cast<int>(status);
-  last_progress_ = progress;
+    const UpdateEngineStatus& update_engine_status) {
+  last_status_ = static_cast<int>(update_engine_status.status);
+  last_progress_ = update_engine_status.progress;
   for (auto& callback : callbacks_) {
     callback->onStatusUpdate(last_status_, last_progress_);
   }
@@ -63,12 +60,15 @@
     const android::sp<IUpdateEngineCallback>& callback, bool* return_value) {
   callbacks_.emplace_back(callback);
 
+  const android::sp<IBinder>& callback_binder =
+      IUpdateEngineCallback::asBinder(callback);
   auto binder_wrapper = android::BinderWrapper::Get();
   binder_wrapper->RegisterForDeathNotifications(
-      IUpdateEngineCallback::asBinder(callback),
-      base::Bind(&BinderUpdateEngineAndroidService::UnbindCallback,
-                 base::Unretained(this),
-                 base::Unretained(callback.get())));
+      callback_binder,
+      base::Bind(
+          base::IgnoreResult(&BinderUpdateEngineAndroidService::UnbindCallback),
+          base::Unretained(this),
+          base::Unretained(callback_binder.get())));
 
   // Send an status update on connection (except when no update sent so far),
   // since the status update is oneway and we don't need to wait for the
@@ -80,6 +80,17 @@
   return Status::ok();
 }
 
+Status BinderUpdateEngineAndroidService::unbind(
+    const android::sp<IUpdateEngineCallback>& callback, bool* return_value) {
+  const android::sp<IBinder>& callback_binder =
+      IUpdateEngineCallback::asBinder(callback);
+  auto binder_wrapper = android::BinderWrapper::Get();
+  binder_wrapper->UnregisterForDeathNotifications(callback_binder);
+
+  *return_value = UnbindCallback(callback_binder.get());
+  return Status::ok();
+}
+
 Status BinderUpdateEngineAndroidService::applyPayload(
     const android::String16& url,
     int64_t payload_offset,
@@ -128,19 +139,19 @@
   return Status::ok();
 }
 
-void BinderUpdateEngineAndroidService::UnbindCallback(
-    IUpdateEngineCallback* callback) {
-  auto it =
-      std::find_if(callbacks_.begin(),
-                   callbacks_.end(),
-                   [&callback](const android::sp<IUpdateEngineCallback>& elem) {
-                     return elem.get() == callback;
-                   });
+bool BinderUpdateEngineAndroidService::UnbindCallback(const IBinder* callback) {
+  auto it = std::find_if(
+      callbacks_.begin(),
+      callbacks_.end(),
+      [&callback](const android::sp<IUpdateEngineCallback>& elem) {
+        return IUpdateEngineCallback::asBinder(elem).get() == callback;
+      });
   if (it == callbacks_.end()) {
-    LOG(ERROR) << "Got death notification for unknown callback.";
-    return;
+    LOG(ERROR) << "Unable to unbind unknown callback.";
+    return false;
   }
   callbacks_.erase(it);
+  return true;
 }
 
 }  // namespace chromeos_update_engine
diff --git a/binder_service_android.h b/binder_service_android.h
index 3293c0e..eb36e4c 100644
--- a/binder_service_android.h
+++ b/binder_service_android.h
@@ -45,11 +45,8 @@
   }
 
   // ServiceObserverInterface overrides.
-  void SendStatusUpdate(int64_t last_checked_time,
-                        double progress,
-                        update_engine::UpdateStatus status,
-                        const std::string& new_version,
-                        int64_t new_size) override;
+  void SendStatusUpdate(
+      const update_engine::UpdateEngineStatus& update_engine_status) override;
   void SendPayloadApplicationComplete(ErrorCode error_code) override;
 
   // android::os::BnUpdateEngine overrides.
@@ -61,6 +58,9 @@
   android::binder::Status bind(
       const android::sp<android::os::IUpdateEngineCallback>& callback,
       bool* return_value) override;
+  android::binder::Status unbind(
+      const android::sp<android::os::IUpdateEngineCallback>& callback,
+      bool* return_value) override;
   android::binder::Status suspend() override;
   android::binder::Status resume() override;
   android::binder::Status cancel() override;
@@ -68,8 +68,9 @@
 
  private:
   // Remove the passed |callback| from the list of registered callbacks. Called
-  // whenever the callback object is destroyed.
-  void UnbindCallback(android::os::IUpdateEngineCallback* callback);
+  // on unbind() or whenever the callback object is destroyed.
+  // Returns true on success.
+  bool UnbindCallback(const IBinder* callback);
 
   // List of currently bound callbacks.
   std::vector<android::sp<android::os::IUpdateEngineCallback>> callbacks_;
diff --git a/binder_service_brillo.cc b/binder_service_brillo.cc
index 14df725..3f01e42 100644
--- a/binder_service_brillo.cc
+++ b/binder_service_brillo.cc
@@ -33,6 +33,7 @@
 using android::sp;
 using brillo::ErrorPtr;
 using std::string;
+using update_engine::UpdateEngineStatus;
 
 namespace chromeos_update_engine {
 
@@ -57,12 +58,20 @@
   return ToStatus(&error);
 }
 
+Status BinderUpdateEngineBrilloService::SetUpdateAttemptFlags(int flags) {
+  return CallCommonHandler(&UpdateEngineService::SetUpdateAttemptFlags, flags);
+}
+
 Status BinderUpdateEngineBrilloService::AttemptUpdate(
-    const String16& app_version, const String16& omaha_url, int flags) {
+    const String16& app_version,
+    const String16& omaha_url,
+    int flags,
+    bool* out_result) {
   return CallCommonHandler(&UpdateEngineService::AttemptUpdate,
                            NormalString(app_version),
                            NormalString(omaha_url),
-                           flags);
+                           flags,
+                           out_result);
 }
 
 Status BinderUpdateEngineBrilloService::AttemptRollback(bool powerwash) {
@@ -79,19 +88,12 @@
 
 Status BinderUpdateEngineBrilloService::GetStatus(
     ParcelableUpdateEngineStatus* status) {
-  string current_op;
-  string new_version;
-
-  auto ret = CallCommonHandler(&UpdateEngineService::GetStatus,
-                               &status->last_checked_time_,
-                               &status->progress_,
-                               &current_op,
-                               &new_version,
-                               &status->new_size_);
+  UpdateEngineStatus update_engine_status;
+  auto ret =
+      CallCommonHandler(&UpdateEngineService::GetStatus, &update_engine_status);
 
   if (ret.isOk()) {
-    status->current_operation_ = String16{current_op.c_str()};
-    status->new_version_ = String16{new_version.c_str()};
+    *status = ParcelableUpdateEngineStatus(update_engine_status);
   }
 
   return ret;
@@ -151,14 +153,6 @@
       &UpdateEngineService::SetUpdateOverCellularPermission, enabled);
 }
 
-Status BinderUpdateEngineBrilloService::SetUpdateOverCellularTarget(
-    const String16& target_version,
-    int64_t target_size) {
-  return CallCommonHandler(
-      &UpdateEngineService::SetUpdateOverCellularTarget,
-      NormalString(target_version), target_size);
-}
-
 Status BinderUpdateEngineBrilloService::GetUpdateOverCellularPermission(
     bool* out_cellular_permission) {
   return CallCommonHandler(
@@ -236,18 +230,10 @@
 }
 
 void BinderUpdateEngineBrilloService::SendStatusUpdate(
-    int64_t last_checked_time,
-    double progress,
-    update_engine::UpdateStatus status,
-    const string& new_version,
-    int64_t new_size) {
-  const string str_status = UpdateStatusToString(status);
+    const UpdateEngineStatus& update_engine_status) {
+  ParcelableUpdateEngineStatus parcelable_status(update_engine_status);
   for (auto& callback : callbacks_) {
-    callback->HandleStatusUpdate(last_checked_time,
-                                 progress,
-                                 String16{str_status.c_str()},
-                                 String16{new_version.c_str()},
-                                 new_size);
+    callback->HandleStatusUpdate(parcelable_status);
   }
 }
 
diff --git a/binder_service_brillo.h b/binder_service_brillo.h
index 4cc007b..c802fca 100644
--- a/binder_service_brillo.h
+++ b/binder_service_brillo.h
@@ -46,17 +46,16 @@
   }
 
   // ServiceObserverInterface overrides.
-  void SendStatusUpdate(int64_t last_checked_time,
-                        double progress,
-                        update_engine::UpdateStatus status,
-                        const std::string& new_version,
-                        int64_t new_size) override;
+  void SendStatusUpdate(
+      const update_engine::UpdateEngineStatus& update_engine_status) override;
   void SendPayloadApplicationComplete(ErrorCode error_code) override {}
 
   // android::brillo::BnUpdateEngine overrides.
+  android::binder::Status SetUpdateAttemptFlags(int flags) override;
   android::binder::Status AttemptUpdate(const android::String16& app_version,
                                         const android::String16& omaha_url,
-                                        int flags) override;
+                                        int flags,
+                                        bool* out_result) override;
   android::binder::Status AttemptRollback(bool powerwash) override;
   android::binder::Status CanRollback(bool* out_can_rollback) override;
   android::binder::Status ResetStatus() override;
@@ -76,9 +75,6 @@
       bool* out_p2p_permission) override;
   android::binder::Status SetUpdateOverCellularPermission(
       bool enabled) override;
-  android::binder::Status SetUpdateOverCellularTarget(
-      const android::String16& target_version,
-      int64_t target_size) override;
   android::binder::Status GetUpdateOverCellularPermission(
       bool* out_cellular_permission) override;
   android::binder::Status GetDurationSinceUpdate(
diff --git a/client_library/client_binder.cc b/client_library/client_binder.cc
index e98c225..54b33ed 100644
--- a/client_library/client_binder.cc
+++ b/client_library/client_binder.cc
@@ -25,15 +25,15 @@
 #include "update_engine/parcelable_update_engine_status.h"
 #include "update_engine/update_status_utils.h"
 
-using android::OK;
-using android::String16;
-using android::String8;
 using android::binder::Status;
 using android::brillo::ParcelableUpdateEngineStatus;
 using android::getService;
+using android::OK;
+using android::String16;
+using android::String8;
 using chromeos_update_engine::StringToUpdateStatus;
-using chromeos_update_engine::UpdateEngineService;
 using std::string;
+using update_engine::UpdateAttemptFlags;
 
 namespace update_engine {
 namespace internal {
@@ -48,10 +48,14 @@
 bool BinderUpdateEngineClient::AttemptUpdate(const string& in_app_version,
                                              const string& in_omaha_url,
                                              bool at_user_request) {
-  return service_->AttemptUpdate(String16{in_app_version.c_str()},
-      String16{in_omaha_url.c_str()},
-      at_user_request ? 0 :
-          UpdateEngineService::kAttemptUpdateFlagNonInteractive).isOk();
+  bool started;
+  return service_
+      ->AttemptUpdate(
+          String16{in_app_version.c_str()},
+          String16{in_omaha_url.c_str()},
+          at_user_request ? 0 : UpdateAttemptFlags::kFlagNonInteractive,
+          &started)
+      .isOk();
 }
 
 bool BinderUpdateEngineClient::GetStatus(int64_t* out_last_checked_time,
@@ -143,18 +147,18 @@
 }
 
 Status BinderUpdateEngineClient::StatusUpdateCallback::HandleStatusUpdate(
-    int64_t last_checked_time,
-    double progress,
-    const String16& current_operation,
-    const String16& new_version,
-    int64_t new_size) {
+    const ParcelableUpdateEngineStatus& status) {
   UpdateStatus update_status;
 
-  StringToUpdateStatus(String8{current_operation}.string(), &update_status);
+  StringToUpdateStatus(String8{status.current_operation_}.string(),
+                       &update_status);
 
   for (auto& handler : client_->handlers_) {
-    handler->HandleStatusUpdate(last_checked_time, progress, update_status,
-                                String8{new_version}.string(), new_size);
+    handler->HandleStatusUpdate(status.last_checked_time_,
+                                status.progress_,
+                                update_status,
+                                String8{status.new_version_}.string(),
+                                status.new_size_);
   }
 
   return Status::ok();
diff --git a/client_library/client_binder.h b/client_library/client_binder.h
index b1b34da..17f2beb 100644
--- a/client_library/client_binder.h
+++ b/client_library/client_binder.h
@@ -94,11 +94,7 @@
         : client_(client) {}
 
     android::binder::Status HandleStatusUpdate(
-        int64_t last_checked_time,
-        double progress,
-        const android::String16& current_operation,
-        const android::String16& new_version,
-        int64_t new_size) override;
+        const android::brillo::ParcelableUpdateEngineStatus& status) override;
 
    private:
     BinderUpdateEngineClient* client_;
diff --git a/client_library/include/update_engine/update_status.h b/client_library/include/update_engine/update_status.h
index 2f6322a..41fab48 100644
--- a/client_library/include/update_engine/update_status.h
+++ b/client_library/include/update_engine/update_status.h
@@ -17,15 +17,16 @@
 #ifndef UPDATE_ENGINE_CLIENT_LIBRARY_INCLUDE_UPDATE_ENGINE_UPDATE_STATUS_H_
 #define UPDATE_ENGINE_CLIENT_LIBRARY_INCLUDE_UPDATE_ENGINE_UPDATE_STATUS_H_
 
+#include <string>
+
+#include <brillo/enum_flags.h>
+
 namespace update_engine {
 
 enum class UpdateStatus {
   IDLE = 0,
   CHECKING_FOR_UPDATE,
   UPDATE_AVAILABLE,
-  // Broadcast this state when an update aborts because user preferences does
-  // not allow update over cellular.
-  NEED_PERMISSION_TO_UPDATE,
   DOWNLOADING,
   VERIFYING,
   FINALIZING,
@@ -35,6 +36,39 @@
   DISABLED,
 };
 
+// Enum of bit-wise flags for controlling how updates are attempted.
+enum UpdateAttemptFlags : int32_t {
+  kNone = 0,
+  // Treat the update like a non-interactive update, even when being triggered
+  // by the interactive APIs.
+  kFlagNonInteractive = (1 << 0),
+  // Restrict (disallow) downloading of updates.
+  kFlagRestrictDownload = (1 << 1),
+};
+
+// Enable bit-wise operators for the above enumeration of flag values.
+DECLARE_FLAGS_ENUM(UpdateAttemptFlags);
+
+struct UpdateEngineStatus {
+  // When the update_engine last checked for updates (time_t: seconds from unix
+  // epoch)
+  int64_t last_checked_time;
+  // the current status/operation of the update_engine
+  UpdateStatus status;
+  // the current product version (oem bundle id)
+  std::string current_version;
+  // the current system version
+  std::string current_system_version;
+  // The current progress (0.0f-1.0f).
+  double progress;
+  // the size of the update (bytes)
+  uint64_t new_size_bytes;
+  // the new product version
+  std::string new_version;
+  // the new system version, if there is one (empty, otherwise)
+  std::string new_system_version;
+};
+
 }  // namespace update_engine
 
 #endif  // UPDATE_ENGINE_CLIENT_LIBRARY_INCLUDE_UPDATE_ENGINE_UPDATE_STATUS_H_
diff --git a/common/constants.cc b/common/constants.cc
index d6d1c6d..5941c93 100644
--- a/common/constants.cc
+++ b/common/constants.cc
@@ -57,6 +57,7 @@
 const char kPrefsP2PFirstAttemptTimestamp[] = "p2p-first-attempt-timestamp";
 const char kPrefsP2PNumAttempts[] = "p2p-num-attempts";
 const char kPrefsPayloadAttemptNumber[] = "payload-attempt-number";
+const char kPrefsPostInstallSucceeded[] = "post-install-succeeded";
 const char kPrefsPreviousVersion[] = "previous-version";
 const char kPrefsResumedUpdateFailures[] = "resumed-update-failures";
 const char kPrefsRollbackVersion[] = "rollback-version";
@@ -74,14 +75,11 @@
 const char kPrefsUpdateFirstSeenAt[] = "update-first-seen-at";
 const char kPrefsUpdateOverCellularPermission[] =
     "update-over-cellular-permission";
-const char kPrefsUpdateOverCellularTargetVersion[] =
-    "update-over-cellular-target-version";
-const char kPrefsUpdateOverCellularTargetSize[] =
-    "update-over-cellular-target-size";
 const char kPrefsUpdateServerCertificate[] = "update-server-cert";
 const char kPrefsUpdateStateNextDataLength[] = "update-state-next-data-length";
 const char kPrefsUpdateStateNextDataOffset[] = "update-state-next-data-offset";
 const char kPrefsUpdateStateNextOperation[] = "update-state-next-operation";
+const char kPrefsUpdateStatePayloadIndex[] = "update-state-payload-index";
 const char kPrefsUpdateStateSHA256Context[] = "update-state-sha-256-context";
 const char kPrefsUpdateStateSignatureBlob[] = "update-state-signature-blob";
 const char kPrefsUpdateStateSignedSHA256Context[] =
@@ -106,5 +104,12 @@
 // This can be used to zero-rate OTA traffic by sending it over the correct
 // network.
 const char kPayloadPropertyNetworkId[] = "NETWORK_ID";
+// Set "SWITCH_SLOT_ON_REBOOT=0" to skip marking the updated partitions active.
+// The default is 1 (always switch slot if update succeeded).
+const char kPayloadPropertySwitchSlotOnReboot[] = "SWITCH_SLOT_ON_REBOOT";
+// Set "RUN_POST_INSTALL=0" to skip running post install, this will only be
+// honored if we're resuming an update and post install has already succeeded.
+// The default is 1 (always run post install).
+const char kPayloadPropertyRunPostInstall[] = "RUN_POST_INSTALL";
 
 }  // namespace chromeos_update_engine
diff --git a/common/constants.h b/common/constants.h
index c571403..26773cf 100644
--- a/common/constants.h
+++ b/common/constants.h
@@ -59,6 +59,7 @@
 extern const char kPrefsP2PFirstAttemptTimestamp[];
 extern const char kPrefsP2PNumAttempts[];
 extern const char kPrefsPayloadAttemptNumber[];
+extern const char kPrefsPostInstallSucceeded[];
 extern const char kPrefsPreviousVersion[];
 extern const char kPrefsResumedUpdateFailures[];
 extern const char kPrefsRollbackVersion[];
@@ -75,12 +76,11 @@
 extern const char kPrefsUpdateDurationUptime[];
 extern const char kPrefsUpdateFirstSeenAt[];
 extern const char kPrefsUpdateOverCellularPermission[];
-extern const char kPrefsUpdateOverCellularTargetVersion[];
-extern const char kPrefsUpdateOverCellularTargetSize[];
 extern const char kPrefsUpdateServerCertificate[];
 extern const char kPrefsUpdateStateNextDataLength[];
 extern const char kPrefsUpdateStateNextDataOffset[];
 extern const char kPrefsUpdateStateNextOperation[];
+extern const char kPrefsUpdateStatePayloadIndex[];
 extern const char kPrefsUpdateStateSHA256Context[];
 extern const char kPrefsUpdateStateSignatureBlob[];
 extern const char kPrefsUpdateStateSignedSHA256Context[];
@@ -97,6 +97,8 @@
 extern const char kPayloadPropertyUserAgent[];
 extern const char kPayloadPropertyPowerwash[];
 extern const char kPayloadPropertyNetworkId[];
+extern const char kPayloadPropertySwitchSlotOnReboot[];
+extern const char kPayloadPropertyRunPostInstall[];
 
 // A download source is any combination of protocol and server (that's of
 // interest to us when looking at UMA metrics) using which we may download
@@ -174,6 +176,7 @@
 // succeeding. When using p2p, this is low in order to fail fast.
 const int kDownloadMaxRetryCount = 20;
 const int kDownloadMaxRetryCountOobeNotComplete = 3;
+const int kDownloadMaxRetryCountInteractive = 3;
 const int kDownloadP2PMaxRetryCount = 5;
 
 // The connect timeout, in seconds.
diff --git a/common/error_code.h b/common/error_code.h
index bcf541c..0b08005 100644
--- a/common/error_code.h
+++ b/common/error_code.h
@@ -73,7 +73,9 @@
   kFilesystemVerifierError = 47,
   kUserCanceled = 48,
   kNonCriticalUpdateInOOBE = 49,
-  kOmahaUpdateIgnoredOverCellular = 50,
+  // kOmahaUpdateIgnoredOverCellular = 50,
+  kPayloadTimestampError = 51,
+  kUpdatedButNotActive = 52,
 
   // VERY IMPORTANT! When adding new error codes:
   //
diff --git a/common/error_code_utils.cc b/common/error_code_utils.cc
index 0169ab8..313a15f 100644
--- a/common/error_code_utils.cc
+++ b/common/error_code_utils.cc
@@ -144,10 +144,12 @@
       return "ErrorCode::kUserCanceled";
     case ErrorCode::kNonCriticalUpdateInOOBE:
       return "ErrorCode::kNonCriticalUpdateInOOBE";
-    case ErrorCode::kOmahaUpdateIgnoredOverCellular:
-      return "ErrorCode::kOmahaUpdateIgnoredOverCellular";
-    // Don't add a default case to let the compiler warn about newly added
-    // error codes which should be added here.
+    case ErrorCode::kPayloadTimestampError:
+      return "ErrorCode::kPayloadTimestampError";
+    case ErrorCode::kUpdatedButNotActive:
+      return "ErrorCode::kUpdatedButNotActive";
+      // Don't add a default case to let the compiler warn about newly added
+      // error codes which should be added here.
   }
 
   return "Unknown error: " + base::UintToString(static_cast<unsigned>(code));
diff --git a/common/fake_hardware.h b/common/fake_hardware.h
index 01d23d0..f2b2c9d 100644
--- a/common/fake_hardware.h
+++ b/common/fake_hardware.h
@@ -81,6 +81,8 @@
     return false;
   }
 
+  int64_t GetBuildTimestamp() const override { return build_timestamp_; }
+
   bool GetFirstActiveOmahaPingSent() const override {
     return first_active_omaha_ping_sent_;
   }
@@ -133,6 +135,10 @@
     powerwash_count_ = powerwash_count;
   }
 
+  void SetBuildTimestamp(int64_t build_timestamp) {
+    build_timestamp_ = build_timestamp;
+  }
+
  private:
   bool is_official_build_{true};
   bool is_normal_boot_mode_{true};
@@ -145,6 +151,7 @@
   std::string ec_version_{"Fake EC v1.0a"};
   int powerwash_count_{kPowerwashCountNotSet};
   bool powerwash_scheduled_{false};
+  int64_t build_timestamp_{0};
   bool first_active_omaha_ping_sent_{false};
 
   DISALLOW_COPY_AND_ASSIGN(FakeHardware);
diff --git a/common/hardware_interface.h b/common/hardware_interface.h
index 541a68a..94442d1 100644
--- a/common/hardware_interface.h
+++ b/common/hardware_interface.h
@@ -17,6 +17,8 @@
 #ifndef UPDATE_ENGINE_COMMON_HARDWARE_INTERFACE_H_
 #define UPDATE_ENGINE_COMMON_HARDWARE_INTERFACE_H_
 
+#include <stdint.h>
+
 #include <string>
 #include <vector>
 
@@ -90,6 +92,9 @@
   // returns false.
   virtual bool GetPowerwashSafeDirectory(base::FilePath* path) const = 0;
 
+  // Returns the timestamp of the current OS build.
+  virtual int64_t GetBuildTimestamp() const = 0;
+
   // Returns whether the first active ping was sent to Omaha at some point, and
   // that the value is persisted across recovery (and powerwash) once set with
   // |SetFirstActiveOmahaPingSent()|.
diff --git a/common/hash_calculator.cc b/common/hash_calculator.cc
index de6e0f9..ebfdb6e 100644
--- a/common/hash_calculator.cc
+++ b/common/hash_calculator.cc
@@ -20,7 +20,6 @@
 
 #include <base/logging.h>
 #include <base/posix/eintr_wrapper.h>
-#include <brillo/data_encoding.h>
 
 #include "update_engine/common/utils.h"
 
@@ -37,7 +36,7 @@
 // Mostly just passes the data through to OpenSSL's SHA256_Update()
 bool HashCalculator::Update(const void* data, size_t length) {
   TEST_AND_RETURN_FALSE(valid_);
-  TEST_AND_RETURN_FALSE(hash_.empty());
+  TEST_AND_RETURN_FALSE(raw_hash_.empty());
   static_assert(sizeof(size_t) <= sizeof(unsigned long),  // NOLINT(runtime/int)
                 "length param may be truncated in SHA256_Update");
   TEST_AND_RETURN_FALSE(SHA256_Update(&ctx_, data, length) == 1);
@@ -73,16 +72,11 @@
 }
 
 // Call Finalize() when all data has been passed in. This mostly just
-// calls OpenSSL's SHA256_Final() and then base64 encodes the hash.
+// calls OpenSSL's SHA256_Final().
 bool HashCalculator::Finalize() {
-  TEST_AND_RETURN_FALSE(hash_.empty());
   TEST_AND_RETURN_FALSE(raw_hash_.empty());
   raw_hash_.resize(SHA256_DIGEST_LENGTH);
   TEST_AND_RETURN_FALSE(SHA256_Final(raw_hash_.data(), &ctx_) == 1);
-
-  // Convert raw_hash_ to base64 encoding and store it in hash_.
-  hash_ = brillo::data_encoding::Base64Encode(raw_hash_.data(),
-                                              raw_hash_.size());
   return true;
 }
 
@@ -115,21 +109,6 @@
   return res;
 }
 
-string HashCalculator::HashOfBytes(const void* data, size_t length) {
-  HashCalculator calc;
-  calc.Update(data, length);
-  calc.Finalize();
-  return calc.hash();
-}
-
-string HashCalculator::HashOfString(const string& str) {
-  return HashOfBytes(str.data(), str.size());
-}
-
-string HashCalculator::HashOfData(const brillo::Blob& data) {
-  return HashOfBytes(data.data(), data.size());
-}
-
 string HashCalculator::GetContext() const {
   return string(reinterpret_cast<const char*>(&ctx_), sizeof(ctx_));
 }
diff --git a/common/hash_calculator.h b/common/hash_calculator.h
index f749585..06d2cfb 100644
--- a/common/hash_calculator.h
+++ b/common/hash_calculator.h
@@ -27,11 +27,11 @@
 #include <base/macros.h>
 #include <brillo/secure_blob.h>
 
-// Omaha uses base64 encoded SHA-256 as the hash. This class provides a simple
-// wrapper around OpenSSL providing such a formatted hash of data passed in.
+// This class provides a simple wrapper around OpenSSL providing a hash of data
+// passed in.
 // The methods of this class must be called in a very specific order: First the
 // ctor (of course), then 0 or more calls to Update(), then Finalize(), then 0
-// or more calls to hash().
+// or more calls to raw_hash().
 
 namespace chromeos_update_engine {
 
@@ -50,17 +50,10 @@
   off_t UpdateFile(const std::string& name, off_t length);
 
   // Call Finalize() when all data has been passed in. This method tells
-  // OpenSSl that no more data will come in and base64 encodes the resulting
-  // hash.
+  // OpenSSL that no more data will come in.
   // Returns true on success.
   bool Finalize();
 
-  // Gets the hash. Finalize() must have been called.
-  const std::string& hash() const {
-    DCHECK(!hash_.empty()) << "Call Finalize() first";
-    return hash_;
-  }
-
   const brillo::Blob& raw_hash() const {
     DCHECK(!raw_hash_.empty()) << "Call Finalize() first";
     return raw_hash_;
@@ -83,15 +76,9 @@
   static off_t RawHashOfFile(const std::string& name, off_t length,
                              brillo::Blob* out_hash);
 
-  // Used by tests
-  static std::string HashOfBytes(const void* data, size_t length);
-  static std::string HashOfString(const std::string& str);
-  static std::string HashOfData(const brillo::Blob& data);
-
  private:
-  // If non-empty, the final base64 encoded hash and the raw hash. Will only be
-  // set to non-empty when Finalize is called.
-  std::string hash_;
+  // If non-empty, the final raw hash. Will only be set to non-empty when
+  // Finalize is called.
   brillo::Blob raw_hash_;
 
   // Init success
diff --git a/common/hash_calculator_unittest.cc b/common/hash_calculator_unittest.cc
index 436e6a7..233237b 100644
--- a/common/hash_calculator_unittest.cc
+++ b/common/hash_calculator_unittest.cc
@@ -22,6 +22,7 @@
 #include <string>
 #include <vector>
 
+#include <brillo/data_encoding.h>
 #include <brillo/secure_blob.h>
 #include <gtest/gtest.h>
 
@@ -33,9 +34,8 @@
 namespace chromeos_update_engine {
 
 // Generated by running this on a linux shell:
-// $ echo -n hi | openssl dgst -sha256 -binary | openssl base64
-static const char kExpectedHash[] =
-    "j0NDRmSPa5bfid2pAcUXaxCm2Dlh3TwayItZstwyeqQ=";
+// $ echo -n hi | openssl dgst -sha256 -binary |
+//   hexdump -v -e '"    " 12/1 "0x%02x, " "\n"'
 static const uint8_t kExpectedRawHash[] = {
   0x8f, 0x43, 0x43, 0x46, 0x64, 0x8f, 0x6b, 0x96,
   0xdf, 0x89, 0xdd, 0xa9, 0x01, 0xc5, 0x17, 0x6b,
@@ -52,7 +52,6 @@
   HashCalculator calc;
   calc.Update("hi", 2);
   calc.Finalize();
-  EXPECT_EQ(kExpectedHash, calc.hash());
   brillo::Blob raw_hash(std::begin(kExpectedRawHash),
                         std::end(kExpectedRawHash));
   EXPECT_TRUE(raw_hash == calc.raw_hash());
@@ -63,7 +62,6 @@
   calc.Update("h", 1);
   calc.Update("i", 1);
   calc.Finalize();
-  EXPECT_EQ(kExpectedHash, calc.hash());
   brillo::Blob raw_hash(std::begin(kExpectedRawHash),
                         std::end(kExpectedRawHash));
   EXPECT_TRUE(raw_hash == calc.raw_hash());
@@ -78,7 +76,6 @@
   calc_next.SetContext(calc_context);
   calc_next.Update("i", 1);
   calc_next.Finalize();
-  EXPECT_EQ(kExpectedHash, calc_next.hash());
   brillo::Blob raw_hash(std::begin(kExpectedRawHash),
                         std::end(kExpectedRawHash));
   EXPECT_TRUE(raw_hash == calc_next.raw_hash());
@@ -106,7 +103,8 @@
   //     echo -n $C
   //     let C=C+1
   //   done | openssl dgst -sha256 -binary | openssl base64
-  EXPECT_EQ("NZf8k6SPBkYMvhaX8YgzuMgbkLP1XZ+neM8K5wcSsf8=", calc.hash());
+  EXPECT_EQ("NZf8k6SPBkYMvhaX8YgzuMgbkLP1XZ+neM8K5wcSsf8=",
+            brillo::data_encoding::Base64Encode(calc.raw_hash()));
 }
 
 TEST_F(HashCalculatorTest, UpdateFileSimpleTest) {
@@ -121,7 +119,6 @@
     HashCalculator calc;
     EXPECT_EQ(2, calc.UpdateFile(data_path, kLengths[i]));
     EXPECT_TRUE(calc.Finalize());
-    EXPECT_EQ(kExpectedHash, calc.hash());
     brillo::Blob raw_hash(std::begin(kExpectedRawHash),
                           std::end(kExpectedRawHash));
     EXPECT_TRUE(raw_hash == calc.raw_hash());
@@ -132,7 +129,8 @@
   EXPECT_EQ(1, calc.UpdateFile(data_path, 1));
   EXPECT_TRUE(calc.Finalize());
   // echo -n h | openssl dgst -sha256 -binary | openssl base64
-  EXPECT_EQ("qqlAJmTxpB9A67xSyZk+tmrrNmYClY/fqig7ceZNsSM=", calc.hash());
+  EXPECT_EQ("qqlAJmTxpB9A67xSyZk+tmrrNmYClY/fqig7ceZNsSM=",
+            brillo::data_encoding::Base64Encode(calc.raw_hash()));
 }
 
 TEST_F(HashCalculatorTest, RawHashOfFileSimpleTest) {
diff --git a/common/http_fetcher_unittest.cc b/common/http_fetcher_unittest.cc
index 73110e9..867216e 100644
--- a/common/http_fetcher_unittest.cc
+++ b/common/http_fetcher_unittest.cc
@@ -32,7 +32,6 @@
 #include <base/strings/string_util.h>
 #include <base/strings/stringprintf.h>
 #include <base/time/time.h>
-#include <brillo/bind_lambda.h>
 #include <brillo/message_loops/base_message_loop.h>
 #include <brillo/message_loops/message_loop.h>
 #include <brillo/message_loops/message_loop_utils.h>
diff --git a/common/hwid_override_unittest.cc b/common/hwid_override_unittest.cc
index 26ef30a..35e6438 100644
--- a/common/hwid_override_unittest.cc
+++ b/common/hwid_override_unittest.cc
@@ -32,7 +32,7 @@
 
   void SetUp() override {
     ASSERT_TRUE(tempdir_.CreateUniqueTempDir());
-    ASSERT_TRUE(base::CreateDirectory(tempdir_.path().Append("etc")));
+    ASSERT_TRUE(base::CreateDirectory(tempdir_.GetPath().Append("etc")));
   }
 
  protected:
@@ -46,22 +46,24 @@
   std::string expected_hwid("expected");
   std::string keyval(HwidOverride::kHwidOverrideKey);
   keyval += ("=" + expected_hwid);
-  ASSERT_EQ(base::WriteFile(tempdir_.path().Append("etc/lsb-release"),
-                            keyval.c_str(), keyval.length()),
+  ASSERT_EQ(base::WriteFile(tempdir_.GetPath().Append("etc/lsb-release"),
+                            keyval.c_str(),
+                            keyval.length()),
             static_cast<int>(keyval.length()));
-  EXPECT_EQ(expected_hwid, HwidOverride::Read(tempdir_.path()));
+  EXPECT_EQ(expected_hwid, HwidOverride::Read(tempdir_.GetPath()));
 }
 
 TEST_F(HwidOverrideTest, ReadNothing) {
   std::string keyval("SOMETHING_ELSE=UNINTERESTING");
-  ASSERT_EQ(base::WriteFile(tempdir_.path().Append("etc/lsb-release"),
-                            keyval.c_str(), keyval.length()),
+  ASSERT_EQ(base::WriteFile(tempdir_.GetPath().Append("etc/lsb-release"),
+                            keyval.c_str(),
+                            keyval.length()),
             static_cast<int>(keyval.length()));
-  EXPECT_EQ(std::string(), HwidOverride::Read(tempdir_.path()));
+  EXPECT_EQ(std::string(), HwidOverride::Read(tempdir_.GetPath()));
 }
 
 TEST_F(HwidOverrideTest, ReadFailure) {
-  EXPECT_EQ(std::string(), HwidOverride::Read(tempdir_.path()));
+  EXPECT_EQ(std::string(), HwidOverride::Read(tempdir_.GetPath()));
 }
 
 }  // namespace chromeos_update_engine
diff --git a/common/prefs_unittest.cc b/common/prefs_unittest.cc
index 73ceb00..aa2eb04 100644
--- a/common/prefs_unittest.cc
+++ b/common/prefs_unittest.cc
@@ -44,7 +44,7 @@
  protected:
   void SetUp() override {
     ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
-    prefs_dir_ = temp_dir_.path();
+    prefs_dir_ = temp_dir_.GetPath();
     ASSERT_TRUE(prefs_.Init(prefs_dir_));
   }
 
diff --git a/common/subprocess_unittest.cc b/common/subprocess_unittest.cc
index cbc9a85..c8996db 100644
--- a/common/subprocess_unittest.cc
+++ b/common/subprocess_unittest.cc
@@ -32,7 +32,6 @@
 #include <base/strings/string_util.h>
 #include <base/strings/stringprintf.h>
 #include <base/time/time.h>
-#include <brillo/bind_lambda.h>
 #include <brillo/message_loops/base_message_loop.h>
 #include <brillo/message_loops/message_loop.h>
 #include <brillo/message_loops/message_loop_utils.h>
@@ -225,7 +224,7 @@
 TEST_F(SubprocessTest, CancelTest) {
   base::ScopedTempDir tempdir;
   ASSERT_TRUE(tempdir.CreateUniqueTempDir());
-  string fifo_path = tempdir.path().Append("fifo").value();
+  string fifo_path = tempdir.GetPath().Append("fifo").value();
   EXPECT_EQ(0, mkfifo(fifo_path.c_str(), 0666));
 
   // Start a process, make sure it is running and try to cancel it. We write
diff --git a/common/test_utils.cc b/common/test_utils.cc
index fb22c80..85f78f9 100644
--- a/common/test_utils.cc
+++ b/common/test_utils.cc
@@ -24,6 +24,7 @@
 #include <stdio.h>
 #include <stdlib.h>
 #include <sys/ioctl.h>
+#include <sys/mount.h>
 #include <sys/stat.h>
 #include <sys/sysmacros.h>
 #include <sys/types.h>
@@ -201,6 +202,13 @@
   device_info.lo_file_name[LO_NAME_SIZE - 1] = '\0';
   TEST_AND_RETURN_FALSE_ERRNO(
       ioctl(loop_device_fd, LOOP_SET_STATUS64, &device_info) == 0);
+  if (writable) {
+    // Make sure loop device isn't read only.
+    int ro = 0;
+    if (ioctl(loop_device_fd, BLKROSET, &ro) != 0) {
+      PLOG(WARNING) << "Failed to mark loop device writable.";
+    }
+  }
   return true;
 }
 
@@ -248,7 +256,7 @@
                                      string* mnt_path,
                                      unsigned long flags) {  // NOLINT - long
   EXPECT_TRUE(temp_dir_.CreateUniqueTempDir());
-  *mnt_path = temp_dir_.path().value();
+  *mnt_path = temp_dir_.GetPath().value();
 
   string loop_dev;
   loop_binder_.reset(
diff --git a/common/test_utils.h b/common/test_utils.h
index ba9f5f2..ddb3d34 100644
--- a/common/test_utils.h
+++ b/common/test_utils.h
@@ -31,6 +31,7 @@
 #include <base/callback.h>
 #include <base/files/file_path.h>
 #include <base/files/scoped_temp_dir.h>
+#include <gmock/gmock.h>
 #include <gtest/gtest.h>
 
 #include "update_engine/common/action.h"
@@ -101,6 +102,11 @@
 
 void FillWithData(brillo::Blob* buffer);
 
+// Compare the value of native array for download source parameter.
+MATCHER_P(DownloadSourceMatcher, source_array, "") {
+  return std::equal(source_array, source_array + kNumDownloadSources, arg);
+}
+
 // Class to unmount FS when object goes out of scope
 class ScopedFilesystemUnmounter {
  public:
diff --git a/common/utils.cc b/common/utils.cc
index b63b867..f651823 100644
--- a/common/utils.cc
+++ b/common/utils.cc
@@ -57,7 +57,6 @@
 #include "update_engine/common/prefs_interface.h"
 #include "update_engine/common/subprocess.h"
 #include "update_engine/payload_consumer/file_descriptor.h"
-#include "update_engine/payload_consumer/file_writer.h"
 
 using base::Time;
 using base::TimeDelta;
@@ -944,8 +943,16 @@
   return str;
 }
 
-string CalculateP2PFileId(const string& payload_hash, size_t payload_size) {
-  string encoded_hash = brillo::data_encoding::Base64Encode(payload_hash);
+// The P2P file id should be the same for devices running new version and old
+// version so that they can share it with each other. The hash in the response
+// was base64 encoded, but now that we switched to use "hash_sha256" field which
+// is hex encoded, we have to convert them back to base64 for P2P. However, the
+// base64 encoded hash was base64 encoded here again historically for some
+// reason, so we keep the same behavior here.
+string CalculateP2PFileId(const brillo::Blob& payload_hash,
+                          size_t payload_size) {
+  string encoded_hash = brillo::data_encoding::Base64Encode(
+      brillo::data_encoding::Base64Encode(payload_hash));
   return base::StringPrintf("cros_update_size_%" PRIuS "_hash_%s",
                             payload_size,
                             encoded_hash.c_str());
diff --git a/common/utils.h b/common/utils.h
index 262a403..e4ffcf8 100644
--- a/common/utils.h
+++ b/common/utils.h
@@ -53,7 +53,7 @@
 std::string StringVectorToString(const std::vector<std::string> &vec_str);
 
 // Calculates the p2p file id from payload hash and size
-std::string CalculateP2PFileId(const std::string& payload_hash,
+std::string CalculateP2PFileId(const brillo::Blob& payload_hash,
                                size_t payload_size);
 
 // Parse the firmware version from one line of output from the
diff --git a/common/utils_unittest.cc b/common/utils_unittest.cc
index 6e9a911..62f9f6c 100644
--- a/common/utils_unittest.cc
+++ b/common/utils_unittest.cc
@@ -100,11 +100,11 @@
 TEST(UtilsTest, IsSymlinkTest) {
   base::ScopedTempDir temp_dir;
   ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
-  string temp_file = temp_dir.path().Append("temp-file").value();
+  string temp_file = temp_dir.GetPath().Append("temp-file").value();
   EXPECT_TRUE(utils::WriteFile(temp_file.c_str(), "", 0));
-  string temp_symlink = temp_dir.path().Append("temp-symlink").value();
+  string temp_symlink = temp_dir.GetPath().Append("temp-symlink").value();
   EXPECT_EQ(0, symlink(temp_file.c_str(), temp_symlink.c_str()));
-  EXPECT_FALSE(utils::IsSymlink(temp_dir.path().value().c_str()));
+  EXPECT_FALSE(utils::IsSymlink(temp_dir.GetPath().value().c_str()));
   EXPECT_FALSE(utils::IsSymlink(temp_file.c_str()));
   EXPECT_TRUE(utils::IsSymlink(temp_symlink.c_str()));
   EXPECT_FALSE(utils::IsSymlink("/non/existent/path"));
@@ -303,8 +303,9 @@
   base::Time::Exploded exploded = (base::Time::Exploded) {
     .year = 2001, .month = 9, .day_of_week = 0, .day_of_month = 9,
     .hour = 1, .minute = 46, .second = 40, .millisecond = 42};
-  EXPECT_EQ(base::Time::FromUTCExploded(exploded),
-            utils::TimeFromStructTimespec(&ts));
+  base::Time time;
+  EXPECT_TRUE(base::Time::FromUTCExploded(exploded, &time));
+  EXPECT_EQ(time, utils::TimeFromStructTimespec(&ts));
 }
 
 TEST(UtilsTest, DecodeAndStoreBase64String) {
@@ -478,23 +479,23 @@
   test_utils::ScopedLoopbackDeviceBinder loop_binder(
       tmp_image, true, &loop_dev);
 
-  EXPECT_FALSE(utils::IsMountpoint(mnt_dir.path().value()));
+  EXPECT_FALSE(utils::IsMountpoint(mnt_dir.GetPath().value()));
   // This is the actual test part. While we hold a file descriptor open for the
   // mounted filesystem, umount should still succeed.
   EXPECT_TRUE(utils::MountFilesystem(
-      loop_dev, mnt_dir.path().value(), MS_RDONLY, "ext4", ""));
+      loop_dev, mnt_dir.GetPath().value(), MS_RDONLY, "ext4", ""));
   // Verify the directory is a mount point now.
-  EXPECT_TRUE(utils::IsMountpoint(mnt_dir.path().value()));
+  EXPECT_TRUE(utils::IsMountpoint(mnt_dir.GetPath().value()));
 
-  string target_file = mnt_dir.path().Append("empty-file").value();
+  string target_file = mnt_dir.GetPath().Append("empty-file").value();
   int fd = HANDLE_EINTR(open(target_file.c_str(), O_RDONLY));
   EXPECT_GE(fd, 0);
-  EXPECT_TRUE(utils::UnmountFilesystem(mnt_dir.path().value()));
+  EXPECT_TRUE(utils::UnmountFilesystem(mnt_dir.GetPath().value()));
   // The filesystem should be already unmounted at this point.
-  EXPECT_FALSE(utils::IsMountpoint(mnt_dir.path().value()));
+  EXPECT_FALSE(utils::IsMountpoint(mnt_dir.GetPath().value()));
   IGNORE_EINTR(close(fd));
   // The filesystem was already unmounted so this call should fail.
-  EXPECT_FALSE(utils::UnmountFilesystem(mnt_dir.path().value()));
+  EXPECT_FALSE(utils::UnmountFilesystem(mnt_dir.GetPath().value()));
 }
 
 TEST(UtilsTest, IsMountpointTest) {
@@ -503,7 +504,7 @@
 
   base::ScopedTempDir mnt_dir;
   EXPECT_TRUE(mnt_dir.CreateUniqueTempDir());
-  EXPECT_FALSE(utils::IsMountpoint(mnt_dir.path().value()));
+  EXPECT_FALSE(utils::IsMountpoint(mnt_dir.GetPath().value()));
 
   base::FilePath file;
   EXPECT_TRUE(base::CreateTemporaryFile(&file));
diff --git a/common_service.cc b/common_service.cc
index ee890e1..9f3b862 100644
--- a/common_service.cc
+++ b/common_service.cc
@@ -16,12 +16,13 @@
 
 #include "update_engine/common_service.h"
 
+#include <set>
 #include <string>
 
+#include <base/bind.h>
 #include <base/location.h>
 #include <base/logging.h>
 #include <base/strings/stringprintf.h>
-#include <brillo/bind_lambda.h>
 #include <brillo/message_loops/message_loop.h>
 #include <brillo/strings/string_utils.h>
 #include <policy/device_policy.h>
@@ -40,7 +41,10 @@
 using base::StringPrintf;
 using brillo::ErrorPtr;
 using brillo::string_utils::ToString;
+using std::set;
 using std::string;
+using update_engine::UpdateAttemptFlags;
+using update_engine::UpdateEngineStatus;
 
 namespace chromeos_update_engine {
 
@@ -69,19 +73,35 @@
 
 // org::chromium::UpdateEngineInterfaceInterface methods implementation.
 
+bool UpdateEngineService::SetUpdateAttemptFlags(ErrorPtr* /* error */,
+                                                int32_t in_flags_as_int) {
+  auto flags = static_cast<UpdateAttemptFlags>(in_flags_as_int);
+  LOG(INFO) << "Setting Update Attempt Flags: "
+            << "flags=0x" << std::hex << flags << " "
+            << "RestrictDownload="
+            << ((flags & UpdateAttemptFlags::kFlagRestrictDownload) ? "yes"
+                                                                    : "no");
+  system_state_->update_attempter()->SetUpdateAttemptFlags(flags);
+  return true;
+}
+
 bool UpdateEngineService::AttemptUpdate(ErrorPtr* /* error */,
                                         const string& in_app_version,
                                         const string& in_omaha_url,
-                                        int32_t in_flags_as_int) {
-  AttemptUpdateFlags flags = static_cast<AttemptUpdateFlags>(in_flags_as_int);
-  bool interactive = !(flags & kAttemptUpdateFlagNonInteractive);
+                                        int32_t in_flags_as_int,
+                                        bool* out_result) {
+  auto flags = static_cast<UpdateAttemptFlags>(in_flags_as_int);
+  bool interactive = !(flags & UpdateAttemptFlags::kFlagNonInteractive);
+  bool restrict_downloads = (flags & UpdateAttemptFlags::kFlagRestrictDownload);
 
   LOG(INFO) << "Attempt update: app_version=\"" << in_app_version << "\" "
             << "omaha_url=\"" << in_omaha_url << "\" "
             << "flags=0x" << std::hex << flags << " "
-            << "interactive=" << (interactive ? "yes" : "no");
-  system_state_->update_attempter()->CheckForUpdate(
-      in_app_version, in_omaha_url, interactive);
+            << "interactive=" << (interactive ? "yes " : "no ")
+            << "RestrictDownload=" << (restrict_downloads ? "yes " : "no ");
+
+  *out_result = system_state_->update_attempter()->CheckForUpdate(
+      in_app_version, in_omaha_url, flags);
   return true;
 }
 
@@ -114,16 +134,8 @@
 }
 
 bool UpdateEngineService::GetStatus(ErrorPtr* error,
-                                    int64_t* out_last_checked_time,
-                                    double* out_progress,
-                                    string* out_current_operation,
-                                    string* out_new_version,
-                                    int64_t* out_new_size) {
-  if (!system_state_->update_attempter()->GetStatus(out_last_checked_time,
-                                                    out_progress,
-                                                    out_current_operation,
-                                                    out_new_version,
-                                                    out_new_size)) {
+                                    UpdateEngineStatus* out_status) {
+  if (!system_state_->update_attempter()->GetStatus(out_status)) {
     LogAndSetError(error, FROM_HERE, "GetStatus failed.");
     return false;
   }
@@ -246,12 +258,24 @@
 
 bool UpdateEngineService::SetUpdateOverCellularPermission(ErrorPtr* error,
                                                           bool in_allowed) {
-  ConnectionManagerInterface* connection_manager =
-      system_state_->connection_manager();
+  set<string> allowed_types;
+  const policy::DevicePolicy* device_policy = system_state_->device_policy();
+
+  // The device_policy is loaded in a lazy way before an update check. Load it
+  // now from the libbrillo cache if it wasn't already loaded.
+  if (!device_policy) {
+    UpdateAttempter* update_attempter = system_state_->update_attempter();
+    if (update_attempter) {
+      update_attempter->RefreshDevicePolicy();
+      device_policy = system_state_->device_policy();
+    }
+  }
 
   // Check if this setting is allowed by the device policy.
-  if (connection_manager->IsAllowedConnectionTypesForUpdateSet()) {
-    LogAndSetError(error, FROM_HERE,
+  if (device_policy &&
+      device_policy->GetAllowedConnectionTypesForUpdate(&allowed_types)) {
+    LogAndSetError(error,
+                   FROM_HERE,
                    "Ignoring the update over cellular setting since there's "
                    "a device policy enforcing this setting.");
     return false;
@@ -262,9 +286,9 @@
 
   PrefsInterface* prefs = system_state_->prefs();
 
-  if (!prefs ||
-      !prefs->SetBoolean(kPrefsUpdateOverCellularPermission, in_allowed)) {
-    LogAndSetError(error, FROM_HERE,
+  if (!prefs->SetBoolean(kPrefsUpdateOverCellularPermission, in_allowed)) {
+    LogAndSetError(error,
+                   FROM_HERE,
                    string("Error setting the update over cellular to ") +
                        (in_allowed ? "true" : "false"));
     return false;
@@ -272,63 +296,24 @@
   return true;
 }
 
-bool UpdateEngineService::SetUpdateOverCellularTarget(
-    brillo::ErrorPtr* error, const std::string &target_version,
-    int64_t target_size) {
-  ConnectionManagerInterface* connection_manager =
-      system_state_->connection_manager();
-
-  // Check if this setting is allowed by the device policy.
-  if (connection_manager->IsAllowedConnectionTypesForUpdateSet()) {
-    LogAndSetError(error, FROM_HERE,
-                   "Ignoring the update over cellular setting since there's "
-                   "a device policy enforcing this setting.");
-    return false;
-  }
-
-  // If the policy wasn't loaded yet, then it is still OK to change the local
-  // setting because the policy will be checked again during the update check.
-
-  PrefsInterface* prefs = system_state_->prefs();
-
-  if (!prefs ||
-      !prefs->SetString(kPrefsUpdateOverCellularTargetVersion,
-                        target_version) ||
-      !prefs->SetInt64(kPrefsUpdateOverCellularTargetSize, target_size)) {
-    LogAndSetError(error, FROM_HERE,
-                   "Error setting the target for update over cellular.");
-    return false;
-  }
-  return true;
-}
-
-bool UpdateEngineService::GetUpdateOverCellularPermission(ErrorPtr* error,
+bool UpdateEngineService::GetUpdateOverCellularPermission(ErrorPtr* /* error */,
                                                           bool* out_allowed) {
-  ConnectionManagerInterface* connection_manager =
-      system_state_->connection_manager();
+  ConnectionManagerInterface* cm = system_state_->connection_manager();
 
-  if (connection_manager->IsAllowedConnectionTypesForUpdateSet()) {
-    // We have device policy, so ignore the user preferences.
-    *out_allowed = connection_manager->IsUpdateAllowedOver(
-        ConnectionType::kCellular, ConnectionTethering::kUnknown);
-  } else {
-    PrefsInterface* prefs = system_state_->prefs();
-
-    if (!prefs || !prefs->Exists(kPrefsUpdateOverCellularPermission)) {
-      // Update is not allowed as user preference is not set or not available.
-      *out_allowed = false;
-      return true;
-    }
-
-    bool is_allowed;
-
-    if (!prefs->GetBoolean(kPrefsUpdateOverCellularPermission, &is_allowed)) {
-      LogAndSetError(error, FROM_HERE,
-                     "Error getting the update over cellular preference.");
-      return false;
-    }
-    *out_allowed = is_allowed;
+  // The device_policy is loaded in a lazy way before an update check and is
+  // used to determine if an update is allowed over cellular. Load the device
+  // policy now from the libbrillo cache if it wasn't already loaded.
+  if (!system_state_->device_policy()) {
+    UpdateAttempter* update_attempter = system_state_->update_attempter();
+    if (update_attempter)
+      update_attempter->RefreshDevicePolicy();
   }
+
+  // Return the current setting based on the same logic used while checking for
+  // updates. A log message could be printed as the result of this test.
+  LOG(INFO) << "Checking if updates over cellular networks are allowed:";
+  *out_allowed = cm->IsUpdateAllowedOver(ConnectionType::kCellular,
+                                         ConnectionTethering::kUnknown);
   return true;
 }
 
diff --git a/common_service.h b/common_service.h
index 4320d09..544dd93 100644
--- a/common_service.h
+++ b/common_service.h
@@ -24,17 +24,13 @@
 #include <base/memory/ref_counted.h>
 #include <brillo/errors/error.h>
 
+#include "update_engine/client_library/include/update_engine/update_status.h"
 #include "update_engine/system_state.h"
 
 namespace chromeos_update_engine {
 
 class UpdateEngineService {
  public:
-  // Flags used in the AttemptUpdateWithFlags() D-Bus method.
-  typedef enum {
-    kAttemptUpdateFlagNonInteractive = (1<<0)
-  } AttemptUpdateFlags;
-
   // Error domain for all the service errors.
   static const char* const kErrorDomain;
 
@@ -44,10 +40,17 @@
   explicit UpdateEngineService(SystemState* system_state);
   virtual ~UpdateEngineService() = default;
 
+  // Set flags that influence how updates and checks are performed.  These
+  // influence all future checks and updates until changed or the device
+  // reboots.  The |in_flags_as_int| values are a union of values from
+  // |UpdateAttemptFlags|
+  bool SetUpdateAttemptFlags(brillo::ErrorPtr* error, int32_t in_flags_as_int);
+
   bool AttemptUpdate(brillo::ErrorPtr* error,
                      const std::string& in_app_version,
                      const std::string& in_omaha_url,
-                     int32_t in_flags_as_int);
+                     int32_t in_flags_as_int,
+                     bool* out_result);
 
   bool AttemptRollback(brillo::ErrorPtr* error, bool in_powerwash);
 
@@ -63,11 +66,7 @@
   // progress, the number of operations, size to download and overall progress
   // is reported.
   bool GetStatus(brillo::ErrorPtr* error,
-                 int64_t* out_last_checked_time,
-                 double* out_progress,
-                 std::string* out_current_operation,
-                 std::string* out_new_version,
-                 int64_t* out_new_size);
+                 update_engine::UpdateEngineStatus* out_status);
 
   // Reboots the device if an update is applied and a reboot is required.
   bool RebootIfNeeded(brillo::ErrorPtr* error);
@@ -115,12 +114,6 @@
   bool SetUpdateOverCellularPermission(brillo::ErrorPtr* error,
                                        bool in_allowed);
 
-  // If there's no device policy installed, sets the update over cellular
-  // target. Otherwise, this method returns with an error.
-  bool SetUpdateOverCellularTarget(brillo::ErrorPtr* error,
-                                   const std::string& target_version,
-                                   int64_t target_size);
-
   // Returns the current value of the update over cellular network setting,
   // either forced by the device policy if the device is enrolled or the current
   // user preference otherwise.
diff --git a/common_service_unittest.cc b/common_service_unittest.cc
index 17265b9..d9ef567 100644
--- a/common_service_unittest.cc
+++ b/common_service_unittest.cc
@@ -28,9 +28,10 @@
 #include "update_engine/omaha_utils.h"
 
 using std::string;
+using testing::_;
 using testing::Return;
 using testing::SetArgPointee;
-using testing::_;
+using update_engine::UpdateAttemptFlags;
 
 namespace chromeos_update_engine {
 
@@ -56,13 +57,32 @@
 };
 
 TEST_F(UpdateEngineServiceTest, AttemptUpdate) {
-  EXPECT_CALL(*mock_update_attempter_, CheckForUpdate(
-      "app_ver", "url", false /* interactive */));
-  // The update is non-interactive when we pass the non-interactive flag.
-  EXPECT_TRUE(common_service_.AttemptUpdate(
-      &error_, "app_ver", "url",
-      UpdateEngineService::kAttemptUpdateFlagNonInteractive));
+  EXPECT_CALL(
+      *mock_update_attempter_,
+      CheckForUpdate("app_ver", "url", UpdateAttemptFlags::kFlagNonInteractive))
+      .WillOnce(Return(true));
+
+  // The non-interactive flag needs to be passed through to CheckForUpdate.
+  bool result = false;
+  EXPECT_TRUE(
+      common_service_.AttemptUpdate(&error_,
+                                    "app_ver",
+                                    "url",
+                                    UpdateAttemptFlags::kFlagNonInteractive,
+                                    &result));
   EXPECT_EQ(nullptr, error_);
+  EXPECT_TRUE(result);
+}
+
+TEST_F(UpdateEngineServiceTest, AttemptUpdateReturnsFalse) {
+  EXPECT_CALL(*mock_update_attempter_,
+              CheckForUpdate("app_ver", "url", UpdateAttemptFlags::kNone))
+      .WillOnce(Return(false));
+  bool result = true;
+  EXPECT_TRUE(common_service_.AttemptUpdate(
+      &error_, "app_ver", "url", UpdateAttemptFlags::kNone, &result));
+  EXPECT_EQ(nullptr, error_);
+  EXPECT_FALSE(result);
 }
 
 // SetChannel is allowed when there's no device policy (the device is not
diff --git a/connection_manager.cc b/connection_manager.cc
index 5085842..d15faf0 100644
--- a/connection_manager.cc
+++ b/connection_manager.cc
@@ -30,7 +30,6 @@
 #include "update_engine/connection_utils.h"
 #include "update_engine/shill_proxy.h"
 #include "update_engine/system_state.h"
-#include "update_engine/update_attempter.h"
 
 using org::chromium::flimflam::ManagerProxyInterface;
 using org::chromium::flimflam::ServiceProxyInterface;
@@ -59,47 +58,54 @@
 
     case ConnectionType::kCellular: {
       set<string> allowed_types;
-
       const policy::DevicePolicy* device_policy =
           system_state_->device_policy();
 
-      // The device_policy is loaded in a lazy way before an update check. Load
-      // it now from the libbrillo cache if it wasn't already loaded.
+      // A device_policy is loaded in a lazy way right before an update check,
+      // so the device_policy should be already loaded at this point. If it's
+      // not, return a safe value for this setting.
       if (!device_policy) {
-        UpdateAttempter* update_attempter = system_state_->update_attempter();
-        if (update_attempter) {
-          update_attempter->RefreshDevicePolicy();
-          device_policy = system_state_->device_policy();
-        }
-      }
-
-      if (!device_policy) {
-        // Device policy fails to be loaded (possibly due to guest account). We
-        // do not check the local user setting here, which should be checked by
-        // |OmahaRequestAction| during checking for update.
-        LOG(INFO) << "Allowing updates over cellular as device policy "
-                     "fails to be loaded.";
-        return true;
+        LOG(INFO) << "Disabling updates over cellular networks as there's no "
+                     "device policy loaded yet.";
+        return false;
       }
 
       if (device_policy->GetAllowedConnectionTypesForUpdate(&allowed_types)) {
         // The update setting is enforced by the device policy.
 
-        if (!ContainsKey(allowed_types, shill::kTypeCellular)) {
-          LOG(INFO) << "Disabling updates over cellular per device policy.";
+        if (!base::ContainsKey(allowed_types, shill::kTypeCellular)) {
+          LOG(INFO) << "Disabling updates over cellular connection as it's not "
+                       "allowed in the device policy.";
           return false;
         }
 
         LOG(INFO) << "Allowing updates over cellular per device policy.";
         return true;
-      }
+      } else {
+        // There's no update setting in the device policy, using the local user
+        // setting.
+        PrefsInterface* prefs = system_state_->prefs();
 
-      // If there's no update setting in the device policy, we do not check
-      // the local user setting here, which should be checked by
-      // |OmahaRequestAction| during checking for update.
-      LOG(INFO) << "Allowing updates over cellular as device policy does "
-                   "not include update setting.";
-      return true;
+        if (!prefs || !prefs->Exists(kPrefsUpdateOverCellularPermission)) {
+          LOG(INFO) << "Disabling updates over cellular connection as there's "
+                       "no device policy setting nor user preference present.";
+          return false;
+        }
+
+        bool stored_value;
+        if (!prefs->GetBoolean(kPrefsUpdateOverCellularPermission,
+                               &stored_value)) {
+          return false;
+        }
+
+        if (!stored_value) {
+          LOG(INFO) << "Disabling updates over cellular connection per user "
+                       "setting.";
+          return false;
+        }
+        LOG(INFO) << "Allowing updates over cellular per user setting.";
+        return true;
+      }
     }
 
     default:
@@ -114,22 +120,6 @@
   }
 }
 
-bool ConnectionManager::IsAllowedConnectionTypesForUpdateSet() const {
-  const policy::DevicePolicy* device_policy = system_state_->device_policy();
-  if (!device_policy) {
-    LOG(INFO) << "There's no device policy loaded yet.";
-    return false;
-  }
-
-  set<string> allowed_types;
-  if (!device_policy->GetAllowedConnectionTypesForUpdate(
-      &allowed_types)) {
-    return false;
-  }
-
-  return true;
-}
-
 bool ConnectionManager::GetConnectionProperties(
     ConnectionType* out_type, ConnectionTethering* out_tethering) {
   dbus::ObjectPath default_service_path;
diff --git a/connection_manager.h b/connection_manager.h
index 864985e..e5a9d49 100644
--- a/connection_manager.h
+++ b/connection_manager.h
@@ -43,7 +43,6 @@
                                ConnectionTethering* out_tethering) override;
   bool IsUpdateAllowedOver(ConnectionType type,
                            ConnectionTethering tethering) const override;
-  bool IsAllowedConnectionTypesForUpdateSet() const override;
 
  private:
   // Returns (via out_path) the default network path, or empty string if
diff --git a/connection_manager_android.cc b/connection_manager_android.cc
index c594b53..2dd824a 100644
--- a/connection_manager_android.cc
+++ b/connection_manager_android.cc
@@ -34,9 +34,5 @@
     ConnectionType type, ConnectionTethering tethering) const {
   return true;
 }
-bool ConnectionManagerAndroid::IsAllowedConnectionTypesForUpdateSet() const {
-  return false;
-}
-
 
 }  // namespace chromeos_update_engine
diff --git a/connection_manager_android.h b/connection_manager_android.h
index 006f4ea..0cd5e73 100644
--- a/connection_manager_android.h
+++ b/connection_manager_android.h
@@ -34,7 +34,6 @@
                                ConnectionTethering* out_tethering) override;
   bool IsUpdateAllowedOver(ConnectionType type,
                            ConnectionTethering tethering) const override;
-  bool IsAllowedConnectionTypesForUpdateSet() const override;
 
   DISALLOW_COPY_AND_ASSIGN(ConnectionManagerAndroid);
 };
diff --git a/connection_manager_interface.h b/connection_manager_interface.h
index 2faeb80..df8eb4b 100644
--- a/connection_manager_interface.h
+++ b/connection_manager_interface.h
@@ -46,10 +46,6 @@
   virtual bool IsUpdateAllowedOver(ConnectionType type,
                                    ConnectionTethering tethering) const = 0;
 
-  // Returns true if the allowed connection types for update is set in the
-  // device policy. Otherwise, returns false.
-  virtual bool IsAllowedConnectionTypesForUpdateSet() const = 0;
-
  protected:
   ConnectionManagerInterface() = default;
 
diff --git a/connection_manager_unittest.cc b/connection_manager_unittest.cc
index 0005b2e..e26a686 100644
--- a/connection_manager_unittest.cc
+++ b/connection_manager_unittest.cc
@@ -276,24 +276,16 @@
                                         ConnectionTethering::kConfirmed));
 }
 
-TEST_F(ConnectionManagerTest, AllowUpdatesOverCellularByDefaultTest) {
-  policy::MockDevicePolicy device_policy;
-  // Set an empty device policy.
-  fake_system_state_.set_device_policy(&device_policy);
-
-  EXPECT_TRUE(cmut_.IsUpdateAllowedOver(ConnectionType::kCellular,
-                                        ConnectionTethering::kUnknown));
+TEST_F(ConnectionManagerTest, BlockUpdatesOverCellularByDefaultTest) {
+  EXPECT_FALSE(cmut_.IsUpdateAllowedOver(ConnectionType::kCellular,
+                                         ConnectionTethering::kUnknown));
 }
 
-TEST_F(ConnectionManagerTest, AllowUpdatesOverTetheredNetworkByDefaultTest) {
-  policy::MockDevicePolicy device_policy;
-  // Set an empty device policy.
-  fake_system_state_.set_device_policy(&device_policy);
-
-  EXPECT_TRUE(cmut_.IsUpdateAllowedOver(ConnectionType::kWifi,
-                                        ConnectionTethering::kConfirmed));
-  EXPECT_TRUE(cmut_.IsUpdateAllowedOver(ConnectionType::kEthernet,
-                                        ConnectionTethering::kConfirmed));
+TEST_F(ConnectionManagerTest, BlockUpdatesOverTetheredNetworkByDefaultTest) {
+  EXPECT_FALSE(cmut_.IsUpdateAllowedOver(ConnectionType::kWifi,
+                                         ConnectionTethering::kConfirmed));
+  EXPECT_FALSE(cmut_.IsUpdateAllowedOver(ConnectionType::kEthernet,
+                                         ConnectionTethering::kConfirmed));
   EXPECT_TRUE(cmut_.IsUpdateAllowedOver(ConnectionType::kWifi,
                                         ConnectionTethering::kSuspected));
 }
@@ -318,28 +310,62 @@
                                          ConnectionTethering::kUnknown));
 }
 
-TEST_F(ConnectionManagerTest, AllowUpdatesOver3GIfPolicyIsNotSet) {
-  policy::MockDevicePolicy device_policy;
+TEST_F(ConnectionManagerTest, BlockUpdatesOver3GIfErrorInPolicyFetchTest) {
+  policy::MockDevicePolicy allow_3g_policy;
 
-  fake_system_state_.set_device_policy(&device_policy);
+  fake_system_state_.set_device_policy(&allow_3g_policy);
+
+  set<string> allowed_set;
+  allowed_set.insert(StringForConnectionType(ConnectionType::kCellular));
 
   // Return false for GetAllowedConnectionTypesForUpdate and see
-  // that updates are allowed as device policy is not set. Further
-  // check is left to |OmahaRequestAction|.
-  EXPECT_CALL(device_policy, GetAllowedConnectionTypesForUpdate(_))
+  // that updates are still blocked for 3G despite the value being in
+  // the string set above.
+  EXPECT_CALL(allow_3g_policy, GetAllowedConnectionTypesForUpdate(_))
       .Times(1)
-      .WillOnce(Return(false));
+      .WillOnce(DoAll(SetArgPointee<0>(allowed_set), Return(false)));
 
-  EXPECT_TRUE(cmut_.IsUpdateAllowedOver(ConnectionType::kCellular,
-                                        ConnectionTethering::kUnknown));
+  EXPECT_FALSE(cmut_.IsUpdateAllowedOver(ConnectionType::kCellular,
+                                         ConnectionTethering::kUnknown));
 }
 
-TEST_F(ConnectionManagerTest,
-       AllowUpdatesOverCellularIfPolicyFailsToBeLoaded) {
-  fake_system_state_.set_device_policy(nullptr);
+TEST_F(ConnectionManagerTest, UseUserPrefForUpdatesOverCellularIfNoPolicyTest) {
+  policy::MockDevicePolicy no_policy;
+  testing::NiceMock<MockPrefs>* prefs = fake_system_state_.mock_prefs();
 
+  fake_system_state_.set_device_policy(&no_policy);
+
+  // No setting enforced by the device policy, user prefs should be used.
+  EXPECT_CALL(no_policy, GetAllowedConnectionTypesForUpdate(_))
+      .Times(3)
+      .WillRepeatedly(Return(false));
+
+  // No user pref: block.
+  EXPECT_CALL(*prefs, Exists(kPrefsUpdateOverCellularPermission))
+      .Times(1)
+      .WillOnce(Return(false));
+  EXPECT_FALSE(cmut_.IsUpdateAllowedOver(ConnectionType::kCellular,
+                                         ConnectionTethering::kUnknown));
+
+  // Allow per user pref.
+  EXPECT_CALL(*prefs, Exists(kPrefsUpdateOverCellularPermission))
+      .Times(1)
+      .WillOnce(Return(true));
+  EXPECT_CALL(*prefs, GetBoolean(kPrefsUpdateOverCellularPermission, _))
+      .Times(1)
+      .WillOnce(DoAll(SetArgPointee<1>(true), Return(true)));
   EXPECT_TRUE(cmut_.IsUpdateAllowedOver(ConnectionType::kCellular,
                                         ConnectionTethering::kUnknown));
+
+  // Block per user pref.
+  EXPECT_CALL(*prefs, Exists(kPrefsUpdateOverCellularPermission))
+      .Times(1)
+      .WillOnce(Return(true));
+  EXPECT_CALL(*prefs, GetBoolean(kPrefsUpdateOverCellularPermission, _))
+      .Times(1)
+      .WillOnce(DoAll(SetArgPointee<1>(false), Return(true)));
+  EXPECT_FALSE(cmut_.IsUpdateAllowedOver(ConnectionType::kCellular,
+                                         ConnectionTethering::kUnknown));
 }
 
 TEST_F(ConnectionManagerTest, StringForConnectionTypeTest) {
diff --git a/dbus_bindings/org.chromium.UpdateEngineInterface.dbus-xml b/dbus_bindings/org.chromium.UpdateEngineInterface.dbus-xml
index a20f33f..848f775 100644
--- a/dbus_bindings/org.chromium.UpdateEngineInterface.dbus-xml
+++ b/dbus_bindings/org.chromium.UpdateEngineInterface.dbus-xml
@@ -67,10 +67,6 @@
     <method name="SetUpdateOverCellularPermission">
       <arg type="b" name="allowed" direction="in" />
     </method>
-    <method name="SetUpdateOverCellularTarget">
-      <arg type="s" name="target_version" direction="in" />
-      <arg type="x" name="target_size" direction="in" />
-    </method>
     <method name="GetUpdateOverCellularPermission">
       <arg type="b" name="allowed" direction="out" />
     </method>
diff --git a/dbus_service.cc b/dbus_service.cc
index 731d489..3a35c71 100644
--- a/dbus_service.cc
+++ b/dbus_service.cc
@@ -25,6 +25,7 @@
 using brillo::ErrorPtr;
 using chromeos_update_engine::UpdateEngineService;
 using std::string;
+using update_engine::UpdateEngineStatus;
 
 DBusUpdateEngineService::DBusUpdateEngineService(SystemState* system_state)
     : common_(new UpdateEngineService{system_state}) {
@@ -123,14 +124,6 @@
   return common_->SetUpdateOverCellularPermission(error, in_allowed);
 }
 
-bool DBusUpdateEngineService::SetUpdateOverCellularTarget(
-    brillo::ErrorPtr* error,
-    const std::string& target_version,
-    int64_t target_size) {
-  return common_->SetUpdateOverCellularTarget(error, target_version,
-                                              target_size);
-}
-
 bool DBusUpdateEngineService::GetUpdateOverCellularPermission(
     ErrorPtr* error, bool* out_allowed) {
   return common_->GetUpdateOverCellularPermission(error, out_allowed);
@@ -180,14 +173,13 @@
                                         dbus::Bus::REQUIRE_PRIMARY);
 }
 
-void UpdateEngineAdaptor::SendStatusUpdate(int64_t last_checked_time,
-                                           double progress,
-                                           update_engine::UpdateStatus status,
-                                           const string& new_version,
-                                           int64_t new_size) {
-  const string str_status = UpdateStatusToString(status);
-  SendStatusUpdateSignal(
-      last_checked_time, progress, str_status, new_version, new_size);
+void UpdateEngineAdaptor::SendStatusUpdate(
+    const UpdateEngineStatus& update_engine_status) {
+  SendStatusUpdateSignal(update_engine_status.last_checked_time,
+                         update_engine_status.progress,
+                         UpdateStatusToString(update_engine_status.status),
+                         update_engine_status.new_version,
+                         update_engine_status.new_size);
 }
 
 }  // namespace chromeos_update_engine
diff --git a/dbus_service.h b/dbus_service.h
index 644dcee..b754661 100644
--- a/dbus_service.h
+++ b/dbus_service.h
@@ -114,12 +114,6 @@
   bool SetUpdateOverCellularPermission(brillo::ErrorPtr* error,
                                        bool in_allowed) override;
 
-  // If there's no device policy installed, sets the update over cellular
-  // target. Otherwise, this method returns with an error.
-  bool SetUpdateOverCellularTarget(brillo::ErrorPtr* error,
-                                   const std::string& target_version,
-                                   int64_t target_size) override;
-
   // Returns the current value of the update over cellular network setting,
   // either forced by the device policy if the device is enrolled or the current
   // user preference otherwise.
@@ -173,11 +167,8 @@
   bool RequestOwnership();
 
   // ServiceObserverInterface overrides.
-  void SendStatusUpdate(int64_t last_checked_time,
-                        double progress,
-                        update_engine::UpdateStatus status,
-                        const std::string& new_version,
-                        int64_t new_size) override;
+  void SendStatusUpdate(
+      const update_engine::UpdateEngineStatus& update_engine_status) override;
 
   void SendPayloadApplicationComplete(ErrorCode error_code) override {}
 
diff --git a/fake_p2p_manager_configuration.h b/fake_p2p_manager_configuration.h
index 1bc1dc8..c1cf4f2 100644
--- a/fake_p2p_manager_configuration.h
+++ b/fake_p2p_manager_configuration.h
@@ -37,9 +37,7 @@
   }
 
   // P2PManager::Configuration override
-  base::FilePath GetP2PDir() override {
-    return p2p_dir_.path();
-  }
+  base::FilePath GetP2PDir() override { return p2p_dir_.GetPath(); }
 
   // P2PManager::Configuration override
   std::vector<std::string> GetInitctlArgs(bool is_start) override {
diff --git a/fake_system_state.cc b/fake_system_state.cc
index 9403f8b..1bfcafa 100644
--- a/fake_system_state.cc
+++ b/fake_system_state.cc
@@ -27,7 +27,7 @@
       clock_(&fake_clock_),
       connection_manager_(&mock_connection_manager_),
       hardware_(&fake_hardware_),
-      metrics_lib_(&mock_metrics_lib_),
+      metrics_reporter_(&mock_metrics_reporter_),
       prefs_(&mock_prefs_),
       powerwash_safe_prefs_(&mock_powerwash_safe_prefs_),
       payload_state_(&mock_payload_state_),
diff --git a/fake_system_state.h b/fake_system_state.h
index 2225933..67ad3aa 100644
--- a/fake_system_state.h
+++ b/fake_system_state.h
@@ -27,6 +27,7 @@
 #include "update_engine/common/fake_hardware.h"
 #include "update_engine/common/mock_prefs.h"
 #include "update_engine/mock_connection_manager.h"
+#include "update_engine/mock_metrics_reporter.h"
 #include "update_engine/mock_omaha_request_params.h"
 #include "update_engine/mock_p2p_manager.h"
 #include "update_engine/mock_payload_state.h"
@@ -66,8 +67,9 @@
 
   inline HardwareInterface* hardware() override { return hardware_; }
 
-  inline MetricsLibraryInterface* metrics_lib() override {
-    return metrics_lib_;
+  inline MetricsReporterInterface* metrics_reporter() override {
+    CHECK(metrics_reporter_ != nullptr);
+    return metrics_reporter_;
   }
 
   inline PrefsInterface* prefs() override { return prefs_; }
@@ -122,8 +124,9 @@
     hardware_ = hardware ? hardware : &fake_hardware_;
   }
 
-  inline void set_metrics_lib(MetricsLibraryInterface* metrics_lib) {
-    metrics_lib_ = metrics_lib ? metrics_lib : &mock_metrics_lib_;
+  inline void set_metrics_reporter(MetricsReporterInterface* metrics_reporter) {
+    metrics_reporter_ =
+        metrics_reporter ? metrics_reporter : &mock_metrics_reporter_;
   }
 
   inline void set_prefs(PrefsInterface* prefs) {
@@ -187,9 +190,9 @@
     return &fake_hardware_;
   }
 
-  inline testing::NiceMock<MetricsLibraryMock>* mock_metrics_lib() {
-    CHECK(metrics_lib_ == &mock_metrics_lib_);
-    return &mock_metrics_lib_;
+  inline testing::NiceMock<MockMetricsReporter>* mock_metrics_reporter() {
+    CHECK(metrics_reporter_ == &mock_metrics_reporter_);
+    return &mock_metrics_reporter_;
   }
 
   inline testing::NiceMock<MockPrefs> *mock_prefs() {
@@ -233,7 +236,7 @@
   FakeClock fake_clock_;
   testing::NiceMock<MockConnectionManager> mock_connection_manager_;
   FakeHardware fake_hardware_;
-  testing::NiceMock<MetricsLibraryMock> mock_metrics_lib_;
+  testing::NiceMock<MockMetricsReporter> mock_metrics_reporter_;
   testing::NiceMock<MockPrefs> mock_prefs_;
   testing::NiceMock<MockPrefs> mock_powerwash_safe_prefs_;
   testing::NiceMock<MockPayloadState> mock_payload_state_;
@@ -249,7 +252,7 @@
   ClockInterface* clock_;
   ConnectionManagerInterface* connection_manager_;
   HardwareInterface* hardware_;
-  MetricsLibraryInterface* metrics_lib_;
+  MetricsReporterInterface* metrics_reporter_;
   PrefsInterface* prefs_;
   PrefsInterface* powerwash_safe_prefs_;
   PayloadStateInterface* payload_state_;
diff --git a/hardware_android.cc b/hardware_android.cc
index c388b82..947b13a 100644
--- a/hardware_android.cc
+++ b/hardware_android.cc
@@ -25,15 +25,18 @@
 
 #include <bootloader.h>
 
+#include <android-base/properties.h>
 #include <base/files/file_util.h>
 #include <base/strings/stringprintf.h>
-#include <cutils/properties.h>
 
 #include "update_engine/common/hardware.h"
 #include "update_engine/common/platform_constants.h"
 #include "update_engine/common/utils.h"
 #include "update_engine/utils_android.h"
 
+using android::base::GetBoolProperty;
+using android::base::GetIntProperty;
+using android::base::GetProperty;
 using std::string;
 
 namespace chromeos_update_engine {
@@ -54,6 +57,7 @@
 const char kPropProductManufacturer[] = "ro.product.manufacturer";
 const char kPropBootHardwareSKU[] = "ro.boot.hardware.sku";
 const char kPropBootRevision[] = "ro.boot.revision";
+const char kPropBuildDateUTC[] = "ro.build.date.utc";
 
 // Write a recovery command line |message| to the BCB. The arguments to recovery
 // must be separated by '\n'. An empty string will erase the BCB.
@@ -71,8 +75,7 @@
            std::min(message.size(), sizeof(boot.recovery) - 1));
   }
 
-  int fd =
-      HANDLE_EINTR(open(misc_device.value().c_str(), O_WRONLY | O_SYNC, 0600));
+  int fd = HANDLE_EINTR(open(misc_device.value().c_str(), O_WRONLY | O_SYNC));
   if (fd < 0) {
     PLOG(ERROR) << "Opening misc";
     return false;
@@ -121,7 +124,7 @@
   //
   // In case of a non-bool value, we take the most restrictive option and
   // assume we are in an official-build.
-  return property_get_bool("ro.secure", 1) != 0;
+  return GetBoolProperty("ro.secure", true);
 }
 
 bool HardwareAndroid::IsNormalBootMode() const {
@@ -129,7 +132,7 @@
   // update_engine will allow extra developers options, such as providing a
   // different update URL. In case of error, we assume the build is in
   // normal-mode.
-  return property_get_bool("ro.debuggable", 0) != 1;
+  return !GetBoolProperty("ro.debuggable", false);
 }
 
 bool HardwareAndroid::AreDevFeaturesEnabled() const {
@@ -149,26 +152,19 @@
 }
 
 string HardwareAndroid::GetHardwareClass() const {
-  char manufacturer[PROPERTY_VALUE_MAX];
-  char sku[PROPERTY_VALUE_MAX];
-  char revision[PROPERTY_VALUE_MAX];
-  property_get(kPropBootHardwareSKU, sku, "");
-  property_get(kPropProductManufacturer, manufacturer, "");
-  property_get(kPropBootRevision, revision, "");
+  auto manufacturer = GetProperty(kPropProductManufacturer, "");
+  auto sku = GetProperty(kPropBootHardwareSKU, "");
+  auto revision = GetProperty(kPropBootRevision, "");
 
-  return base::StringPrintf("%s:%s:%s", manufacturer, sku, revision);
+  return manufacturer + ":" + sku + ":" + revision;
 }
 
 string HardwareAndroid::GetFirmwareVersion() const {
-  char bootloader[PROPERTY_VALUE_MAX];
-  property_get(kPropBootBootloader, bootloader, "");
-  return bootloader;
+  return GetProperty(kPropBootBootloader, "");
 }
 
 string HardwareAndroid::GetECVersion() const {
-  char baseband[PROPERTY_VALUE_MAX];
-  property_get(kPropBootBaseband, baseband, "");
-  return baseband;
+  return GetProperty(kPropBootBaseband, "");
 }
 
 int HardwareAndroid::GetPowerwashCount() const {
@@ -200,12 +196,16 @@
   return false;
 }
 
+int64_t HardwareAndroid::GetBuildTimestamp() const {
+  return GetIntProperty<int64_t>(kPropBuildDateUTC, 0);
+}
+
 bool HardwareAndroid::GetFirstActiveOmahaPingSent() const {
   LOG(WARNING) << "STUB: Assuming first active omaha was never set.";
   return false;
 }
 
-void HardwareChromeOS::SetFirstActiveOmahaPingSent() {
+void HardwareAndroid::SetFirstActiveOmahaPingSent() {
   LOG(WARNING) << "STUB: Assuming first active omaha is never set.";
   return;
 }
diff --git a/hardware_android.h b/hardware_android.h
index 37393ce..ca90b62 100644
--- a/hardware_android.h
+++ b/hardware_android.h
@@ -47,6 +47,7 @@
   bool CancelPowerwash() override;
   bool GetNonVolatileDirectory(base::FilePath* path) const override;
   bool GetPowerwashSafeDirectory(base::FilePath* path) const override;
+  int64_t GetBuildTimestamp() const override;
   bool GetFirstActiveOmahaPingSent() const override;
   void SetFirstActiveOmahaPingSent() override;
 
diff --git a/hardware_chromeos.cc b/hardware_chromeos.cc
index 8c19aa7..f2bb28a 100644
--- a/hardware_chromeos.cc
+++ b/hardware_chromeos.cc
@@ -231,6 +231,11 @@
   return true;
 }
 
+int64_t HardwareChromeOS::GetBuildTimestamp() const {
+  // TODO(senj): implement this in Chrome OS.
+  return 0;
+}
+
 void HardwareChromeOS::LoadConfig(const string& root_prefix, bool normal_mode) {
   brillo::KeyValueStore store;
 
diff --git a/hardware_chromeos.h b/hardware_chromeos.h
index 3a0bba2..0cf1214 100644
--- a/hardware_chromeos.h
+++ b/hardware_chromeos.h
@@ -51,6 +51,7 @@
   bool CancelPowerwash() override;
   bool GetNonVolatileDirectory(base::FilePath* path) const override;
   bool GetPowerwashSafeDirectory(base::FilePath* path) const override;
+  int64_t GetBuildTimestamp() const override;
   bool GetFirstActiveOmahaPingSent() const override;
   void SetFirstActiveOmahaPingSent() override;
 
diff --git a/hardware_chromeos_unittest.cc b/hardware_chromeos_unittest.cc
index a6bad54..162dec4 100644
--- a/hardware_chromeos_unittest.cc
+++ b/hardware_chromeos_unittest.cc
@@ -37,21 +37,22 @@
   void SetUp() override { ASSERT_TRUE(root_dir_.CreateUniqueTempDir()); }
 
   void WriteStatefulConfig(const string& config) {
-    base::FilePath kFile(root_dir_.path().value() + kStatefulPartition +
+    base::FilePath kFile(root_dir_.GetPath().value() + kStatefulPartition +
                          "/etc/update_manager.conf");
     ASSERT_TRUE(base::CreateDirectory(kFile.DirName()));
     ASSERT_TRUE(WriteFileString(kFile.value(), config));
   }
 
   void WriteRootfsConfig(const string& config) {
-    base::FilePath kFile(root_dir_.path().value() + "/etc/update_manager.conf");
+    base::FilePath kFile(root_dir_.GetPath().value() +
+                         "/etc/update_manager.conf");
     ASSERT_TRUE(base::CreateDirectory(kFile.DirName()));
     ASSERT_TRUE(WriteFileString(kFile.value(), config));
   }
 
   // Helper method to call HardwareChromeOS::LoadConfig with the test directory.
   void CallLoadConfig(bool normal_mode) {
-    hardware_.LoadConfig(root_dir_.path().value(), normal_mode);
+    hardware_.LoadConfig(root_dir_.GetPath().value(), normal_mode);
   }
 
   HardwareChromeOS hardware_;
diff --git a/image_properties.h b/image_properties.h
index e03b8dc..f8fe095 100644
--- a/image_properties.h
+++ b/image_properties.h
@@ -33,14 +33,25 @@
   std::string product_id;
   // The canary-channel product id.
   std::string canary_product_id;
+  // The system id for the Android Things SoM, empty for Chrome OS.
+  std::string system_id;
 
   // The product version of this image.
   std::string version;
+  // The system version of this image.
+  std::string system_version;
+
+  // The version of all product components in key values pairs.
+  std::string product_components;
 
   // A unique string that identifies this build. Normally a combination of the
   // the version, signing keys and build target.
   std::string build_fingerprint;
 
+  // The Android build type, should be either 'user', 'userdebug' or 'eng'.
+  // It's empty string on other platform.
+  std::string build_type;
+
   // The board name this image was built for.
   std::string board;
 
diff --git a/image_properties_android.cc b/image_properties_android.cc
index 765b4ba..1ec425d 100644
--- a/image_properties_android.cc
+++ b/image_properties_android.cc
@@ -18,25 +18,35 @@
 
 #include <string>
 
+#include <android-base/properties.h>
 #include <base/logging.h>
 #include <brillo/osrelease_reader.h>
-#include <cutils/properties.h>
+#include <brillo/strings/string_utils.h>
 
 #include "update_engine/common/boot_control_interface.h"
 #include "update_engine/common/constants.h"
 #include "update_engine/common/platform_constants.h"
 #include "update_engine/common/prefs_interface.h"
+#include "update_engine/common/utils.h"
 #include "update_engine/system_state.h"
 
+using android::base::GetProperty;
+using std::string;
+
 namespace chromeos_update_engine {
 
 namespace {
 
-// Build time properties name used in Brillo.
+// Build time properties name used in Android Things.
 const char kProductId[] = "product_id";
 const char kProductVersion[] = "product_version";
+const char kSystemId[] = "system_id";
 const char kSystemVersion[] = "system_version";
 
+// The path to the product_components file which stores the version of each
+// components in OEM partition.
+const char kProductComponentsPath[] = "/oem/os-release.d/product_components";
+
 // Prefs used to store the target channel and powerwash settings.
 const char kPrefsImgPropChannelName[] = "img-prop-channel-name";
 const char kPrefsImgPropPowerwashAllowed[] = "img-prop-powerwash-allowed";
@@ -44,11 +54,15 @@
 // System properties that identifies the "board".
 const char kPropProductName[] = "ro.product.name";
 const char kPropBuildFingerprint[] = "ro.build.fingerprint";
+const char kPropBuildType[] = "ro.build.type";
 
-std::string GetStringWithDefault(const brillo::OsReleaseReader& osrelease,
-                                 const std::string& key,
-                                 const std::string& default_value) {
-  std::string result;
+// A prefix added to the path, used for testing.
+const char* root_prefix = nullptr;
+
+string GetStringWithDefault(const brillo::OsReleaseReader& osrelease,
+                            const string& key,
+                            const string& default_value) {
+  string result;
   if (osrelease.GetString(key, &result))
     return result;
   LOG(INFO) << "Cannot load ImageProperty " << key << ", using default value "
@@ -59,38 +73,50 @@
 }  // namespace
 
 namespace test {
-void SetImagePropertiesRootPrefix(const char* /* test_root_prefix */) {}
+void SetImagePropertiesRootPrefix(const char* test_root_prefix) {
+  root_prefix = test_root_prefix;
+}
 }  // namespace test
 
 ImageProperties LoadImageProperties(SystemState* system_state) {
   ImageProperties result;
 
   brillo::OsReleaseReader osrelease;
-  osrelease.Load();
-  result.product_id = GetStringWithDefault(
-      osrelease, kProductId, "developer-boards:brillo-starter-board");
+  if (root_prefix)
+    osrelease.LoadTestingOnly(base::FilePath(root_prefix));
+  else
+    osrelease.Load();
+  result.product_id =
+      GetStringWithDefault(osrelease, kProductId, "invalid-product");
+  result.system_id = GetStringWithDefault(
+      osrelease, kSystemId, "developer-boards:brillo-starter-board");
+  // Update the system id to match the prefix of product id for testing.
+  string prefix, not_used, system_id;
+  if (brillo::string_utils::SplitAtFirst(
+          result.product_id, ":", &prefix, &not_used, false) &&
+      brillo::string_utils::SplitAtFirst(
+          result.system_id, ":", &not_used, &system_id, false)) {
+    result.system_id = prefix + ":" + system_id;
+  }
   result.canary_product_id = result.product_id;
-  std::string system_version =
-      GetStringWithDefault(osrelease, kSystemVersion, "0.0.0");
-  std::string product_version =
-      GetStringWithDefault(osrelease, kProductVersion, "0");
-  result.version = system_version + "." + product_version;
+  result.version = GetStringWithDefault(osrelease, kProductVersion, "0.0.0.0");
+  result.system_version =
+      GetStringWithDefault(osrelease, kSystemVersion, "0.0.0.0");
+  // Can't read it with OsReleaseReader because it has multiple lines.
+  utils::ReadFile(kProductComponentsPath, &result.product_components);
 
-  char prop[PROPERTY_VALUE_MAX];
-  property_get(kPropProductName, prop, "brillo");
-  result.board = prop;
-
-  property_get(kPropBuildFingerprint, prop, "none");
-  result.build_fingerprint = prop;
+  result.board = GetProperty(kPropProductName, "brillo");
+  result.build_fingerprint = GetProperty(kPropBuildFingerprint, "none");
+  result.build_type = GetProperty(kPropBuildType, "");
 
   // Brillo images don't have a channel assigned. We stored the name of the
   // channel where we got the image from in prefs at the time of the update, so
   // we use that as the current channel if available. During provisioning, there
   // is no value assigned, so we default to the "stable-channel".
-  std::string current_channel_key =
+  string current_channel_key =
       kPrefsChannelOnSlotPrefix +
       std::to_string(system_state->boot_control()->GetCurrentSlot());
-  std::string current_channel;
+  string current_channel;
   if (!system_state->prefs()->Exists(current_channel_key) ||
       !system_state->prefs()->GetString(current_channel_key, &current_channel))
     current_channel = "stable-channel";
diff --git a/image_properties_android_unittest.cc b/image_properties_android_unittest.cc
new file mode 100644
index 0000000..7327554
--- /dev/null
+++ b/image_properties_android_unittest.cc
@@ -0,0 +1,90 @@
+//
+// Copyright (C) 2017 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/image_properties.h"
+
+#include <string>
+
+#include <base/files/file_util.h>
+#include <base/files/scoped_temp_dir.h>
+#include <gtest/gtest.h>
+
+#include "update_engine/common/constants.h"
+#include "update_engine/common/test_utils.h"
+#include "update_engine/fake_system_state.h"
+
+using chromeos_update_engine::test_utils::WriteFileString;
+using std::string;
+
+namespace chromeos_update_engine {
+
+class ImagePropertiesTest : public ::testing::Test {
+ protected:
+  void SetUp() override {
+    // Create a uniquely named test directory.
+    ASSERT_TRUE(tempdir_.CreateUniqueTempDir());
+    osrelease_dir_ = tempdir_.GetPath().Append("etc/os-release.d");
+    EXPECT_TRUE(base::CreateDirectory(osrelease_dir_));
+    test::SetImagePropertiesRootPrefix(tempdir_.GetPath().value().c_str());
+  }
+
+  void WriteOsRelease(const string& key, const string& value) {
+    ASSERT_TRUE(WriteFileString(osrelease_dir_.Append(key).value(), value));
+  }
+
+  FakeSystemState fake_system_state_;
+
+  base::ScopedTempDir tempdir_;
+  base::FilePath osrelease_dir_;
+};
+
+TEST_F(ImagePropertiesTest, SimpleTest) {
+  WriteOsRelease("product_id", "abc");
+  WriteOsRelease("system_id", "def");
+  WriteOsRelease("product_version", "1.2.3.4");
+  WriteOsRelease("system_version", "5.6.7.8");
+  ImageProperties props = LoadImageProperties(&fake_system_state_);
+  EXPECT_EQ("abc", props.product_id);
+  EXPECT_EQ("def", props.system_id);
+  EXPECT_EQ("1.2.3.4", props.version);
+  EXPECT_EQ("5.6.7.8", props.system_version);
+  EXPECT_EQ("stable-channel", props.current_channel);
+  EXPECT_EQ(constants::kOmahaDefaultProductionURL, props.omaha_url);
+}
+
+TEST_F(ImagePropertiesTest, IDPrefixTest) {
+  WriteOsRelease("product_id", "abc:def");
+  WriteOsRelease("system_id", "foo:bar");
+  ImageProperties props = LoadImageProperties(&fake_system_state_);
+  EXPECT_EQ("abc:def", props.product_id);
+  EXPECT_EQ("abc:bar", props.system_id);
+}
+
+TEST_F(ImagePropertiesTest, IDInvalidPrefixTest) {
+  WriteOsRelease("product_id", "def");
+  WriteOsRelease("system_id", "foo:bar");
+  ImageProperties props = LoadImageProperties(&fake_system_state_);
+  EXPECT_EQ("def", props.product_id);
+  EXPECT_EQ("foo:bar", props.system_id);
+
+  WriteOsRelease("product_id", "abc:def");
+  WriteOsRelease("system_id", "bar");
+  props = LoadImageProperties(&fake_system_state_);
+  EXPECT_EQ("abc:def", props.product_id);
+  EXPECT_EQ("bar", props.system_id);
+}
+
+}  // namespace chromeos_update_engine
diff --git a/image_properties_chromeos_unittest.cc b/image_properties_chromeos_unittest.cc
index 12c2039..d9ed688 100644
--- a/image_properties_chromeos_unittest.cc
+++ b/image_properties_chromeos_unittest.cc
@@ -36,10 +36,10 @@
   void SetUp() override {
     // Create a uniquely named test directory.
     ASSERT_TRUE(tempdir_.CreateUniqueTempDir());
-    EXPECT_TRUE(base::CreateDirectory(tempdir_.path().Append("etc")));
-    EXPECT_TRUE(base::CreateDirectory(
-        base::FilePath(tempdir_.path().value() + kStatefulPartition + "/etc")));
-    test::SetImagePropertiesRootPrefix(tempdir_.path().value().c_str());
+    EXPECT_TRUE(base::CreateDirectory(tempdir_.GetPath().Append("etc")));
+    EXPECT_TRUE(base::CreateDirectory(base::FilePath(
+        tempdir_.GetPath().value() + kStatefulPartition + "/etc")));
+    test::SetImagePropertiesRootPrefix(tempdir_.GetPath().value().c_str());
     SetLockDown(false);
   }
 
@@ -54,12 +54,13 @@
 };
 
 TEST_F(ImagePropertiesTest, SimpleTest) {
-  ASSERT_TRUE(WriteFileString(tempdir_.path().Append("etc/lsb-release").value(),
-                              "CHROMEOS_RELEASE_BOARD=arm-generic\n"
-                              "CHROMEOS_RELEASE_FOO=bar\n"
-                              "CHROMEOS_RELEASE_VERSION=0.2.2.3\n"
-                              "CHROMEOS_RELEASE_TRACK=dev-channel\n"
-                              "CHROMEOS_AUSERVER=http://www.google.com"));
+  ASSERT_TRUE(
+      WriteFileString(tempdir_.GetPath().Append("etc/lsb-release").value(),
+                      "CHROMEOS_RELEASE_BOARD=arm-generic\n"
+                      "CHROMEOS_RELEASE_FOO=bar\n"
+                      "CHROMEOS_RELEASE_VERSION=0.2.2.3\n"
+                      "CHROMEOS_RELEASE_TRACK=dev-channel\n"
+                      "CHROMEOS_AUSERVER=http://www.google.com"));
   ImageProperties props = LoadImageProperties(&fake_system_state_);
   EXPECT_EQ("arm-generic", props.board);
   EXPECT_EQ("{87efface-864d-49a5-9bb3-4b050a7c227a}", props.product_id);
@@ -70,7 +71,7 @@
 
 TEST_F(ImagePropertiesTest, AppIDTest) {
   ASSERT_TRUE(WriteFileString(
-      tempdir_.path().Append("etc/lsb-release").value(),
+      tempdir_.GetPath().Append("etc/lsb-release").value(),
       "CHROMEOS_RELEASE_APPID={58c35cef-9d30-476e-9098-ce20377d535d}"));
   ImageProperties props = LoadImageProperties(&fake_system_state_);
   EXPECT_EQ("{58c35cef-9d30-476e-9098-ce20377d535d}", props.product_id);
@@ -78,7 +79,7 @@
 
 TEST_F(ImagePropertiesTest, ConfusingReleaseTest) {
   ASSERT_TRUE(
-      WriteFileString(tempdir_.path().Append("etc/lsb-release").value(),
+      WriteFileString(tempdir_.GetPath().Append("etc/lsb-release").value(),
                       "CHROMEOS_RELEASE_FOO=CHROMEOS_RELEASE_VERSION=1.2.3.4\n"
                       "CHROMEOS_RELEASE_VERSION=0.2.2.3"));
   ImageProperties props = LoadImageProperties(&fake_system_state_);
@@ -91,13 +92,14 @@
 }
 
 TEST_F(ImagePropertiesTest, OverrideTest) {
-  ASSERT_TRUE(WriteFileString(tempdir_.path().Append("etc/lsb-release").value(),
-                              "CHROMEOS_RELEASE_BOARD=arm-generic\n"
-                              "CHROMEOS_RELEASE_FOO=bar\n"
-                              "CHROMEOS_RELEASE_TRACK=dev-channel\n"
-                              "CHROMEOS_AUSERVER=http://www.google.com"));
+  ASSERT_TRUE(
+      WriteFileString(tempdir_.GetPath().Append("etc/lsb-release").value(),
+                      "CHROMEOS_RELEASE_BOARD=arm-generic\n"
+                      "CHROMEOS_RELEASE_FOO=bar\n"
+                      "CHROMEOS_RELEASE_TRACK=dev-channel\n"
+                      "CHROMEOS_AUSERVER=http://www.google.com"));
   ASSERT_TRUE(WriteFileString(
-      tempdir_.path().value() + kStatefulPartition + "/etc/lsb-release",
+      tempdir_.GetPath().value() + kStatefulPartition + "/etc/lsb-release",
       "CHROMEOS_RELEASE_BOARD=x86-generic\n"
       "CHROMEOS_RELEASE_TRACK=beta-channel\n"
       "CHROMEOS_AUSERVER=https://www.google.com"));
@@ -111,13 +113,14 @@
 }
 
 TEST_F(ImagePropertiesTest, OverrideLockDownTest) {
-  ASSERT_TRUE(WriteFileString(tempdir_.path().Append("etc/lsb-release").value(),
-                              "CHROMEOS_RELEASE_BOARD=arm-generic\n"
-                              "CHROMEOS_RELEASE_FOO=bar\n"
-                              "CHROMEOS_RELEASE_TRACK=dev-channel\n"
-                              "CHROMEOS_AUSERVER=https://www.google.com"));
+  ASSERT_TRUE(
+      WriteFileString(tempdir_.GetPath().Append("etc/lsb-release").value(),
+                      "CHROMEOS_RELEASE_BOARD=arm-generic\n"
+                      "CHROMEOS_RELEASE_FOO=bar\n"
+                      "CHROMEOS_RELEASE_TRACK=dev-channel\n"
+                      "CHROMEOS_AUSERVER=https://www.google.com"));
   ASSERT_TRUE(WriteFileString(
-      tempdir_.path().value() + kStatefulPartition + "/etc/lsb-release",
+      tempdir_.GetPath().value() + kStatefulPartition + "/etc/lsb-release",
       "CHROMEOS_RELEASE_BOARD=x86-generic\n"
       "CHROMEOS_RELEASE_TRACK=stable-channel\n"
       "CHROMEOS_AUSERVER=http://www.google.com"));
@@ -132,32 +135,35 @@
 }
 
 TEST_F(ImagePropertiesTest, BoardAppIdUsedForNonCanaryChannelTest) {
-  ASSERT_TRUE(WriteFileString(tempdir_.path().Append("etc/lsb-release").value(),
-                              "CHROMEOS_RELEASE_APPID=r\n"
-                              "CHROMEOS_BOARD_APPID=b\n"
-                              "CHROMEOS_CANARY_APPID=c\n"
-                              "CHROMEOS_RELEASE_TRACK=stable-channel\n"));
+  ASSERT_TRUE(
+      WriteFileString(tempdir_.GetPath().Append("etc/lsb-release").value(),
+                      "CHROMEOS_RELEASE_APPID=r\n"
+                      "CHROMEOS_BOARD_APPID=b\n"
+                      "CHROMEOS_CANARY_APPID=c\n"
+                      "CHROMEOS_RELEASE_TRACK=stable-channel\n"));
   ImageProperties props = LoadImageProperties(&fake_system_state_);
   EXPECT_EQ("stable-channel", props.current_channel);
   EXPECT_EQ("b", props.product_id);
 }
 
 TEST_F(ImagePropertiesTest, CanaryAppIdUsedForCanaryChannelTest) {
-  ASSERT_TRUE(WriteFileString(tempdir_.path().Append("etc/lsb-release").value(),
-                              "CHROMEOS_RELEASE_APPID=r\n"
-                              "CHROMEOS_BOARD_APPID=b\n"
-                              "CHROMEOS_CANARY_APPID=c\n"
-                              "CHROMEOS_RELEASE_TRACK=canary-channel\n"));
+  ASSERT_TRUE(
+      WriteFileString(tempdir_.GetPath().Append("etc/lsb-release").value(),
+                      "CHROMEOS_RELEASE_APPID=r\n"
+                      "CHROMEOS_BOARD_APPID=b\n"
+                      "CHROMEOS_CANARY_APPID=c\n"
+                      "CHROMEOS_RELEASE_TRACK=canary-channel\n"));
   ImageProperties props = LoadImageProperties(&fake_system_state_);
   EXPECT_EQ("canary-channel", props.current_channel);
   EXPECT_EQ("c", props.canary_product_id);
 }
 
 TEST_F(ImagePropertiesTest, ReleaseAppIdUsedAsDefaultTest) {
-  ASSERT_TRUE(WriteFileString(tempdir_.path().Append("etc/lsb-release").value(),
-                              "CHROMEOS_RELEASE_APPID=r\n"
-                              "CHROMEOS_CANARY_APPID=c\n"
-                              "CHROMEOS_RELEASE_TRACK=stable-channel\n"));
+  ASSERT_TRUE(
+      WriteFileString(tempdir_.GetPath().Append("etc/lsb-release").value(),
+                      "CHROMEOS_RELEASE_APPID=r\n"
+                      "CHROMEOS_CANARY_APPID=c\n"
+                      "CHROMEOS_RELEASE_TRACK=stable-channel\n"));
   ImageProperties props = LoadImageProperties(&fake_system_state_);
   EXPECT_EQ("stable-channel", props.current_channel);
   EXPECT_EQ("r", props.product_id);
diff --git a/main.cc b/main.cc
index 4275bc1..0b96307 100644
--- a/main.cc
+++ b/main.cc
@@ -38,6 +38,7 @@
 namespace chromeos_update_engine {
 namespace {
 
+#ifndef __ANDROID__
 void SetupLogSymlink(const string& symlink_path, const string& log_path) {
   // TODO(petkov): To ensure a smooth transition between non-timestamped and
   // timestamped logs, move an existing log to start the first timestamped
@@ -75,30 +76,41 @@
   SetupLogSymlink(kLogSymlink, kLogPath);
   return kLogSymlink;
 }
+#endif  // __ANDROID__
 
-void SetupLogging(bool log_to_std_err) {
-  string log_file;
+void SetupLogging(bool log_to_system, bool log_to_file) {
   logging::LoggingSettings log_settings;
   log_settings.lock_log = logging::DONT_LOCK_LOG_FILE;
-  log_settings.delete_old = logging::APPEND_TO_OLD_LOG_FILE;
+  log_settings.logging_dest = static_cast<logging::LoggingDestination>(
+      (log_to_system ? logging::LOG_TO_SYSTEM_DEBUG_LOG : 0) |
+      (log_to_file ? logging::LOG_TO_FILE : 0));
+  log_settings.log_file = nullptr;
 
-  if (log_to_std_err) {
-    // Log to stderr initially.
-    log_settings.log_file = nullptr;
-    log_settings.logging_dest = logging::LOG_TO_SYSTEM_DEBUG_LOG;
-  } else {
+  string log_file;
+  if (log_to_file) {
+#ifdef __ANDROID__
+    log_file = "/data/misc/update_engine_log/update_engine.log";
+    log_settings.delete_old = logging::DELETE_OLD_LOG_FILE;
+#else
     log_file = SetupLogFile("/var/log");
+    log_settings.delete_old = logging::APPEND_TO_OLD_LOG_FILE;
+#endif  // __ANDROID__
     log_settings.log_file = log_file.c_str();
-    log_settings.logging_dest = logging::LOG_TO_FILE;
   }
-
   logging::InitLogging(log_settings);
+
+#ifdef __ANDROID__
+  // The log file will have AID_LOG as group ID; this GID is inherited from the
+  // parent directory "/data/misc/update_engine_log" which sets the SGID bit.
+  chmod(log_file.c_str(), 0640);
+#endif
 }
 
 }  // namespace
 }  // namespace chromeos_update_engine
 
 int main(int argc, char** argv) {
+  DEFINE_bool(logtofile, false, "Write logs to a file in log_dir.");
   DEFINE_bool(logtostderr, false,
               "Write logs to stderr instead of to a file in log_dir.");
   DEFINE_bool(foreground, false,
@@ -106,7 +118,15 @@
 
   chromeos_update_engine::Terminator::Init();
   brillo::FlagHelper::Init(argc, argv, "Chromium OS Update Engine");
-  chromeos_update_engine::SetupLogging(FLAGS_logtostderr);
+
+  // We have two logging flags "--logtostderr" and "--logtofile"; and the logic
+  // to choose the logging destination is:
+  // 1. --logtostderr --logtofile -> logs to both
+  // 2. --logtostderr             -> logs to system debug
+  // 3. --logtofile or no flags   -> logs to file
+  bool log_to_system = FLAGS_logtostderr;
+  bool log_to_file = FLAGS_logtofile || !FLAGS_logtostderr;
+  chromeos_update_engine::SetupLogging(log_to_system, log_to_file);
   if (!FLAGS_foreground)
     PLOG_IF(FATAL, daemon(0, 0) == 1) << "daemon() failed";
 
diff --git a/metrics.cc b/metrics.cc
deleted file mode 100644
index 742ba7e..0000000
--- a/metrics.cc
+++ /dev/null
@@ -1,526 +0,0 @@
-//
-// Copyright (C) 2014 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/metrics.h"
-
-#include <string>
-
-#include <base/logging.h>
-#include <metrics/metrics_library.h>
-
-#include "update_engine/common/clock_interface.h"
-#include "update_engine/common/constants.h"
-#include "update_engine/common/prefs_interface.h"
-#include "update_engine/common/utils.h"
-#include "update_engine/metrics_utils.h"
-#include "update_engine/system_state.h"
-
-using std::string;
-
-namespace chromeos_update_engine {
-
-namespace metrics {
-
-// UpdateEngine.Daily.* metrics.
-const char kMetricDailyOSAgeDays[] = "UpdateEngine.Daily.OSAgeDays";
-
-// UpdateEngine.Check.* metrics.
-const char kMetricCheckDownloadErrorCode[] =
-    "UpdateEngine.Check.DownloadErrorCode";
-const char kMetricCheckReaction[] = "UpdateEngine.Check.Reaction";
-const char kMetricCheckResult[] = "UpdateEngine.Check.Result";
-const char kMetricCheckTimeSinceLastCheckMinutes[] =
-    "UpdateEngine.Check.TimeSinceLastCheckMinutes";
-const char kMetricCheckTimeSinceLastCheckUptimeMinutes[] =
-    "UpdateEngine.Check.TimeSinceLastCheckUptimeMinutes";
-
-// UpdateEngine.Attempt.* metrics.
-const char kMetricAttemptNumber[] = "UpdateEngine.Attempt.Number";
-const char kMetricAttemptPayloadType[] =
-    "UpdateEngine.Attempt.PayloadType";
-const char kMetricAttemptPayloadSizeMiB[] =
-    "UpdateEngine.Attempt.PayloadSizeMiB";
-const char kMetricAttemptConnectionType[] =
-    "UpdateEngine.Attempt.ConnectionType";
-const char kMetricAttemptDurationMinutes[] =
-    "UpdateEngine.Attempt.DurationMinutes";
-const char kMetricAttemptDurationUptimeMinutes[] =
-    "UpdateEngine.Attempt.DurationUptimeMinutes";
-const char kMetricAttemptTimeSinceLastAttemptMinutes[] =
-    "UpdateEngine.Attempt.TimeSinceLastAttemptMinutes";
-const char kMetricAttemptTimeSinceLastAttemptUptimeMinutes[] =
-    "UpdateEngine.Attempt.TimeSinceLastAttemptUptimeMinutes";
-const char kMetricAttemptPayloadBytesDownloadedMiB[] =
-    "UpdateEngine.Attempt.PayloadBytesDownloadedMiB";
-const char kMetricAttemptPayloadDownloadSpeedKBps[] =
-    "UpdateEngine.Attempt.PayloadDownloadSpeedKBps";
-const char kMetricAttemptDownloadSource[] =
-    "UpdateEngine.Attempt.DownloadSource";
-const char kMetricAttemptResult[] =
-    "UpdateEngine.Attempt.Result";
-const char kMetricAttemptInternalErrorCode[] =
-    "UpdateEngine.Attempt.InternalErrorCode";
-const char kMetricAttemptDownloadErrorCode[] =
-    "UpdateEngine.Attempt.DownloadErrorCode";
-
-// UpdateEngine.SuccessfulUpdate.* metrics.
-const char kMetricSuccessfulUpdateAttemptCount[] =
-    "UpdateEngine.SuccessfulUpdate.AttemptCount";
-const char kMetricSuccessfulUpdateBytesDownloadedMiB[] =
-    "UpdateEngine.SuccessfulUpdate.BytesDownloadedMiB";
-const char kMetricSuccessfulUpdateDownloadOverheadPercentage[] =
-    "UpdateEngine.SuccessfulUpdate.DownloadOverheadPercentage";
-const char kMetricSuccessfulUpdateDownloadSourcesUsed[] =
-    "UpdateEngine.SuccessfulUpdate.DownloadSourcesUsed";
-const char kMetricSuccessfulUpdatePayloadType[] =
-    "UpdateEngine.SuccessfulUpdate.PayloadType";
-const char kMetricSuccessfulUpdatePayloadSizeMiB[] =
-    "UpdateEngine.SuccessfulUpdate.PayloadSizeMiB";
-const char kMetricSuccessfulUpdateRebootCount[] =
-    "UpdateEngine.SuccessfulUpdate.RebootCount";
-const char kMetricSuccessfulUpdateTotalDurationMinutes[] =
-    "UpdateEngine.SuccessfulUpdate.TotalDurationMinutes";
-const char kMetricSuccessfulUpdateUpdatesAbandonedCount[] =
-    "UpdateEngine.SuccessfulUpdate.UpdatesAbandonedCount";
-const char kMetricSuccessfulUpdateUrlSwitchCount[] =
-    "UpdateEngine.SuccessfulUpdate.UrlSwitchCount";
-
-// UpdateEngine.Rollback.* metric.
-const char kMetricRollbackResult[] = "UpdateEngine.Rollback.Result";
-
-// UpdateEngine.CertificateCheck.* metrics.
-const char kMetricCertificateCheckUpdateCheck[] =
-    "UpdateEngine.CertificateCheck.UpdateCheck";
-const char kMetricCertificateCheckDownload[] =
-    "UpdateEngine.CertificateCheck.Download";
-
-// UpdateEngine.* metrics.
-const char kMetricFailedUpdateCount[] = "UpdateEngine.FailedUpdateCount";
-const char kMetricInstallDateProvisioningSource[] =
-    "UpdateEngine.InstallDateProvisioningSource";
-const char kMetricTimeToRebootMinutes[] =
-    "UpdateEngine.TimeToRebootMinutes";
-
-void ReportDailyMetrics(SystemState *system_state,
-                        base::TimeDelta os_age) {
-  string metric = metrics::kMetricDailyOSAgeDays;
-  LOG(INFO) << "Uploading " << utils::FormatTimeDelta(os_age)
-            << " for metric " <<  metric;
-  system_state->metrics_lib()->SendToUMA(
-      metric,
-      static_cast<int>(os_age.InDays()),
-      0,     // min: 0 days
-      6*30,  // max: 6 months (approx)
-      50);   // num_buckets
-}
-
-void ReportUpdateCheckMetrics(SystemState *system_state,
-                              CheckResult result,
-                              CheckReaction reaction,
-                              DownloadErrorCode download_error_code) {
-  string metric;
-  int value;
-  int max_value;
-
-  if (result != metrics::CheckResult::kUnset) {
-    metric = metrics::kMetricCheckResult;
-    value = static_cast<int>(result);
-    max_value = static_cast<int>(metrics::CheckResult::kNumConstants) - 1;
-    LOG(INFO) << "Sending " << value << " for metric " << metric << " (enum)";
-    system_state->metrics_lib()->SendEnumToUMA(metric, value, max_value);
-  }
-  if (reaction != metrics::CheckReaction::kUnset) {
-    metric = metrics::kMetricCheckReaction;
-    value = static_cast<int>(reaction);
-    max_value = static_cast<int>(metrics::CheckReaction::kNumConstants) - 1;
-    LOG(INFO) << "Sending " << value << " for metric " << metric << " (enum)";
-    system_state->metrics_lib()->SendEnumToUMA(metric, value, max_value);
-  }
-  if (download_error_code != metrics::DownloadErrorCode::kUnset) {
-    metric = metrics::kMetricCheckDownloadErrorCode;
-    value = static_cast<int>(download_error_code);
-    LOG(INFO) << "Sending " << value << " for metric " << metric << " (sparse)";
-    system_state->metrics_lib()->SendSparseToUMA(metric, value);
-  }
-
-  base::TimeDelta time_since_last;
-  if (metrics_utils::WallclockDurationHelper(
-          system_state,
-          kPrefsMetricsCheckLastReportingTime,
-          &time_since_last)) {
-    metric = kMetricCheckTimeSinceLastCheckMinutes;
-    LOG(INFO) << "Sending " << utils::FormatTimeDelta(time_since_last)
-              << " for metric " << metric;
-    system_state->metrics_lib()->SendToUMA(
-        metric,
-        time_since_last.InMinutes(),
-        0,         // min: 0 min
-        30*24*60,  // max: 30 days
-        50);       // num_buckets
-  }
-
-  base::TimeDelta uptime_since_last;
-  static int64_t uptime_since_last_storage = 0;
-  if (metrics_utils::MonotonicDurationHelper(system_state,
-                                             &uptime_since_last_storage,
-                                             &uptime_since_last)) {
-    metric = kMetricCheckTimeSinceLastCheckUptimeMinutes;
-    LOG(INFO) << "Sending " << utils::FormatTimeDelta(uptime_since_last)
-              << " for metric " << metric;
-    system_state->metrics_lib()->SendToUMA(
-        metric,
-        uptime_since_last.InMinutes(),
-        0,         // min: 0 min
-        30*24*60,  // max: 30 days
-        50);       // num_buckets
-  }
-}
-
-void ReportAbnormallyTerminatedUpdateAttemptMetrics(
-    SystemState *system_state) {
-
-  string metric = metrics::kMetricAttemptResult;
-  AttemptResult attempt_result = AttemptResult::kAbnormalTermination;
-
-  LOG(INFO) << "Uploading " << static_cast<int>(attempt_result)
-            << " for metric " <<  metric;
-  system_state->metrics_lib()->SendEnumToUMA(
-      metric,
-      static_cast<int>(attempt_result),
-      static_cast<int>(AttemptResult::kNumConstants));
-}
-
-void ReportUpdateAttemptMetrics(
-    SystemState *system_state,
-    int attempt_number,
-    PayloadType payload_type,
-    base::TimeDelta duration,
-    base::TimeDelta duration_uptime,
-    int64_t payload_size,
-    int64_t payload_bytes_downloaded,
-    int64_t payload_download_speed_bps,
-    DownloadSource download_source,
-    AttemptResult attempt_result,
-    ErrorCode internal_error_code,
-    DownloadErrorCode payload_download_error_code,
-    ConnectionType connection_type) {
-  string metric;
-
-  metric = metrics::kMetricAttemptNumber;
-  LOG(INFO) << "Uploading " << attempt_number << " for metric " <<  metric;
-  system_state->metrics_lib()->SendToUMA(metric,
-                                         attempt_number,
-                                         0,    // min: 0 attempts
-                                         49,   // max: 49 attempts
-                                         50);  // num_buckets
-
-  metric = metrics::kMetricAttemptPayloadType;
-  LOG(INFO) << "Uploading " << utils::ToString(payload_type)
-            << " for metric " <<  metric;
-  system_state->metrics_lib()->SendEnumToUMA(metric,
-                                             payload_type,
-                                             kNumPayloadTypes);
-
-  metric = metrics::kMetricAttemptDurationMinutes;
-  LOG(INFO) << "Uploading " << utils::FormatTimeDelta(duration)
-            << " for metric " <<  metric;
-  system_state->metrics_lib()->SendToUMA(metric,
-                                         duration.InMinutes(),
-                                         0,         // min: 0 min
-                                         10*24*60,  // max: 10 days
-                                         50);       // num_buckets
-
-  metric = metrics::kMetricAttemptDurationUptimeMinutes;
-  LOG(INFO) << "Uploading " << utils::FormatTimeDelta(duration_uptime)
-            << " for metric " <<  metric;
-  system_state->metrics_lib()->SendToUMA(metric,
-                                         duration_uptime.InMinutes(),
-                                         0,         // min: 0 min
-                                         10*24*60,  // max: 10 days
-                                         50);       // num_buckets
-
-  metric = metrics::kMetricAttemptPayloadSizeMiB;
-  int64_t payload_size_mib = payload_size / kNumBytesInOneMiB;
-  LOG(INFO) << "Uploading " << payload_size_mib << " for metric " <<  metric;
-  system_state->metrics_lib()->SendToUMA(metric,
-                                         payload_size_mib,
-                                         0,     // min: 0 MiB
-                                         1024,  // max: 1024 MiB = 1 GiB
-                                         50);   // num_buckets
-
-  metric = metrics::kMetricAttemptPayloadBytesDownloadedMiB;
-  int64_t payload_bytes_downloaded_mib =
-       payload_bytes_downloaded / kNumBytesInOneMiB;
-  LOG(INFO) << "Uploading " << payload_bytes_downloaded_mib
-            << " for metric " <<  metric;
-  system_state->metrics_lib()->SendToUMA(metric,
-                                         payload_bytes_downloaded_mib,
-                                         0,     // min: 0 MiB
-                                         1024,  // max: 1024 MiB = 1 GiB
-                                         50);   // num_buckets
-
-  metric = metrics::kMetricAttemptPayloadDownloadSpeedKBps;
-  int64_t payload_download_speed_kbps = payload_download_speed_bps / 1000;
-  LOG(INFO) << "Uploading " << payload_download_speed_kbps
-            << " for metric " <<  metric;
-  system_state->metrics_lib()->SendToUMA(metric,
-                                         payload_download_speed_kbps,
-                                         0,        // min: 0 kB/s
-                                         10*1000,  // max: 10000 kB/s = 10 MB/s
-                                         50);      // num_buckets
-
-  metric = metrics::kMetricAttemptDownloadSource;
-  LOG(INFO) << "Uploading " << download_source
-            << " for metric " <<  metric;
-  system_state->metrics_lib()->SendEnumToUMA(metric,
-                                             download_source,
-                                             kNumDownloadSources);
-
-  metric = metrics::kMetricAttemptResult;
-  LOG(INFO) << "Uploading " << static_cast<int>(attempt_result)
-            << " for metric " <<  metric;
-  system_state->metrics_lib()->SendEnumToUMA(
-      metric,
-      static_cast<int>(attempt_result),
-      static_cast<int>(AttemptResult::kNumConstants));
-
-  if (internal_error_code != ErrorCode::kSuccess) {
-    metric = metrics::kMetricAttemptInternalErrorCode;
-    LOG(INFO) << "Uploading " << internal_error_code
-              << " for metric " <<  metric;
-    system_state->metrics_lib()->SendEnumToUMA(
-        metric,
-        static_cast<int>(internal_error_code),
-        static_cast<int>(ErrorCode::kUmaReportedMax));
-  }
-
-  if (payload_download_error_code != DownloadErrorCode::kUnset) {
-    metric = metrics::kMetricAttemptDownloadErrorCode;
-    LOG(INFO) << "Uploading " << static_cast<int>(payload_download_error_code)
-              << " for metric " <<  metric << " (sparse)";
-    system_state->metrics_lib()->SendSparseToUMA(
-        metric,
-        static_cast<int>(payload_download_error_code));
-  }
-
-  base::TimeDelta time_since_last;
-  if (metrics_utils::WallclockDurationHelper(
-          system_state,
-          kPrefsMetricsAttemptLastReportingTime,
-          &time_since_last)) {
-    metric = kMetricAttemptTimeSinceLastAttemptMinutes;
-    LOG(INFO) << "Sending " << utils::FormatTimeDelta(time_since_last)
-              << " for metric " << metric;
-    system_state->metrics_lib()->SendToUMA(
-        metric,
-        time_since_last.InMinutes(),
-        0,         // min: 0 min
-        30*24*60,  // max: 30 days
-        50);       // num_buckets
-  }
-
-  static int64_t uptime_since_last_storage = 0;
-  base::TimeDelta uptime_since_last;
-  if (metrics_utils::MonotonicDurationHelper(system_state,
-                                             &uptime_since_last_storage,
-                                             &uptime_since_last)) {
-    metric = kMetricAttemptTimeSinceLastAttemptUptimeMinutes;
-    LOG(INFO) << "Sending " << utils::FormatTimeDelta(uptime_since_last)
-              << " for metric " << metric;
-    system_state->metrics_lib()->SendToUMA(
-        metric,
-        uptime_since_last.InMinutes(),
-        0,         // min: 0 min
-        30*24*60,  // max: 30 days
-        50);       // num_buckets
-  }
-
-  metric = metrics::kMetricAttemptConnectionType;
-  LOG(INFO) << "Uploading " << static_cast<int>(connection_type)
-            << " for metric " <<  metric;
-  system_state->metrics_lib()->SendEnumToUMA(
-      metric,
-      static_cast<int>(connection_type),
-      static_cast<int>(ConnectionType::kNumConstants));
-}
-
-
-void ReportSuccessfulUpdateMetrics(
-         SystemState *system_state,
-         int attempt_count,
-         int updates_abandoned_count,
-         PayloadType payload_type,
-         int64_t payload_size,
-         int64_t num_bytes_downloaded[kNumDownloadSources],
-         int download_overhead_percentage,
-         base::TimeDelta total_duration,
-         int reboot_count,
-         int url_switch_count) {
-  string metric;
-  int64_t mbs;
-
-  metric = kMetricSuccessfulUpdatePayloadSizeMiB;
-  mbs = payload_size / kNumBytesInOneMiB;
-  LOG(INFO) << "Uploading " << mbs << " (MiBs) for metric " << metric;
-  system_state->metrics_lib()->SendToUMA(metric,
-                                         mbs,
-                                         0,     // min: 0 MiB
-                                         1024,  // max: 1024 MiB = 1 GiB
-                                         50);   // num_buckets
-
-  int64_t total_bytes = 0;
-  int download_sources_used = 0;
-  for (int i = 0; i < kNumDownloadSources + 1; i++) {
-    DownloadSource source = static_cast<DownloadSource>(i);
-
-    // Only consider this download source (and send byte counts) as
-    // having been used if we downloaded a non-trivial amount of bytes
-    // (e.g. at least 1 MiB) that contributed to the
-    // update. Otherwise we're going to end up with a lot of zero-byte
-    // events in the histogram.
-
-    metric = metrics::kMetricSuccessfulUpdateBytesDownloadedMiB;
-    if (i < kNumDownloadSources) {
-      metric += utils::ToString(source);
-      mbs = num_bytes_downloaded[i] / kNumBytesInOneMiB;
-      total_bytes += num_bytes_downloaded[i];
-      if (mbs > 0)
-        download_sources_used |= (1 << i);
-    } else {
-      mbs = total_bytes / kNumBytesInOneMiB;
-    }
-
-    if (mbs > 0) {
-      LOG(INFO) << "Uploading " << mbs << " (MiBs) for metric " << metric;
-      system_state->metrics_lib()->SendToUMA(metric,
-                                             mbs,
-                                             0,     // min: 0 MiB
-                                             1024,  // max: 1024 MiB = 1 GiB
-                                             50);   // num_buckets
-    }
-  }
-
-  metric = metrics::kMetricSuccessfulUpdateDownloadSourcesUsed;
-  LOG(INFO) << "Uploading 0x" << std::hex << download_sources_used
-            << " (bit flags) for metric " << metric;
-  system_state->metrics_lib()->SendToUMA(
-      metric,
-      download_sources_used,
-      0,                               // min
-      (1 << kNumDownloadSources) - 1,  // max
-      1 << kNumDownloadSources);       // num_buckets
-
-  metric = metrics::kMetricSuccessfulUpdateDownloadOverheadPercentage;
-  LOG(INFO) << "Uploading " << download_overhead_percentage
-            << "% for metric " << metric;
-  system_state->metrics_lib()->SendToUMA(metric,
-                                         download_overhead_percentage,
-                                         0,     // min: 0% overhead
-                                         1000,  // max: 1000% overhead
-                                         50);   // num_buckets
-
-  metric = metrics::kMetricSuccessfulUpdateUrlSwitchCount;
-  LOG(INFO) << "Uploading " << url_switch_count
-            << " (count) for metric " <<  metric;
-  system_state->metrics_lib()->SendToUMA(metric,
-                                         url_switch_count,
-                                         0,    // min: 0 URL switches
-                                         49,   // max: 49 URL switches
-                                         50);  // num_buckets
-
-  metric = metrics::kMetricSuccessfulUpdateTotalDurationMinutes;
-  LOG(INFO) << "Uploading " << utils::FormatTimeDelta(total_duration)
-            << " for metric " <<  metric;
-  system_state->metrics_lib()->SendToUMA(
-       metric,
-       static_cast<int>(total_duration.InMinutes()),
-       0,          // min: 0 min
-       365*24*60,  // max: 365 days ~= 1 year
-       50);        // num_buckets
-
-  metric = metrics::kMetricSuccessfulUpdateRebootCount;
-  LOG(INFO) << "Uploading reboot count of " << reboot_count << " for metric "
-            <<  metric;
-  system_state->metrics_lib()->SendToUMA(metric,
-                                         reboot_count,
-                                         0,    // min: 0 reboots
-                                         49,   // max: 49 reboots
-                                         50);  // num_buckets
-
-  metric = metrics::kMetricSuccessfulUpdatePayloadType;
-  system_state->metrics_lib()->SendEnumToUMA(metric,
-                                             payload_type,
-                                             kNumPayloadTypes);
-  LOG(INFO) << "Uploading " << utils::ToString(payload_type)
-            << " for metric " <<  metric;
-
-  metric = metrics::kMetricSuccessfulUpdateAttemptCount;
-  system_state->metrics_lib()->SendToUMA(metric,
-                                         attempt_count,
-                                         1,    // min: 1 attempt
-                                         50,   // max: 50 attempts
-                                         50);  // num_buckets
-  LOG(INFO) << "Uploading " << attempt_count
-            << " for metric " <<  metric;
-
-  metric = metrics::kMetricSuccessfulUpdateUpdatesAbandonedCount;
-  LOG(INFO) << "Uploading " << updates_abandoned_count
-            << " (count) for metric " <<  metric;
-  system_state->metrics_lib()->SendToUMA(metric,
-                                         updates_abandoned_count,
-                                         0,    // min: 0 counts
-                                         49,   // max: 49 counts
-                                         50);  // num_buckets
-}
-
-void ReportRollbackMetrics(SystemState *system_state,
-                           RollbackResult result) {
-  string metric;
-  int value;
-
-  metric = metrics::kMetricRollbackResult;
-  value = static_cast<int>(result);
-  LOG(INFO) << "Sending " << value << " for metric " << metric << " (enum)";
-  system_state->metrics_lib()->SendEnumToUMA(
-      metric,
-      value,
-      static_cast<int>(metrics::RollbackResult::kNumConstants));
-}
-
-void ReportCertificateCheckMetrics(SystemState* system_state,
-                                   ServerToCheck server_to_check,
-                                   CertificateCheckResult result) {
-  string metric;
-  switch (server_to_check) {
-    case ServerToCheck::kUpdate:
-      metric = kMetricCertificateCheckUpdateCheck;
-      break;
-    case ServerToCheck::kDownload:
-      metric = kMetricCertificateCheckDownload;
-      break;
-    case ServerToCheck::kNone:
-      return;
-  }
-  LOG(INFO) << "Uploading " << static_cast<int>(result) << " for metric "
-            << metric;
-  system_state->metrics_lib()->SendEnumToUMA(
-      metric, static_cast<int>(result),
-      static_cast<int>(CertificateCheckResult::kNumConstants));
-}
-
-}  // namespace metrics
-
-}  // namespace chromeos_update_engine
diff --git a/metrics.h b/metrics.h
deleted file mode 100644
index 7c369ee..0000000
--- a/metrics.h
+++ /dev/null
@@ -1,321 +0,0 @@
-//
-// Copyright (C) 2014 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_METRICS_H_
-#define UPDATE_ENGINE_METRICS_H_
-
-#include <base/time/time.h>
-
-#include "update_engine/certificate_checker.h"
-#include "update_engine/common/constants.h"
-#include "update_engine/common/error_code.h"
-
-namespace chromeos_update_engine {
-
-class SystemState;
-
-namespace metrics {
-
-// UpdateEngine.Daily.* metrics.
-extern const char kMetricDailyOSAgeDays[];
-
-// UpdateEngine.Check.* metrics.
-extern const char kMetricCheckDownloadErrorCode[];
-extern const char kMetricCheckReaction[];
-extern const char kMetricCheckResult[];
-extern const char kMetricCheckTimeSinceLastCheckMinutes[];
-extern const char kMetricCheckTimeSinceLastCheckUptimeMinutes[];
-
-// UpdateEngine.Attempt.* metrics.
-extern const char kMetricAttemptNumber[];
-extern const char kMetricAttemptPayloadType[];
-extern const char kMetricAttemptPayloadSizeMiB[];
-extern const char kMetricAttemptConnectionType[];
-extern const char kMetricAttemptDurationMinutes[];
-extern const char kMetricAttemptDurationUptimeMinutes[];
-extern const char kMetricAttemptTimeSinceLastAttemptSeconds[];
-extern const char kMetricAttemptTimeSinceLastAttemptUptimeSeconds[];
-extern const char kMetricAttemptPayloadBytesDownloaded[];
-extern const char kMetricAttemptPayloadDownloadSpeedKBps[];
-extern const char kMetricAttemptDownloadSource[];
-extern const char kMetricAttemptResult[];
-extern const char kMetricAttemptInternalErrorCode[];
-extern const char kMetricAttemptDownloadErrorCode[];
-
-// UpdateEngine.SuccessfulUpdate.* metrics.
-extern const char kMetricSuccessfulUpdateAttemptCount[];
-extern const char kMetricSuccessfulUpdateBytesDownloadedMiB[];
-extern const char kMetricSuccessfulUpdateDownloadOverheadPercentage[];
-extern const char kMetricSuccessfulUpdateDownloadSourcesUsed[];
-extern const char kMetricSuccessfulUpdatePayloadType[];
-extern const char kMetricSuccessfulUpdatePayloadSizeMiB[];
-extern const char kMetricSuccessfulUpdateTotalDurationMinutes[];
-extern const char kMetricSuccessfulUpdateRebootCount[];
-extern const char kMetricSuccessfulUpdateUpdatesAbandonedCount[];
-extern const char kMetricSuccessfulUpdateUrlSwitchCount[];
-
-// UpdateEngine.Rollback.* metric.
-extern const char kMetricRollbackResult[];
-
-// UpdateEngine.* metrics.
-extern const char kMetricFailedUpdateCount[];
-extern const char kMetricInstallDateProvisioningSource[];
-extern const char kMetricTimeToRebootMinutes[];
-
-// The possible outcomes when checking for updates.
-//
-// This is used in the UpdateEngine.Check.Result histogram.
-enum class CheckResult {
-  kUpdateAvailable,    // Response indicates an update is available.
-  kNoUpdateAvailable,  // Response indicates no updates are available.
-  kDownloadError,      // Error downloading response from Omaha.
-  kParsingError,       // Error parsing response.
-  kRebootPending,      // No update check was performed a reboot is pending.
-
-  kNumConstants,
-  kUnset = -1
-};
-
-// Possible ways a device can react to a new update being available.
-//
-// This is used in the UpdateEngine.Check.Reaction histogram.
-enum class CheckReaction {
-  kUpdating,    // Device proceeds to download and apply update.
-  kIgnored  ,   // Device-policy dictates ignoring the update.
-  kDeferring,   // Device-policy dictates waiting.
-  kBackingOff,  // Previous errors dictates waiting.
-
-  kNumConstants,
-  kUnset = -1
-};
-
-// The possible ways that downloading from a HTTP or HTTPS server can fail.
-//
-// This is used in the UpdateEngine.Check.DownloadErrorCode and
-// UpdateEngine.Attempt.DownloadErrorCode histograms.
-enum class DownloadErrorCode {
-  // Errors that can happen in the field. See http://crbug.com/355745
-  // for how we plan to add more detail in the future.
-  kDownloadError = 0,  // Error downloading data from server.
-
-  // IMPORTANT: When adding a new error code, add at the bottom of the
-  // above block and before the kInputMalformed field. This
-  // is to ensure that error codes are not reordered.
-
-  // This error code is used to convey that malformed input was given
-  // to the utils::GetDownloadErrorCode() function. This should never
-  // happen but if it does it's because of an internal update_engine
-  // error and we're interested in knowing this.
-  kInputMalformed = 100,
-
-  // Bucket for capturing HTTP status codes not in the 200-599
-  // range. This should never happen in practice but if it does we
-  // want to know.
-  kHttpStatusOther = 101,
-
-  // Above 200 and below 600, the value is the HTTP status code.
-  kHttpStatus200 = 200,
-
-  kNumConstants = 600,
-
-  kUnset = -1
-};
-
-// Possible ways an update attempt can end.
-//
-// This is used in the UpdateEngine.Attempt.Result histogram.
-enum class AttemptResult {
-  kUpdateSucceeded,             // The update succeeded.
-  kInternalError,               // An internal error occurred.
-  kPayloadDownloadError,        // Failure while downloading payload.
-  kMetadataMalformed,           // Metadata was malformed.
-  kOperationMalformed,          // An operation was malformed.
-  kOperationExecutionError,     // An operation failed to execute.
-  kMetadataVerificationFailed,  // Metadata verification failed.
-  kPayloadVerificationFailed,   // Payload verification failed.
-  kVerificationFailed,          // Root or Kernel partition verification failed.
-  kPostInstallFailed,           // The postinstall step failed.
-  kAbnormalTermination,         // The attempt ended abnormally.
-  kUpdateCanceled,              // Update canceled by the user.
-
-  kNumConstants,
-
-  kUnset = -1
-};
-
-// Possible ways the device is connected to the Internet.
-//
-// 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).
-
-  kNumConstants,
-  kUnset = -1
-};
-
-// Possible ways a rollback can end.
-//
-// This is used in the UpdateEngine.Rollback histogram.
-enum class RollbackResult {
-  kFailed,
-  kSuccess,
-
-  kNumConstants
-};
-
-// Helper function to report metrics related to rollback. The
-// following metrics are reported:
-//
-//  |kMetricRollbackResult|
-void ReportRollbackMetrics(SystemState *system_state,
-                           RollbackResult result);
-
-// Helper function to report metrics reported once a day. The
-// following metrics are reported:
-//
-//  |kMetricDailyOSAgeDays|
-void ReportDailyMetrics(SystemState *system_state,
-                        base::TimeDelta os_age);
-
-// Helper function to report metrics after completing an update check
-// with the ChromeOS update server ("Omaha"). The following metrics
-// are reported:
-//
-//  |kMetricCheckResult|
-//  |kMetricCheckReaction|
-//  |kMetricCheckDownloadErrorCode|
-//  |kMetricCheckTimeSinceLastCheckMinutes|
-//  |kMetricCheckTimeSinceLastCheckUptimeMinutes|
-//
-// The |kMetricCheckResult| metric will only be reported if |result|
-// is not |kUnset|.
-//
-// The |kMetricCheckReaction| metric will only be reported if
-// |reaction| is not |kUnset|.
-//
-// The |kMetricCheckDownloadErrorCode| will only be reported if
-// |download_error_code| is not |kUnset|.
-//
-// The values for the |kMetricCheckTimeSinceLastCheckMinutes| and
-// |kMetricCheckTimeSinceLastCheckUptimeMinutes| metrics are
-// automatically reported and calculated by maintaining persistent
-// and process-local state variables.
-void ReportUpdateCheckMetrics(SystemState *system_state,
-                              CheckResult result,
-                              CheckReaction reaction,
-                              DownloadErrorCode download_error_code);
-
-
-// Helper function to report metrics after the completion of each
-// update attempt. The following metrics are reported:
-//
-//  |kMetricAttemptNumber|
-//  |kMetricAttemptPayloadType|
-//  |kMetricAttemptPayloadSizeMiB|
-//  |kMetricAttemptDurationSeconds|
-//  |kMetricAttemptDurationUptimeSeconds|
-//  |kMetricAttemptTimeSinceLastAttemptMinutes|
-//  |kMetricAttemptTimeSinceLastAttemptUptimeMinutes|
-//  |kMetricAttemptPayloadBytesDownloadedMiB|
-//  |kMetricAttemptPayloadDownloadSpeedKBps|
-//  |kMetricAttemptDownloadSource|
-//  |kMetricAttemptResult|
-//  |kMetricAttemptInternalErrorCode|
-//  |kMetricAttemptDownloadErrorCode|
-//
-// The |kMetricAttemptInternalErrorCode| metric will only be reported
-// if |internal_error_code| is not |kErrorSuccess|.
-//
-// The |kMetricAttemptDownloadErrorCode| metric will only be
-// reported if |payload_download_error_code| is not |kUnset|.
-//
-// The values for the |kMetricAttemptTimeSinceLastAttemptMinutes| and
-// |kMetricAttemptTimeSinceLastAttemptUptimeMinutes| metrics are
-// automatically calculated and reported by maintaining persistent and
-// process-local state variables.
-void ReportUpdateAttemptMetrics(
-    SystemState *system_state,
-    int attempt_number,
-    PayloadType payload_type,
-    base::TimeDelta duration,
-    base::TimeDelta duration_uptime,
-    int64_t payload_size,
-    int64_t payload_bytes_downloaded,
-    int64_t payload_download_speed_bps,
-    DownloadSource download_source,
-    AttemptResult attempt_result,
-    ErrorCode internal_error_code,
-    DownloadErrorCode payload_download_error_code,
-    ConnectionType connection_type);
-
-// Reports the |kAbnormalTermination| for the |kMetricAttemptResult|
-// metric. No other metrics in the UpdateEngine.Attempt.* namespace
-// will be reported.
-void ReportAbnormallyTerminatedUpdateAttemptMetrics(SystemState *system_state);
-
-// Helper function to report the after the completion of a successful
-// update attempt. The following metrics are reported:
-//
-//  |kMetricSuccessfulUpdateAttemptCount|
-//  |kMetricSuccessfulUpdateUpdatesAbandonedCount|
-//  |kMetricSuccessfulUpdatePayloadType|
-//  |kMetricSuccessfulUpdatePayloadSizeMiB|
-//  |kMetricSuccessfulUpdateBytesDownloadedMiBHttpsServer|
-//  |kMetricSuccessfulUpdateBytesDownloadedMiBHttpServer|
-//  |kMetricSuccessfulUpdateBytesDownloadedMiBHttpPeer|
-//  |kMetricSuccessfulUpdateBytesDownloadedMiB|
-//  |kMetricSuccessfulUpdateDownloadSourcesUsed|
-//  |kMetricSuccessfulUpdateDownloadOverheadPercentage|
-//  |kMetricSuccessfulUpdateTotalDurationMinutes|
-//  |kMetricSuccessfulUpdateRebootCount|
-//  |kMetricSuccessfulUpdateUrlSwitchCount|
-//
-// The values for the |kMetricSuccessfulUpdateDownloadSourcesUsed| are
-// |kMetricSuccessfulUpdateBytesDownloadedMiB| metrics automatically
-// calculated from examining the |num_bytes_downloaded| array.
-void ReportSuccessfulUpdateMetrics(
-    SystemState *system_state,
-    int attempt_count,
-    int updates_abandoned_count,
-    PayloadType payload_type,
-    int64_t payload_size,
-    int64_t num_bytes_downloaded[kNumDownloadSources],
-    int download_overhead_percentage,
-    base::TimeDelta total_duration,
-    int reboot_count,
-    int url_switch_count);
-
-// Helper function to report the after the completion of a SSL certificate
-// check. One of the following metrics is reported:
-//
-//  |kMetricCertificateCheckUpdateCheck|
-//  |kMetricCertificateCheckDownload|
-void ReportCertificateCheckMetrics(SystemState* system_state,
-                                   ServerToCheck server_to_check,
-                                   CertificateCheckResult result);
-
-}  // namespace metrics
-
-}  // namespace chromeos_update_engine
-
-#endif  // UPDATE_ENGINE_METRICS_H_
diff --git a/metrics_constants.h b/metrics_constants.h
new file mode 100644
index 0000000..abec2ad
--- /dev/null
+++ b/metrics_constants.h
@@ -0,0 +1,137 @@
+//
+// Copyright (C) 2017 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_METRICS_CONSTANTS_H_
+#define UPDATE_ENGINE_METRICS_CONSTANTS_H_
+
+namespace chromeos_update_engine {
+
+namespace metrics {
+// The possible outcomes when checking for updates.
+//
+// This is used in the UpdateEngine.Check.Result histogram.
+enum class CheckResult {
+  kUpdateAvailable,    // Response indicates an update is available.
+  kNoUpdateAvailable,  // Response indicates no updates are available.
+  kDownloadError,      // Error downloading response from Omaha.
+  kParsingError,       // Error parsing response.
+  kRebootPending,      // No update check was performed a reboot is pending.
+
+  kNumConstants,
+  kUnset = -1
+};
+
+// Possible ways a device can react to a new update being available.
+//
+// This is used in the UpdateEngine.Check.Reaction histogram.
+enum class CheckReaction {
+  kUpdating,    // Device proceeds to download and apply update.
+  kIgnored,     // Device-policy dictates ignoring the update.
+  kDeferring,   // Device-policy dictates waiting.
+  kBackingOff,  // Previous errors dictates waiting.
+
+  kNumConstants,
+  kUnset = -1
+};
+
+// The possible ways that downloading from a HTTP or HTTPS server can fail.
+//
+// This is used in the UpdateEngine.Check.DownloadErrorCode and
+// UpdateEngine.Attempt.DownloadErrorCode histograms.
+enum class DownloadErrorCode {
+  // Errors that can happen in the field. See http://crbug.com/355745
+  // for how we plan to add more detail in the future.
+  kDownloadError = 0,  // Error downloading data from server.
+
+  // IMPORTANT: When adding a new error code, add at the bottom of the
+  // above block and before the kInputMalformed field. This
+  // is to ensure that error codes are not reordered.
+
+  // This error code is used to convey that malformed input was given
+  // to the utils::GetDownloadErrorCode() function. This should never
+  // happen but if it does it's because of an internal update_engine
+  // error and we're interested in knowing this.
+  kInputMalformed = 100,
+
+  // Bucket for capturing HTTP status codes not in the 200-599
+  // range. This should never happen in practice but if it does we
+  // want to know.
+  kHttpStatusOther = 101,
+
+  // Above 200 and below 600, the value is the HTTP status code.
+  kHttpStatus200 = 200,
+
+  kNumConstants = 600,
+
+  kUnset = -1
+};
+
+// Possible ways an update attempt can end.
+//
+// This is used in the UpdateEngine.Attempt.Result histogram.
+enum class AttemptResult {
+  kUpdateSucceeded,             // The update succeeded.
+  kInternalError,               // An internal error occurred.
+  kPayloadDownloadError,        // Failure while downloading payload.
+  kMetadataMalformed,           // Metadata was malformed.
+  kOperationMalformed,          // An operation was malformed.
+  kOperationExecutionError,     // An operation failed to execute.
+  kMetadataVerificationFailed,  // Metadata verification failed.
+  kPayloadVerificationFailed,   // Payload verification failed.
+  kVerificationFailed,          // Root or Kernel partition verification failed.
+  kPostInstallFailed,           // The postinstall step failed.
+  kAbnormalTermination,         // The attempt ended abnormally.
+  kUpdateCanceled,              // Update canceled by the user.
+  kUpdateSucceededNotActive,    // Update succeeded but the new slot is not
+                                // active.
+
+  kNumConstants,
+
+  kUnset = -1
+};
+
+// Possible ways the device is connected to the Internet.
+//
+// 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).
+
+  kNumConstants,
+  kUnset = -1
+};
+
+// Possible ways a rollback can end.
+//
+// This is used in the UpdateEngine.Rollback histogram.
+enum class RollbackResult {
+  kFailed,
+  kSuccess,
+
+  kNumConstants
+};
+
+}  // namespace metrics
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_METRICS_CONSTANTS_H_
diff --git a/metrics_reporter_android.cc b/metrics_reporter_android.cc
new file mode 100644
index 0000000..3cb356f
--- /dev/null
+++ b/metrics_reporter_android.cc
@@ -0,0 +1,158 @@
+//
+// Copyright (C) 2017 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/metrics_reporter_android.h"
+
+#include <memory>
+#include <string>
+
+#include <metricslogger/metrics_logger.h>
+
+#include "update_engine/common/constants.h"
+
+namespace {
+void LogHistogram(const std::string& metrics, int value) {
+  android::metricslogger::LogHistogram(metrics, value);
+  LOG(INFO) << "uploading " << value << " to histogram for metric " << metrics;
+}
+}  // namespace
+
+namespace chromeos_update_engine {
+
+namespace metrics {
+
+// The histograms are defined in:
+// depot/google3/analysis/uma/configs/clearcut/TRON/histograms.xml
+constexpr char kMetricsUpdateEngineAttemptNumber[] =
+    "ota_update_engine_attempt_number";
+constexpr char kMetricsUpdateEngineAttemptResult[] =
+    "ota_update_engine_attempt_result";
+constexpr char kMetricsUpdateEngineAttemptDurationInMinutes[] =
+    "ota_update_engine_attempt_duration_boottime_in_minutes";
+constexpr char kMetricsUpdateEngineAttemptDurationUptimeInMinutes[] =
+    "ota_update_engine_attempt_duration_monotonic_in_minutes";
+constexpr char kMetricsUpdateEngineAttemptErrorCode[] =
+    "ota_update_engine_attempt_error_code";
+constexpr char kMetricsUpdateEngineAttemptPayloadSizeMiB[] =
+    "ota_update_engine_attempt_payload_size_mib";
+constexpr char kMetricsUpdateEngineAttemptPayloadType[] =
+    "ota_update_engine_attempt_payload_type";
+constexpr char kMetricsUpdateEngineAttemptCurrentBytesDownloadedMiB[] =
+    "ota_update_engine_attempt_current_bytes_downloaded_mib";
+
+constexpr char kMetricsUpdateEngineSuccessfulUpdateAttemptCount[] =
+    "ota_update_engine_successful_update_attempt_count";
+constexpr char kMetricsUpdateEngineSuccessfulUpdateTotalDurationInMinutes[] =
+    "ota_update_engine_successful_update_total_duration_in_minutes";
+constexpr char kMetricsUpdateEngineSuccessfulUpdatePayloadSizeMiB[] =
+    "ota_update_engine_successful_update_payload_size_mib";
+constexpr char kMetricsUpdateEngineSuccessfulUpdatePayloadType[] =
+    "ota_update_engine_successful_update_payload_type";
+constexpr char kMetricsUpdateEngineSuccessfulUpdateRebootCount[] =
+    "ota_update_engine_successful_update_reboot_count";
+constexpr char kMetricsUpdateEngineSuccessfulUpdateTotalBytesDownloadedMiB[] =
+    "ota_update_engine_successful_update_total_bytes_downloaded_mib";
+constexpr char
+    kMetricsUpdateEngineSuccessfulUpdateDownloadOverheadPercentage[] =
+        "ota_update_engine_successful_update_download_overhead_percentage";
+
+std::unique_ptr<MetricsReporterInterface> CreateMetricsReporter() {
+  return std::make_unique<MetricsReporterAndroid>();
+}
+
+}  // namespace metrics
+
+void MetricsReporterAndroid::ReportUpdateAttemptMetrics(
+    SystemState* /* system_state */,
+    int attempt_number,
+    PayloadType payload_type,
+    base::TimeDelta duration,
+    base::TimeDelta duration_uptime,
+    int64_t payload_size,
+    metrics::AttemptResult attempt_result,
+    ErrorCode error_code) {
+  LogHistogram(metrics::kMetricsUpdateEngineAttemptNumber, attempt_number);
+  LogHistogram(metrics::kMetricsUpdateEngineAttemptPayloadType,
+               static_cast<int>(payload_type));
+  LogHistogram(metrics::kMetricsUpdateEngineAttemptDurationInMinutes,
+               duration.InMinutes());
+  LogHistogram(metrics::kMetricsUpdateEngineAttemptDurationUptimeInMinutes,
+               duration_uptime.InMinutes());
+
+  int64_t payload_size_mib = payload_size / kNumBytesInOneMiB;
+  LogHistogram(metrics::kMetricsUpdateEngineAttemptPayloadSizeMiB,
+               payload_size_mib);
+
+  LogHistogram(metrics::kMetricsUpdateEngineAttemptResult,
+               static_cast<int>(attempt_result));
+  LogHistogram(metrics::kMetricsUpdateEngineAttemptErrorCode,
+               static_cast<int>(error_code));
+}
+
+void MetricsReporterAndroid::ReportUpdateAttemptDownloadMetrics(
+    int64_t payload_bytes_downloaded,
+    int64_t /* payload_download_speed_bps */,
+    DownloadSource /* download_source */,
+    metrics::DownloadErrorCode /* payload_download_error_code */,
+    metrics::ConnectionType /* connection_type */) {
+  LogHistogram(metrics::kMetricsUpdateEngineAttemptCurrentBytesDownloadedMiB,
+               payload_bytes_downloaded);
+}
+
+void MetricsReporterAndroid::ReportSuccessfulUpdateMetrics(
+    int attempt_count,
+    int /* updates_abandoned_count */,
+    PayloadType payload_type,
+    int64_t payload_size,
+    int64_t num_bytes_downloaded[kNumDownloadSources],
+    int download_overhead_percentage,
+    base::TimeDelta total_duration,
+    int reboot_count,
+    int /* url_switch_count */) {
+  LogHistogram(metrics::kMetricsUpdateEngineSuccessfulUpdateAttemptCount,
+               attempt_count);
+  LogHistogram(metrics::kMetricsUpdateEngineSuccessfulUpdatePayloadType,
+               static_cast<int>(payload_type));
+
+  int64_t payload_size_mib = payload_size / kNumBytesInOneMiB;
+  LogHistogram(metrics::kMetricsUpdateEngineSuccessfulUpdatePayloadSizeMiB,
+               payload_size_mib);
+
+  int64_t total_bytes_downloaded = 0;
+  for (size_t i = 0; i < kNumDownloadSources; i++) {
+    total_bytes_downloaded += num_bytes_downloaded[i] / kNumBytesInOneMiB;
+  }
+  LogHistogram(
+      metrics::kMetricsUpdateEngineSuccessfulUpdateTotalBytesDownloadedMiB,
+      total_bytes_downloaded);
+  LogHistogram(
+      metrics::kMetricsUpdateEngineSuccessfulUpdateDownloadOverheadPercentage,
+      download_overhead_percentage);
+
+  LogHistogram(
+      metrics::kMetricsUpdateEngineSuccessfulUpdateTotalDurationInMinutes,
+      total_duration.InMinutes());
+  LogHistogram(metrics::kMetricsUpdateEngineSuccessfulUpdateRebootCount,
+               reboot_count);
+}
+
+void MetricsReporterAndroid::ReportAbnormallyTerminatedUpdateAttemptMetrics() {
+  int attempt_result =
+      static_cast<int>(metrics::AttemptResult::kAbnormalTermination);
+  LogHistogram(metrics::kMetricsUpdateEngineAttemptResult, attempt_result);
+}
+
+};  // namespace chromeos_update_engine
diff --git a/metrics_reporter_android.h b/metrics_reporter_android.h
new file mode 100644
index 0000000..ee94e43
--- /dev/null
+++ b/metrics_reporter_android.h
@@ -0,0 +1,88 @@
+//
+// Copyright (C) 2017 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_METRICS_REPORTER_ANDROID_H_
+#define UPDATE_ENGINE_METRICS_REPORTER_ANDROID_H_
+
+#include "update_engine/common/error_code.h"
+#include "update_engine/metrics_constants.h"
+#include "update_engine/metrics_reporter_interface.h"
+
+namespace chromeos_update_engine {
+
+class MetricsReporterAndroid : public MetricsReporterInterface {
+ public:
+  MetricsReporterAndroid() = default;
+
+  ~MetricsReporterAndroid() override = default;
+
+  void Initialize() override {}
+
+  void ReportRollbackMetrics(metrics::RollbackResult result) override {}
+
+  void ReportDailyMetrics(base::TimeDelta os_age) override {}
+
+  void ReportUpdateCheckMetrics(
+      SystemState* system_state,
+      metrics::CheckResult result,
+      metrics::CheckReaction reaction,
+      metrics::DownloadErrorCode download_error_code) override {}
+
+  void ReportUpdateAttemptMetrics(SystemState* system_state,
+                                  int attempt_number,
+                                  PayloadType payload_type,
+                                  base::TimeDelta duration,
+                                  base::TimeDelta duration_uptime,
+                                  int64_t payload_size,
+                                  metrics::AttemptResult attempt_result,
+                                  ErrorCode internal_error_code) override;
+
+  void ReportUpdateAttemptDownloadMetrics(
+      int64_t payload_bytes_downloaded,
+      int64_t payload_download_speed_bps,
+      DownloadSource download_source,
+      metrics::DownloadErrorCode payload_download_error_code,
+      metrics::ConnectionType connection_type) override;
+
+  void ReportAbnormallyTerminatedUpdateAttemptMetrics() override;
+
+  void ReportSuccessfulUpdateMetrics(
+      int attempt_count,
+      int updates_abandoned_count,
+      PayloadType payload_type,
+      int64_t payload_size,
+      int64_t num_bytes_downloaded[kNumDownloadSources],
+      int download_overhead_percentage,
+      base::TimeDelta total_duration,
+      int reboot_count,
+      int url_switch_count) override;
+
+  void ReportCertificateCheckMetrics(ServerToCheck server_to_check,
+                                     CertificateCheckResult result) override {}
+
+  void ReportFailedUpdateCount(int target_attempt) override {}
+
+  void ReportTimeToReboot(int time_to_reboot_minutes) override {}
+
+  void ReportInstallDateProvisioningSource(int source, int max) override {}
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(MetricsReporterAndroid);
+};
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_METRICS_REPORTER_ANDROID_H_
diff --git a/metrics_reporter_interface.h b/metrics_reporter_interface.h
new file mode 100644
index 0000000..2c7ce5b
--- /dev/null
+++ b/metrics_reporter_interface.h
@@ -0,0 +1,200 @@
+//
+// Copyright (C) 2017 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_METRICS_REPORTER_INTERFACE_H_
+#define UPDATE_ENGINE_METRICS_REPORTER_INTERFACE_H_
+
+#include <memory>
+
+#include <base/time/time.h>
+
+#include "update_engine/common/constants.h"
+#include "update_engine/common/error_code.h"
+#include "update_engine/metrics_constants.h"
+#include "update_engine/system_state.h"
+
+namespace chromeos_update_engine {
+
+enum class ServerToCheck;
+enum class CertificateCheckResult;
+
+namespace metrics {
+
+std::unique_ptr<MetricsReporterInterface> CreateMetricsReporter();
+
+}  // namespace metrics
+
+class MetricsReporterInterface {
+ public:
+  virtual ~MetricsReporterInterface() = default;
+
+  virtual void Initialize() = 0;
+
+  // Helper function to report metrics related to rollback. The
+  // following metrics are reported:
+  //
+  //  |kMetricRollbackResult|
+  virtual void ReportRollbackMetrics(metrics::RollbackResult result) = 0;
+
+  // Helper function to report metrics reported once a day. The
+  // following metrics are reported:
+  //
+  //  |kMetricDailyOSAgeDays|
+  virtual void ReportDailyMetrics(base::TimeDelta os_age) = 0;
+
+  // Helper function to report metrics after completing an update check
+  // with the ChromeOS update server ("Omaha"). The following metrics
+  // are reported:
+  //
+  //  |kMetricCheckResult|
+  //  |kMetricCheckReaction|
+  //  |kMetricCheckDownloadErrorCode|
+  //  |kMetricCheckTimeSinceLastCheckMinutes|
+  //  |kMetricCheckTimeSinceLastCheckUptimeMinutes|
+  //
+  // The |kMetricCheckResult| metric will only be reported if |result|
+  // is not |kUnset|.
+  //
+  // The |kMetricCheckReaction| metric will only be reported if
+  // |reaction| is not |kUnset|.
+  //
+  // The |kMetricCheckDownloadErrorCode| will only be reported if
+  // |download_error_code| is not |kUnset|.
+  //
+  // The values for the |kMetricCheckTimeSinceLastCheckMinutes| and
+  // |kMetricCheckTimeSinceLastCheckUptimeMinutes| metrics are
+  // automatically reported and calculated by maintaining persistent
+  // and process-local state variables.
+  virtual void ReportUpdateCheckMetrics(
+      SystemState* system_state,
+      metrics::CheckResult result,
+      metrics::CheckReaction reaction,
+      metrics::DownloadErrorCode download_error_code) = 0;
+
+  // Helper function to report metrics after the completion of each
+  // update attempt. The following metrics are reported:
+  //
+  //  |kMetricAttemptNumber|
+  //  |kMetricAttemptPayloadType|
+  //  |kMetricAttemptPayloadSizeMiB|
+  //  |kMetricAttemptDurationMinutes|
+  //  |kMetricAttemptDurationUptimeMinutes|
+  //  |kMetricAttemptTimeSinceLastAttemptMinutes|
+  //  |kMetricAttemptTimeSinceLastAttemptUptimeMinutes|
+  //  |kMetricAttemptResult|
+  //  |kMetricAttemptInternalErrorCode|
+  //
+  // The |kMetricAttemptInternalErrorCode| metric will only be reported
+  // if |internal_error_code| is not |kErrorSuccess|.
+  //
+  // The |kMetricAttemptDownloadErrorCode| metric will only be
+  // reported if |payload_download_error_code| is not |kUnset|.
+  //
+  // The values for the |kMetricAttemptTimeSinceLastAttemptMinutes| and
+  // |kMetricAttemptTimeSinceLastAttemptUptimeMinutes| metrics are
+  // automatically calculated and reported by maintaining persistent and
+  // process-local state variables.
+  virtual void ReportUpdateAttemptMetrics(SystemState* system_state,
+                                          int attempt_number,
+                                          PayloadType payload_type,
+                                          base::TimeDelta duration,
+                                          base::TimeDelta duration_uptime,
+                                          int64_t payload_size,
+                                          metrics::AttemptResult attempt_result,
+                                          ErrorCode internal_error_code) = 0;
+
+  // Helper function to report download metrics after the completion of each
+  // update attempt. The following metrics are reported:
+  //
+  // |kMetricAttemptPayloadBytesDownloadedMiB|
+  // |kMetricAttemptPayloadDownloadSpeedKBps|
+  // |kMetricAttemptDownloadSource|
+  // |kMetricAttemptDownloadErrorCode|
+  // |kMetricAttemptConnectionType|
+  virtual void ReportUpdateAttemptDownloadMetrics(
+      int64_t payload_bytes_downloaded,
+      int64_t payload_download_speed_bps,
+      DownloadSource download_source,
+      metrics::DownloadErrorCode payload_download_error_code,
+      metrics::ConnectionType connection_type) = 0;
+
+  // Reports the |kAbnormalTermination| for the |kMetricAttemptResult|
+  // metric. No other metrics in the UpdateEngine.Attempt.* namespace
+  // will be reported.
+  virtual void ReportAbnormallyTerminatedUpdateAttemptMetrics() = 0;
+
+  // Helper function to report the after the completion of a successful
+  // update attempt. The following metrics are reported:
+  //
+  //  |kMetricSuccessfulUpdateAttemptCount|
+  //  |kMetricSuccessfulUpdateUpdatesAbandonedCount|
+  //  |kMetricSuccessfulUpdatePayloadType|
+  //  |kMetricSuccessfulUpdatePayloadSizeMiB|
+  //  |kMetricSuccessfulUpdateBytesDownloadedMiBHttpsServer|
+  //  |kMetricSuccessfulUpdateBytesDownloadedMiBHttpServer|
+  //  |kMetricSuccessfulUpdateBytesDownloadedMiBHttpPeer|
+  //  |kMetricSuccessfulUpdateBytesDownloadedMiB|
+  //  |kMetricSuccessfulUpdateDownloadSourcesUsed|
+  //  |kMetricSuccessfulUpdateDownloadOverheadPercentage|
+  //  |kMetricSuccessfulUpdateTotalDurationMinutes|
+  //  |kMetricSuccessfulUpdateRebootCount|
+  //  |kMetricSuccessfulUpdateUrlSwitchCount|
+  //
+  // The values for the |kMetricSuccessfulUpdateDownloadSourcesUsed| are
+  // |kMetricSuccessfulUpdateBytesDownloadedMiB| metrics automatically
+  // calculated from examining the |num_bytes_downloaded| array.
+  virtual void ReportSuccessfulUpdateMetrics(
+      int attempt_count,
+      int updates_abandoned_count,
+      PayloadType payload_type,
+      int64_t payload_size,
+      int64_t num_bytes_downloaded[kNumDownloadSources],
+      int download_overhead_percentage,
+      base::TimeDelta total_duration,
+      int reboot_count,
+      int url_switch_count) = 0;
+
+  // Helper function to report the after the completion of a SSL certificate
+  // check. One of the following metrics is reported:
+  //
+  //  |kMetricCertificateCheckUpdateCheck|
+  //  |kMetricCertificateCheckDownload|
+  virtual void ReportCertificateCheckMetrics(ServerToCheck server_to_check,
+                                             CertificateCheckResult result) = 0;
+
+  // Helper function to report the number failed update attempts. The following
+  // metrics are reported:
+  //
+  // |kMetricFailedUpdateCount|
+  virtual void ReportFailedUpdateCount(int target_attempt) = 0;
+
+  // Helper function to report the time interval in minutes between a
+  // successful update and the reboot into the updated system. The following
+  // metrics are reported:
+  //
+  // |kMetricTimeToRebootMinutes|
+  virtual void ReportTimeToReboot(int time_to_reboot_minutes) = 0;
+
+  // Helper function to report the source of installation data. The following
+  // metrics are reported:
+  //
+  // |kMetricInstallDateProvisioningSource|
+  virtual void ReportInstallDateProvisioningSource(int source, int max) = 0;
+};
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_METRICS_REPORTER_INTERFACE_H_
diff --git a/metrics_reporter_omaha.cc b/metrics_reporter_omaha.cc
new file mode 100644
index 0000000..0397b83
--- /dev/null
+++ b/metrics_reporter_omaha.cc
@@ -0,0 +1,538 @@
+//
+// Copyright (C) 2014 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/metrics_reporter_omaha.h"
+
+#include <memory>
+#include <string>
+
+#include <base/logging.h>
+#include <metrics/metrics_library.h>
+
+#include "update_engine/common/clock_interface.h"
+#include "update_engine/common/constants.h"
+#include "update_engine/common/prefs_interface.h"
+#include "update_engine/common/utils.h"
+#include "update_engine/metrics_utils.h"
+#include "update_engine/system_state.h"
+
+using std::string;
+
+namespace chromeos_update_engine {
+
+namespace metrics {
+
+// UpdateEngine.Daily.* metrics.
+const char kMetricDailyOSAgeDays[] = "UpdateEngine.Daily.OSAgeDays";
+
+// UpdateEngine.Check.* metrics.
+const char kMetricCheckDownloadErrorCode[] =
+    "UpdateEngine.Check.DownloadErrorCode";
+const char kMetricCheckReaction[] = "UpdateEngine.Check.Reaction";
+const char kMetricCheckResult[] = "UpdateEngine.Check.Result";
+const char kMetricCheckTimeSinceLastCheckMinutes[] =
+    "UpdateEngine.Check.TimeSinceLastCheckMinutes";
+const char kMetricCheckTimeSinceLastCheckUptimeMinutes[] =
+    "UpdateEngine.Check.TimeSinceLastCheckUptimeMinutes";
+
+// UpdateEngine.Attempt.* metrics.
+const char kMetricAttemptNumber[] = "UpdateEngine.Attempt.Number";
+const char kMetricAttemptPayloadType[] = "UpdateEngine.Attempt.PayloadType";
+const char kMetricAttemptPayloadSizeMiB[] =
+    "UpdateEngine.Attempt.PayloadSizeMiB";
+const char kMetricAttemptConnectionType[] =
+    "UpdateEngine.Attempt.ConnectionType";
+const char kMetricAttemptDurationMinutes[] =
+    "UpdateEngine.Attempt.DurationMinutes";
+const char kMetricAttemptDurationUptimeMinutes[] =
+    "UpdateEngine.Attempt.DurationUptimeMinutes";
+const char kMetricAttemptTimeSinceLastAttemptMinutes[] =
+    "UpdateEngine.Attempt.TimeSinceLastAttemptMinutes";
+const char kMetricAttemptTimeSinceLastAttemptUptimeMinutes[] =
+    "UpdateEngine.Attempt.TimeSinceLastAttemptUptimeMinutes";
+const char kMetricAttemptPayloadBytesDownloadedMiB[] =
+    "UpdateEngine.Attempt.PayloadBytesDownloadedMiB";
+const char kMetricAttemptPayloadDownloadSpeedKBps[] =
+    "UpdateEngine.Attempt.PayloadDownloadSpeedKBps";
+const char kMetricAttemptDownloadSource[] =
+    "UpdateEngine.Attempt.DownloadSource";
+const char kMetricAttemptResult[] = "UpdateEngine.Attempt.Result";
+const char kMetricAttemptInternalErrorCode[] =
+    "UpdateEngine.Attempt.InternalErrorCode";
+const char kMetricAttemptDownloadErrorCode[] =
+    "UpdateEngine.Attempt.DownloadErrorCode";
+
+// UpdateEngine.SuccessfulUpdate.* metrics.
+const char kMetricSuccessfulUpdateAttemptCount[] =
+    "UpdateEngine.SuccessfulUpdate.AttemptCount";
+const char kMetricSuccessfulUpdateBytesDownloadedMiB[] =
+    "UpdateEngine.SuccessfulUpdate.BytesDownloadedMiB";
+const char kMetricSuccessfulUpdateDownloadOverheadPercentage[] =
+    "UpdateEngine.SuccessfulUpdate.DownloadOverheadPercentage";
+const char kMetricSuccessfulUpdateDownloadSourcesUsed[] =
+    "UpdateEngine.SuccessfulUpdate.DownloadSourcesUsed";
+const char kMetricSuccessfulUpdatePayloadType[] =
+    "UpdateEngine.SuccessfulUpdate.PayloadType";
+const char kMetricSuccessfulUpdatePayloadSizeMiB[] =
+    "UpdateEngine.SuccessfulUpdate.PayloadSizeMiB";
+const char kMetricSuccessfulUpdateRebootCount[] =
+    "UpdateEngine.SuccessfulUpdate.RebootCount";
+const char kMetricSuccessfulUpdateTotalDurationMinutes[] =
+    "UpdateEngine.SuccessfulUpdate.TotalDurationMinutes";
+const char kMetricSuccessfulUpdateUpdatesAbandonedCount[] =
+    "UpdateEngine.SuccessfulUpdate.UpdatesAbandonedCount";
+const char kMetricSuccessfulUpdateUrlSwitchCount[] =
+    "UpdateEngine.SuccessfulUpdate.UrlSwitchCount";
+
+// UpdateEngine.Rollback.* metric.
+const char kMetricRollbackResult[] = "UpdateEngine.Rollback.Result";
+
+// UpdateEngine.CertificateCheck.* metrics.
+const char kMetricCertificateCheckUpdateCheck[] =
+    "UpdateEngine.CertificateCheck.UpdateCheck";
+const char kMetricCertificateCheckDownload[] =
+    "UpdateEngine.CertificateCheck.Download";
+
+// UpdateEngine.* metrics.
+const char kMetricFailedUpdateCount[] = "UpdateEngine.FailedUpdateCount";
+const char kMetricInstallDateProvisioningSource[] =
+    "UpdateEngine.InstallDateProvisioningSource";
+const char kMetricTimeToRebootMinutes[] = "UpdateEngine.TimeToRebootMinutes";
+
+std::unique_ptr<MetricsReporterInterface> CreateMetricsReporter() {
+  return std::make_unique<MetricsReporterOmaha>();
+}
+
+}  // namespace metrics
+
+MetricsReporterOmaha::MetricsReporterOmaha()
+    : metrics_lib_(new MetricsLibrary()) {}
+
+void MetricsReporterOmaha::Initialize() {
+  metrics_lib_->Init();
+}
+
+void MetricsReporterOmaha::ReportDailyMetrics(base::TimeDelta os_age) {
+  string metric = metrics::kMetricDailyOSAgeDays;
+  LOG(INFO) << "Uploading " << utils::FormatTimeDelta(os_age) << " for metric "
+            << metric;
+  metrics_lib_->SendToUMA(metric,
+                          static_cast<int>(os_age.InDays()),
+                          0,       // min: 0 days
+                          6 * 30,  // max: 6 months (approx)
+                          50);     // num_buckets
+}
+
+void MetricsReporterOmaha::ReportUpdateCheckMetrics(
+    SystemState* system_state,
+    metrics::CheckResult result,
+    metrics::CheckReaction reaction,
+    metrics::DownloadErrorCode download_error_code) {
+  string metric;
+  int value;
+  int max_value;
+
+  if (result != metrics::CheckResult::kUnset) {
+    metric = metrics::kMetricCheckResult;
+    value = static_cast<int>(result);
+    max_value = static_cast<int>(metrics::CheckResult::kNumConstants) - 1;
+    LOG(INFO) << "Sending " << value << " for metric " << metric << " (enum)";
+    metrics_lib_->SendEnumToUMA(metric, value, max_value);
+  }
+  if (reaction != metrics::CheckReaction::kUnset) {
+    metric = metrics::kMetricCheckReaction;
+    value = static_cast<int>(reaction);
+    max_value = static_cast<int>(metrics::CheckReaction::kNumConstants) - 1;
+    LOG(INFO) << "Sending " << value << " for metric " << metric << " (enum)";
+    metrics_lib_->SendEnumToUMA(metric, value, max_value);
+  }
+  if (download_error_code != metrics::DownloadErrorCode::kUnset) {
+    metric = metrics::kMetricCheckDownloadErrorCode;
+    value = static_cast<int>(download_error_code);
+    LOG(INFO) << "Sending " << value << " for metric " << metric << " (sparse)";
+    metrics_lib_->SendSparseToUMA(metric, value);
+  }
+
+  base::TimeDelta time_since_last;
+  if (metrics_utils::WallclockDurationHelper(
+          system_state,
+          kPrefsMetricsCheckLastReportingTime,
+          &time_since_last)) {
+    metric = metrics::kMetricCheckTimeSinceLastCheckMinutes;
+    LOG(INFO) << "Sending " << utils::FormatTimeDelta(time_since_last)
+              << " for metric " << metric;
+    metrics_lib_->SendToUMA(metric,
+                            time_since_last.InMinutes(),
+                            0,             // min: 0 min
+                            30 * 24 * 60,  // max: 30 days
+                            50);           // num_buckets
+  }
+
+  base::TimeDelta uptime_since_last;
+  static int64_t uptime_since_last_storage = 0;
+  if (metrics_utils::MonotonicDurationHelper(
+          system_state, &uptime_since_last_storage, &uptime_since_last)) {
+    metric = metrics::kMetricCheckTimeSinceLastCheckUptimeMinutes;
+    LOG(INFO) << "Sending " << utils::FormatTimeDelta(uptime_since_last)
+              << " for metric " << metric;
+    metrics_lib_->SendToUMA(metric,
+                            uptime_since_last.InMinutes(),
+                            0,             // min: 0 min
+                            30 * 24 * 60,  // max: 30 days
+                            50);           // num_buckets
+  }
+}
+
+void MetricsReporterOmaha::ReportAbnormallyTerminatedUpdateAttemptMetrics() {
+  string metric = metrics::kMetricAttemptResult;
+  metrics::AttemptResult attempt_result =
+      metrics::AttemptResult::kAbnormalTermination;
+
+  LOG(INFO) << "Uploading " << static_cast<int>(attempt_result)
+            << " for metric " << metric;
+  metrics_lib_->SendEnumToUMA(
+      metric,
+      static_cast<int>(attempt_result),
+      static_cast<int>(metrics::AttemptResult::kNumConstants));
+}
+
+void MetricsReporterOmaha::ReportUpdateAttemptMetrics(
+    SystemState* system_state,
+    int attempt_number,
+    PayloadType payload_type,
+    base::TimeDelta duration,
+    base::TimeDelta duration_uptime,
+    int64_t payload_size,
+    metrics::AttemptResult attempt_result,
+    ErrorCode internal_error_code) {
+  string metric = metrics::kMetricAttemptNumber;
+  LOG(INFO) << "Uploading " << attempt_number << " for metric " << metric;
+  metrics_lib_->SendToUMA(metric,
+                          attempt_number,
+                          0,    // min: 0 attempts
+                          49,   // max: 49 attempts
+                          50);  // num_buckets
+
+  metric = metrics::kMetricAttemptPayloadType;
+  LOG(INFO) << "Uploading " << utils::ToString(payload_type) << " for metric "
+            << metric;
+  metrics_lib_->SendEnumToUMA(metric, payload_type, kNumPayloadTypes);
+
+  metric = metrics::kMetricAttemptDurationMinutes;
+  LOG(INFO) << "Uploading " << utils::FormatTimeDelta(duration)
+            << " for metric " << metric;
+  metrics_lib_->SendToUMA(metric,
+                          duration.InMinutes(),
+                          0,             // min: 0 min
+                          10 * 24 * 60,  // max: 10 days
+                          50);           // num_buckets
+
+  metric = metrics::kMetricAttemptDurationUptimeMinutes;
+  LOG(INFO) << "Uploading " << utils::FormatTimeDelta(duration_uptime)
+            << " for metric " << metric;
+  metrics_lib_->SendToUMA(metric,
+                          duration_uptime.InMinutes(),
+                          0,             // min: 0 min
+                          10 * 24 * 60,  // max: 10 days
+                          50);           // num_buckets
+
+  metric = metrics::kMetricAttemptPayloadSizeMiB;
+  int64_t payload_size_mib = payload_size / kNumBytesInOneMiB;
+  LOG(INFO) << "Uploading " << payload_size_mib << " for metric " << metric;
+  metrics_lib_->SendToUMA(metric,
+                          payload_size_mib,
+                          0,     // min: 0 MiB
+                          1024,  // max: 1024 MiB = 1 GiB
+                          50);   // num_buckets
+
+
+
+  metric = metrics::kMetricAttemptResult;
+  LOG(INFO) << "Uploading " << static_cast<int>(attempt_result)
+            << " for metric " << metric;
+  metrics_lib_->SendEnumToUMA(
+      metric,
+      static_cast<int>(attempt_result),
+      static_cast<int>(metrics::AttemptResult::kNumConstants));
+
+  if (internal_error_code != ErrorCode::kSuccess) {
+    metric = metrics::kMetricAttemptInternalErrorCode;
+    LOG(INFO) << "Uploading " << internal_error_code << " for metric "
+              << metric;
+    metrics_lib_->SendEnumToUMA(metric,
+                                static_cast<int>(internal_error_code),
+                                static_cast<int>(ErrorCode::kUmaReportedMax));
+  }
+
+  base::TimeDelta time_since_last;
+  if (metrics_utils::WallclockDurationHelper(
+          system_state,
+          kPrefsMetricsAttemptLastReportingTime,
+          &time_since_last)) {
+    metric = metrics::kMetricAttemptTimeSinceLastAttemptMinutes;
+    LOG(INFO) << "Sending " << utils::FormatTimeDelta(time_since_last)
+              << " for metric " << metric;
+    metrics_lib_->SendToUMA(metric,
+                            time_since_last.InMinutes(),
+                            0,             // min: 0 min
+                            30 * 24 * 60,  // max: 30 days
+                            50);           // num_buckets
+  }
+
+  static int64_t uptime_since_last_storage = 0;
+  base::TimeDelta uptime_since_last;
+  if (metrics_utils::MonotonicDurationHelper(
+          system_state, &uptime_since_last_storage, &uptime_since_last)) {
+    metric = metrics::kMetricAttemptTimeSinceLastAttemptUptimeMinutes;
+    LOG(INFO) << "Sending " << utils::FormatTimeDelta(uptime_since_last)
+              << " for metric " << metric;
+    metrics_lib_->SendToUMA(metric,
+                            uptime_since_last.InMinutes(),
+                            0,             // min: 0 min
+                            30 * 24 * 60,  // max: 30 days
+                            50);           // num_buckets
+  }
+}
+
+void MetricsReporterOmaha::ReportUpdateAttemptDownloadMetrics(
+    int64_t payload_bytes_downloaded,
+    int64_t payload_download_speed_bps,
+    DownloadSource download_source,
+    metrics::DownloadErrorCode payload_download_error_code,
+    metrics::ConnectionType connection_type) {
+  string metric = metrics::kMetricAttemptPayloadBytesDownloadedMiB;
+  int64_t payload_bytes_downloaded_mib =
+      payload_bytes_downloaded / kNumBytesInOneMiB;
+  LOG(INFO) << "Uploading " << payload_bytes_downloaded_mib << " for metric "
+            << metric;
+  metrics_lib_->SendToUMA(metric,
+                          payload_bytes_downloaded_mib,
+                          0,     // min: 0 MiB
+                          1024,  // max: 1024 MiB = 1 GiB
+                          50);   // num_buckets
+
+  metric = metrics::kMetricAttemptPayloadDownloadSpeedKBps;
+  int64_t payload_download_speed_kbps = payload_download_speed_bps / 1000;
+  LOG(INFO) << "Uploading " << payload_download_speed_kbps << " for metric "
+            << metric;
+  metrics_lib_->SendToUMA(metric,
+                          payload_download_speed_kbps,
+                          0,          // min: 0 kB/s
+                          10 * 1000,  // max: 10000 kB/s = 10 MB/s
+                          50);        // num_buckets
+
+  metric = metrics::kMetricAttemptDownloadSource;
+  LOG(INFO) << "Uploading " << download_source << " for metric " << metric;
+  metrics_lib_->SendEnumToUMA(metric, download_source, kNumDownloadSources);
+
+  if (payload_download_error_code != metrics::DownloadErrorCode::kUnset) {
+    metric = metrics::kMetricAttemptDownloadErrorCode;
+    LOG(INFO) << "Uploading " << static_cast<int>(payload_download_error_code)
+              << " for metric " << metric << " (sparse)";
+    metrics_lib_->SendSparseToUMA(
+        metric, static_cast<int>(payload_download_error_code));
+  }
+
+  metric = metrics::kMetricAttemptConnectionType;
+  LOG(INFO) << "Uploading " << static_cast<int>(connection_type)
+            << " for metric " << metric;
+  metrics_lib_->SendEnumToUMA(
+      metric,
+      static_cast<int>(connection_type),
+      static_cast<int>(metrics::ConnectionType::kNumConstants));
+}
+
+void MetricsReporterOmaha::ReportSuccessfulUpdateMetrics(
+    int attempt_count,
+    int updates_abandoned_count,
+    PayloadType payload_type,
+    int64_t payload_size,
+    int64_t num_bytes_downloaded[kNumDownloadSources],
+    int download_overhead_percentage,
+    base::TimeDelta total_duration,
+    int reboot_count,
+    int url_switch_count) {
+  string metric = metrics::kMetricSuccessfulUpdatePayloadSizeMiB;
+  int64_t mbs = payload_size / kNumBytesInOneMiB;
+  LOG(INFO) << "Uploading " << mbs << " (MiBs) for metric " << metric;
+  metrics_lib_->SendToUMA(metric,
+                          mbs,
+                          0,     // min: 0 MiB
+                          1024,  // max: 1024 MiB = 1 GiB
+                          50);   // num_buckets
+
+  int64_t total_bytes = 0;
+  int download_sources_used = 0;
+  for (int i = 0; i < kNumDownloadSources + 1; i++) {
+    DownloadSource source = static_cast<DownloadSource>(i);
+
+    // Only consider this download source (and send byte counts) as
+    // having been used if we downloaded a non-trivial amount of bytes
+    // (e.g. at least 1 MiB) that contributed to the
+    // update. Otherwise we're going to end up with a lot of zero-byte
+    // events in the histogram.
+
+    metric = metrics::kMetricSuccessfulUpdateBytesDownloadedMiB;
+    if (i < kNumDownloadSources) {
+      metric += utils::ToString(source);
+      mbs = num_bytes_downloaded[i] / kNumBytesInOneMiB;
+      total_bytes += num_bytes_downloaded[i];
+      if (mbs > 0)
+        download_sources_used |= (1 << i);
+    } else {
+      mbs = total_bytes / kNumBytesInOneMiB;
+    }
+
+    if (mbs > 0) {
+      LOG(INFO) << "Uploading " << mbs << " (MiBs) for metric " << metric;
+      metrics_lib_->SendToUMA(metric,
+                              mbs,
+                              0,     // min: 0 MiB
+                              1024,  // max: 1024 MiB = 1 GiB
+                              50);   // num_buckets
+    }
+  }
+
+  metric = metrics::kMetricSuccessfulUpdateDownloadSourcesUsed;
+  LOG(INFO) << "Uploading 0x" << std::hex << download_sources_used
+            << " (bit flags) for metric " << metric;
+  metrics_lib_->SendToUMA(metric,
+                          download_sources_used,
+                          0,                               // min
+                          (1 << kNumDownloadSources) - 1,  // max
+                          1 << kNumDownloadSources);       // num_buckets
+
+  metric = metrics::kMetricSuccessfulUpdateDownloadOverheadPercentage;
+  LOG(INFO) << "Uploading " << download_overhead_percentage << "% for metric "
+            << metric;
+  metrics_lib_->SendToUMA(metric,
+                          download_overhead_percentage,
+                          0,     // min: 0% overhead
+                          1000,  // max: 1000% overhead
+                          50);   // num_buckets
+
+  metric = metrics::kMetricSuccessfulUpdateUrlSwitchCount;
+  LOG(INFO) << "Uploading " << url_switch_count << " (count) for metric "
+            << metric;
+  metrics_lib_->SendToUMA(metric,
+                          url_switch_count,
+                          0,    // min: 0 URL switches
+                          49,   // max: 49 URL switches
+                          50);  // num_buckets
+
+  metric = metrics::kMetricSuccessfulUpdateTotalDurationMinutes;
+  LOG(INFO) << "Uploading " << utils::FormatTimeDelta(total_duration)
+            << " for metric " << metric;
+  metrics_lib_->SendToUMA(metric,
+                          static_cast<int>(total_duration.InMinutes()),
+                          0,              // min: 0 min
+                          365 * 24 * 60,  // max: 365 days ~= 1 year
+                          50);            // num_buckets
+
+  metric = metrics::kMetricSuccessfulUpdateRebootCount;
+  LOG(INFO) << "Uploading reboot count of " << reboot_count << " for metric "
+            << metric;
+  metrics_lib_->SendToUMA(metric,
+                          reboot_count,
+                          0,    // min: 0 reboots
+                          49,   // max: 49 reboots
+                          50);  // num_buckets
+
+  metric = metrics::kMetricSuccessfulUpdatePayloadType;
+  metrics_lib_->SendEnumToUMA(metric, payload_type, kNumPayloadTypes);
+  LOG(INFO) << "Uploading " << utils::ToString(payload_type) << " for metric "
+            << metric;
+
+  metric = metrics::kMetricSuccessfulUpdateAttemptCount;
+  metrics_lib_->SendToUMA(metric,
+                          attempt_count,
+                          1,    // min: 1 attempt
+                          50,   // max: 50 attempts
+                          50);  // num_buckets
+  LOG(INFO) << "Uploading " << attempt_count << " for metric " << metric;
+
+  metric = metrics::kMetricSuccessfulUpdateUpdatesAbandonedCount;
+  LOG(INFO) << "Uploading " << updates_abandoned_count << " (count) for metric "
+            << metric;
+  metrics_lib_->SendToUMA(metric,
+                          updates_abandoned_count,
+                          0,    // min: 0 counts
+                          49,   // max: 49 counts
+                          50);  // num_buckets
+}
+
+void MetricsReporterOmaha::ReportRollbackMetrics(
+    metrics::RollbackResult result) {
+  string metric = metrics::kMetricRollbackResult;
+  int value = static_cast<int>(result);
+  LOG(INFO) << "Sending " << value << " for metric " << metric << " (enum)";
+  metrics_lib_->SendEnumToUMA(
+      metric, value, static_cast<int>(metrics::RollbackResult::kNumConstants));
+}
+
+void MetricsReporterOmaha::ReportCertificateCheckMetrics(
+    ServerToCheck server_to_check, CertificateCheckResult result) {
+  string metric;
+  switch (server_to_check) {
+    case ServerToCheck::kUpdate:
+      metric = metrics::kMetricCertificateCheckUpdateCheck;
+      break;
+    case ServerToCheck::kDownload:
+      metric = metrics::kMetricCertificateCheckDownload;
+      break;
+    case ServerToCheck::kNone:
+      return;
+  }
+  LOG(INFO) << "Uploading " << static_cast<int>(result) << " for metric "
+            << metric;
+  metrics_lib_->SendEnumToUMA(
+      metric,
+      static_cast<int>(result),
+      static_cast<int>(CertificateCheckResult::kNumConstants));
+}
+
+void MetricsReporterOmaha::ReportFailedUpdateCount(int target_attempt) {
+  string metric = metrics::kMetricFailedUpdateCount;
+  metrics_lib_->SendToUMA(metric,
+                          target_attempt,
+                          1,   // min value
+                          50,  // max value
+                          kNumDefaultUmaBuckets);
+
+  LOG(INFO) << "Uploading " << target_attempt << " (count) for metric "
+            << metric;
+}
+
+void MetricsReporterOmaha::ReportTimeToReboot(int time_to_reboot_minutes) {
+  string metric = metrics::kMetricTimeToRebootMinutes;
+  metrics_lib_->SendToUMA(metric,
+                          time_to_reboot_minutes,
+                          0,             // min: 0 minute
+                          30 * 24 * 60,  // max: 1 month (approx)
+                          kNumDefaultUmaBuckets);
+
+  LOG(INFO) << "Uploading " << time_to_reboot_minutes << " for metric "
+            << metric;
+}
+
+void MetricsReporterOmaha::ReportInstallDateProvisioningSource(int source,
+                                                               int max) {
+  metrics_lib_->SendEnumToUMA(metrics::kMetricInstallDateProvisioningSource,
+                              source,  // Sample.
+                              max);
+}
+
+}  // namespace chromeos_update_engine
diff --git a/metrics_reporter_omaha.h b/metrics_reporter_omaha.h
new file mode 100644
index 0000000..c19fe86
--- /dev/null
+++ b/metrics_reporter_omaha.h
@@ -0,0 +1,156 @@
+//
+// Copyright (C) 2017 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_METRICS_REPORTER_OMAHA_H_
+#define UPDATE_ENGINE_METRICS_REPORTER_OMAHA_H_
+
+#include <memory>
+
+#include <base/time/time.h>
+#include <metrics/metrics_library.h>
+
+#include "update_engine/certificate_checker.h"
+#include "update_engine/common/constants.h"
+#include "update_engine/common/error_code.h"
+#include "update_engine/metrics_constants.h"
+#include "update_engine/metrics_reporter_interface.h"
+#include "update_engine/system_state.h"
+
+namespace chromeos_update_engine {
+
+class SystemState;
+
+namespace metrics {
+
+// UpdateEngine.Daily.* metrics.
+extern const char kMetricDailyOSAgeDays[];
+
+// UpdateEngine.Check.* metrics.
+extern const char kMetricCheckDownloadErrorCode[];
+extern const char kMetricCheckReaction[];
+extern const char kMetricCheckResult[];
+extern const char kMetricCheckTimeSinceLastCheckMinutes[];
+extern const char kMetricCheckTimeSinceLastCheckUptimeMinutes[];
+
+// UpdateEngine.Attempt.* metrics.
+extern const char kMetricAttemptNumber[];
+extern const char kMetricAttemptPayloadType[];
+extern const char kMetricAttemptPayloadSizeMiB[];
+extern const char kMetricAttemptConnectionType[];
+extern const char kMetricAttemptDurationMinutes[];
+extern const char kMetricAttemptDurationUptimeMinutes[];
+extern const char kMetricAttemptTimeSinceLastAttemptMinutes[];
+extern const char kMetricAttemptTimeSinceLastAttemptUptimeMinutes[];
+extern const char kMetricAttemptPayloadBytesDownloadedMiB[];
+extern const char kMetricAttemptPayloadDownloadSpeedKBps[];
+extern const char kMetricAttemptDownloadSource[];
+extern const char kMetricAttemptResult[];
+extern const char kMetricAttemptInternalErrorCode[];
+extern const char kMetricAttemptDownloadErrorCode[];
+
+// UpdateEngine.SuccessfulUpdate.* metrics.
+extern const char kMetricSuccessfulUpdateAttemptCount[];
+extern const char kMetricSuccessfulUpdateBytesDownloadedMiB[];
+extern const char kMetricSuccessfulUpdateDownloadOverheadPercentage[];
+extern const char kMetricSuccessfulUpdateDownloadSourcesUsed[];
+extern const char kMetricSuccessfulUpdatePayloadType[];
+extern const char kMetricSuccessfulUpdatePayloadSizeMiB[];
+extern const char kMetricSuccessfulUpdateRebootCount[];
+extern const char kMetricSuccessfulUpdateTotalDurationMinutes[];
+extern const char kMetricSuccessfulUpdateUpdatesAbandonedCount[];
+extern const char kMetricSuccessfulUpdateUrlSwitchCount[];
+
+// UpdateEngine.Rollback.* metric.
+extern const char kMetricRollbackResult[];
+
+// UpdateEngine.CertificateCheck.* metrics.
+extern const char kMetricCertificateCheckUpdateCheck[];
+extern const char kMetricCertificateCheckDownload[];
+
+// UpdateEngine.* metrics.
+extern const char kMetricFailedUpdateCount[];
+extern const char kMetricInstallDateProvisioningSource[];
+extern const char kMetricTimeToRebootMinutes[];
+
+}  // namespace metrics
+
+class MetricsReporterOmaha : public MetricsReporterInterface {
+ public:
+  MetricsReporterOmaha();
+
+  ~MetricsReporterOmaha() override = default;
+
+  void Initialize() override;
+
+  void ReportRollbackMetrics(metrics::RollbackResult result) override;
+
+  void ReportDailyMetrics(base::TimeDelta os_age) override;
+
+  void ReportUpdateCheckMetrics(
+      SystemState* system_state,
+      metrics::CheckResult result,
+      metrics::CheckReaction reaction,
+      metrics::DownloadErrorCode download_error_code) override;
+
+  void ReportUpdateAttemptMetrics(SystemState* system_state,
+                                  int attempt_number,
+                                  PayloadType payload_type,
+                                  base::TimeDelta duration,
+                                  base::TimeDelta duration_uptime,
+                                  int64_t payload_size,
+                                  metrics::AttemptResult attempt_result,
+                                  ErrorCode internal_error_code) override;
+
+  void ReportUpdateAttemptDownloadMetrics(
+      int64_t payload_bytes_downloaded,
+      int64_t payload_download_speed_bps,
+      DownloadSource download_source,
+      metrics::DownloadErrorCode payload_download_error_code,
+      metrics::ConnectionType connection_type) override;
+
+  void ReportAbnormallyTerminatedUpdateAttemptMetrics() override;
+
+  void ReportSuccessfulUpdateMetrics(
+      int attempt_count,
+      int updates_abandoned_count,
+      PayloadType payload_type,
+      int64_t payload_size,
+      int64_t num_bytes_downloaded[kNumDownloadSources],
+      int download_overhead_percentage,
+      base::TimeDelta total_duration,
+      int reboot_count,
+      int url_switch_count) override;
+
+  void ReportCertificateCheckMetrics(ServerToCheck server_to_check,
+                                     CertificateCheckResult result) override;
+
+  void ReportFailedUpdateCount(int target_attempt) override;
+
+  void ReportTimeToReboot(int time_to_reboot_minutes) override;
+
+  void ReportInstallDateProvisioningSource(int source, int max) override;
+
+ private:
+  friend class MetricsReporterOmahaTest;
+
+  std::unique_ptr<MetricsLibraryInterface> metrics_lib_;
+
+  DISALLOW_COPY_AND_ASSIGN(MetricsReporterOmaha);
+};  // class metrics
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_METRICS_REPORTER_OMAHA_H_
diff --git a/metrics_reporter_omaha_unittest.cc b/metrics_reporter_omaha_unittest.cc
new file mode 100644
index 0000000..76e33c6
--- /dev/null
+++ b/metrics_reporter_omaha_unittest.cc
@@ -0,0 +1,394 @@
+//
+// Copyright (C) 2017 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/metrics_reporter_omaha.h"
+
+#include <memory>
+#include <string>
+
+#include <base/time/time.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <metrics/metrics_library_mock.h>
+
+#include "update_engine/common/fake_clock.h"
+#include "update_engine/common/fake_prefs.h"
+#include "update_engine/fake_system_state.h"
+
+using base::TimeDelta;
+using testing::AnyNumber;
+using testing::_;
+
+namespace chromeos_update_engine {
+class MetricsReporterOmahaTest : public ::testing::Test {
+ protected:
+  MetricsReporterOmahaTest() = default;
+
+  // Reset the metrics_lib_ to a mock library.
+  void SetUp() override {
+    mock_metrics_lib_ = new testing::NiceMock<MetricsLibraryMock>();
+    reporter_.metrics_lib_.reset(mock_metrics_lib_);
+  }
+
+  testing::NiceMock<MetricsLibraryMock>* mock_metrics_lib_;
+  MetricsReporterOmaha reporter_;
+};
+
+TEST_F(MetricsReporterOmahaTest, ReportDailyMetrics) {
+  TimeDelta age = TimeDelta::FromDays(10);
+  EXPECT_CALL(*mock_metrics_lib_,
+              SendToUMA(metrics::kMetricDailyOSAgeDays, _, _, _, _))
+      .Times(1);
+
+  reporter_.ReportDailyMetrics(age);
+}
+
+TEST_F(MetricsReporterOmahaTest, ReportUpdateCheckMetrics) {
+  FakeSystemState fake_system_state;
+  FakeClock fake_clock;
+  FakePrefs fake_prefs;
+
+  // We need to execute the report twice to test the time since last report.
+  fake_system_state.set_clock(&fake_clock);
+  fake_system_state.set_prefs(&fake_prefs);
+  fake_clock.SetWallclockTime(base::Time::FromInternalValue(1000000));
+  fake_clock.SetMonotonicTime(base::Time::FromInternalValue(1000000));
+
+  metrics::CheckResult result = metrics::CheckResult::kUpdateAvailable;
+  metrics::CheckReaction reaction = metrics::CheckReaction::kIgnored;
+  metrics::DownloadErrorCode error_code =
+      metrics::DownloadErrorCode::kHttpStatus200;
+
+  EXPECT_CALL(
+      *mock_metrics_lib_,
+      SendEnumToUMA(metrics::kMetricCheckResult, static_cast<int>(result), _))
+      .Times(2);
+  EXPECT_CALL(*mock_metrics_lib_,
+              SendEnumToUMA(
+                  metrics::kMetricCheckReaction, static_cast<int>(reaction), _))
+      .Times(2);
+  EXPECT_CALL(*mock_metrics_lib_,
+              SendSparseToUMA(metrics::kMetricCheckDownloadErrorCode,
+                              static_cast<int>(error_code)))
+      .Times(2);
+
+  EXPECT_CALL(
+      *mock_metrics_lib_,
+      SendToUMA(metrics::kMetricCheckTimeSinceLastCheckMinutes, 1, _, _, _))
+      .Times(1);
+  EXPECT_CALL(
+      *mock_metrics_lib_,
+      SendToUMA(
+          metrics::kMetricCheckTimeSinceLastCheckUptimeMinutes, 1, _, _, _))
+      .Times(1);
+
+  reporter_.ReportUpdateCheckMetrics(
+      &fake_system_state, result, reaction, error_code);
+
+  // Advance the clock by 1 minute and report the same metrics again.
+  fake_clock.SetWallclockTime(base::Time::FromInternalValue(61000000));
+  fake_clock.SetMonotonicTime(base::Time::FromInternalValue(61000000));
+  reporter_.ReportUpdateCheckMetrics(
+      &fake_system_state, result, reaction, error_code);
+}
+
+TEST_F(MetricsReporterOmahaTest,
+       ReportAbnormallyTerminatedUpdateAttemptMetrics) {
+  EXPECT_CALL(*mock_metrics_lib_,
+              SendEnumToUMA(metrics::kMetricAttemptResult,
+                            static_cast<int>(
+                                metrics::AttemptResult::kAbnormalTermination),
+                            _))
+      .Times(1);
+
+  reporter_.ReportAbnormallyTerminatedUpdateAttemptMetrics();
+}
+
+TEST_F(MetricsReporterOmahaTest, ReportUpdateAttemptMetrics) {
+  FakeSystemState fake_system_state;
+  FakeClock fake_clock;
+  FakePrefs fake_prefs;
+
+  fake_system_state.set_clock(&fake_clock);
+  fake_system_state.set_prefs(&fake_prefs);
+  fake_clock.SetWallclockTime(base::Time::FromInternalValue(1000000));
+  fake_clock.SetMonotonicTime(base::Time::FromInternalValue(1000000));
+
+  int attempt_number = 1;
+  PayloadType payload_type = kPayloadTypeFull;
+  TimeDelta duration = TimeDelta::FromMinutes(1000);
+  TimeDelta duration_uptime = TimeDelta::FromMinutes(1000);
+
+  int64_t payload_size = 100 * kNumBytesInOneMiB;
+
+  metrics::AttemptResult attempt_result =
+      metrics::AttemptResult::kInternalError;
+  ErrorCode internal_error_code = ErrorCode::kDownloadInvalidMetadataSignature;
+
+  EXPECT_CALL(*mock_metrics_lib_,
+              SendToUMA(metrics::kMetricAttemptNumber, attempt_number, _, _, _))
+      .Times(2);
+  EXPECT_CALL(*mock_metrics_lib_,
+              SendEnumToUMA(metrics::kMetricAttemptPayloadType,
+                            static_cast<int>(payload_type),
+                            _))
+      .Times(2);
+  EXPECT_CALL(*mock_metrics_lib_,
+              SendToUMA(metrics::kMetricAttemptDurationMinutes,
+                        duration.InMinutes(),
+                        _,
+                        _,
+                        _))
+      .Times(2);
+  EXPECT_CALL(*mock_metrics_lib_,
+              SendToUMA(metrics::kMetricAttemptDurationUptimeMinutes,
+                        duration_uptime.InMinutes(),
+                        _,
+                        _,
+                        _))
+      .Times(2);
+
+
+  // Check the report of attempt result.
+  EXPECT_CALL(
+      *mock_metrics_lib_,
+      SendEnumToUMA(
+          metrics::kMetricAttemptResult, static_cast<int>(attempt_result), _))
+      .Times(2);
+  EXPECT_CALL(*mock_metrics_lib_,
+              SendEnumToUMA(metrics::kMetricAttemptInternalErrorCode,
+                            static_cast<int>(internal_error_code),
+                            _))
+      .Times(2);
+  EXPECT_CALL(*mock_metrics_lib_,
+              SendToUMA(metrics::kMetricAttemptPayloadSizeMiB, 100, _, _, _))
+      .Times(2);
+
+  // Check the duration between two reports.
+  EXPECT_CALL(
+      *mock_metrics_lib_,
+      SendToUMA(metrics::kMetricAttemptTimeSinceLastAttemptMinutes, 1, _, _, _))
+      .Times(1);
+  EXPECT_CALL(
+      *mock_metrics_lib_,
+      SendToUMA(
+          metrics::kMetricAttemptTimeSinceLastAttemptUptimeMinutes, 1, _, _, _))
+      .Times(1);
+
+  reporter_.ReportUpdateAttemptMetrics(&fake_system_state,
+                                       attempt_number,
+                                       payload_type,
+                                       duration,
+                                       duration_uptime,
+                                       payload_size,
+                                       attempt_result,
+                                       internal_error_code);
+
+  // Advance the clock by 1 minute and report the same metrics again.
+  fake_clock.SetWallclockTime(base::Time::FromInternalValue(61000000));
+  fake_clock.SetMonotonicTime(base::Time::FromInternalValue(61000000));
+  reporter_.ReportUpdateAttemptMetrics(&fake_system_state,
+                                       attempt_number,
+                                       payload_type,
+                                       duration,
+                                       duration_uptime,
+                                       payload_size,
+                                       attempt_result,
+                                       internal_error_code);
+}
+
+TEST_F(MetricsReporterOmahaTest, ReportUpdateAttemptDownloadMetrics) {
+  int64_t payload_bytes_downloaded = 200 * kNumBytesInOneMiB;
+  int64_t payload_download_speed_bps = 100 * 1000;
+  DownloadSource download_source = kDownloadSourceHttpServer;
+  metrics::DownloadErrorCode payload_download_error_code =
+      metrics::DownloadErrorCode::kDownloadError;
+  metrics::ConnectionType connection_type = metrics::ConnectionType::kCellular;
+
+  EXPECT_CALL(
+      *mock_metrics_lib_,
+      SendToUMA(metrics::kMetricAttemptPayloadBytesDownloadedMiB, 200, _, _, _))
+      .Times(1);
+  EXPECT_CALL(
+      *mock_metrics_lib_,
+      SendToUMA(metrics::kMetricAttemptPayloadDownloadSpeedKBps, 100, _, _, _))
+      .Times(1);
+  EXPECT_CALL(*mock_metrics_lib_,
+              SendEnumToUMA(metrics::kMetricAttemptDownloadSource,
+                            static_cast<int>(download_source),
+                            _))
+      .Times(1);
+  EXPECT_CALL(*mock_metrics_lib_,
+              SendSparseToUMA(metrics::kMetricAttemptDownloadErrorCode,
+                              static_cast<int>(payload_download_error_code)))
+      .Times(1);
+  EXPECT_CALL(*mock_metrics_lib_,
+              SendEnumToUMA(metrics::kMetricAttemptConnectionType,
+                            static_cast<int>(connection_type),
+                            _))
+      .Times(1);
+
+  reporter_.ReportUpdateAttemptDownloadMetrics(payload_bytes_downloaded,
+                                               payload_download_speed_bps,
+                                               download_source,
+                                               payload_download_error_code,
+                                               connection_type);
+}
+
+TEST_F(MetricsReporterOmahaTest, ReportSuccessfulUpdateMetrics) {
+  int attempt_count = 3;
+  int updates_abandoned_count = 2;
+  PayloadType payload_type = kPayloadTypeDelta;
+  int64_t payload_size = 200 * kNumBytesInOneMiB;
+  int64_t num_bytes_downloaded[kNumDownloadSources] = {};
+  // 200MiB payload downloaded from HttpsServer.
+  num_bytes_downloaded[0] = 200 * kNumBytesInOneMiB;
+  int download_overhead_percentage = 20;
+  TimeDelta total_duration = TimeDelta::FromMinutes(30);
+  int reboot_count = 2;
+  int url_switch_count = 2;
+
+  EXPECT_CALL(
+      *mock_metrics_lib_,
+      SendToUMA(metrics::kMetricSuccessfulUpdatePayloadSizeMiB, 200, _, _, _))
+      .Times(1);
+
+  // Check the report to both BytesDownloadedMiBHttpsServer and
+  // BytesDownloadedMiB
+  std::string DownloadedMiBMetric =
+      metrics::kMetricSuccessfulUpdateBytesDownloadedMiB;
+  DownloadedMiBMetric += "HttpsServer";
+  EXPECT_CALL(*mock_metrics_lib_, SendToUMA(DownloadedMiBMetric, 200, _, _, _))
+      .Times(1);
+  EXPECT_CALL(
+      *mock_metrics_lib_,
+      SendToUMA(
+          metrics::kMetricSuccessfulUpdateBytesDownloadedMiB, 200, _, _, _))
+      .Times(1);
+
+  EXPECT_CALL(
+      *mock_metrics_lib_,
+      SendToUMA(
+          metrics::kMetricSuccessfulUpdateDownloadSourcesUsed, 1, _, _, _))
+      .Times(1);
+  EXPECT_CALL(
+      *mock_metrics_lib_,
+      SendToUMA(metrics::kMetricSuccessfulUpdateDownloadOverheadPercentage,
+                20,
+                _,
+                _,
+                _));
+
+  EXPECT_CALL(*mock_metrics_lib_,
+              SendToUMA(metrics::kMetricSuccessfulUpdateUrlSwitchCount,
+                        url_switch_count,
+                        _,
+                        _,
+                        _))
+      .Times(1);
+  EXPECT_CALL(
+      *mock_metrics_lib_,
+      SendToUMA(
+          metrics::kMetricSuccessfulUpdateTotalDurationMinutes, 30, _, _, _))
+      .Times(1);
+  EXPECT_CALL(
+      *mock_metrics_lib_,
+      SendToUMA(
+          metrics::kMetricSuccessfulUpdateRebootCount, reboot_count, _, _, _))
+      .Times(1);
+  EXPECT_CALL(*mock_metrics_lib_,
+              SendEnumToUMA(
+                  metrics::kMetricSuccessfulUpdatePayloadType, payload_type, _))
+      .Times(1);
+  EXPECT_CALL(
+      *mock_metrics_lib_,
+      SendToUMA(
+          metrics::kMetricSuccessfulUpdateAttemptCount, attempt_count, _, _, _))
+      .Times(1);
+  EXPECT_CALL(*mock_metrics_lib_,
+              SendToUMA(metrics::kMetricSuccessfulUpdateUpdatesAbandonedCount,
+                        updates_abandoned_count,
+                        _,
+                        _,
+                        _))
+      .Times(1);
+
+  reporter_.ReportSuccessfulUpdateMetrics(attempt_count,
+                                          updates_abandoned_count,
+                                          payload_type,
+                                          payload_size,
+                                          num_bytes_downloaded,
+                                          download_overhead_percentage,
+                                          total_duration,
+                                          reboot_count,
+                                          url_switch_count);
+}
+
+TEST_F(MetricsReporterOmahaTest, ReportRollbackMetrics) {
+  metrics::RollbackResult result = metrics::RollbackResult::kSuccess;
+  EXPECT_CALL(*mock_metrics_lib_,
+              SendEnumToUMA(
+                  metrics::kMetricRollbackResult, static_cast<int>(result), _))
+      .Times(1);
+
+  reporter_.ReportRollbackMetrics(result);
+}
+
+TEST_F(MetricsReporterOmahaTest, ReportCertificateCheckMetrics) {
+  ServerToCheck server_to_check = ServerToCheck::kUpdate;
+  CertificateCheckResult result = CertificateCheckResult::kValid;
+  EXPECT_CALL(*mock_metrics_lib_,
+              SendEnumToUMA(metrics::kMetricCertificateCheckUpdateCheck,
+                            static_cast<int>(result),
+                            _))
+      .Times(1);
+
+  reporter_.ReportCertificateCheckMetrics(server_to_check, result);
+}
+
+TEST_F(MetricsReporterOmahaTest, ReportFailedUpdateCount) {
+  int target_attempt = 3;
+  EXPECT_CALL(
+      *mock_metrics_lib_,
+      SendToUMA(metrics::kMetricFailedUpdateCount, target_attempt, _, _, _))
+      .Times(1);
+
+  reporter_.ReportFailedUpdateCount(target_attempt);
+}
+
+TEST_F(MetricsReporterOmahaTest, ReportTimeToReboot) {
+  int time_to_reboot_minutes = 1000;
+  EXPECT_CALL(
+      *mock_metrics_lib_,
+      SendToUMA(
+          metrics::kMetricTimeToRebootMinutes, time_to_reboot_minutes, _, _, _))
+      .Times(1);
+
+  reporter_.ReportTimeToReboot(time_to_reboot_minutes);
+}
+
+TEST_F(MetricsReporterOmahaTest, ReportInstallDateProvisioningSource) {
+  int source = 2;
+  int max = 5;
+  EXPECT_CALL(
+      *mock_metrics_lib_,
+      SendEnumToUMA(metrics::kMetricInstallDateProvisioningSource, source, max))
+      .Times(1);
+
+  reporter_.ReportInstallDateProvisioningSource(source, max);
+}
+
+}  // namespace chromeos_update_engine
diff --git a/metrics_reporter_stub.cc b/metrics_reporter_stub.cc
new file mode 100644
index 0000000..81664a5
--- /dev/null
+++ b/metrics_reporter_stub.cc
@@ -0,0 +1,31 @@
+//
+// Copyright (C) 2017 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/metrics_reporter_stub.h"
+
+#include <memory>
+
+namespace chromeos_update_engine {
+
+namespace metrics {
+
+std::unique_ptr<MetricsReporterInterface> CreateMetricsReporter() {
+  return std::make_unique<MetricsReporterStub>();
+}
+
+}  // namespace metrics
+
+}  // namespace chromeos_update_engine
diff --git a/metrics_reporter_stub.h b/metrics_reporter_stub.h
new file mode 100644
index 0000000..d0f75ab
--- /dev/null
+++ b/metrics_reporter_stub.h
@@ -0,0 +1,88 @@
+//
+// Copyright (C) 2017 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_METRICS_REPORTER_STUB_H_
+#define UPDATE_ENGINE_METRICS_REPORTER_STUB_H_
+
+#include "update_engine/common/error_code.h"
+#include "update_engine/metrics_constants.h"
+#include "update_engine/metrics_reporter_interface.h"
+
+namespace chromeos_update_engine {
+
+class MetricsReporterStub : public MetricsReporterInterface {
+ public:
+  MetricsReporterStub() = default;
+
+  ~MetricsReporterStub() override = default;
+
+  void Initialize() override {}
+
+  void ReportRollbackMetrics(metrics::RollbackResult result) override {}
+
+  void ReportDailyMetrics(base::TimeDelta os_age) override {}
+
+  void ReportUpdateCheckMetrics(
+      SystemState* system_state,
+      metrics::CheckResult result,
+      metrics::CheckReaction reaction,
+      metrics::DownloadErrorCode download_error_code) override {}
+
+  void ReportUpdateAttemptMetrics(SystemState* system_state,
+                                  int attempt_number,
+                                  PayloadType payload_type,
+                                  base::TimeDelta duration,
+                                  base::TimeDelta duration_uptime,
+                                  int64_t payload_size,
+                                  metrics::AttemptResult attempt_result,
+                                  ErrorCode internal_error_code) override {}
+
+  void ReportUpdateAttemptDownloadMetrics(
+      int64_t payload_bytes_downloaded,
+      int64_t payload_download_speed_bps,
+      DownloadSource download_source,
+      metrics::DownloadErrorCode payload_download_error_code,
+      metrics::ConnectionType connection_type) override {}
+
+  void ReportAbnormallyTerminatedUpdateAttemptMetrics() override {}
+
+  void ReportSuccessfulUpdateMetrics(
+      int attempt_count,
+      int updates_abandoned_count,
+      PayloadType payload_type,
+      int64_t payload_size,
+      int64_t num_bytes_downloaded[kNumDownloadSources],
+      int download_overhead_percentage,
+      base::TimeDelta total_duration,
+      int reboot_count,
+      int url_switch_count) override {}
+
+  void ReportCertificateCheckMetrics(ServerToCheck server_to_check,
+                                     CertificateCheckResult result) override {}
+
+  void ReportFailedUpdateCount(int target_attempt) override {}
+
+  void ReportTimeToReboot(int time_to_reboot_minutes) override {}
+
+  void ReportInstallDateProvisioningSource(int source, int max) override {}
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(MetricsReporterStub);
+};
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_METRICS_REPORTER_STUB_H_
diff --git a/metrics_utils.cc b/metrics_utils.cc
index 15e3a8a..46530f0 100644
--- a/metrics_utils.cc
+++ b/metrics_utils.cc
@@ -21,7 +21,8 @@
 #include <base/time/time.h>
 
 #include "update_engine/common/clock_interface.h"
-#include "update_engine/common/prefs_interface.h"
+#include "update_engine/common/constants.h"
+#include "update_engine/common/utils.h"
 #include "update_engine/system_state.h"
 
 using base::Time;
@@ -38,6 +39,9 @@
     case ErrorCode::kSuccess:
       return metrics::AttemptResult::kUpdateSucceeded;
 
+    case ErrorCode::kUpdatedButNotActive:
+      return metrics::AttemptResult::kUpdateSucceededNotActive;
+
     case ErrorCode::kDownloadTransferError:
       return metrics::AttemptResult::kPayloadDownloadError;
 
@@ -74,6 +78,7 @@
     case ErrorCode::kDownloadPayloadVerificationError:
     case ErrorCode::kSignedDeltaPayloadExpectedError:
     case ErrorCode::kDownloadPayloadPubKeyVerificationError:
+    case ErrorCode::kPayloadTimestampError:
       return metrics::AttemptResult::kPayloadVerificationFailed;
 
     case ErrorCode::kNewRootfsVerificationError:
@@ -109,7 +114,6 @@
     case ErrorCode::kPostinstallPowerwashError:
     case ErrorCode::kUpdateCanceledByChannelChange:
     case ErrorCode::kOmahaRequestXMLHasEntityDecl:
-    case ErrorCode::kOmahaUpdateIgnoredOverCellular:
       return metrics::AttemptResult::kInternalError;
 
     // Special flags. These can't happen (we mask them out above) but
@@ -210,7 +214,8 @@
     case ErrorCode::kOmahaRequestXMLHasEntityDecl:
     case ErrorCode::kFilesystemVerifierError:
     case ErrorCode::kUserCanceled:
-    case ErrorCode::kOmahaUpdateIgnoredOverCellular:
+    case ErrorCode::kPayloadTimestampError:
+    case ErrorCode::kUpdatedButNotActive:
       break;
 
     // Special flags. These can't happen (we mask them out above) but
@@ -307,5 +312,76 @@
   return ret;
 }
 
+int64_t GetPersistedValue(const std::string& key, PrefsInterface* prefs) {
+  CHECK(prefs);
+  if (!prefs->Exists(key))
+    return 0;
+
+  int64_t stored_value;
+  if (!prefs->GetInt64(key, &stored_value))
+    return 0;
+
+  if (stored_value < 0) {
+    LOG(ERROR) << key << ": Invalid value (" << stored_value
+               << ") in persisted state. Defaulting to 0";
+    return 0;
+  }
+
+  return stored_value;
+}
+
+void SetNumReboots(int64_t num_reboots, PrefsInterface* prefs) {
+  CHECK(prefs);
+  prefs->SetInt64(kPrefsNumReboots, num_reboots);
+  LOG(INFO) << "Number of Reboots during current update attempt = "
+            << num_reboots;
+}
+
+void SetPayloadAttemptNumber(int64_t payload_attempt_number,
+                             PrefsInterface* prefs) {
+  CHECK(prefs);
+  prefs->SetInt64(kPrefsPayloadAttemptNumber, payload_attempt_number);
+  LOG(INFO) << "Payload Attempt Number = " << payload_attempt_number;
+}
+
+void SetSystemUpdatedMarker(ClockInterface* clock, PrefsInterface* prefs) {
+  CHECK(prefs);
+  CHECK(clock);
+  Time update_finish_time = clock->GetMonotonicTime();
+  prefs->SetInt64(kPrefsSystemUpdatedMarker,
+                  update_finish_time.ToInternalValue());
+  LOG(INFO) << "Updated Marker = " << utils::ToString(update_finish_time);
+}
+
+void SetUpdateTimestampStart(const Time& update_start_time,
+                             PrefsInterface* prefs) {
+  CHECK(prefs);
+  prefs->SetInt64(kPrefsUpdateTimestampStart,
+                  update_start_time.ToInternalValue());
+  LOG(INFO) << "Update Timestamp Start = "
+            << utils::ToString(update_start_time);
+}
+
+bool LoadAndReportTimeToReboot(MetricsReporterInterface* metrics_reporter,
+                               PrefsInterface* prefs,
+                               ClockInterface* clock) {
+  CHECK(prefs);
+  CHECK(clock);
+  int64_t stored_value = GetPersistedValue(kPrefsSystemUpdatedMarker, prefs);
+  if (stored_value == 0)
+    return false;
+
+  Time system_updated_at = Time::FromInternalValue(stored_value);
+  base::TimeDelta time_to_reboot =
+      clock->GetMonotonicTime() - system_updated_at;
+  if (time_to_reboot.ToInternalValue() < 0) {
+    LOG(ERROR) << "time_to_reboot is negative - system_updated_at: "
+               << utils::ToString(system_updated_at);
+    return false;
+  }
+  metrics_reporter->ReportTimeToReboot(time_to_reboot.InMinutes());
+  return true;
+}
+
 }  // namespace metrics_utils
 }  // namespace chromeos_update_engine
diff --git a/metrics_utils.h b/metrics_utils.h
index d9826c1..d08cc4a 100644
--- a/metrics_utils.h
+++ b/metrics_utils.h
@@ -17,8 +17,16 @@
 #ifndef UPDATE_ENGINE_METRICS_UTILS_H_
 #define UPDATE_ENGINE_METRICS_UTILS_H_
 
+#include <string>
+
+#include <base/time/time.h>
+
+#include "update_engine/common/clock_interface.h"
+#include "update_engine/common/error_code.h"
+#include "update_engine/common/prefs_interface.h"
 #include "update_engine/connection_utils.h"
-#include "update_engine/metrics.h"
+#include "update_engine/metrics_constants.h"
+#include "update_engine/metrics_reporter_interface.h"
 
 namespace chromeos_update_engine {
 
@@ -65,6 +73,33 @@
                              int64_t* storage,
                              base::TimeDelta* out_duration);
 
+// Returns the persisted value from prefs for the given key. It also
+// validates that the value returned is non-negative.
+int64_t GetPersistedValue(const std::string& key, PrefsInterface* prefs);
+
+// Persists the reboot count of the update attempt to |kPrefsNumReboots|.
+void SetNumReboots(int64_t num_reboots, PrefsInterface* prefs);
+
+// Persists the payload attempt number to |kPrefsPayloadAttemptNumber|.
+void SetPayloadAttemptNumber(int64_t payload_attempt_number,
+                             PrefsInterface* prefs);
+
+// Persists the finished time of an update to the |kPrefsSystemUpdatedMarker|.
+void SetSystemUpdatedMarker(ClockInterface* clock, PrefsInterface* prefs);
+
+// Persists the start time of an update to |kPrefsUpdateTimestampStart|.
+void SetUpdateTimestampStart(const base::Time& update_start_time,
+                             PrefsInterface* prefs);
+
+// Called at program startup if the device booted into a new update.
+// The |time_to_reboot| parameter contains the (monotonic-clock) duration
+// from when the update successfully completed (the value in
+// |kPrefsSystemUpdatedMarker|) until the device was booted into the update
+// (current monotonic-clock time).
+bool LoadAndReportTimeToReboot(MetricsReporterInterface* metrics_reporter,
+                               PrefsInterface* prefs,
+                               ClockInterface* clock);
+
 }  // namespace metrics_utils
 }  // namespace chromeos_update_engine
 
diff --git a/mock_connection_manager.h b/mock_connection_manager.h
index 2fff68c..e37460b 100644
--- a/mock_connection_manager.h
+++ b/mock_connection_manager.h
@@ -36,7 +36,6 @@
 
   MOCK_CONST_METHOD2(IsUpdateAllowedOver,
                      bool(ConnectionType type, ConnectionTethering tethering));
-  MOCK_CONST_METHOD0(IsAllowedConnectionTypesForUpdateSet, bool());
 };
 
 }  // namespace chromeos_update_engine
diff --git a/mock_file_writer.h b/mock_file_writer.h
index 72d6a86..26cd45d 100644
--- a/mock_file_writer.h
+++ b/mock_file_writer.h
@@ -24,7 +24,8 @@
 
 class MockFileWriter : public FileWriter {
  public:
-  MOCK_METHOD2(Write, ssize_t(const void* bytes, size_t count));
+  MOCK_METHOD2(Write, bool(const void* bytes, size_t count));
+  MOCK_METHOD3(Write, bool(const void* bytes, size_t count, ErrorCode* error));
   MOCK_METHOD0(Close, int());
 };
 
diff --git a/mock_metrics_reporter.h b/mock_metrics_reporter.h
new file mode 100644
index 0000000..a0f164b
--- /dev/null
+++ b/mock_metrics_reporter.h
@@ -0,0 +1,83 @@
+//
+// Copyright (C) 2017 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_MOCK_METRICS_REPORTER_H
+#define UPDATE_ENGINE_MOCK_METRICS_REPORTER_H
+
+#include <gmock/gmock.h>
+
+#include "update_engine/metrics_reporter_interface.h"
+
+namespace chromeos_update_engine {
+
+class MockMetricsReporter : public MetricsReporterInterface {
+ public:
+  MOCK_METHOD0(Initialize, void());
+
+  MOCK_METHOD1(ReportRollbackMetrics, void(metrics::RollbackResult result));
+
+  MOCK_METHOD1(ReportDailyMetrics, void(base::TimeDelta os_age));
+
+  MOCK_METHOD4(ReportUpdateCheckMetrics,
+               void(SystemState* system_state,
+                    metrics::CheckResult result,
+                    metrics::CheckReaction reaction,
+                    metrics::DownloadErrorCode download_error_code));
+
+  MOCK_METHOD8(ReportUpdateAttemptMetrics,
+               void(SystemState* system_state,
+                    int attempt_number,
+                    PayloadType payload_type,
+                    base::TimeDelta duration,
+                    base::TimeDelta duration_uptime,
+                    int64_t payload_size,
+                    metrics::AttemptResult attempt_result,
+                    ErrorCode internal_error_code));
+
+  MOCK_METHOD5(ReportUpdateAttemptDownloadMetrics,
+               void(int64_t payload_bytes_downloaded,
+                    int64_t payload_download_speed_bps,
+                    DownloadSource download_source,
+                    metrics::DownloadErrorCode payload_download_error_code,
+                    metrics::ConnectionType connection_type));
+
+  MOCK_METHOD0(ReportAbnormallyTerminatedUpdateAttemptMetrics, void());
+
+  MOCK_METHOD9(ReportSuccessfulUpdateMetrics,
+               void(int attempt_count,
+                    int updates_abandoned_count,
+                    PayloadType payload_type,
+                    int64_t payload_size,
+                    int64_t num_bytes_downloaded[kNumDownloadSources],
+                    int download_overhead_percentage,
+                    base::TimeDelta total_duration,
+                    int reboot_count,
+                    int url_switch_count));
+
+  MOCK_METHOD2(ReportCertificateCheckMetrics,
+               void(ServerToCheck server_to_check,
+                    CertificateCheckResult result));
+
+  MOCK_METHOD1(ReportFailedUpdateCount, void(int target_attempt));
+
+  MOCK_METHOD1(ReportTimeToReboot, void(int time_to_reboot_minutes));
+
+  MOCK_METHOD2(ReportInstallDateProvisioningSource, void(int source, int max));
+};
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_MOCK_METRICS_REPORTER_H
diff --git a/mock_payload_state.h b/mock_payload_state.h
index 2f654c7..6dccc64 100644
--- a/mock_payload_state.h
+++ b/mock_payload_state.h
@@ -52,6 +52,7 @@
   MOCK_METHOD1(SetUsingP2PForSharing, void(bool value));
   MOCK_METHOD1(SetScatteringWaitPeriod, void(base::TimeDelta));
   MOCK_METHOD1(SetP2PUrl, void(const std::string&));
+  MOCK_METHOD0(NextPayload, bool());
 
   // Getters.
   MOCK_METHOD0(GetResponseSignature, std::string());
diff --git a/mock_service_observer.h b/mock_service_observer.h
new file mode 100644
index 0000000..e434eab
--- /dev/null
+++ b/mock_service_observer.h
@@ -0,0 +1,35 @@
+//
+// Copyright (C) 2017 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_MOCK_SERVICE_OBSERVER_H_
+#define UPDATE_ENGINE_MOCK_SERVICE_OBSERVER_H_
+
+#include <gmock/gmock.h>
+#include "update_engine/service_observer_interface.h"
+
+namespace chromeos_update_engine {
+
+class MockServiceObserver : public ServiceObserverInterface {
+ public:
+  MOCK_METHOD1(
+      SendStatusUpdate,
+      void(const update_engine::UpdateEngineStatus& update_engine_status));
+  MOCK_METHOD1(SendPayloadApplicationComplete, void(ErrorCode error_code));
+};
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_MOCK_SERVICE_OBSERVER_H_
diff --git a/mock_update_attempter.h b/mock_update_attempter.h
index 89f163e..d88b840 100644
--- a/mock_update_attempter.h
+++ b/mock_update_attempter.h
@@ -36,19 +36,18 @@
                             bool obey_proxies,
                             bool interactive));
 
-  MOCK_METHOD5(GetStatus, bool(int64_t* last_checked_time,
-                               double* progress,
-                               std::string* current_operation,
-                               std::string* new_version,
-                               int64_t* new_size));
+  MOCK_METHOD1(GetStatus, bool(update_engine::UpdateEngineStatus* out_status));
 
   MOCK_METHOD1(GetBootTimeAtUpdate, bool(base::Time* out_boot_time));
 
   MOCK_METHOD0(ResetStatus, bool(void));
 
-  MOCK_METHOD3(CheckForUpdate, void(const std::string& app_version,
-                                    const std::string& omaha_url,
-                                    bool is_interactive));
+  MOCK_METHOD0(GetCurrentUpdateAttemptFlags, UpdateAttemptFlags(void));
+
+  MOCK_METHOD3(CheckForUpdate,
+               bool(const std::string& app_version,
+                    const std::string& omaha_url,
+                    UpdateAttemptFlags flags));
 
   MOCK_METHOD0(RefreshDevicePolicy, void(void));
 
diff --git a/omaha_request_action.cc b/omaha_request_action.cc
index ee9e920..c4db0c7 100644
--- a/omaha_request_action.cc
+++ b/omaha_request_action.cc
@@ -27,9 +27,11 @@
 #include <base/logging.h>
 #include <base/rand_util.h>
 #include <base/strings/string_number_conversions.h>
+#include <base/strings/string_split.h>
 #include <base/strings/string_util.h>
 #include <base/strings/stringprintf.h>
 #include <base/time/time.h>
+#include <brillo/key_value_store.h>
 #include <expat.h>
 #include <metrics/metrics_library.h>
 
@@ -41,7 +43,7 @@
 #include "update_engine/common/prefs_interface.h"
 #include "update_engine/common/utils.h"
 #include "update_engine/connection_manager_interface.h"
-#include "update_engine/metrics.h"
+#include "update_engine/metrics_reporter_interface.h"
 #include "update_engine/metrics_utils.h"
 #include "update_engine/omaha_request_params.h"
 #include "update_engine/p2p_manager.h"
@@ -70,7 +72,6 @@
 static const char* kTagMoreInfo = "MoreInfo";
 // Deprecated: "NeedsAdmin"
 static const char* kTagPrompt = "Prompt";
-static const char* kTagSha256 = "sha256";
 static const char* kTagDisableP2PForDownloading = "DisableP2PForDownloading";
 static const char* kTagDisableP2PForSharing = "DisableP2PForSharing";
 static const char* kTagPublicKeyRsa = "PublicKeyRsa";
@@ -204,10 +205,25 @@
                             arg_name.c_str(), escaped_xml_value.c_str());
 }
 
+struct OmahaAppData {
+  string id;
+  string version;
+  string product_components;
+};
+
+bool IsValidComponentID(const string& id) {
+  for (char c : id) {
+    if (!isalnum(c) && c != '-' && c != '_' && c != '.')
+      return false;
+  }
+  return true;
+}
+
 // Returns an XML that corresponds to the entire <app> node of the Omaha
 // request based on the given parameters.
 string GetAppXml(const OmahaEvent* event,
                  OmahaRequestParams* params,
+                 const OmahaAppData& app_data,
                  bool ping_only,
                  bool include_ping,
                  int ping_active_days,
@@ -226,10 +242,10 @@
     LOG(INFO) << "Passing OS version as 0.0.0.0 as we are set to powerwash "
               << "on downgrading to the version in the more stable channel";
     app_versions = "version=\"0.0.0.0\" from_version=\"" +
-        XmlEncodeWithDefault(params->app_version(), "0.0.0.0") + "\" ";
+                   XmlEncodeWithDefault(app_data.version, "0.0.0.0") + "\" ";
   } else {
     app_versions = "version=\"" +
-        XmlEncodeWithDefault(params->app_version(), "0.0.0.0") + "\" ";
+                   XmlEncodeWithDefault(app_data.version, "0.0.0.0") + "\" ";
   }
 
   string download_channel = params->download_channel();
@@ -264,12 +280,47 @@
         "fingerprint=\"" + XmlEncodeWithDefault(params->os_build_fingerprint(), "") + "\" ";
   }
 
+  string buildtype_arg;
+  if (!params->os_build_type().empty()) {
+    buildtype_arg = "os_build_type=\"" +
+                    XmlEncodeWithDefault(params->os_build_type(), "") + "\" ";
+  }
+
+  string product_components_args;
+  if (!app_data.product_components.empty()) {
+    brillo::KeyValueStore store;
+    if (store.LoadFromString(app_data.product_components)) {
+      for (const string& key : store.GetKeys()) {
+        if (!IsValidComponentID(key)) {
+          LOG(ERROR) << "Invalid component id: " << key;
+          continue;
+        }
+        string version;
+        if (!store.GetString(key, &version)) {
+          LOG(ERROR) << "Failed to get version for " << key
+                     << " in product_components.";
+          continue;
+        }
+        product_components_args +=
+            base::StringPrintf("_%s.version=\"%s\" ",
+                               key.c_str(),
+                               XmlEncodeWithDefault(version, "").c_str());
+      }
+    } else {
+      LOG(ERROR) << "Failed to parse product_components:\n"
+                 << app_data.product_components;
+    }
+  }
+
+  // clang-format off
   string app_xml = "    <app "
-      "appid=\"" + XmlEncodeWithDefault(params->GetAppId(), "") + "\" " +
+      "appid=\"" + XmlEncodeWithDefault(app_data.id, "") + "\" " +
       app_cohort_args +
       app_versions +
       app_channels +
+      product_components_args +
       fingerprint_arg +
+      buildtype_arg +
       "lang=\"" + XmlEncodeWithDefault(params->app_lang(), "en-US") + "\" " +
       "board=\"" + XmlEncodeWithDefault(params->os_board(), "") + "\" " +
       "hardware_class=\"" + XmlEncodeWithDefault(params->hwid(), "") + "\" " +
@@ -280,7 +331,7 @@
       ">\n" +
          app_body +
       "    </app>\n";
-
+  // clang-format on
   return app_xml;
 }
 
@@ -306,9 +357,32 @@
                      int install_date_in_days,
                      SystemState* system_state) {
   string os_xml = GetOsXml(params);
-  string app_xml = GetAppXml(event, params, ping_only, include_ping,
-                             ping_active_days, ping_roll_call_days,
-                             install_date_in_days, system_state);
+  OmahaAppData product_app = {
+      .id = params->GetAppId(),
+      .version = params->app_version(),
+      .product_components = params->product_components()};
+  string app_xml = GetAppXml(event,
+                             params,
+                             product_app,
+                             ping_only,
+                             include_ping,
+                             ping_active_days,
+                             ping_roll_call_days,
+                             install_date_in_days,
+                             system_state);
+  if (!params->system_app_id().empty()) {
+    OmahaAppData system_app = {.id = params->system_app_id(),
+                               .version = params->system_version()};
+    app_xml += GetAppXml(event,
+                         params,
+                         system_app,
+                         ping_only,
+                         include_ping,
+                         ping_active_days,
+                         ping_roll_call_days,
+                         install_date_in_days,
+                         system_state);
+  }
 
   string install_source = base::StringPrintf("installsource=\"%s\" ",
       (params->interactive() ? "ondemandupdate" : "scheduler"));
@@ -346,22 +420,32 @@
   string current_path;
 
   // These are the values extracted from the XML.
-  string app_cohort;
-  string app_cohorthint;
-  string app_cohortname;
-  bool app_cohort_set = false;
-  bool app_cohorthint_set = false;
-  bool app_cohortname_set = false;
-  string updatecheck_status;
   string updatecheck_poll_interval;
   map<string, string> updatecheck_attrs;
   string daystart_elapsed_days;
   string daystart_elapsed_seconds;
-  vector<string> url_codebase;
-  string package_name;
-  string package_size;
-  string manifest_version;
-  map<string, string> action_postinstall_attrs;
+
+  struct App {
+    string id;
+    vector<string> url_codebase;
+    string manifest_version;
+    map<string, string> action_postinstall_attrs;
+    string updatecheck_status;
+    string cohort;
+    string cohorthint;
+    string cohortname;
+    bool cohort_set = false;
+    bool cohorthint_set = false;
+    bool cohortname_set = false;
+
+    struct Package {
+      string name;
+      string size;
+      string hash;
+    };
+    vector<Package> packages;
+  };
+  vector<App> apps;
 };
 
 namespace {
@@ -386,22 +470,28 @@
   }
 
   if (data->current_path == "/response/app") {
+    OmahaParserData::App app;
+    if (attrs.find("appid") != attrs.end()) {
+      app.id = attrs["appid"];
+    }
     if (attrs.find("cohort") != attrs.end()) {
-      data->app_cohort_set = true;
-      data->app_cohort = attrs["cohort"];
+      app.cohort_set = true;
+      app.cohort = attrs["cohort"];
     }
     if (attrs.find("cohorthint") != attrs.end()) {
-      data->app_cohorthint_set = true;
-      data->app_cohorthint = attrs["cohorthint"];
+      app.cohorthint_set = true;
+      app.cohorthint = attrs["cohorthint"];
     }
     if (attrs.find("cohortname") != attrs.end()) {
-      data->app_cohortname_set = true;
-      data->app_cohortname = attrs["cohortname"];
+      app.cohortname_set = true;
+      app.cohortname = attrs["cohortname"];
     }
+    data->apps.push_back(std::move(app));
   } else if (data->current_path == "/response/app/updatecheck") {
-    // There is only supposed to be a single <updatecheck> element.
-    data->updatecheck_status = attrs["status"];
-    data->updatecheck_poll_interval = attrs["PollInterval"];
+    if (!data->apps.empty())
+      data->apps.back().updatecheck_status = attrs["status"];
+    if (data->updatecheck_poll_interval.empty())
+      data->updatecheck_poll_interval = attrs["PollInterval"];
     // Omaha sends arbitrary key-value pairs as extra attributes starting with
     // an underscore.
     for (const auto& attr : attrs) {
@@ -414,20 +504,24 @@
     data->daystart_elapsed_seconds = attrs["elapsed_seconds"];
   } else if (data->current_path == "/response/app/updatecheck/urls/url") {
     // Look at all <url> elements.
-    data->url_codebase.push_back(attrs["codebase"]);
-  } else if (data->package_name.empty() && data->current_path ==
+    if (!data->apps.empty())
+      data->apps.back().url_codebase.push_back(attrs["codebase"]);
+  } else if (data->current_path ==
              "/response/app/updatecheck/manifest/packages/package") {
-    // Only look at the first <package>.
-    data->package_name = attrs["name"];
-    data->package_size = attrs["size"];
+    // 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"]});
   } else if (data->current_path == "/response/app/updatecheck/manifest") {
     // Get the version.
-    data->manifest_version = attrs[kTagVersion];
+    if (!data->apps.empty())
+      data->apps.back().manifest_version = attrs[kTagVersion];
   } else if (data->current_path ==
              "/response/app/updatecheck/manifest/actions/action") {
     // We only care about the postinstall action.
-    if (attrs["event"] == "postinstall") {
-      data->action_postinstall_attrs = attrs;
+    if (attrs["event"] == "postinstall" && !data->apps.empty()) {
+      data->apps.back().action_postinstall_attrs = std::move(attrs);
     }
   }
 }
@@ -731,15 +825,112 @@
   prefs->SetInt64(kPrefsLastRollCallPingDay, daystart.ToInternalValue());
   return true;
 }
+
+// Parses the package node in the given XML document and populates
+// |output_object| if valid. Returns true if we should continue the parsing.
+// False otherwise, in which case it sets any error code using |completer|.
+bool ParsePackage(OmahaParserData::App* app,
+                  OmahaResponse* output_object,
+                  ScopedActionCompleter* completer) {
+  if (app->updatecheck_status == "noupdate") {
+    if (!app->packages.empty()) {
+      LOG(ERROR) << "No update in this <app> but <package> is not empty.";
+      completer->set_code(ErrorCode::kOmahaResponseInvalid);
+      return false;
+    }
+    return true;
+  }
+  if (app->packages.empty()) {
+    LOG(ERROR) << "Omaha Response has no packages";
+    completer->set_code(ErrorCode::kOmahaResponseInvalid);
+    return false;
+  }
+  if (app->url_codebase.empty()) {
+    LOG(ERROR) << "No Omaha Response URLs";
+    completer->set_code(ErrorCode::kOmahaResponseInvalid);
+    return false;
+  }
+  LOG(INFO) << "Found " << app->url_codebase.size() << " url(s)";
+  vector<string> metadata_sizes =
+      base::SplitString(app->action_postinstall_attrs[kTagMetadataSize],
+                        ":",
+                        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> is_delta_payloads =
+      base::SplitString(app->action_postinstall_attrs[kTagIsDeltaPayload],
+                        ":",
+                        base::TRIM_WHITESPACE,
+                        base::SPLIT_WANT_ALL);
+  for (size_t i = 0; i < app->packages.size(); i++) {
+    const auto& package = app->packages[i];
+    if (package.name.empty()) {
+      LOG(ERROR) << "Omaha Response has empty package name";
+      completer->set_code(ErrorCode::kOmahaResponseInvalid);
+      return false;
+    }
+    LOG(INFO) << "Found package " << package.name;
+
+    OmahaResponse::Package out_package;
+    for (const string& codebase : app->url_codebase) {
+      if (codebase.empty()) {
+        LOG(ERROR) << "Omaha Response URL has empty codebase";
+        completer->set_code(ErrorCode::kOmahaResponseInvalid);
+        return false;
+      }
+      out_package.payload_urls.push_back(codebase + package.name);
+    }
+    // Parse the payload size.
+    base::StringToUint64(package.size, &out_package.size);
+    if (out_package.size <= 0) {
+      LOG(ERROR) << "Omaha Response has invalid payload size: " << package.size;
+      completer->set_code(ErrorCode::kOmahaResponseInvalid);
+      return false;
+    }
+    LOG(INFO) << "Payload size = " << out_package.size << " bytes";
+
+    if (i < metadata_sizes.size())
+      base::StringToUint64(metadata_sizes[i], &out_package.metadata_size);
+    LOG(INFO) << "Payload metadata size = " << out_package.metadata_size
+              << " bytes";
+
+    if (i < metadata_signatures.size())
+      out_package.metadata_signature = metadata_signatures[i];
+    LOG(INFO) << "Payload metadata signature = "
+              << out_package.metadata_signature;
+
+    out_package.hash = package.hash;
+    if (out_package.hash.empty()) {
+      LOG(ERROR) << "Omaha Response has empty hash_sha256 value";
+      completer->set_code(ErrorCode::kOmahaResponseInvalid);
+      return false;
+    }
+    LOG(INFO) << "Payload hash = " << out_package.hash;
+
+    if (i < is_delta_payloads.size())
+      out_package.is_delta = ParseBool(is_delta_payloads[i]);
+    LOG(INFO) << "Payload is delta = " << utils::ToString(out_package.is_delta);
+
+    output_object->packages.push_back(std::move(out_package));
+  }
+
+  return true;
+}
+
 }  // namespace
 
 bool OmahaRequestAction::ParseResponse(OmahaParserData* parser_data,
                                        OmahaResponse* output_object,
                                        ScopedActionCompleter* completer) {
-  if (parser_data->updatecheck_status.empty()) {
+  if (parser_data->apps.empty()) {
     completer->set_code(ErrorCode::kOmahaResponseInvalid);
     return false;
   }
+  LOG(INFO) << "Found " << parser_data->apps.size() << " <app>.";
 
   // chromium-os:37289: The PollInterval is not supported by Omaha server
   // currently.  But still keeping this existing code in case we ever decide to
@@ -778,12 +969,17 @@
   }
 
   // We persist the cohorts sent by omaha even if the status is "noupdate".
-  if (parser_data->app_cohort_set)
-    PersistCohortData(kPrefsOmahaCohort, parser_data->app_cohort);
-  if (parser_data->app_cohorthint_set)
-    PersistCohortData(kPrefsOmahaCohortHint, parser_data->app_cohorthint);
-  if (parser_data->app_cohortname_set)
-    PersistCohortData(kPrefsOmahaCohortName, parser_data->app_cohortname);
+  for (const auto& app : parser_data->apps) {
+    if (app.id == params_->GetAppId()) {
+      if (app.cohort_set)
+        PersistCohortData(kPrefsOmahaCohort, app.cohort);
+      if (app.cohorthint_set)
+        PersistCohortData(kPrefsOmahaCohortHint, app.cohorthint);
+      if (app.cohortname_set)
+        PersistCohortData(kPrefsOmahaCohortName, app.cohortname);
+      break;
+    }
+  }
 
   // Parse the updatecheck attributes.
   PersistEolStatus(parser_data->updatecheck_attrs);
@@ -791,97 +987,70 @@
   if (!ParseStatus(parser_data, output_object, completer))
     return false;
 
-  // Note: ParseUrls MUST be called before ParsePackage as ParsePackage
-  // appends the package name to the URLs populated in this method.
-  if (!ParseUrls(parser_data, output_object, completer))
-    return false;
-
-  if (!ParsePackage(parser_data, output_object, completer))
-    return false;
-
   if (!ParseParams(parser_data, output_object, completer))
     return false;
 
+  // Package has to be parsed after Params now because ParseParams need to make
+  // sure that postinstall action exists.
+  for (auto& app : parser_data->apps)
+    if (!ParsePackage(&app, output_object, completer))
+      return false;
+
   return true;
 }
 
 bool OmahaRequestAction::ParseStatus(OmahaParserData* parser_data,
                                      OmahaResponse* output_object,
                                      ScopedActionCompleter* completer) {
-  const string& status = parser_data->updatecheck_status;
-  if (status == "noupdate") {
-    LOG(INFO) << "No update.";
-    output_object->update_exists = false;
-    SetOutputObject(*output_object);
-    completer->set_code(ErrorCode::kSuccess);
-    return false;
-  }
-
-  if (status != "ok") {
-    LOG(ERROR) << "Unknown Omaha response status: " << status;
-    completer->set_code(ErrorCode::kOmahaResponseInvalid);
-    return false;
-  }
-
-  return true;
-}
-
-bool OmahaRequestAction::ParseUrls(OmahaParserData* parser_data,
-                                   OmahaResponse* output_object,
-                                   ScopedActionCompleter* completer) {
-  if (parser_data->url_codebase.empty()) {
-    LOG(ERROR) << "No Omaha Response URLs";
-    completer->set_code(ErrorCode::kOmahaResponseInvalid);
-    return false;
-  }
-
-  LOG(INFO) << "Found " << parser_data->url_codebase.size() << " url(s)";
-  output_object->payload_urls.clear();
-  for (const auto& codebase : parser_data->url_codebase) {
-    if (codebase.empty()) {
-      LOG(ERROR) << "Omaha Response URL has empty codebase";
+  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") {
+      // Don't update if any app has status="noupdate".
+      LOG(INFO) << "No update for <app> " << i;
+      output_object->update_exists = false;
+      break;
+    } else if (status == "ok") {
+      if (parser_data->apps[i].action_postinstall_attrs["noupdate"] == "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;
+      } else {
+        LOG(INFO) << "Update for <app> " << i;
+        output_object->update_exists = true;
+      }
+    } else {
+      LOG(ERROR) << "Unknown Omaha response status: " << status;
       completer->set_code(ErrorCode::kOmahaResponseInvalid);
       return false;
     }
-    output_object->payload_urls.push_back(codebase);
+  }
+  if (!output_object->update_exists) {
+    SetOutputObject(*output_object);
+    completer->set_code(ErrorCode::kSuccess);
   }
 
-  return true;
-}
-
-bool OmahaRequestAction::ParsePackage(OmahaParserData* parser_data,
-                                      OmahaResponse* output_object,
-                                      ScopedActionCompleter* completer) {
-  if (parser_data->package_name.empty()) {
-    LOG(ERROR) << "Omaha Response has empty package name";
-    completer->set_code(ErrorCode::kOmahaResponseInvalid);
-    return false;
-  }
-
-  // Append the package name to each URL in our list so that we don't
-  // propagate the urlBase vs packageName distinctions beyond this point.
-  // From now on, we only need to use payload_urls.
-  for (auto& payload_url : output_object->payload_urls)
-    payload_url += parser_data->package_name;
-
-  // Parse the payload size.
-  off_t size = ParseInt(parser_data->package_size);
-  if (size <= 0) {
-    LOG(ERROR) << "Omaha Response has invalid payload size: " << size;
-    completer->set_code(ErrorCode::kOmahaResponseInvalid);
-    return false;
-  }
-  output_object->size = size;
-
-  LOG(INFO) << "Payload size = " << output_object->size << " bytes";
-
-  return true;
+  return output_object->update_exists;
 }
 
 bool OmahaRequestAction::ParseParams(OmahaParserData* parser_data,
                                      OmahaResponse* output_object,
                                      ScopedActionCompleter* completer) {
-  output_object->version = parser_data->manifest_version;
+  map<string, string> attrs;
+  for (auto& app : parser_data->apps) {
+    if (app.id == params_->GetAppId()) {
+      // this is the app (potentially the only app)
+      output_object->version = app.manifest_version;
+    } else if (!params_->system_app_id().empty() &&
+               app.id == params_->system_app_id()) {
+      // 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;
+    }
+    if (!app.action_postinstall_attrs.empty() && attrs.empty()) {
+      attrs = app.action_postinstall_attrs;
+    }
+  }
   if (output_object->version.empty()) {
     LOG(ERROR) << "Omaha Response does not have version in manifest!";
     completer->set_code(ErrorCode::kOmahaResponseInvalid);
@@ -891,24 +1060,14 @@
   LOG(INFO) << "Received omaha response to update to version "
             << output_object->version;
 
-  map<string, string> attrs = parser_data->action_postinstall_attrs;
   if (attrs.empty()) {
     LOG(ERROR) << "Omaha Response has no postinstall event action";
     completer->set_code(ErrorCode::kOmahaResponseInvalid);
     return false;
   }
 
-  output_object->hash = attrs[kTagSha256];
-  if (output_object->hash.empty()) {
-    LOG(ERROR) << "Omaha Response has empty sha256 value";
-    completer->set_code(ErrorCode::kOmahaResponseInvalid);
-    return false;
-  }
-
   // Get the optional properties one by one.
   output_object->more_info_url = attrs[kTagMoreInfo];
-  output_object->metadata_size = ParseInt(attrs[kTagMetadataSize]);
-  output_object->metadata_signature = attrs[kTagMetadataSignatureRsa];
   output_object->prompt = ParseBool(attrs[kTagPrompt]);
   output_object->deadline = attrs[kTagDeadline];
   output_object->max_days_to_scatter = ParseInt(attrs[kTagMaxDaysToScatter]);
@@ -922,8 +1081,6 @@
   if (!base::StringToUint(max, &output_object->max_failure_count_per_url))
     output_object->max_failure_count_per_url = kDefaultMaxFailureCountPerUrl;
 
-  output_object->is_delta_payload = ParseBool(attrs[kTagIsDeltaPayload]);
-
   output_object->disable_payload_backoff =
       ParseBool(attrs[kTagDisablePayloadBackoff]);
 
@@ -1016,11 +1173,9 @@
   output_object.update_exists = true;
   SetOutputObject(output_object);
 
-  ErrorCode error = ErrorCode::kSuccess;
-  if (ShouldIgnoreUpdate(&error, output_object)) {
-    // No need to change output_object.update_exists here, since the value
-    // has been output to the pipe.
-    completer.set_code(error);
+  if (ShouldIgnoreUpdate(output_object)) {
+    output_object.update_exists = false;
+    completer.set_code(ErrorCode::kOmahaUpdateIgnoredPerPolicy);
     return;
   }
 
@@ -1150,10 +1305,15 @@
                    next_data_offset + next_data_length;
   }
 
-  string file_id = utils::CalculateP2PFileId(response.hash, response.size);
+  // TODO(senj): Fix P2P for multiple package.
+  brillo::Blob raw_hash;
+  if (!base::HexStringToBytes(response.packages[0].hash, &raw_hash))
+    return;
+  string file_id =
+      utils::CalculateP2PFileId(raw_hash, response.packages[0].size);
   if (system_state_->p2p_manager()) {
-    LOG(INFO) << "Checking if payload is available via p2p, file_id="
-              << file_id << " minimum_size=" << minimum_size;
+    LOG(INFO) << "Checking if payload is available via p2p, file_id=" << file_id
+              << " minimum_size=" << minimum_size;
     system_state_->p2p_manager()->LookupUrlForFile(
         file_id,
         minimum_size,
@@ -1416,12 +1576,9 @@
   if (!prefs->SetInt64(kPrefsInstallDateDays, install_date_days))
     return false;
 
-  string metric_name = metrics::kMetricInstallDateProvisioningSource;
-  system_state->metrics_lib()->SendEnumToUMA(
-      metric_name,
+  system_state->metrics_reporter()->ReportInstallDateProvisioningSource(
       static_cast<int>(source),  // Sample.
       kProvisionedMax);          // Maximum.
-
   return true;
 }
 
@@ -1478,7 +1635,6 @@
     break;
 
   case ErrorCode::kOmahaUpdateIgnoredPerPolicy:
-  case ErrorCode::kOmahaUpdateIgnoredOverCellular:
     result = metrics::CheckResult::kUpdateAvailable;
     reaction = metrics::CheckReaction::kIgnored;
     break;
@@ -1508,12 +1664,12 @@
     break;
   }
 
-  metrics::ReportUpdateCheckMetrics(system_state_,
-                                    result, reaction, download_error_code);
+  system_state_->metrics_reporter()->ReportUpdateCheckMetrics(
+      system_state_, result, reaction, download_error_code);
 }
 
 bool OmahaRequestAction::ShouldIgnoreUpdate(
-    ErrorCode* error, const OmahaResponse& response) const {
+    const OmahaResponse& response) const {
   // Note: policy decision to not update to a version we rolled back from.
   string rollback_version =
       system_state_->payload_state()->GetRollbackVersion();
@@ -1521,12 +1677,11 @@
     LOG(INFO) << "Detected previous rollback from version " << rollback_version;
     if (rollback_version == response.version) {
       LOG(INFO) << "Received version that we rolled back from. Ignoring.";
-      *error = ErrorCode::kOmahaUpdateIgnoredPerPolicy;
       return true;
     }
   }
 
-  if (!IsUpdateAllowedOverCurrentConnection(error, response)) {
+  if (!IsUpdateAllowedOverCurrentConnection()) {
     LOG(INFO) << "Update is not allowed over current connection.";
     return true;
   }
@@ -1541,59 +1696,7 @@
   return false;
 }
 
-bool OmahaRequestAction::IsUpdateAllowedOverCellularByPrefs(
-    const OmahaResponse& response) const {
-  PrefsInterface* prefs = system_state_->prefs();
-
-  if (!prefs) {
-    LOG(INFO) << "Disabling updates over cellular as the preferences are "
-                 "not available.";
-    return false;
-  }
-
-  bool is_allowed;
-
-  if (prefs->Exists(kPrefsUpdateOverCellularPermission) &&
-      prefs->GetBoolean(kPrefsUpdateOverCellularPermission, &is_allowed) &&
-      is_allowed) {
-    LOG(INFO) << "Allowing updates over cellular as permission preference is "
-                 "set to true.";
-    return true;
-  }
-
-  if (!prefs->Exists(kPrefsUpdateOverCellularTargetVersion) ||
-      !prefs->Exists(kPrefsUpdateOverCellularTargetSize)) {
-    LOG(INFO) << "Disabling updates over cellular as permission preference is "
-                 "set to false or does not exist while target does not exist.";
-    return false;
-  }
-
-  std::string target_version;
-  int64_t target_size;
-
-  if (!prefs->GetString(kPrefsUpdateOverCellularTargetVersion,
-                        &target_version) ||
-      !prefs->GetInt64(kPrefsUpdateOverCellularTargetSize,
-                       &target_size)) {
-    LOG(INFO) << "Disabling updates over cellular as the target version or "
-                 "size is not accessible.";
-    return false;
-  }
-
-  if (target_version == response.version && target_size == response.size) {
-    LOG(INFO) << "Allowing updates over cellular as the target matches the"
-                 "omaha response.";
-    return true;
-  } else {
-    LOG(INFO) << "Disabling updates over cellular as the target does not"
-                 "match the omaha response.";
-    return false;
-  }
-}
-
-bool OmahaRequestAction::IsUpdateAllowedOverCurrentConnection(
-    ErrorCode* error,
-    const OmahaResponse& response) const {
+bool OmahaRequestAction::IsUpdateAllowedOverCurrentConnection() const {
   ConnectionType type;
   ConnectionTethering tethering;
   ConnectionManagerInterface* connection_manager =
@@ -1603,32 +1706,7 @@
               << "Defaulting to allow updates.";
     return true;
   }
-
   bool is_allowed = connection_manager->IsUpdateAllowedOver(type, tethering);
-  bool is_device_policy_set = connection_manager->
-                                  IsAllowedConnectionTypesForUpdateSet();
-  // Treats tethered connection as if it is cellular connection.
-  bool is_over_cellular = type == ConnectionType::kCellular ||
-                          tethering == ConnectionTethering::kConfirmed;
-
-  if (!is_over_cellular) {
-    // There's no need to further check user preferences as we are not over
-    // cellular connection.
-    if (!is_allowed)
-      *error = ErrorCode::kOmahaUpdateIgnoredPerPolicy;
-  } else if (is_device_policy_set) {
-    // There's no need to further check user preferences as the device policy
-    // is set regarding updates over cellular.
-    if (!is_allowed)
-      *error = ErrorCode::kOmahaUpdateIgnoredPerPolicy;
-  } else {
-    // Deivce policy is not set, so user preferences overwrite whether to
-    // allow updates over cellular.
-    is_allowed = IsUpdateAllowedOverCellularByPrefs(response);
-    if (!is_allowed)
-      *error = ErrorCode::kOmahaUpdateIgnoredOverCellular;
-  }
-
   LOG(INFO) << "We are connected via "
             << connection_utils::StringForConnectionType(type)
             << ", Updates allowed: " << (is_allowed ? "Yes" : "No");
diff --git a/omaha_request_action.h b/omaha_request_action.h
index 7d35c23..924da40 100644
--- a/omaha_request_action.h
+++ b/omaha_request_action.h
@@ -274,13 +274,6 @@
                  OmahaResponse* output_object,
                  ScopedActionCompleter* completer);
 
-  // Parses the package node in the given XML document and populates
-  // |output_object| if valid. Returns true if we should continue the parsing.
-  // False otherwise, in which case it sets any error code using |completer|.
-  bool ParsePackage(OmahaParserData* parser_data,
-                    OmahaResponse* output_object,
-                    ScopedActionCompleter* completer);
-
   // Parses the other parameters in the given XML document and populates
   // |output_object| if valid. Returns true if we should continue the parsing.
   // False otherwise, in which case it sets any error code using |completer|.
@@ -299,17 +292,11 @@
   void OnLookupPayloadViaP2PCompleted(const std::string& url);
 
   // Returns true if the current update should be ignored.
-  bool ShouldIgnoreUpdate(ErrorCode* error,
-                          const OmahaResponse& response) const;
-
-  // Return true if updates are allowed by user preferences.
-  bool IsUpdateAllowedOverCellularByPrefs(const OmahaResponse& response) const;
+  bool ShouldIgnoreUpdate(const OmahaResponse& response) const;
 
   // Returns true if updates are allowed over the current type of connection.
   // False otherwise.
-  bool IsUpdateAllowedOverCurrentConnection(
-      ErrorCode* error,
-      const OmahaResponse& response) const;
+  bool IsUpdateAllowedOverCurrentConnection() const;
 
   // Global system context.
   SystemState* system_state_;
diff --git a/omaha_request_action_unittest.cc b/omaha_request_action_unittest.cc
index c310e72..d57abe5 100644
--- a/omaha_request_action_unittest.cc
+++ b/omaha_request_action_unittest.cc
@@ -45,7 +45,7 @@
 #include "update_engine/common/prefs.h"
 #include "update_engine/common/test_utils.h"
 #include "update_engine/fake_system_state.h"
-#include "update_engine/metrics.h"
+#include "update_engine/metrics_reporter_interface.h"
 #include "update_engine/mock_connection_manager.h"
 #include "update_engine/mock_payload_state.h"
 #include "update_engine/omaha_request_params.h"
@@ -69,6 +69,7 @@
 namespace {
 
 const char kTestAppId[] = "test-app-id";
+const char kTestAppId2[] = "test-app2-id";
 
 // This is a helper struct to allow unit tests build an update response with the
 // values they care about.
@@ -77,47 +78,91 @@
     string entity_str;
     if (include_entity)
       entity_str = "<!DOCTYPE response [<!ENTITY CrOS \"ChromeOS\">]>";
-    return
-        "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" +
-        entity_str + "<response protocol=\"3.0\">"
-        "<daystart elapsed_seconds=\"100\"/>"
-        "<app appid=\"" + app_id + "\" " +
-        (include_cohorts ? "cohort=\"" + cohort + "\" cohorthint=\"" +
-         cohorthint + "\" cohortname=\"" + cohortname + "\" " : "") +
-        " status=\"ok\">"
-        "<ping status=\"ok\"/>"
-        "<updatecheck status=\"noupdate\"/></app></response>";
+    return "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" + entity_str +
+           "<response protocol=\"3.0\">"
+           "<daystart elapsed_seconds=\"100\"/>"
+           "<app appid=\"" +
+           app_id + "\" " +
+           (include_cohorts
+                ? "cohort=\"" + cohort + "\" cohorthint=\"" + cohorthint +
+                      "\" cohortname=\"" + cohortname + "\" "
+                : "") +
+           " status=\"ok\">"
+           "<ping status=\"ok\"/>"
+           "<updatecheck status=\"noupdate\"/></app>" +
+           (multi_app_no_update
+                ? "<app appid=\"" + app_id2 +
+                      "\"><updatecheck status=\"noupdate\"/></app>"
+                : "") +
+           "</response>";
   }
 
   string GetUpdateResponse() const {
-    return
-        "<?xml version=\"1.0\" encoding=\"UTF-8\"?><response "
-        "protocol=\"3.0\">"
-        "<daystart elapsed_seconds=\"100\"" +
-        (elapsed_days.empty() ? "" : (" elapsed_days=\"" + elapsed_days + "\""))
-        + "/>"
-        "<app appid=\"" + app_id + "\" " +
-        (include_cohorts ? "cohort=\"" + cohort + "\" cohorthint=\"" +
-         cohorthint + "\" cohortname=\"" + cohortname + "\" " : "") +
-        " status=\"ok\">"
-        "<ping status=\"ok\"/><updatecheck status=\"ok\">"
-        "<urls><url codebase=\"" + codebase + "\"/></urls>"
-        "<manifest version=\"" + version + "\">"
-        "<packages><package hash=\"not-used\" name=\"" + filename +  "\" "
-        "size=\"" + base::Int64ToString(size) + "\"/></packages>"
-        "<actions><action event=\"postinstall\" "
-        "ChromeOSVersion=\"" + version + "\" "
-        "MoreInfo=\"" + more_info_url + "\" Prompt=\"" + prompt + "\" "
-        "IsDelta=\"true\" "
-        "IsDeltaPayload=\"true\" "
-        "MaxDaysToScatter=\"" + max_days_to_scatter + "\" "
-        "sha256=\"" + hash + "\" "
-        "needsadmin=\"" + needsadmin + "\" " +
-        (deadline.empty() ? "" : ("deadline=\"" + deadline + "\" ")) +
-        (disable_p2p_for_downloading ?
-            "DisableP2PForDownloading=\"true\" " : "") +
-        (disable_p2p_for_sharing ? "DisableP2PForSharing=\"true\" " : "") +
-        "/></actions></manifest></updatecheck></app></response>";
+    return "<?xml version=\"1.0\" encoding=\"UTF-8\"?><response "
+           "protocol=\"3.0\">"
+           "<daystart elapsed_seconds=\"100\"" +
+           (elapsed_days.empty() ? ""
+                                 : (" elapsed_days=\"" + elapsed_days + "\"")) +
+           "/>"
+           "<app appid=\"" +
+           app_id + "\" " +
+           (include_cohorts
+                ? "cohort=\"" + cohort + "\" cohorthint=\"" + cohorthint +
+                      "\" cohortname=\"" + cohortname + "\" "
+                : "") +
+           " status=\"ok\">"
+           "<ping status=\"ok\"/><updatecheck status=\"ok\">"
+           "<urls><url codebase=\"" +
+           codebase +
+           "\"/></urls>"
+           "<manifest version=\"" +
+           version +
+           "\">"
+           "<packages><package hash=\"not-used\" name=\"" +
+           filename + "\" size=\"" + base::Int64ToString(size) +
+           "\" hash_sha256=\"" + hash + "\"/>" +
+           (multi_package ? "<package name=\"package2\" size=\"222\" "
+                            "hash_sha256=\"hash2\"/>"
+                          : "") +
+           "</packages>"
+           "<actions><action event=\"postinstall\" MetadataSize=\"11" +
+           (multi_package ? ":22" : "") + "\" ChromeOSVersion=\"" + version +
+           "\" MoreInfo=\"" + more_info_url + "\" Prompt=\"" + prompt +
+           "\" "
+           "IsDelta=\"true\" "
+           "IsDeltaPayload=\"true" +
+           (multi_package ? ":false" : "") +
+           "\" "
+           "MaxDaysToScatter=\"" +
+           max_days_to_scatter +
+           "\" "
+           "sha256=\"not-used\" "
+           "needsadmin=\"" +
+           needsadmin + "\" " +
+           (deadline.empty() ? "" : ("deadline=\"" + deadline + "\" ")) +
+           (disable_p2p_for_downloading ? "DisableP2PForDownloading=\"true\" "
+                                        : "") +
+           (disable_p2p_for_sharing ? "DisableP2PForSharing=\"true\" " : "") +
+           "/></actions></manifest></updatecheck></app>" +
+           (multi_app
+                ? "<app appid=\"" + app_id2 + "\"" +
+                      (include_cohorts ? " cohort=\"cohort2\"" : "") +
+                      "><updatecheck status=\"ok\"><urls><url codebase=\"" +
+                      codebase2 + "\"/></urls><manifest version=\"" + version2 +
+                      "\"><packages>"
+                      "<package name=\"package3\" size=\"333\" "
+                      "hash_sha256=\"hash3\"/></packages>"
+                      "<actions><action event=\"postinstall\" " +
+                      (multi_app_self_update
+                           ? "noupdate=\"true\" IsDeltaPayload=\"true\" "
+                           : "IsDeltaPayload=\"false\" ") +
+                      "MetadataSize=\"33\"/></actions>"
+                      "</manifest></updatecheck></app>"
+                : "") +
+           (multi_app_no_update
+                ? "<app><updatecheck status=\"noupdate\"/></app>"
+                : "") +
+           "</response>";
   }
 
   // Return the payload URL, which is split in two fields in the XML response.
@@ -126,14 +171,17 @@
   }
 
   string app_id = kTestAppId;
+  string app_id2 = kTestAppId2;
   string version = "1.2.3.4";
+  string version2 = "2.3.4.5";
   string more_info_url = "http://more/info";
   string prompt = "true";
   string codebase = "http://code/base/";
+  string codebase2 = "http://code/base/2/";
   string filename = "file.signed";
-  string hash = "HASH1234=";
+  string hash = "4841534831323334";
   string needsadmin = "false";
-  int64_t size = 123;
+  uint64_t size = 123;
   string deadline = "";
   string max_days_to_scatter = "7";
   string elapsed_days = "42";
@@ -150,6 +198,15 @@
 
   // Whether to include the CrOS <!ENTITY> in the XML response.
   bool include_entity = false;
+
+  // Whether to include more than one app.
+  bool multi_app = false;
+  // Whether to include an app with noupdate="true".
+  bool multi_app_self_update = false;
+  // Whether to include an additional app with status="noupdate".
+  bool multi_app_no_update = false;
+  // Whether to include more than one package in an app.
+  bool multi_package = false;
 };
 
 }  // namespace
@@ -335,23 +392,16 @@
   BondActions(&action, &collector_action);
   processor.EnqueueAction(&collector_action);
 
-  EXPECT_CALL(*fake_system_state_.mock_metrics_lib(), SendEnumToUMA(_, _, _))
+  EXPECT_CALL(*fake_system_state_.mock_metrics_reporter(),
+              ReportUpdateCheckMetrics(_, _, _, _))
       .Times(AnyNumber());
-  EXPECT_CALL(*fake_system_state_.mock_metrics_lib(),
-      SendEnumToUMA(metrics::kMetricCheckResult,
-          static_cast<int>(expected_check_result),
-          static_cast<int>(metrics::CheckResult::kNumConstants) - 1))
-      .Times(expected_check_result == metrics::CheckResult::kUnset ? 0 : 1);
-  EXPECT_CALL(*fake_system_state_.mock_metrics_lib(),
-      SendEnumToUMA(metrics::kMetricCheckReaction,
-          static_cast<int>(expected_check_reaction),
-          static_cast<int>(metrics::CheckReaction::kNumConstants) - 1))
-      .Times(expected_check_reaction == metrics::CheckReaction::kUnset ? 0 : 1);
-  EXPECT_CALL(*fake_system_state_.mock_metrics_lib(),
-      SendSparseToUMA(metrics::kMetricCheckDownloadErrorCode,
-          static_cast<int>(expected_download_error_code)))
-      .Times(expected_download_error_code == metrics::DownloadErrorCode::kUnset
-             ? 0 : 1);
+
+  EXPECT_CALL(*fake_system_state_.mock_metrics_reporter(),
+              ReportUpdateCheckMetrics(_,
+                                       expected_check_result,
+                                       expected_check_reaction,
+                                       expected_download_error_code))
+      .Times(ping_only ? 0 : 1);
 
   loop.PostTask(base::Bind(
       [](ActionProcessor* processor) { processor->StartProcessing(); },
@@ -431,6 +481,56 @@
   EXPECT_FALSE(response.update_exists);
 }
 
+TEST_F(OmahaRequestActionTest, MultiAppNoUpdateTest) {
+  OmahaResponse response;
+  fake_update_response_.multi_app_no_update = true;
+  ASSERT_TRUE(TestUpdateCheck(nullptr,  // request_params
+                              fake_update_response_.GetNoUpdateResponse(),
+                              -1,
+                              false,  // ping_only
+                              ErrorCode::kSuccess,
+                              metrics::CheckResult::kNoUpdateAvailable,
+                              metrics::CheckReaction::kUnset,
+                              metrics::DownloadErrorCode::kUnset,
+                              &response,
+                              nullptr));
+  EXPECT_FALSE(response.update_exists);
+}
+
+TEST_F(OmahaRequestActionTest, MultiAppNoPartialUpdateTest) {
+  OmahaResponse response;
+  fake_update_response_.multi_app_no_update = true;
+  ASSERT_TRUE(TestUpdateCheck(nullptr,  // request_params
+                              fake_update_response_.GetUpdateResponse(),
+                              -1,
+                              false,  // ping_only
+                              ErrorCode::kSuccess,
+                              metrics::CheckResult::kNoUpdateAvailable,
+                              metrics::CheckReaction::kUnset,
+                              metrics::DownloadErrorCode::kUnset,
+                              &response,
+                              nullptr));
+  EXPECT_FALSE(response.update_exists);
+}
+
+TEST_F(OmahaRequestActionTest, NoSelfUpdateTest) {
+  OmahaResponse response;
+  ASSERT_TRUE(TestUpdateCheck(
+      nullptr,  // request_params
+      "<response><app><updatecheck status=\"ok\"><manifest><actions><action "
+      "event=\"postinstall\" noupdate=\"true\"/></actions>"
+      "</manifest></updatecheck></app></response>",
+      -1,
+      false,  // ping_only
+      ErrorCode::kSuccess,
+      metrics::CheckResult::kNoUpdateAvailable,
+      metrics::CheckReaction::kUnset,
+      metrics::DownloadErrorCode::kUnset,
+      &response,
+      nullptr));
+  EXPECT_FALSE(response.update_exists);
+}
+
 // Test that all the values in the response are parsed in a normal update
 // response.
 TEST_F(OmahaRequestActionTest, ValidUpdateTest) {
@@ -448,21 +548,184 @@
                       &response,
                       nullptr));
   EXPECT_TRUE(response.update_exists);
-  EXPECT_TRUE(response.update_exists);
   EXPECT_EQ(fake_update_response_.version, response.version);
-  EXPECT_EQ(fake_update_response_.GetPayloadUrl(), response.payload_urls[0]);
+  EXPECT_EQ("", response.system_version);
+  EXPECT_EQ(fake_update_response_.GetPayloadUrl(),
+            response.packages[0].payload_urls[0]);
   EXPECT_EQ(fake_update_response_.more_info_url, response.more_info_url);
-  EXPECT_EQ(fake_update_response_.hash, response.hash);
-  EXPECT_EQ(fake_update_response_.size, response.size);
+  EXPECT_EQ(fake_update_response_.hash, response.packages[0].hash);
+  EXPECT_EQ(fake_update_response_.size, response.packages[0].size);
+  EXPECT_EQ(true, response.packages[0].is_delta);
   EXPECT_EQ(fake_update_response_.prompt == "true", response.prompt);
   EXPECT_EQ(fake_update_response_.deadline, response.deadline);
-  // Omaha cohort attribets are not set in the response, so they should not be
+  // Omaha cohort attributes are not set in the response, so they should not be
   // persisted.
   EXPECT_FALSE(fake_prefs_.Exists(kPrefsOmahaCohort));
   EXPECT_FALSE(fake_prefs_.Exists(kPrefsOmahaCohortHint));
   EXPECT_FALSE(fake_prefs_.Exists(kPrefsOmahaCohortName));
 }
 
+TEST_F(OmahaRequestActionTest, MultiPackageUpdateTest) {
+  OmahaResponse response;
+  fake_update_response_.multi_package = true;
+  ASSERT_TRUE(TestUpdateCheck(nullptr,  // request_params
+                              fake_update_response_.GetUpdateResponse(),
+                              -1,
+                              false,  // ping_only
+                              ErrorCode::kSuccess,
+                              metrics::CheckResult::kUpdateAvailable,
+                              metrics::CheckReaction::kUpdating,
+                              metrics::DownloadErrorCode::kUnset,
+                              &response,
+                              nullptr));
+  EXPECT_TRUE(response.update_exists);
+  EXPECT_EQ(fake_update_response_.version, response.version);
+  EXPECT_EQ(fake_update_response_.GetPayloadUrl(),
+            response.packages[0].payload_urls[0]);
+  EXPECT_EQ(fake_update_response_.codebase + "package2",
+            response.packages[1].payload_urls[0]);
+  EXPECT_EQ(fake_update_response_.hash, response.packages[0].hash);
+  EXPECT_EQ(fake_update_response_.size, response.packages[0].size);
+  EXPECT_EQ(true, response.packages[0].is_delta);
+  EXPECT_EQ(11u, response.packages[0].metadata_size);
+  ASSERT_EQ(2u, response.packages.size());
+  EXPECT_EQ(string("hash2"), response.packages[1].hash);
+  EXPECT_EQ(222u, response.packages[1].size);
+  EXPECT_EQ(22u, response.packages[1].metadata_size);
+  EXPECT_EQ(false, response.packages[1].is_delta);
+}
+
+TEST_F(OmahaRequestActionTest, MultiAppUpdateTest) {
+  OmahaResponse response;
+  fake_update_response_.multi_app = true;
+  ASSERT_TRUE(TestUpdateCheck(nullptr,  // request_params
+                              fake_update_response_.GetUpdateResponse(),
+                              -1,
+                              false,  // ping_only
+                              ErrorCode::kSuccess,
+                              metrics::CheckResult::kUpdateAvailable,
+                              metrics::CheckReaction::kUpdating,
+                              metrics::DownloadErrorCode::kUnset,
+                              &response,
+                              nullptr));
+  EXPECT_TRUE(response.update_exists);
+  EXPECT_EQ(fake_update_response_.version, response.version);
+  EXPECT_EQ(fake_update_response_.GetPayloadUrl(),
+            response.packages[0].payload_urls[0]);
+  EXPECT_EQ(fake_update_response_.codebase2 + "package3",
+            response.packages[1].payload_urls[0]);
+  EXPECT_EQ(fake_update_response_.hash, response.packages[0].hash);
+  EXPECT_EQ(fake_update_response_.size, response.packages[0].size);
+  EXPECT_EQ(11u, response.packages[0].metadata_size);
+  EXPECT_EQ(true, response.packages[0].is_delta);
+  ASSERT_EQ(2u, response.packages.size());
+  EXPECT_EQ(string("hash3"), response.packages[1].hash);
+  EXPECT_EQ(333u, response.packages[1].size);
+  EXPECT_EQ(33u, response.packages[1].metadata_size);
+  EXPECT_EQ(false, response.packages[1].is_delta);
+}
+
+TEST_F(OmahaRequestActionTest, MultiAppAndSystemUpdateTest) {
+  OmahaResponse response;
+  fake_update_response_.multi_app = true;
+  // trigger the lining up of the app and system versions
+  request_params_.set_system_app_id(fake_update_response_.app_id2);
+
+  ASSERT_TRUE(TestUpdateCheck(nullptr,  // request_params
+                              fake_update_response_.GetUpdateResponse(),
+                              -1,
+                              false,  // ping_only
+                              ErrorCode::kSuccess,
+                              metrics::CheckResult::kUpdateAvailable,
+                              metrics::CheckReaction::kUpdating,
+                              metrics::DownloadErrorCode::kUnset,
+                              &response,
+                              nullptr));
+  EXPECT_TRUE(response.update_exists);
+  EXPECT_EQ(fake_update_response_.version, response.version);
+  EXPECT_EQ(fake_update_response_.version2, response.system_version);
+  EXPECT_EQ(fake_update_response_.GetPayloadUrl(),
+            response.packages[0].payload_urls[0]);
+  EXPECT_EQ(fake_update_response_.codebase2 + "package3",
+            response.packages[1].payload_urls[0]);
+  EXPECT_EQ(fake_update_response_.hash, response.packages[0].hash);
+  EXPECT_EQ(fake_update_response_.size, response.packages[0].size);
+  EXPECT_EQ(11u, response.packages[0].metadata_size);
+  EXPECT_EQ(true, response.packages[0].is_delta);
+  ASSERT_EQ(2u, response.packages.size());
+  EXPECT_EQ(string("hash3"), response.packages[1].hash);
+  EXPECT_EQ(333u, response.packages[1].size);
+  EXPECT_EQ(33u, response.packages[1].metadata_size);
+  EXPECT_EQ(false, response.packages[1].is_delta);
+}
+
+TEST_F(OmahaRequestActionTest, MultiAppPartialUpdateTest) {
+  OmahaResponse response;
+  fake_update_response_.multi_app = true;
+  fake_update_response_.multi_app_self_update = true;
+  ASSERT_TRUE(TestUpdateCheck(nullptr,  // request_params
+                              fake_update_response_.GetUpdateResponse(),
+                              -1,
+                              false,  // ping_only
+                              ErrorCode::kSuccess,
+                              metrics::CheckResult::kUpdateAvailable,
+                              metrics::CheckReaction::kUpdating,
+                              metrics::DownloadErrorCode::kUnset,
+                              &response,
+                              nullptr));
+  EXPECT_TRUE(response.update_exists);
+  EXPECT_EQ(fake_update_response_.version, response.version);
+  EXPECT_EQ("", response.system_version);
+  EXPECT_EQ(fake_update_response_.GetPayloadUrl(),
+            response.packages[0].payload_urls[0]);
+  EXPECT_EQ(fake_update_response_.hash, response.packages[0].hash);
+  EXPECT_EQ(fake_update_response_.size, response.packages[0].size);
+  EXPECT_EQ(11u, response.packages[0].metadata_size);
+  ASSERT_EQ(2u, response.packages.size());
+  EXPECT_EQ(string("hash3"), response.packages[1].hash);
+  EXPECT_EQ(333u, response.packages[1].size);
+  EXPECT_EQ(33u, response.packages[1].metadata_size);
+  EXPECT_EQ(true, response.packages[1].is_delta);
+}
+
+TEST_F(OmahaRequestActionTest, MultiAppMultiPackageUpdateTest) {
+  OmahaResponse response;
+  fake_update_response_.multi_app = true;
+  fake_update_response_.multi_package = true;
+  ASSERT_TRUE(TestUpdateCheck(nullptr,  // request_params
+                              fake_update_response_.GetUpdateResponse(),
+                              -1,
+                              false,  // ping_only
+                              ErrorCode::kSuccess,
+                              metrics::CheckResult::kUpdateAvailable,
+                              metrics::CheckReaction::kUpdating,
+                              metrics::DownloadErrorCode::kUnset,
+                              &response,
+                              nullptr));
+  EXPECT_TRUE(response.update_exists);
+  EXPECT_EQ(fake_update_response_.version, response.version);
+  EXPECT_EQ("", response.system_version);
+  EXPECT_EQ(fake_update_response_.GetPayloadUrl(),
+            response.packages[0].payload_urls[0]);
+  EXPECT_EQ(fake_update_response_.codebase + "package2",
+            response.packages[1].payload_urls[0]);
+  EXPECT_EQ(fake_update_response_.codebase2 + "package3",
+            response.packages[2].payload_urls[0]);
+  EXPECT_EQ(fake_update_response_.hash, response.packages[0].hash);
+  EXPECT_EQ(fake_update_response_.size, response.packages[0].size);
+  EXPECT_EQ(11u, response.packages[0].metadata_size);
+  EXPECT_EQ(true, response.packages[0].is_delta);
+  ASSERT_EQ(3u, response.packages.size());
+  EXPECT_EQ(string("hash2"), response.packages[1].hash);
+  EXPECT_EQ(222u, response.packages[1].size);
+  EXPECT_EQ(22u, response.packages[1].metadata_size);
+  EXPECT_EQ(false, response.packages[1].is_delta);
+  EXPECT_EQ(string("hash3"), response.packages[2].hash);
+  EXPECT_EQ(333u, response.packages[2].size);
+  EXPECT_EQ(33u, response.packages[2].metadata_size);
+  EXPECT_EQ(false, response.packages[2].is_delta);
+}
+
 TEST_F(OmahaRequestActionTest, ExtraHeadersSentTest) {
   const string http_response = "<?xml invalid response";
   request_params_.set_interactive(true);
@@ -519,185 +782,6 @@
   EXPECT_FALSE(response.update_exists);
 }
 
-TEST_F(OmahaRequestActionTest, ValidUpdateOverCellularAllowedByDevicePolicy) {
-  // This test tests that update over cellular is allowed as device policy
-  // says yes.
-  OmahaResponse response;
-  MockConnectionManager mock_cm;
-
-  fake_system_state_.set_connection_manager(&mock_cm);
-
-  EXPECT_CALL(mock_cm, GetConnectionProperties(_, _))
-      .WillRepeatedly(
-          DoAll(SetArgPointee<0>(ConnectionType::kCellular),
-                SetArgPointee<1>(ConnectionTethering::kUnknown),
-                Return(true)));
-  EXPECT_CALL(mock_cm, IsAllowedConnectionTypesForUpdateSet())
-      .WillRepeatedly(Return(true));
-  EXPECT_CALL(mock_cm, IsUpdateAllowedOver(ConnectionType::kCellular, _))
-      .WillRepeatedly(Return(true));
-
-  ASSERT_TRUE(
-      TestUpdateCheck(nullptr,  // request_params
-                      fake_update_response_.GetUpdateResponse(),
-                      -1,
-                      false,  // ping_only
-                      ErrorCode::kSuccess,
-                      metrics::CheckResult::kUpdateAvailable,
-                      metrics::CheckReaction::kUpdating,
-                      metrics::DownloadErrorCode::kUnset,
-                      &response,
-                      nullptr));
-  EXPECT_TRUE(response.update_exists);
-}
-
-TEST_F(OmahaRequestActionTest, ValidUpdateOverCellularBlockedByDevicePolicy) {
-  // This test tests that update over cellular is blocked as device policy
-  // says no.
-  OmahaResponse response;
-  MockConnectionManager mock_cm;
-
-  fake_system_state_.set_connection_manager(&mock_cm);
-
-  EXPECT_CALL(mock_cm, GetConnectionProperties(_, _))
-      .WillRepeatedly(
-          DoAll(SetArgPointee<0>(ConnectionType::kCellular),
-                SetArgPointee<1>(ConnectionTethering::kUnknown),
-                Return(true)));
-  EXPECT_CALL(mock_cm, IsAllowedConnectionTypesForUpdateSet())
-      .WillRepeatedly(Return(true));
-  EXPECT_CALL(mock_cm, IsUpdateAllowedOver(ConnectionType::kCellular, _))
-      .WillRepeatedly(Return(false));
-
-  ASSERT_FALSE(
-      TestUpdateCheck(nullptr,  // request_params
-                      fake_update_response_.GetUpdateResponse(),
-                      -1,
-                      false,  // ping_only
-                      ErrorCode::kOmahaUpdateIgnoredPerPolicy,
-                      metrics::CheckResult::kUpdateAvailable,
-                      metrics::CheckReaction::kIgnored,
-                      metrics::DownloadErrorCode::kUnset,
-                      &response,
-                      nullptr));
-  EXPECT_FALSE(response.update_exists);
-}
-
-TEST_F(OmahaRequestActionTest,
-       ValidUpdateOverCellularAllowedByUserPermissionTrue) {
-  // This test tests that, when device policy is not set, update over cellular
-  // is allowed as permission for update over cellular is set to true.
-  OmahaResponse response;
-  MockConnectionManager mock_cm;
-
-  fake_prefs_.SetBoolean(kPrefsUpdateOverCellularPermission, true);
-  fake_system_state_.set_connection_manager(&mock_cm);
-
-  EXPECT_CALL(mock_cm, GetConnectionProperties(_, _))
-      .WillRepeatedly(
-          DoAll(SetArgPointee<0>(ConnectionType::kCellular),
-                SetArgPointee<1>(ConnectionTethering::kUnknown),
-                Return(true)));
-  EXPECT_CALL(mock_cm, IsAllowedConnectionTypesForUpdateSet())
-      .WillRepeatedly(Return(false));
-  EXPECT_CALL(mock_cm, IsUpdateAllowedOver(ConnectionType::kCellular, _))
-      .WillRepeatedly(Return(true));
-
-  ASSERT_TRUE(
-      TestUpdateCheck(nullptr,  // request_params
-                      fake_update_response_.GetUpdateResponse(),
-                      -1,
-                      false,  // ping_only
-                      ErrorCode::kSuccess,
-                      metrics::CheckResult::kUpdateAvailable,
-                      metrics::CheckReaction::kUpdating,
-                      metrics::DownloadErrorCode::kUnset,
-                      &response,
-                      nullptr));
-  EXPECT_TRUE(response.update_exists);
-}
-
-TEST_F(OmahaRequestActionTest,
-       ValidUpdateOverCellularBlockedByUpdateTargetNotMatch) {
-  // This test tests that, when device policy is not set and permission for
-  // update over cellular is set to false or does not exist, update over
-  // cellular is blocked as update target does not match the omaha response.
-  OmahaResponse response;
-  MockConnectionManager mock_cm;
-  // A version different from the version in omaha response.
-  string diff_version = "99.99.99";
-  // A size different from the size in omaha response.
-  int64_t diff_size = 999;
-
-  fake_prefs_.SetString(kPrefsUpdateOverCellularTargetVersion, diff_version);
-  fake_prefs_.SetInt64(kPrefsUpdateOverCellularTargetSize, diff_size);
-  // This test tests cellular (3G) being the only connection type being allowed.
-  fake_system_state_.set_connection_manager(&mock_cm);
-
-  EXPECT_CALL(mock_cm, GetConnectionProperties(_, _))
-      .WillRepeatedly(
-          DoAll(SetArgPointee<0>(ConnectionType::kCellular),
-                SetArgPointee<1>(ConnectionTethering::kUnknown),
-                Return(true)));
-  EXPECT_CALL(mock_cm, IsAllowedConnectionTypesForUpdateSet())
-      .WillRepeatedly(Return(false));
-  EXPECT_CALL(mock_cm, IsUpdateAllowedOver(ConnectionType::kCellular, _))
-      .WillRepeatedly(Return(true));
-
-  ASSERT_FALSE(
-      TestUpdateCheck(nullptr,  // request_params
-                      fake_update_response_.GetUpdateResponse(),
-                      -1,
-                      false,  // ping_only
-                      ErrorCode::kOmahaUpdateIgnoredOverCellular,
-                      metrics::CheckResult::kUpdateAvailable,
-                      metrics::CheckReaction::kIgnored,
-                      metrics::DownloadErrorCode::kUnset,
-                      &response,
-                      nullptr));
-  EXPECT_FALSE(response.update_exists);
-}
-
-TEST_F(OmahaRequestActionTest,
-       ValidUpdateOverCellularAllowedByUpdateTargetMatch) {
-  // This test tests that, when device policy is not set and permission for
-  // update over cellular is set to false or does not exist, update over
-  // cellular is allowed as update target matches the omaha response.
-  OmahaResponse response;
-  MockConnectionManager mock_cm;
-  // A version same as the version in omaha response.
-  string new_version = fake_update_response_.version;
-  // A size same as the size in omaha response.
-  int64_t new_size = fake_update_response_.size;
-
-  fake_prefs_.SetString(kPrefsUpdateOverCellularTargetVersion, new_version);
-  fake_prefs_.SetInt64(kPrefsUpdateOverCellularTargetSize, new_size);
-  fake_system_state_.set_connection_manager(&mock_cm);
-
-  EXPECT_CALL(mock_cm, GetConnectionProperties(_, _))
-      .WillRepeatedly(
-          DoAll(SetArgPointee<0>(ConnectionType::kCellular),
-                SetArgPointee<1>(ConnectionTethering::kUnknown),
-                Return(true)));
-  EXPECT_CALL(mock_cm, IsAllowedConnectionTypesForUpdateSet())
-      .WillRepeatedly(Return(false));
-  EXPECT_CALL(mock_cm, IsUpdateAllowedOver(ConnectionType::kCellular, _))
-      .WillRepeatedly(Return(true));
-
-  ASSERT_TRUE(
-      TestUpdateCheck(nullptr,  // request_params
-                      fake_update_response_.GetUpdateResponse(),
-                      -1,
-                      false,  // ping_only
-                      ErrorCode::kSuccess,
-                      metrics::CheckResult::kUpdateAvailable,
-                      metrics::CheckReaction::kUpdating,
-                      metrics::DownloadErrorCode::kUnset,
-                      &response,
-                      nullptr));
-  EXPECT_TRUE(response.update_exists);
-}
-
 TEST_F(OmahaRequestActionTest, ValidUpdateBlockedByRollback) {
   string rollback_version = "1234.0.0";
   OmahaResponse response;
@@ -728,18 +812,19 @@
 TEST_F(OmahaRequestActionTest, SkipNonCriticalUpdatesBeforeOOBE) {
   OmahaResponse response;
 
+  // TODO set better default value for metrics::checkresult in
+  // OmahaRequestAction::ActionCompleted.
   fake_system_state_.fake_hardware()->UnsetIsOOBEComplete();
-  ASSERT_FALSE(
-      TestUpdateCheck(nullptr,  // request_params
-                      fake_update_response_.GetUpdateResponse(),
-                      -1,
-                      false,  // ping_only
-                      ErrorCode::kNonCriticalUpdateInOOBE,
-                      metrics::CheckResult::kUnset,
-                      metrics::CheckReaction::kUnset,
-                      metrics::DownloadErrorCode::kUnset,
-                      &response,
-                      nullptr));
+  ASSERT_FALSE(TestUpdateCheck(nullptr,  // request_params
+                               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);
 
   // The IsOOBEComplete() value is ignored when the OOBE flow is not enabled.
@@ -1093,6 +1178,37 @@
   EXPECT_EQ(fake_update_response_.cohortname, value);
 }
 
+TEST_F(OmahaRequestActionTest, MultiAppCohortTest) {
+  OmahaResponse response;
+  OmahaRequestParams params = request_params_;
+  fake_update_response_.multi_app = true;
+  fake_update_response_.include_cohorts = true;
+  fake_update_response_.cohort = "s/154454/8479665";
+  fake_update_response_.cohorthint = "please-put-me-on-beta";
+  fake_update_response_.cohortname = "stable";
+
+  ASSERT_TRUE(TestUpdateCheck(&params,
+                              fake_update_response_.GetUpdateResponse(),
+                              -1,
+                              false,  // ping_only
+                              ErrorCode::kSuccess,
+                              metrics::CheckResult::kUpdateAvailable,
+                              metrics::CheckReaction::kUpdating,
+                              metrics::DownloadErrorCode::kUnset,
+                              &response,
+                              nullptr));
+
+  string value;
+  EXPECT_TRUE(fake_prefs_.GetString(kPrefsOmahaCohort, &value));
+  EXPECT_EQ(fake_update_response_.cohort, value);
+
+  EXPECT_TRUE(fake_prefs_.GetString(kPrefsOmahaCohortHint, &value));
+  EXPECT_EQ(fake_update_response_.cohorthint, value);
+
+  EXPECT_TRUE(fake_prefs_.GetString(kPrefsOmahaCohortName, &value));
+  EXPECT_EQ(fake_update_response_.cohortname, value);
+}
+
 TEST_F(OmahaRequestActionTest, NoOutputPipeTest) {
   const string http_response(fake_update_response_.GetNoUpdateResponse());
 
@@ -1217,18 +1333,21 @@
   string input_response =
       "<?xml version=\"1.0\" encoding=\"UTF-8\"?><response protocol=\"3.0\">"
       "<daystart elapsed_seconds=\"100\"/>"
-      "<app appid=\"xyz\" status=\"ok\">"
+      // the appid needs to match that in the request params
+      "<app appid=\"" +
+      fake_update_response_.app_id +
+      "\" status=\"ok\">"
       "<updatecheck status=\"ok\">"
       "<urls><url codebase=\"http://missing/field/test/\"/></urls>"
       "<manifest version=\"10.2.3.4\">"
       "<packages><package hash=\"not-used\" name=\"f\" "
-      "size=\"587\"/></packages>"
+      "size=\"587\" hash_sha256=\"lkq34j5345\"/></packages>"
       "<actions><action event=\"postinstall\" "
       "ChromeOSVersion=\"10.2.3.4\" "
       "Prompt=\"false\" "
       "IsDelta=\"true\" "
       "IsDeltaPayload=\"false\" "
-      "sha256=\"lkq34j5345\" "
+      "sha256=\"not-used\" "
       "needsadmin=\"true\" "
       "/></actions></manifest></updatecheck></app></response>";
   LOG(INFO) << "Input Response = " << input_response;
@@ -1246,10 +1365,11 @@
                               nullptr));
   EXPECT_TRUE(response.update_exists);
   EXPECT_EQ("10.2.3.4", response.version);
-  EXPECT_EQ("http://missing/field/test/f", response.payload_urls[0]);
+  EXPECT_EQ("http://missing/field/test/f",
+            response.packages[0].payload_urls[0]);
   EXPECT_EQ("", response.more_info_url);
-  EXPECT_EQ("lkq34j5345", response.hash);
-  EXPECT_EQ(587, response.size);
+  EXPECT_EQ("lkq34j5345", response.packages[0].hash);
+  EXPECT_EQ(587u, response.packages[0].size);
   EXPECT_FALSE(response.prompt);
   EXPECT_TRUE(response.deadline.empty());
 }
@@ -1384,15 +1504,16 @@
                       &response,
                       nullptr));
 
-  EXPECT_EQ(response.more_info_url, "testthe<url");
-  EXPECT_EQ(response.payload_urls[0], "testthe&codebase/file.signed");
-  EXPECT_EQ(response.deadline, "<20110101");
+  EXPECT_EQ("testthe<url", response.more_info_url);
+  EXPECT_EQ("testthe&codebase/file.signed",
+            response.packages[0].payload_urls[0]);
+  EXPECT_EQ("<20110101", response.deadline);
 }
 
 TEST_F(OmahaRequestActionTest, ParseIntTest) {
   OmahaResponse response;
   // overflows int32_t:
-  fake_update_response_.size = 123123123123123ll;
+  fake_update_response_.size = 123123123123123ull;
   ASSERT_TRUE(
       TestUpdateCheck(nullptr,  // request_params
                       fake_update_response_.GetUpdateResponse(),
@@ -1405,7 +1526,7 @@
                       &response,
                       nullptr));
 
-  EXPECT_EQ(response.size, 123123123123123ll);
+  EXPECT_EQ(fake_update_response_.size, response.packages[0].size);
 }
 
 TEST_F(OmahaRequestActionTest, FormatUpdateCheckOutputTest) {
@@ -1626,17 +1747,16 @@
   EXPECT_CALL(prefs, GetInt64(kPrefsLastRollCallPingDay, _))
       .WillOnce(DoAll(SetArgPointee<1>(five_days_ago), Return(true)));
   brillo::Blob post_data;
-  ASSERT_TRUE(
-      TestUpdateCheck(nullptr,  // request_params
-                      fake_update_response_.GetNoUpdateResponse(),
-                      -1,
-                      ping_only,
-                      ErrorCode::kSuccess,
-                      metrics::CheckResult::kUnset,
-                      metrics::CheckReaction::kUnset,
-                      metrics::DownloadErrorCode::kUnset,
-                      nullptr,
-                      &post_data));
+  ASSERT_TRUE(TestUpdateCheck(nullptr,  // request_params
+                              fake_update_response_.GetNoUpdateResponse(),
+                              -1,
+                              ping_only,
+                              ErrorCode::kSuccess,
+                              metrics::CheckResult::kNoUpdateAvailable,
+                              metrics::CheckReaction::kUnset,
+                              metrics::DownloadErrorCode::kUnset,
+                              nullptr,
+                              &post_data));
   string post_str(post_data.begin(), post_data.end());
   EXPECT_NE(post_str.find("<ping active=\"1\" a=\"6\" r=\"5\"></ping>"),
             string::npos);
@@ -1989,7 +2109,7 @@
   params.set_update_check_count_wait_enabled(false);
 
   Time arbitrary_date;
-  Time::FromString("6/4/1989", &arbitrary_date);
+  ASSERT_TRUE(Time::FromString("6/4/1989", &arbitrary_date));
   fake_system_state_.fake_clock()->SetWallclockTime(arbitrary_date);
   ASSERT_FALSE(TestUpdateCheck(&params,
                                fake_update_response_.GetUpdateResponse(),
@@ -2030,8 +2150,8 @@
   params.set_update_check_count_wait_enabled(false);
 
   Time t1, t2;
-  Time::FromString("1/1/2012", &t1);
-  Time::FromString("1/3/2012", &t2);
+  ASSERT_TRUE(Time::FromString("1/1/2012", &t1));
+  ASSERT_TRUE(Time::FromString("1/3/2012", &t2));
   ASSERT_TRUE(
       fake_prefs_.SetInt64(kPrefsUpdateFirstSeenAt, t1.ToInternalValue()));
   fake_system_state_.fake_clock()->SetWallclockTime(t2);
@@ -2061,7 +2181,7 @@
 
   brillo::Blob post_data;
   OmahaRequestParams params(&fake_system_state_);
-  params.set_root(tempdir.path().value());
+  params.set_root(tempdir.GetPath().value());
   params.set_app_id("{22222222-2222-2222-2222-222222222222}");
   params.set_app_version("1.2.3.4");
   params.set_current_channel("canary-channel");
@@ -2094,7 +2214,7 @@
 
   brillo::Blob post_data;
   OmahaRequestParams params(&fake_system_state_);
-  params.set_root(tempdir.path().value());
+  params.set_root(tempdir.GetPath().value());
   params.set_app_id("{11111111-1111-1111-1111-111111111111}");
   params.set_app_version("5.6.7.8");
   params.set_current_channel("stable-channel");
diff --git a/omaha_request_params.cc b/omaha_request_params.cc
index 3402451..3ba7037 100644
--- a/omaha_request_params.cc
+++ b/omaha_request_params.cc
@@ -77,7 +77,10 @@
   LOG(INFO) << "Running from channel " << image_props_.current_channel;
 
   os_platform_ = constants::kOmahaPlatformName;
-  os_version_ = OmahaRequestParams::kOsVersion;
+  if (!image_props_.system_version.empty())
+    os_version_ = image_props_.system_version;
+  else
+    os_version_ = OmahaRequestParams::kOsVersion;
   if (!in_app_version.empty())
     image_props_.version = in_app_version;
 
diff --git a/omaha_request_params.h b/omaha_request_params.h
index 3a28ed1..73edd6f 100644
--- a/omaha_request_params.h
+++ b/omaha_request_params.h
@@ -105,10 +105,15 @@
   inline std::string os_build_fingerprint() const {
     return image_props_.build_fingerprint;
   }
+  inline std::string os_build_type() const { return image_props_.build_type; }
   inline std::string board_app_id() const { return image_props_.product_id; }
   inline std::string canary_app_id() const {
     return image_props_.canary_product_id;
   }
+  inline std::string system_app_id() const { return image_props_.system_id; }
+  inline void set_system_app_id(const std::string& system_app_id) {
+    image_props_.system_id = system_app_id;
+  }
   inline void set_app_id(const std::string& app_id) {
     image_props_.product_id = app_id;
     image_props_.canary_product_id = app_id;
@@ -122,6 +127,12 @@
     image_props_.version = version;
   }
   inline std::string app_version() const { return image_props_.version; }
+  inline std::string system_version() const {
+    return image_props_.system_version;
+  }
+  inline std::string product_components() const {
+    return image_props_.product_components;
+  }
 
   inline std::string current_channel() const {
     return image_props_.current_channel;
diff --git a/omaha_request_params_unittest.cc b/omaha_request_params_unittest.cc
index 7d4dc2d..57ecf24 100644
--- a/omaha_request_params_unittest.cc
+++ b/omaha_request_params_unittest.cc
@@ -47,7 +47,7 @@
     // Create a fresh copy of the params for each test, so there's no
     // unintended reuse of state across tests.
     params_ = OmahaRequestParams(&fake_system_state_);
-    params_.set_root(tempdir_.path().value());
+    params_.set_root(tempdir_.GetPath().value());
     SetLockDown(false);
     fake_system_state_.set_prefs(&fake_prefs_);
   }
@@ -105,7 +105,8 @@
 }
 
 TEST_F(OmahaRequestParamsTest, NoDeltasTest) {
-  ASSERT_TRUE(WriteFileString(tempdir_.path().Append(".nodelta").value(), ""));
+  ASSERT_TRUE(
+      WriteFileString(tempdir_.GetPath().Append(".nodelta").value(), ""));
   EXPECT_TRUE(params_.Init("", "", false));
   EXPECT_FALSE(params_.delta_okay());
 }
@@ -113,12 +114,12 @@
 TEST_F(OmahaRequestParamsTest, SetTargetChannelTest) {
   {
     OmahaRequestParams params(&fake_system_state_);
-    params.set_root(tempdir_.path().value());
+    params.set_root(tempdir_.GetPath().value());
     EXPECT_TRUE(params.Init("", "", false));
     EXPECT_TRUE(params.SetTargetChannel("canary-channel", false, nullptr));
     EXPECT_FALSE(params.is_powerwash_allowed());
   }
-  params_.set_root(tempdir_.path().value());
+  params_.set_root(tempdir_.GetPath().value());
   EXPECT_TRUE(params_.Init("", "", false));
   EXPECT_EQ("canary-channel", params_.target_channel());
   EXPECT_FALSE(params_.is_powerwash_allowed());
@@ -127,12 +128,12 @@
 TEST_F(OmahaRequestParamsTest, SetIsPowerwashAllowedTest) {
   {
     OmahaRequestParams params(&fake_system_state_);
-    params.set_root(tempdir_.path().value());
+    params.set_root(tempdir_.GetPath().value());
     EXPECT_TRUE(params.Init("", "", false));
     EXPECT_TRUE(params.SetTargetChannel("canary-channel", true, nullptr));
     EXPECT_TRUE(params.is_powerwash_allowed());
   }
-  params_.set_root(tempdir_.path().value());
+  params_.set_root(tempdir_.GetPath().value());
   EXPECT_TRUE(params_.Init("", "", false));
   EXPECT_EQ("canary-channel", params_.target_channel());
   EXPECT_TRUE(params_.is_powerwash_allowed());
@@ -141,7 +142,7 @@
 TEST_F(OmahaRequestParamsTest, SetTargetChannelInvalidTest) {
   {
     OmahaRequestParams params(&fake_system_state_);
-    params.set_root(tempdir_.path().value());
+    params.set_root(tempdir_.GetPath().value());
     SetLockDown(true);
     EXPECT_TRUE(params.Init("", "", false));
     string error_message;
@@ -151,7 +152,7 @@
     EXPECT_NE(string::npos, error_message.find("stable-channel"));
     EXPECT_FALSE(params.is_powerwash_allowed());
   }
-  params_.set_root(tempdir_.path().value());
+  params_.set_root(tempdir_.GetPath().value());
   EXPECT_TRUE(params_.Init("", "", false));
   EXPECT_EQ("stable-channel", params_.target_channel());
   EXPECT_FALSE(params_.is_powerwash_allowed());
diff --git a/omaha_response.h b/omaha_response.h
index 60ec4ac..b973eb5 100644
--- a/omaha_response.h
+++ b/omaha_response.h
@@ -37,17 +37,24 @@
 
   // These are only valid if update_exists is true:
   std::string version;
+  std::string system_version;
 
-  // The ordered list of URLs in the Omaha response. Each item is a complete
-  // URL (i.e. in terms of Omaha XML, each value is a urlBase + packageName)
-  std::vector<std::string> payload_urls;
+  struct Package {
+    // The ordered list of URLs in the Omaha response. Each item is a complete
+    // URL (i.e. in terms of Omaha XML, each value is a urlBase + packageName)
+    std::vector<std::string> payload_urls;
+    uint64_t size = 0;
+    uint64_t metadata_size = 0;
+    std::string metadata_signature;
+    std::string hash;
+    // True if the payload described in this response is a delta payload.
+    // False if it's a full payload.
+    bool is_delta = false;
+  };
+  std::vector<Package> packages;
 
   std::string more_info_url;
-  std::string hash;
-  std::string metadata_signature;
   std::string deadline;
-  off_t size = 0;
-  off_t metadata_size = 0;
   int max_days_to_scatter = 0;
   // The number of URL-related failures to tolerate before moving on to the
   // next URL in the current pass. This is a configurable value from the
@@ -55,10 +62,6 @@
   uint32_t max_failure_count_per_url = 0;
   bool prompt = false;
 
-  // True if the payload described in this response is a delta payload.
-  // False if it's a full payload.
-  bool is_delta_payload = false;
-
   // True if the Omaha rule instructs us to disable the back-off logic
   // on the client altogether. False otherwise.
   bool disable_payload_backoff = false;
diff --git a/omaha_response_handler_action.cc b/omaha_response_handler_action.cc
index 33380d7..9c5fb4a 100644
--- a/omaha_response_handler_action.cc
+++ b/omaha_response_handler_action.cc
@@ -19,6 +19,7 @@
 #include <string>
 
 #include <base/logging.h>
+#include <base/strings/string_number_conversions.h>
 #include <base/strings/string_util.h>
 #include <policy/device_policy.h>
 
@@ -30,7 +31,11 @@
 #include "update_engine/omaha_request_params.h"
 #include "update_engine/payload_consumer/delta_performer.h"
 #include "update_engine/payload_state_interface.h"
+#include "update_engine/update_manager/policy.h"
+#include "update_engine/update_manager/update_manager.h"
 
+using chromeos_update_manager::Policy;
+using chromeos_update_manager::UpdateManager;
 using std::string;
 
 namespace chromeos_update_engine {
@@ -68,8 +73,10 @@
     return;
   }
 
+  // This is the url to the first package, not all packages.
   install_plan_.download_url = current_url;
   install_plan_.version = response.version;
+  install_plan_.system_version = response.system_version;
 
   OmahaRequestParams* const params = system_state_->request_params();
   PayloadStateInterface* const payload_state = system_state_->payload_state();
@@ -85,28 +92,40 @@
   }
 
   // Fill up the other properties based on the response.
-  install_plan_.payload_size = response.size;
-  install_plan_.payload_hash = response.hash;
-  install_plan_.metadata_size = response.metadata_size;
-  install_plan_.metadata_signature = response.metadata_signature;
+  string update_check_response_hash;
+  for (const auto& package : response.packages) {
+    brillo::Blob raw_hash;
+    if (!base::HexStringToBytes(package.hash, &raw_hash)) {
+      LOG(ERROR) << "Failed to convert payload hash from hex string to bytes: "
+                 << package.hash;
+      completer.set_code(ErrorCode::kOmahaResponseInvalid);
+      return;
+    }
+    install_plan_.payloads.push_back(
+        {.size = package.size,
+         .metadata_size = package.metadata_size,
+         .metadata_signature = package.metadata_signature,
+         .hash = raw_hash,
+         .type = package.is_delta ? InstallPayloadType::kDelta
+                                  : InstallPayloadType::kFull});
+    update_check_response_hash += package.hash + ":";
+  }
   install_plan_.public_key_rsa = response.public_key_rsa;
   install_plan_.hash_checks_mandatory = AreHashChecksMandatory(response);
-  install_plan_.is_resume =
-      DeltaPerformer::CanResumeUpdate(system_state_->prefs(), response.hash);
+  install_plan_.is_resume = DeltaPerformer::CanResumeUpdate(
+      system_state_->prefs(), update_check_response_hash);
   if (install_plan_.is_resume) {
     payload_state->UpdateResumed();
   } else {
     payload_state->UpdateRestarted();
-    LOG_IF(WARNING, !DeltaPerformer::ResetUpdateProgress(
-        system_state_->prefs(), false))
+    LOG_IF(WARNING,
+           !DeltaPerformer::ResetUpdateProgress(system_state_->prefs(), false))
         << "Unable to reset the update progress.";
-    LOG_IF(WARNING, !system_state_->prefs()->SetString(
-        kPrefsUpdateCheckResponseHash, response.hash))
+    LOG_IF(WARNING,
+           !system_state_->prefs()->SetString(kPrefsUpdateCheckResponseHash,
+                                              update_check_response_hash))
         << "Unable to save the update check response hash.";
   }
-  install_plan_.payload_type = response.is_delta_payload
-                                   ? InstallPayloadType::kDelta
-                                   : InstallPayloadType::kFull;
 
   install_plan_.source_slot = system_state_->boot_control()->GetCurrentSlot();
   install_plan_.target_slot = install_plan_.source_slot == 0 ? 1 : 0;
@@ -143,7 +162,14 @@
     chmod(deadline_file_.c_str(), S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
   }
 
-  completer.set_code(ErrorCode::kSuccess);
+  // Check the generated install-plan with the Policy to confirm that
+  // it can be applied at this time (or at all).
+  UpdateManager* const update_manager = system_state_->update_manager();
+  CHECK(update_manager);
+  auto ec = ErrorCode::kSuccess;
+  update_manager->PolicyRequest(
+      &Policy::UpdateCanBeApplied, &ec, &install_plan_);
+  completer.set_code(ec);
 }
 
 bool OmahaResponseHandlerAction::AreHashChecksMandatory(
@@ -193,12 +219,14 @@
   // mandatory because we could be downloading the payload from any URL later
   // on. It's really hard to do book-keeping based on each byte being
   // downloaded to see whether we only used HTTPS throughout.
-  for (size_t i = 0; i < response.payload_urls.size(); i++) {
-    if (!base::StartsWith(response.payload_urls[i], "https://",
-                          base::CompareCase::INSENSITIVE_ASCII)) {
-      LOG(INFO) << "Mandating payload hash checks since Omaha response "
-                << "contains non-HTTPS URL(s)";
-      return true;
+  for (const auto& package : response.packages) {
+    for (const string& payload_url : package.payload_urls) {
+      if (!base::StartsWith(
+              payload_url, "https://", base::CompareCase::INSENSITIVE_ASCII)) {
+        LOG(INFO) << "Mandating payload hash checks since Omaha response "
+                  << "contains non-HTTPS URL(s)";
+        return true;
+      }
     }
   }
 
diff --git a/omaha_response_handler_action.h b/omaha_response_handler_action.h
index 51dfa7a..2974841 100644
--- a/omaha_response_handler_action.h
+++ b/omaha_response_handler_action.h
@@ -89,6 +89,7 @@
   friend class OmahaResponseHandlerActionTest;
 
   FRIEND_TEST(UpdateAttempterTest, CreatePendingErrorEventResumedTest);
+  FRIEND_TEST(UpdateAttempterTest, UpdateDeferredByPolicyTest);
 
   DISALLOW_COPY_AND_ASSIGN(OmahaResponseHandlerAction);
 };
diff --git a/omaha_response_handler_action_unittest.cc b/omaha_response_handler_action_unittest.cc
index 60b139b..568d11d 100644
--- a/omaha_response_handler_action_unittest.cc
+++ b/omaha_response_handler_action_unittest.cc
@@ -16,10 +16,12 @@
 
 #include "update_engine/omaha_response_handler_action.h"
 
+#include <memory>
 #include <string>
 
 #include <base/files/file_util.h>
 #include <base/files/scoped_temp_dir.h>
+#include <brillo/message_loops/fake_message_loop.h>
 #include <gtest/gtest.h>
 
 #include "update_engine/common/constants.h"
@@ -29,12 +31,18 @@
 #include "update_engine/fake_system_state.h"
 #include "update_engine/mock_payload_state.h"
 #include "update_engine/payload_consumer/payload_constants.h"
+#include "update_engine/update_manager/mock_policy.h"
 
 using chromeos_update_engine::test_utils::System;
 using chromeos_update_engine::test_utils::WriteFileString;
+using chromeos_update_manager::EvalStatus;
+using chromeos_update_manager::FakeUpdateManager;
+using chromeos_update_manager::MockPolicy;
 using std::string;
-using testing::Return;
 using testing::_;
+using testing::DoAll;
+using testing::Return;
+using testing::SetArgPointee;
 
 namespace chromeos_update_engine {
 
@@ -58,15 +66,23 @@
               const string& deadline_file,
               InstallPlan* out);
 
+  // Pointer to the Action, valid after |DoTest|, released when the test is
+  // finished.
+  std::unique_ptr<OmahaResponseHandlerAction> action_;
+  // Captures the action's result code, for tests that need to directly verify
+  // it in non-success cases.
+  ErrorCode action_result_code_;
+
   FakeSystemState fake_system_state_;
+  // "Hash+"
+  const brillo::Blob expected_hash_ = {0x48, 0x61, 0x73, 0x68, 0x2b};
 };
 
 class OmahaResponseHandlerActionProcessorDelegate
     : public ActionProcessorDelegate {
  public:
   OmahaResponseHandlerActionProcessorDelegate()
-      : code_(ErrorCode::kError),
-        code_set_(false) {}
+      : code_(ErrorCode::kError), code_set_(false) {}
   void ActionCompleted(ActionProcessor* processor,
                        AbstractAction* action,
                        ErrorCode code) {
@@ -90,12 +106,14 @@
     "very_long_name_and_no_slashes-very_long_name_and_no_slashes"
     "-the_update_a.b.c.d_DELTA_.tgz";
 const char* const kBadVersion = "don't update me";
+const char* const kPayloadHashHex = "486173682b";
 }  // namespace
 
-bool OmahaResponseHandlerActionTest::DoTest(
-    const OmahaResponse& in,
-    const string& test_deadline_file,
-    InstallPlan* out) {
+bool OmahaResponseHandlerActionTest::DoTest(const OmahaResponse& in,
+                                            const string& test_deadline_file,
+                                            InstallPlan* out) {
+  brillo::FakeMessageLoop loop(nullptr);
+  loop.SetAsCurrent();
   ActionProcessor processor;
   OmahaResponseHandlerActionProcessorDelegate delegate;
   processor.set_delegate(&delegate);
@@ -103,8 +121,11 @@
   ObjectFeederAction<OmahaResponse> feeder_action;
   feeder_action.set_obj(in);
   if (in.update_exists && in.version != kBadVersion) {
+    string expected_hash;
+    for (const auto& package : in.packages)
+      expected_hash += package.hash + ":";
     EXPECT_CALL(*(fake_system_state_.mock_prefs()),
-                SetString(kPrefsUpdateCheckResponseHash, in.hash))
+                SetString(kPrefsUpdateCheckResponseHash, expected_hash))
         .WillOnce(Return(true));
 
     int slot = 1 - fake_system_state_.fake_boot_control()->GetCurrentSlot();
@@ -113,19 +134,19 @@
         .WillOnce(Return(true));
   }
 
-  string current_url = in.payload_urls.size() ? in.payload_urls[0] : "";
+  string current_url = in.packages.size() ? in.packages[0].payload_urls[0] : "";
   EXPECT_CALL(*(fake_system_state_.mock_payload_state()), GetCurrentUrl())
       .WillRepeatedly(Return(current_url));
 
-  OmahaResponseHandlerAction response_handler_action(
+  action_.reset(new OmahaResponseHandlerAction(
       &fake_system_state_,
-      (test_deadline_file.empty() ?
-       constants::kOmahaResponseDeadlineFile : test_deadline_file));
-  BondActions(&feeder_action, &response_handler_action);
+      (test_deadline_file.empty() ? constants::kOmahaResponseDeadlineFile
+                                  : test_deadline_file)));
+  BondActions(&feeder_action, action_.get());
   ObjectCollectorAction<InstallPlan> collector_action;
-  BondActions(&response_handler_action, &collector_action);
+  BondActions(action_.get(), &collector_action);
   processor.EnqueueAction(&feeder_action);
-  processor.EnqueueAction(&response_handler_action);
+  processor.EnqueueAction(action_.get());
   processor.EnqueueAction(&collector_action);
   processor.StartProcessing();
   EXPECT_TRUE(!processor.IsRunning())
@@ -133,29 +154,31 @@
   if (out)
     *out = collector_action.object();
   EXPECT_TRUE(delegate.code_set_);
+  action_result_code_ = delegate.code_;
   return delegate.code_ == ErrorCode::kSuccess;
 }
 
 TEST_F(OmahaResponseHandlerActionTest, SimpleTest) {
   string test_deadline_file;
-  CHECK(utils::MakeTempFile(
-          "omaha_response_handler_action_unittest-XXXXXX",
-          &test_deadline_file, nullptr));
+  CHECK(utils::MakeTempFile("omaha_response_handler_action_unittest-XXXXXX",
+                            &test_deadline_file,
+                            nullptr));
   ScopedPathUnlinker deadline_unlinker(test_deadline_file);
   {
     OmahaResponse in;
     in.update_exists = true;
     in.version = "a.b.c.d";
-    in.payload_urls.push_back("http://foo/the_update_a.b.c.d.tgz");
+    in.packages.push_back(
+        {.payload_urls = {"http://foo/the_update_a.b.c.d.tgz"},
+         .size = 12,
+         .hash = kPayloadHashHex});
     in.more_info_url = "http://more/info";
-    in.hash = "HASH+";
-    in.size = 12;
     in.prompt = false;
     in.deadline = "20101020";
     InstallPlan install_plan;
     EXPECT_TRUE(DoTest(in, test_deadline_file, &install_plan));
-    EXPECT_EQ(in.payload_urls[0], install_plan.download_url);
-    EXPECT_EQ(in.hash, install_plan.payload_hash);
+    EXPECT_EQ(in.packages[0].payload_urls[0], install_plan.download_url);
+    EXPECT_EQ(expected_hash_, install_plan.payloads[0].hash);
     EXPECT_EQ(1U, install_plan.target_slot);
     string deadline;
     EXPECT_TRUE(utils::ReadFile(test_deadline_file, &deadline));
@@ -171,17 +194,18 @@
     OmahaResponse in;
     in.update_exists = true;
     in.version = "a.b.c.d";
-    in.payload_urls.push_back("http://foo/the_update_a.b.c.d.tgz");
+    in.packages.push_back(
+        {.payload_urls = {"http://foo/the_update_a.b.c.d.tgz"},
+         .size = 12,
+         .hash = kPayloadHashHex});
     in.more_info_url = "http://more/info";
-    in.hash = "HASHj+";
-    in.size = 12;
     in.prompt = true;
     InstallPlan install_plan;
     // Set the other slot as current.
     fake_system_state_.fake_boot_control()->SetCurrentSlot(1);
     EXPECT_TRUE(DoTest(in, test_deadline_file, &install_plan));
-    EXPECT_EQ(in.payload_urls[0], install_plan.download_url);
-    EXPECT_EQ(in.hash, install_plan.payload_hash);
+    EXPECT_EQ(in.packages[0].payload_urls[0], install_plan.download_url);
+    EXPECT_EQ(expected_hash_, install_plan.payloads[0].hash);
     EXPECT_EQ(0U, install_plan.target_slot);
     string deadline;
     EXPECT_TRUE(utils::ReadFile(test_deadline_file, &deadline) &&
@@ -192,17 +216,16 @@
     OmahaResponse in;
     in.update_exists = true;
     in.version = "a.b.c.d";
-    in.payload_urls.push_back(kLongName);
+    in.packages.push_back(
+        {.payload_urls = {kLongName}, .size = 12, .hash = kPayloadHashHex});
     in.more_info_url = "http://more/info";
-    in.hash = "HASHj+";
-    in.size = 12;
     in.prompt = true;
     in.deadline = "some-deadline";
     InstallPlan install_plan;
     fake_system_state_.fake_boot_control()->SetCurrentSlot(0);
     EXPECT_TRUE(DoTest(in, test_deadline_file, &install_plan));
-    EXPECT_EQ(in.payload_urls[0], install_plan.download_url);
-    EXPECT_EQ(in.hash, install_plan.payload_hash);
+    EXPECT_EQ(in.packages[0].payload_urls[0], install_plan.download_url);
+    EXPECT_EQ(expected_hash_, install_plan.payloads[0].hash);
     EXPECT_EQ(1U, install_plan.target_slot);
     string deadline;
     EXPECT_TRUE(utils::ReadFile(test_deadline_file, &deadline));
@@ -219,22 +242,45 @@
   EXPECT_TRUE(install_plan.partitions.empty());
 }
 
+TEST_F(OmahaResponseHandlerActionTest, MultiPackageTest) {
+  OmahaResponse in;
+  in.update_exists = true;
+  in.version = "a.b.c.d";
+  in.packages.push_back({.payload_urls = {"http://package/1"},
+                         .size = 1,
+                         .hash = kPayloadHashHex});
+  in.packages.push_back({.payload_urls = {"http://package/2"},
+                         .size = 2,
+                         .hash = kPayloadHashHex});
+  in.more_info_url = "http://more/info";
+  InstallPlan install_plan;
+  EXPECT_TRUE(DoTest(in, "", &install_plan));
+  EXPECT_EQ(in.packages[0].payload_urls[0], install_plan.download_url);
+  EXPECT_EQ(2u, install_plan.payloads.size());
+  EXPECT_EQ(in.packages[0].size, install_plan.payloads[0].size);
+  EXPECT_EQ(in.packages[1].size, install_plan.payloads[1].size);
+  EXPECT_EQ(expected_hash_, install_plan.payloads[0].hash);
+  EXPECT_EQ(expected_hash_, install_plan.payloads[1].hash);
+  EXPECT_EQ(in.version, install_plan.version);
+}
+
 TEST_F(OmahaResponseHandlerActionTest, HashChecksForHttpTest) {
   OmahaResponse in;
   in.update_exists = true;
   in.version = "a.b.c.d";
-  in.payload_urls.push_back("http://test.should/need/hash.checks.signed");
+  in.packages.push_back(
+      {.payload_urls = {"http://test.should/need/hash.checks.signed"},
+       .size = 12,
+       .hash = kPayloadHashHex});
   in.more_info_url = "http://more/info";
-  in.hash = "HASHj+";
-  in.size = 12;
   // Hash checks are always skipped for non-official update URLs.
   EXPECT_CALL(*(fake_system_state_.mock_request_params()),
               IsUpdateUrlOfficial())
       .WillRepeatedly(Return(true));
   InstallPlan install_plan;
   EXPECT_TRUE(DoTest(in, "", &install_plan));
-  EXPECT_EQ(in.payload_urls[0], install_plan.download_url);
-  EXPECT_EQ(in.hash, install_plan.payload_hash);
+  EXPECT_EQ(in.packages[0].payload_urls[0], install_plan.download_url);
+  EXPECT_EQ(expected_hash_, install_plan.payloads[0].hash);
   EXPECT_TRUE(install_plan.hash_checks_mandatory);
   EXPECT_EQ(in.version, install_plan.version);
 }
@@ -243,17 +289,18 @@
   OmahaResponse in;
   in.update_exists = true;
   in.version = "a.b.c.d";
-  in.payload_urls.push_back("http://url.normally/needs/hash.checks.signed");
+  in.packages.push_back(
+      {.payload_urls = {"http://url.normally/needs/hash.checks.signed"},
+       .size = 12,
+       .hash = kPayloadHashHex});
   in.more_info_url = "http://more/info";
-  in.hash = "HASHj+";
-  in.size = 12;
   EXPECT_CALL(*(fake_system_state_.mock_request_params()),
               IsUpdateUrlOfficial())
       .WillRepeatedly(Return(false));
   InstallPlan install_plan;
   EXPECT_TRUE(DoTest(in, "", &install_plan));
-  EXPECT_EQ(in.payload_urls[0], install_plan.download_url);
-  EXPECT_EQ(in.hash, install_plan.payload_hash);
+  EXPECT_EQ(in.packages[0].payload_urls[0], install_plan.download_url);
+  EXPECT_EQ(expected_hash_, install_plan.payloads[0].hash);
   EXPECT_FALSE(install_plan.hash_checks_mandatory);
   EXPECT_EQ(in.version, install_plan.version);
 }
@@ -264,18 +311,19 @@
   OmahaResponse in;
   in.update_exists = true;
   in.version = "a.b.c.d";
-  in.payload_urls.push_back("http://url.normally/needs/hash.checks.signed");
+  in.packages.push_back(
+      {.payload_urls = {"http://url.normally/needs/hash.checks.signed"},
+       .size = 12,
+       .hash = kPayloadHashHex});
   in.more_info_url = "http://more/info";
-  in.hash = "HASHj+";
-  in.size = 12;
   EXPECT_CALL(*(fake_system_state_.mock_request_params()),
               IsUpdateUrlOfficial())
       .WillRepeatedly(Return(true));
   fake_system_state_.fake_hardware()->SetIsOfficialBuild(false);
   InstallPlan install_plan;
   EXPECT_TRUE(DoTest(in, "", &install_plan));
-  EXPECT_EQ(in.payload_urls[0], install_plan.download_url);
-  EXPECT_EQ(in.hash, install_plan.payload_hash);
+  EXPECT_EQ(in.packages[0].payload_urls[0], install_plan.download_url);
+  EXPECT_EQ(expected_hash_, install_plan.payloads[0].hash);
   EXPECT_FALSE(install_plan.hash_checks_mandatory);
   EXPECT_EQ(in.version, install_plan.version);
 }
@@ -284,17 +332,18 @@
   OmahaResponse in;
   in.update_exists = true;
   in.version = "a.b.c.d";
-  in.payload_urls.push_back("https://test.should.not/need/hash.checks.signed");
+  in.packages.push_back(
+      {.payload_urls = {"https://test.should/need/hash.checks.signed"},
+       .size = 12,
+       .hash = kPayloadHashHex});
   in.more_info_url = "http://more/info";
-  in.hash = "HASHj+";
-  in.size = 12;
   EXPECT_CALL(*(fake_system_state_.mock_request_params()),
               IsUpdateUrlOfficial())
       .WillRepeatedly(Return(true));
   InstallPlan install_plan;
   EXPECT_TRUE(DoTest(in, "", &install_plan));
-  EXPECT_EQ(in.payload_urls[0], install_plan.download_url);
-  EXPECT_EQ(in.hash, install_plan.payload_hash);
+  EXPECT_EQ(in.packages[0].payload_urls[0], install_plan.download_url);
+  EXPECT_EQ(expected_hash_, install_plan.payloads[0].hash);
   EXPECT_FALSE(install_plan.hash_checks_mandatory);
   EXPECT_EQ(in.version, install_plan.version);
 }
@@ -303,18 +352,19 @@
   OmahaResponse in;
   in.update_exists = true;
   in.version = "a.b.c.d";
-  in.payload_urls.push_back("http://test.should.still/need/hash.checks");
-  in.payload_urls.push_back("https://test.should.still/need/hash.checks");
+  in.packages.push_back(
+      {.payload_urls = {"http://test.should.still/need/hash.checks",
+                        "https://test.should.still/need/hash.checks"},
+       .size = 12,
+       .hash = kPayloadHashHex});
   in.more_info_url = "http://more/info";
-  in.hash = "HASHj+";
-  in.size = 12;
   EXPECT_CALL(*(fake_system_state_.mock_request_params()),
               IsUpdateUrlOfficial())
       .WillRepeatedly(Return(true));
   InstallPlan install_plan;
   EXPECT_TRUE(DoTest(in, "", &install_plan));
-  EXPECT_EQ(in.payload_urls[0], install_plan.download_url);
-  EXPECT_EQ(in.hash, install_plan.payload_hash);
+  EXPECT_EQ(in.packages[0].payload_urls[0], install_plan.download_url);
+  EXPECT_EQ(expected_hash_, install_plan.payloads[0].hash);
   EXPECT_TRUE(install_plan.hash_checks_mandatory);
   EXPECT_EQ(in.version, install_plan.version);
 }
@@ -323,10 +373,10 @@
   OmahaResponse in;
   in.update_exists = true;
   in.version = "a.b.c.d";
-  in.payload_urls.push_back("https://MoreStableChannelTest");
+  in.packages.push_back({.payload_urls = {"https://MoreStableChannelTest"},
+                         .size = 1,
+                         .hash = kPayloadHashHex});
   in.more_info_url = "http://more/info";
-  in.hash = "HASHjk";
-  in.size = 15;
 
   // Create a uniquely named test directory.
   base::ScopedTempDir tempdir;
@@ -334,7 +384,7 @@
 
   OmahaRequestParams params(&fake_system_state_);
   fake_system_state_.fake_hardware()->SetIsOfficialBuild(false);
-  params.set_root(tempdir.path().value());
+  params.set_root(tempdir.GetPath().value());
   params.set_current_channel("canary-channel");
   // The ImageProperties in Android uses prefs to store MutableImageProperties.
 #ifdef __ANDROID__
@@ -358,10 +408,10 @@
   OmahaResponse in;
   in.update_exists = true;
   in.version = "a.b.c.d";
-  in.payload_urls.push_back("https://LessStableChannelTest");
+  in.packages.push_back({.payload_urls = {"https://LessStableChannelTest"},
+                         .size = 15,
+                         .hash = kPayloadHashHex});
   in.more_info_url = "http://more/info";
-  in.hash = "HASHjk";
-  in.size = 15;
 
   // Create a uniquely named test directory.
   base::ScopedTempDir tempdir;
@@ -369,7 +419,7 @@
 
   OmahaRequestParams params(&fake_system_state_);
   fake_system_state_.fake_hardware()->SetIsOfficialBuild(false);
-  params.set_root(tempdir.path().value());
+  params.set_root(tempdir.GetPath().value());
   params.set_current_channel("stable-channel");
   // The ImageProperties in Android uses prefs to store MutableImageProperties.
 #ifdef __ANDROID__
@@ -393,10 +443,11 @@
   OmahaResponse in;
   in.update_exists = true;
   in.version = "a.b.c.d";
-  in.payload_urls.push_back("https://would.not/cause/hash/checks");
+  in.packages.push_back(
+      {.payload_urls = {"https://would.not/cause/hash/checks"},
+       .size = 12,
+       .hash = kPayloadHashHex});
   in.more_info_url = "http://more/info";
-  in.hash = "HASHj+";
-  in.size = 12;
 
   OmahaRequestParams params(&fake_system_state_);
   // We're using a real OmahaRequestParams object here so we can't mock
@@ -412,13 +463,70 @@
   EXPECT_CALL(*fake_system_state_.mock_payload_state(), GetP2PUrl())
       .WillRepeatedly(Return(p2p_url));
   EXPECT_CALL(*fake_system_state_.mock_payload_state(),
-              GetUsingP2PForDownloading()).WillRepeatedly(Return(true));
+              GetUsingP2PForDownloading())
+      .WillRepeatedly(Return(true));
 
   InstallPlan install_plan;
   EXPECT_TRUE(DoTest(in, "", &install_plan));
-  EXPECT_EQ(in.hash, install_plan.payload_hash);
-  EXPECT_EQ(install_plan.download_url, p2p_url);
+  EXPECT_EQ(expected_hash_, install_plan.payloads[0].hash);
+  EXPECT_EQ(p2p_url, install_plan.download_url);
   EXPECT_TRUE(install_plan.hash_checks_mandatory);
 }
 
+TEST_F(OmahaResponseHandlerActionTest, SystemVersionTest) {
+  OmahaResponse in;
+  in.update_exists = true;
+  in.version = "a.b.c.d";
+  in.system_version = "b.c.d.e";
+  in.packages.push_back({.payload_urls = {"http://package/1"},
+                         .size = 1,
+                         .hash = kPayloadHashHex});
+  in.packages.push_back({.payload_urls = {"http://package/2"},
+                         .size = 2,
+                         .hash = kPayloadHashHex});
+  in.more_info_url = "http://more/info";
+  InstallPlan install_plan;
+  EXPECT_TRUE(DoTest(in, "", &install_plan));
+  EXPECT_EQ(in.packages[0].payload_urls[0], install_plan.download_url);
+  EXPECT_EQ(2u, install_plan.payloads.size());
+  EXPECT_EQ(in.packages[0].size, install_plan.payloads[0].size);
+  EXPECT_EQ(in.packages[1].size, install_plan.payloads[1].size);
+  EXPECT_EQ(expected_hash_, install_plan.payloads[0].hash);
+  EXPECT_EQ(expected_hash_, install_plan.payloads[1].hash);
+  EXPECT_EQ(in.version, install_plan.version);
+  EXPECT_EQ(in.system_version, install_plan.system_version);
+}
+
+TEST_F(OmahaResponseHandlerActionTest, TestDeferredByPolicy) {
+  OmahaResponse in;
+  in.update_exists = true;
+  in.version = "a.b.c.d";
+  in.packages.push_back({.payload_urls = {"http://foo/the_update_a.b.c.d.tgz"},
+                         .size = 12,
+                         .hash = kPayloadHashHex});
+  // Setup the UpdateManager to disallow the update.
+  FakeClock fake_clock;
+  MockPolicy* mock_policy = new MockPolicy(&fake_clock);
+  FakeUpdateManager* fake_update_manager =
+      fake_system_state_.fake_update_manager();
+  fake_update_manager->set_policy(mock_policy);
+  EXPECT_CALL(*mock_policy, UpdateCanBeApplied(_, _, _, _, _))
+      .WillOnce(
+          DoAll(SetArgPointee<3>(ErrorCode::kOmahaUpdateDeferredPerPolicy),
+                Return(EvalStatus::kSucceeded)));
+  // Perform the Action. It should "fail" with kOmahaUpdateDeferredPerPolicy.
+  InstallPlan install_plan;
+  EXPECT_FALSE(DoTest(in, "", &install_plan));
+  EXPECT_EQ(ErrorCode::kOmahaUpdateDeferredPerPolicy, action_result_code_);
+  // Verify that DoTest() didn't set the output install plan.
+  EXPECT_EQ("", install_plan.version);
+  // Copy the underlying InstallPlan from the Action (like a real Delegate).
+  install_plan = action_->install_plan();
+  // Now verify the InstallPlan that was generated.
+  EXPECT_EQ(in.packages[0].payload_urls[0], install_plan.download_url);
+  EXPECT_EQ(expected_hash_, install_plan.payloads[0].hash);
+  EXPECT_EQ(1U, install_plan.target_slot);
+  EXPECT_EQ(in.version, install_plan.version);
+}
+
 }  // namespace chromeos_update_engine
diff --git a/parcelable_update_engine_status.cc b/parcelable_update_engine_status.cc
index d8eb6db..8a2dbeb 100644
--- a/parcelable_update_engine_status.cc
+++ b/parcelable_update_engine_status.cc
@@ -15,12 +15,27 @@
 //
 
 #include "update_engine/parcelable_update_engine_status.h"
+#include "update_engine/update_status_utils.h"
 
 #include <binder/Parcel.h>
 
+using update_engine::UpdateEngineStatus;
+
 namespace android {
 namespace brillo {
 
+ParcelableUpdateEngineStatus::ParcelableUpdateEngineStatus(
+    const UpdateEngineStatus& status)
+    : last_checked_time_(status.last_checked_time),
+      current_operation_(
+          chromeos_update_engine::UpdateStatusToString(status.status)),
+      progress_(status.progress),
+      current_version_(String16{status.current_version.c_str()}),
+      current_system_version_(String16{status.current_system_version.c_str()}),
+      new_size_(status.new_size_bytes),
+      new_version_(String16{status.new_version.c_str()}),
+      new_system_version_(String16{status.new_system_version.c_str()}) {}
+
 status_t ParcelableUpdateEngineStatus::writeToParcel(Parcel* parcel) const {
   status_t status;
 
@@ -29,12 +44,27 @@
     return status;
   }
 
+  status = parcel->writeString16(current_operation_);
+  if (status != OK) {
+    return status;
+  }
+
   status = parcel->writeDouble(progress_);
   if (status != OK) {
     return status;
   }
 
-  status = parcel->writeString16(current_operation_);
+  status = parcel->writeString16(current_version_);
+  if (status != OK) {
+    return status;
+  }
+
+  status = parcel->writeString16(current_system_version_);
+  if (status != OK) {
+    return status;
+  }
+
+  status = parcel->writeInt64(new_size_);
   if (status != OK) {
     return status;
   }
@@ -44,7 +74,7 @@
     return status;
   }
 
-  return parcel->writeInt64(new_size_);
+  return parcel->writeString16(new_system_version_);
 }
 
 status_t ParcelableUpdateEngineStatus::readFromParcel(const Parcel* parcel) {
@@ -55,12 +85,27 @@
     return status;
   }
 
+  status = parcel->readString16(&current_operation_);
+  if (status != OK) {
+    return status;
+  }
+
   status = parcel->readDouble(&progress_);
   if (status != OK) {
     return status;
   }
 
-  status = parcel->readString16(&current_operation_);
+  status = parcel->readString16(&current_version_);
+  if (status != OK) {
+    return status;
+  }
+
+  status = parcel->readString16(&current_system_version_);
+  if (status != OK) {
+    return status;
+  }
+
+  status = parcel->readInt64(&new_size_);
   if (status != OK) {
     return status;
   }
@@ -70,7 +115,7 @@
     return status;
   }
 
-  return parcel->readInt64(&new_size_);
+  return parcel->readString16(&new_system_version_);
 }
 
 }  // namespace brillo
diff --git a/parcelable_update_engine_status.h b/parcelable_update_engine_status.h
index 2cfedd9..82006e4 100644
--- a/parcelable_update_engine_status.h
+++ b/parcelable_update_engine_status.h
@@ -20,6 +20,8 @@
 #include <binder/Parcelable.h>
 #include <utils/String16.h>
 
+#include "update_engine/client_library/include/update_engine/update_status.h"
+
 namespace android {
 namespace brillo {
 
@@ -28,16 +30,31 @@
 class ParcelableUpdateEngineStatus : public Parcelable {
  public:
   ParcelableUpdateEngineStatus() = default;
+  explicit ParcelableUpdateEngineStatus(
+      const update_engine::UpdateEngineStatus& status);
   virtual ~ParcelableUpdateEngineStatus() = default;
 
   status_t writeToParcel(Parcel* parcel) const override;
   status_t readFromParcel(const Parcel* parcel) override;
 
+  // This list is kept in the Parcelable serialization order.
+
+  // When the update_engine last checked for updates (seconds since unix Epoch)
   int64_t last_checked_time_;
-  double progress_;
+  // The current status/operation of the update_engine.
   android::String16 current_operation_;
-  android::String16 new_version_;
+  // The current progress (0.0f-1.0f).
+  double progress_;
+  // The current product version.
+  android::String16 current_version_;
+  // The current system version.
+  android::String16 current_system_version_;
+  // The size of the update (bytes).  This is int64_t for java compatibility.
   int64_t new_size_;
+  // The new product version.
+  android::String16 new_version_;
+  // The new system version, if there is one (empty, otherwise).
+  android::String16 new_system_version_;
 };
 
 }  // namespace brillo
diff --git a/parcelable_update_engine_status_unittest.cc b/parcelable_update_engine_status_unittest.cc
new file mode 100644
index 0000000..f4bd518
--- /dev/null
+++ b/parcelable_update_engine_status_unittest.cc
@@ -0,0 +1,92 @@
+//
+// Copyright (C) 2017 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/parcelable_update_engine_status.h"
+#include "update_engine/update_status_utils.h"
+
+#include <binder/Parcel.h>
+#include <gtest/gtest.h>
+
+using android::Parcel;
+using android::String16;
+using android::brillo::ParcelableUpdateEngineStatus;
+using android::status_t;
+using update_engine::UpdateEngineStatus;
+using update_engine::UpdateStatus;
+
+TEST(ParcelableUpdateEngineStatusTest, TestCreationFromUpdateEngineStatus) {
+  // This test creates an object and verifies that all the UpdateEngineStatus
+  // values are properly reflected in the Parcelable version of the class.
+
+  UpdateEngineStatus ue_status = {123456789,
+                                  UpdateStatus::DOWNLOADING,
+                                  "0.1.2.3",
+                                  "1.2.3.4",
+                                  0.5f,
+                                  34567,
+                                  "2.3.4.5",
+                                  "3.4.5.6"};
+  ParcelableUpdateEngineStatus parcelable_status(ue_status);
+  EXPECT_EQ(ue_status.last_checked_time, parcelable_status.last_checked_time_);
+  EXPECT_EQ(
+      String16{chromeos_update_engine::UpdateStatusToString(ue_status.status)},
+      parcelable_status.current_operation_);
+  EXPECT_EQ(String16{ue_status.current_version.c_str()},
+            parcelable_status.current_version_);
+  EXPECT_EQ(String16{ue_status.current_system_version.c_str()},
+            parcelable_status.current_system_version_);
+  EXPECT_EQ(ue_status.progress, parcelable_status.progress_);
+  EXPECT_EQ(static_cast<int64_t>(ue_status.new_size_bytes),
+            parcelable_status.new_size_);
+  EXPECT_EQ(String16{ue_status.new_version.c_str()},
+            parcelable_status.new_version_);
+  EXPECT_EQ(String16{ue_status.new_system_version.c_str()},
+            parcelable_status.new_system_version_);
+}
+
+TEST(ParcelableUpdateEngineStatusTest, TestParceling) {
+  // This tests the writeToParcel and readFromParcel methods for being correctly
+  // matched.
+  UpdateEngineStatus ue_status = {123456789,
+                                  UpdateStatus::DOWNLOADING,
+                                  "0.1.2.3",
+                                  "1.2.3.4",
+                                  0.5f,
+                                  34567,
+                                  "2.3.4.5",
+                                  "3.4.5.6"};
+  ParcelableUpdateEngineStatus source_status(ue_status);
+  Parcel parcel_source, parcel_target;
+  status_t status = source_status.writeToParcel(&parcel_source);
+  EXPECT_EQ(::android::OK, status);
+  size_t parcel_len = parcel_source.dataSize();
+  status = parcel_target.setData(parcel_source.data(), parcel_len);
+  EXPECT_EQ(::android::OK, status);
+  ParcelableUpdateEngineStatus target_status;
+  status = target_status.readFromParcel(&parcel_target);
+  EXPECT_EQ(::android::OK, status);
+
+  EXPECT_EQ(source_status.last_checked_time_, target_status.last_checked_time_);
+  EXPECT_EQ(source_status.current_operation_, target_status.current_operation_);
+  EXPECT_EQ(source_status.current_version_, target_status.current_version_);
+  EXPECT_EQ(source_status.current_system_version_,
+            target_status.current_system_version_);
+  EXPECT_EQ(source_status.progress_, target_status.progress_);
+  EXPECT_EQ(source_status.new_size_, target_status.new_size_);
+  EXPECT_EQ(source_status.new_version_, target_status.new_version_);
+  EXPECT_EQ(source_status.new_system_version_,
+            target_status.new_system_version_);
+}
diff --git a/payload_consumer/cached_file_descriptor_unittest.cc b/payload_consumer/cached_file_descriptor_unittest.cc
index 3c13486..6a6302a 100644
--- a/payload_consumer/cached_file_descriptor_unittest.cc
+++ b/payload_consumer/cached_file_descriptor_unittest.cc
@@ -117,7 +117,7 @@
     size_t start = rand_r(&rand_seed) % blob_in.size();
     size_t size = rand_r(&rand_seed) % (blob_in.size() - start);
     std::fill_n(&blob_in[start], size, idx % 256);
-    EXPECT_EQ(cfd_->Seek(start, SEEK_SET), start);
+    EXPECT_EQ(cfd_->Seek(start, SEEK_SET), static_cast<off64_t>(start));
     Write(&blob_in[start], size);
   }
   EXPECT_TRUE(cfd_->Flush());
@@ -130,16 +130,19 @@
 TEST_F(CachedFileDescriptorTest, SeekTest) {
   EXPECT_EQ(cfd_->Seek(0, SEEK_SET), 0);
   EXPECT_EQ(cfd_->Seek(1, SEEK_SET), 1);
-  EXPECT_EQ(cfd_->Seek(kFileSize - 1, SEEK_SET), kFileSize - 1);
-  EXPECT_EQ(cfd_->Seek(kFileSize, SEEK_SET), kFileSize);
-  EXPECT_EQ(cfd_->Seek(kFileSize + 1, SEEK_SET), kFileSize + 1);
+  EXPECT_EQ(cfd_->Seek(kFileSize - 1, SEEK_SET),
+            static_cast<off64_t>(kFileSize - 1));
+  EXPECT_EQ(cfd_->Seek(kFileSize, SEEK_SET), static_cast<off64_t>(kFileSize));
+  EXPECT_EQ(cfd_->Seek(kFileSize + 1, SEEK_SET),
+            static_cast<off64_t>(kFileSize + 1));
 
   EXPECT_EQ(cfd_->Seek(0, SEEK_SET), 0);
   EXPECT_EQ(cfd_->Seek(1, SEEK_CUR), 1);
   EXPECT_EQ(cfd_->Seek(1, SEEK_CUR), 2);
-  EXPECT_EQ(cfd_->Seek(kFileSize - 1, SEEK_SET), kFileSize - 1);
-  EXPECT_EQ(cfd_->Seek(1, SEEK_CUR), kFileSize);
-  EXPECT_EQ(cfd_->Seek(1, SEEK_CUR), kFileSize + 1);
+  EXPECT_EQ(cfd_->Seek(kFileSize - 1, SEEK_SET),
+            static_cast<off64_t>(kFileSize - 1));
+  EXPECT_EQ(cfd_->Seek(1, SEEK_CUR), static_cast<off64_t>(kFileSize));
+  EXPECT_EQ(cfd_->Seek(1, SEEK_CUR), static_cast<off64_t>(kFileSize + 1));
 }
 
 TEST_F(CachedFileDescriptorTest, NoFlushTest) {
@@ -153,7 +156,7 @@
 }
 
 TEST_F(CachedFileDescriptorTest, CacheSizeWriteTest) {
-  size_t seek = 10;
+  off64_t seek = 10;
   brillo::Blob blob_in(kFileSize, 0);
   std::fill_n(&blob_in[seek], kCacheSize, value_);
   // We are writing exactly one cache size; Then it should be commited.
@@ -166,7 +169,7 @@
 }
 
 TEST_F(CachedFileDescriptorTest, UnderCacheSizeWriteTest) {
-  size_t seek = 100;
+  off64_t seek = 100;
   size_t less_than_cache_size = kCacheSize - 1;
   EXPECT_EQ(cfd_->Seek(seek, SEEK_SET), seek);
   brillo::Blob blob_in(kFileSize, 0);
@@ -182,7 +185,7 @@
 }
 
 TEST_F(CachedFileDescriptorTest, SeekAfterWriteTest) {
-  size_t seek = 100;
+  off64_t seek = 100;
   size_t less_than_cache_size = kCacheSize - 3;
   EXPECT_EQ(cfd_->Seek(seek, SEEK_SET), seek);
   brillo::Blob blob_in(kFileSize, 0);
diff --git a/payload_consumer/delta_performer.cc b/payload_consumer/delta_performer.cc
index 5ad4e16..001c84a 100644
--- a/payload_consumer/delta_performer.cc
+++ b/payload_consumer/delta_performer.cc
@@ -203,7 +203,7 @@
   }
 
   // Format download total count and percentage.
-  size_t payload_size = install_plan_->payload_size;
+  size_t payload_size = payload_->size;
   string payload_size_str("?");
   string downloaded_percentage_str("");
   if (payload_size) {
@@ -238,7 +238,7 @@
   // eliminated once we ensure that the payload_size in the install plan is
   // always given and is non-zero. This currently isn't the case during unit
   // tests (see chromium-os:37969).
-  size_t payload_size = install_plan_->payload_size;
+  size_t payload_size = payload_->size;
   unsigned actual_operations_weight = kProgressOperationsWeight;
   if (payload_size)
     new_overall_progress += min(
@@ -287,6 +287,7 @@
   size_t read_len = min(count, max - buffer_.size());
   const char* bytes_start = *bytes_p;
   const char* bytes_end = bytes_start + read_len;
+  buffer_.reserve(max);
   buffer_.insert(buffer_.end(), bytes_start, bytes_end);
   *bytes_p = bytes_end;
   *count_p = count - read_len;
@@ -351,10 +352,14 @@
     return false;
 
   const PartitionUpdate& partition = partitions_[current_partition_];
+  size_t num_previous_partitions =
+      install_plan_->partitions.size() - partitions_.size();
+  const InstallPlan::Partition& install_part =
+      install_plan_->partitions[num_previous_partitions + current_partition_];
   // Open source fds if we have a delta payload with minor version >= 2.
-  if (install_plan_->payload_type == InstallPayloadType::kDelta &&
+  if (payload_->type == InstallPayloadType::kDelta &&
       GetMinorVersion() != kInPlaceMinorPayloadVersion) {
-    source_path_ = install_plan_->partitions[current_partition_].source_path;
+    source_path_ = install_part.source_path;
     int err;
     source_fd_ = OpenFile(source_path_.c_str(), O_RDONLY, false, &err);
     if (!source_fd_) {
@@ -366,7 +371,7 @@
     }
   }
 
-  target_path_ = install_plan_->partitions[current_partition_].target_path;
+  target_path_ = install_part.target_path;
   int err;
 
   int flags = O_RDWR;
@@ -390,8 +395,7 @@
             << "\"";
 
   // Discard the end of the partition, but ignore failures.
-  DiscardPartitionTail(
-      target_fd_, install_plan_->partitions[current_partition_].target_size);
+  DiscardPartitionTail(target_fd_, install_part.target_size);
 
   return true;
 }
@@ -452,7 +456,7 @@
   if (manifest_.has_minor_version()) {
     return manifest_.minor_version();
   } else {
-    return install_plan_->payload_type == InstallPayloadType::kDelta
+    return payload_->type == InstallPayloadType::kDelta
                ? kSupportedMinorPayloadVersion
                : kFullPayloadMinorVersion;
   }
@@ -542,9 +546,9 @@
     // beyond the expected metadata size.
     metadata_size_ = manifest_offset + manifest_size_;
     if (install_plan_->hash_checks_mandatory) {
-      if (install_plan_->metadata_size != metadata_size_) {
+      if (payload_->metadata_size != metadata_size_) {
         LOG(ERROR) << "Mandatory metadata size in Omaha response ("
-                   << install_plan_->metadata_size
+                   << payload_->metadata_size
                    << ") is missing/incorrect, actual = " << metadata_size_;
         *error = ErrorCode::kDownloadInvalidMetadataSize;
         return kMetadataParseError;
@@ -561,13 +565,13 @@
   // here. This is logged here (after we received the full metadata data) so
   // that we just log once (instead of logging n times) if it takes n
   // DeltaPerformer::Write calls to download the full manifest.
-  if (install_plan_->metadata_size == metadata_size_) {
+  if (payload_->metadata_size == metadata_size_) {
     LOG(INFO) << "Manifest size in payload matches expected value from Omaha";
   } else {
     // For mandatory-cases, we'd have already returned a kMetadataParseError
     // above. We'll be here only for non-mandatory cases. Just send a UMA stat.
     LOG(WARNING) << "Ignoring missing/incorrect metadata size ("
-                 << install_plan_->metadata_size
+                 << payload_->metadata_size
                  << ") in Omaha response as validation is not mandatory. "
                  << "Trusting metadata size in payload = " << metadata_size_;
   }
@@ -655,6 +659,12 @@
     if (!ParseManifestPartitions(error))
       return false;
 
+    // |install_plan.partitions| was filled in, nothing need to be done here if
+    // the payload was already applied, returns false to terminate http fetcher,
+    // but keep |error| as ErrorCode::kSuccess.
+    if (payload_->already_applied)
+      return false;
+
     num_total_operations_ = 0;
     for (const auto& partition : partitions_) {
       num_total_operations_ += partition.operations_size();
@@ -719,7 +729,7 @@
     // NOTE: If hash checks are mandatory and if metadata_signature is empty,
     // we would have already failed in ParsePayloadMetadata method and thus not
     // even be here. So no need to handle that case again here.
-    if (!install_plan_->metadata_signature.empty()) {
+    if (!payload_->metadata_signature.empty()) {
       // Note: Validate must be called only if CanPerformInstallOperation is
       // called. Otherwise, we might be failing operations before even if there
       // isn't sufficient data to compute the proper hash.
@@ -876,7 +886,6 @@
 
   // Fill in the InstallPlan::partitions based on the partitions from the
   // payload.
-  install_plan_->partitions.clear();
   for (const auto& partition : partitions_) {
     InstallPlan::Partition install_part;
     install_part.name = partition.partition_name();
@@ -1491,18 +1500,18 @@
     return ErrorCode::kDownloadMetadataSignatureError;
 
   brillo::Blob metadata_signature_blob, metadata_signature_protobuf_blob;
-  if (!install_plan_->metadata_signature.empty()) {
+  if (!payload_->metadata_signature.empty()) {
     // Convert base64-encoded signature to raw bytes.
-    if (!brillo::data_encoding::Base64Decode(
-        install_plan_->metadata_signature, &metadata_signature_blob)) {
+    if (!brillo::data_encoding::Base64Decode(payload_->metadata_signature,
+                                             &metadata_signature_blob)) {
       LOG(ERROR) << "Unable to decode base64 metadata signature: "
-                 << install_plan_->metadata_signature;
+                 << payload_->metadata_signature;
       return ErrorCode::kDownloadMetadataSignatureError;
     }
   } else if (major_payload_version_ == kBrilloMajorPayloadVersion) {
-    metadata_signature_protobuf_blob.assign(payload.begin() + metadata_size_,
-                                            payload.begin() + metadata_size_ +
-                                            metadata_signature_size_);
+    metadata_signature_protobuf_blob.assign(
+        payload.begin() + metadata_size_,
+        payload.begin() + metadata_size_ + metadata_signature_size_);
   }
 
   if (metadata_signature_blob.empty() &&
@@ -1529,14 +1538,13 @@
   LOG(INFO) << "Verifying metadata hash signature using public key: "
             << path_to_public_key.value();
 
-  HashCalculator metadata_hasher;
-  metadata_hasher.Update(payload.data(), metadata_size_);
-  if (!metadata_hasher.Finalize()) {
+  brillo::Blob calculated_metadata_hash;
+  if (!HashCalculator::RawHashOfBytes(
+          payload.data(), metadata_size_, &calculated_metadata_hash)) {
     LOG(ERROR) << "Unable to compute actual hash of manifest";
     return ErrorCode::kDownloadMetadataSignatureVerificationError;
   }
 
-  brillo::Blob calculated_metadata_hash = metadata_hasher.raw_hash();
   PayloadVerifier::PadRSA2048SHA256Hash(&calculated_metadata_hash);
   if (calculated_metadata_hash.empty()) {
     LOG(ERROR) << "Computed actual hash of metadata is empty.";
@@ -1588,14 +1596,14 @@
   InstallPayloadType actual_payload_type =
       has_old_fields ? InstallPayloadType::kDelta : InstallPayloadType::kFull;
 
-  if (install_plan_->payload_type == InstallPayloadType::kUnknown) {
+  if (payload_->type == InstallPayloadType::kUnknown) {
     LOG(INFO) << "Detected a '"
               << InstallPayloadTypeToString(actual_payload_type)
               << "' payload.";
-    install_plan_->payload_type = actual_payload_type;
-  } else if (install_plan_->payload_type != actual_payload_type) {
+    payload_->type = actual_payload_type;
+  } else if (payload_->type != actual_payload_type) {
     LOG(ERROR) << "InstallPlan expected a '"
-               << InstallPayloadTypeToString(install_plan_->payload_type)
+               << InstallPayloadTypeToString(payload_->type)
                << "' payload but the downloaded manifest contains a '"
                << InstallPayloadTypeToString(actual_payload_type)
                << "' payload.";
@@ -1635,6 +1643,14 @@
     }
   }
 
+  if (manifest_.max_timestamp() < hardware_->GetBuildTimestamp()) {
+    LOG(ERROR) << "The current OS build timestamp ("
+               << hardware_->GetBuildTimestamp()
+               << ") is newer than the maximum timestamp in the manifest ("
+               << manifest_.max_timestamp() << ")";
+    return ErrorCode::kPayloadTimestampError;
+  }
+
   // TODO(garnold) we should be adding more and more manifest checks, such as
   // partition boundaries etc (see chromium-os:37661).
 
@@ -1683,15 +1699,14 @@
                           (operation.data_sha256_hash().data() +
                            operation.data_sha256_hash().size()));
 
-  HashCalculator operation_hasher;
-  operation_hasher.Update(buffer_.data(), operation.data_length());
-  if (!operation_hasher.Finalize()) {
+  brillo::Blob calculated_op_hash;
+  if (!HashCalculator::RawHashOfBytes(
+          buffer_.data(), operation.data_length(), &calculated_op_hash)) {
     LOG(ERROR) << "Unable to compute actual hash of operation "
                << next_operation_num_;
     return ErrorCode::kDownloadOperationHashVerificationError;
   }
 
-  brillo::Blob calculated_op_hash = operation_hasher.raw_hash();
   if (calculated_op_hash != expected_op_hash) {
     LOG(ERROR) << "Hash verification failed for operation "
                << next_operation_num_ << ". Expected hash = ";
@@ -1714,7 +1729,7 @@
   } while (0);
 
 ErrorCode DeltaPerformer::VerifyPayload(
-    const string& update_check_response_hash,
+    const brillo::Blob& update_check_response_hash,
     const uint64_t update_check_response_size) {
 
   // See if we should use the public RSA key in the Omaha response.
@@ -1736,11 +1751,11 @@
                       buffer_offset_);
 
   // Verifies the payload hash.
-  const string& payload_hash_data = payload_hash_calculator_.hash();
   TEST_AND_RETURN_VAL(ErrorCode::kDownloadPayloadVerificationError,
-                      !payload_hash_data.empty());
-  TEST_AND_RETURN_VAL(ErrorCode::kPayloadHashMismatchError,
-                      payload_hash_data == update_check_response_hash);
+                      !payload_hash_calculator_.raw_hash().empty());
+  TEST_AND_RETURN_VAL(
+      ErrorCode::kPayloadHashMismatchError,
+      payload_hash_calculator_.raw_hash() == update_check_response_hash);
 
   // Verifies the signed payload hash.
   if (!utils::FileExists(path_to_public_key.value().c_str())) {
@@ -1841,7 +1856,6 @@
   TEST_AND_RETURN_FALSE(prefs->SetInt64(kPrefsUpdateStateNextOperation,
                                         kUpdateStateOperationInvalid));
   if (!quick) {
-    prefs->SetString(kPrefsUpdateCheckResponseHash, "");
     prefs->SetInt64(kPrefsUpdateStateNextDataOffset, -1);
     prefs->SetInt64(kPrefsUpdateStateNextDataLength, 0);
     prefs->SetString(kPrefsUpdateStateSHA256Context, "");
@@ -1850,6 +1864,7 @@
     prefs->SetInt64(kPrefsManifestMetadataSize, -1);
     prefs->SetInt64(kPrefsManifestSignatureSize, -1);
     prefs->SetInt64(kPrefsResumedUpdateFailures, 0);
+    prefs->Delete(kPrefsPostInstallSucceeded);
   }
   return true;
 }
diff --git a/payload_consumer/delta_performer.h b/payload_consumer/delta_performer.h
index 8aa036e..731e7f1 100644
--- a/payload_consumer/delta_performer.h
+++ b/payload_consumer/delta_performer.h
@@ -80,12 +80,14 @@
                  HardwareInterface* hardware,
                  DownloadActionDelegate* download_delegate,
                  InstallPlan* install_plan,
+                 InstallPlan::Payload* payload,
                  bool is_interactive)
       : prefs_(prefs),
         boot_control_(boot_control),
         hardware_(hardware),
         download_delegate_(download_delegate),
         install_plan_(install_plan),
+        payload_(payload),
         is_interactive_(is_interactive) {}
 
   // FileWriter's Write implementation where caller doesn't care about
@@ -116,13 +118,13 @@
   bool IsManifestValid();
 
   // Verifies the downloaded payload against the signed hash included in the
-  // payload, against the update check hash (which is in base64 format)  and
-  // size using the public key and returns ErrorCode::kSuccess on success, an
-  // error code on failure.  This method should be called after closing the
-  // stream. Note this method skips the signed hash check if the public key is
-  // unavailable; it returns ErrorCode::kSignedDeltaPayloadExpectedError if the
-  // public key is available but the delta payload doesn't include a signature.
-  ErrorCode VerifyPayload(const std::string& update_check_response_hash,
+  // payload, against the update check hash and size using the public key and
+  // returns ErrorCode::kSuccess on success, an error code on failure.
+  // This method should be called after closing the stream. Note this method
+  // skips the signed hash check if the public key is unavailable; it returns
+  // ErrorCode::kSignedDeltaPayloadExpectedError if the public key is available
+  // but the delta payload doesn't include a signature.
+  ErrorCode VerifyPayload(const brillo::Blob& update_check_response_hash,
                           const uint64_t update_check_response_size);
 
   // Converts an ordered collection of Extent objects which contain data of
@@ -312,6 +314,9 @@
   // Install Plan based on Omaha Response.
   InstallPlan* install_plan_;
 
+  // Pointer to the current payload in install_plan_.payloads.
+  InstallPlan::Payload* payload_{nullptr};
+
   // File descriptor of the source partition. Only set while updating a
   // partition when using a delta payload.
   FileDescriptorPtr source_fd_{nullptr};
diff --git a/payload_consumer/delta_performer_integration_test.cc b/payload_consumer/delta_performer_integration_test.cc
index 6fdaba5..3572a6d 100644
--- a/payload_consumer/delta_performer_integration_test.cc
+++ b/payload_consumer/delta_performer_integration_test.cc
@@ -720,10 +720,10 @@
   // Update the A image in place.
   InstallPlan* install_plan = &state->install_plan;
   install_plan->hash_checks_mandatory = hash_checks_mandatory;
-  install_plan->metadata_size = state->metadata_size;
-  install_plan->payload_type = (full_kernel && full_rootfs)
-                                   ? InstallPayloadType::kFull
-                                   : InstallPayloadType::kDelta;
+  install_plan->payloads = {{.metadata_size = state->metadata_size,
+                             .type = (full_kernel && full_rootfs)
+                                         ? InstallPayloadType::kFull
+                                         : InstallPayloadType::kDelta}};
   install_plan->source_slot = 0;
   install_plan->target_slot = 1;
 
@@ -739,14 +739,15 @@
       state->delta.data(),
       state->metadata_size,
       GetBuildArtifactsPath(kUnittestPrivateKeyPath),
-      &install_plan->metadata_signature));
-  EXPECT_FALSE(install_plan->metadata_signature.empty());
+      &install_plan->payloads[0].metadata_signature));
+  EXPECT_FALSE(install_plan->payloads[0].metadata_signature.empty());
 
   *performer = new DeltaPerformer(&prefs,
                                   &state->fake_boot_control_,
                                   &state->fake_hardware_,
                                   &state->mock_delegate_,
                                   install_plan,
+                                  &install_plan->payloads[0],
                                   false /* is_interactive */);
   string public_key_path = GetBuildArtifactsPath(kUnittestPublicKeyPath);
   EXPECT_TRUE(utils::FileExists(public_key_path.c_str()));
@@ -762,9 +763,8 @@
                   state->old_kernel_data,
                   &kernel_part.source_hash));
 
-  // This partitions are normally filed by the FilesystemVerifierAction with
-  // the source hashes used for deltas.
-  install_plan->partitions = {root_part, kernel_part};
+  // The partitions should be empty before DeltaPerformer.
+  install_plan->partitions.clear();
 
   // With minor version 2, we want the target to be the new image, result_img,
   // but with version 1, we want to update A in place.
@@ -855,11 +855,11 @@
   int expected_times = (expected_result == ErrorCode::kSuccess) ? 1 : 0;
   EXPECT_CALL(state->mock_delegate_, DownloadComplete()).Times(expected_times);
 
-  LOG(INFO) << "Verifying payload for expected result "
-            << expected_result;
-  EXPECT_EQ(expected_result, performer->VerifyPayload(
-      HashCalculator::HashOfData(state->delta),
-      state->delta.size()));
+  LOG(INFO) << "Verifying payload for expected result " << expected_result;
+  brillo::Blob expected_hash;
+  HashCalculator::RawHashOfData(state->delta, &expected_hash);
+  EXPECT_EQ(expected_result,
+            performer->VerifyPayload(expected_hash, state->delta.size()));
   LOG(INFO) << "Verified payload.";
 
   if (expected_result != ErrorCode::kSuccess) {
diff --git a/payload_consumer/delta_performer_unittest.cc b/payload_consumer/delta_performer_unittest.cc
index ea35c47..420efd2 100644
--- a/payload_consumer/delta_performer_unittest.cc
+++ b/payload_consumer/delta_performer_unittest.cc
@@ -158,7 +158,7 @@
                              uint64_t major_version,
                              InstallPayloadType payload_type,
                              ErrorCode expected) {
-    install_plan_.payload_type = payload_type;
+    payload_.type = payload_type;
 
     // The Manifest we are validating.
     performer_.manifest_.CopyFrom(manifest);
@@ -219,7 +219,7 @@
     string private_key =
         sign_payload ? GetBuildArtifactsPath(kUnittestPrivateKeyPath) : "";
     EXPECT_TRUE(payload.WritePayload(
-        payload_path, blob_path, private_key, &install_plan_.metadata_size));
+        payload_path, blob_path, private_key, &payload_.metadata_size));
 
     brillo::Blob payload_data;
     EXPECT_TRUE(utils::ReadFile(payload_path, &payload_data));
@@ -286,7 +286,7 @@
     uint64_t version = htobe64(kChromeOSMajorPayloadVersion);
     EXPECT_TRUE(performer_.Write(&version, 8));
 
-    install_plan_.metadata_size = expected_metadata_size;
+    payload_.metadata_size = expected_metadata_size;
     ErrorCode error_code;
     // When filling in size in manifest, exclude the size of the 20-byte header.
     uint64_t size_in_manifest = htobe64(actual_metadata_size - 20);
@@ -323,13 +323,13 @@
     // Fill up the metadata signature in install plan according to the test.
     switch (metadata_signature_test) {
       case kEmptyMetadataSignature:
-        install_plan_.metadata_signature.clear();
+        payload_.metadata_signature.clear();
         expected_result = DeltaPerformer::kMetadataParseError;
         expected_error = ErrorCode::kDownloadMetadataSignatureMissingError;
         break;
 
       case kInvalidMetadataSignature:
-        install_plan_.metadata_signature = kBogusMetadataSignature1;
+        payload_.metadata_signature = kBogusMetadataSignature1;
         expected_result = DeltaPerformer::kMetadataParseError;
         expected_error = ErrorCode::kDownloadMetadataSignatureMismatch;
         break;
@@ -341,10 +341,10 @@
         // then we can get to manifest signature checks.
         ASSERT_TRUE(PayloadSigner::GetMetadataSignature(
             payload.data(),
-            install_plan_.metadata_size,
+            payload_.metadata_size,
             GetBuildArtifactsPath(kUnittestPrivateKeyPath),
-            &install_plan_.metadata_signature));
-        EXPECT_FALSE(install_plan_.metadata_signature.empty());
+            &payload_.metadata_signature));
+        EXPECT_FALSE(payload_.metadata_signature.empty());
         expected_result = DeltaPerformer::kMetadataParseSuccess;
         expected_error = ErrorCode::kSuccess;
         break;
@@ -372,7 +372,7 @@
 
     // Check that the parsed metadata size is what's expected. This test
     // implicitly confirms that the metadata signature is valid, if required.
-    EXPECT_EQ(install_plan_.metadata_size, performer_.GetMetadataSize());
+    EXPECT_EQ(payload_.metadata_size, performer_.GetMetadataSize());
   }
 
   void SetSupportedMajorVersion(uint64_t major_version) {
@@ -380,6 +380,7 @@
   }
   FakePrefs prefs_;
   InstallPlan install_plan_;
+  InstallPlan::Payload payload_;
   FakeBootControl fake_boot_control_;
   FakeHardware fake_hardware_;
   MockDownloadActionDelegate mock_delegate_;
@@ -388,11 +389,12 @@
                             &fake_hardware_,
                             &mock_delegate_,
                             &install_plan_,
+                            &payload_,
                             false /* is_interactive*/};
 };
 
 TEST_F(DeltaPerformerTest, FullPayloadWriteTest) {
-  install_plan_.payload_type = InstallPayloadType::kFull;
+  payload_.type = InstallPayloadType::kFull;
   brillo::Blob expected_data = brillo::Blob(std::begin(kRandomString),
                                             std::end(kRandomString));
   expected_data.resize(4096);  // block size
@@ -411,7 +413,7 @@
 }
 
 TEST_F(DeltaPerformerTest, ShouldCancelTest) {
-  install_plan_.payload_type = InstallPayloadType::kFull;
+  payload_.type = InstallPayloadType::kFull;
   brillo::Blob expected_data = brillo::Blob(std::begin(kRandomString),
                                             std::end(kRandomString));
   expected_data.resize(4096);  // block size
@@ -719,6 +721,20 @@
                         ErrorCode::kUnsupportedMinorPayloadVersion);
 }
 
+TEST_F(DeltaPerformerTest, ValidateManifestDowngrade) {
+  // The Manifest we are validating.
+  DeltaArchiveManifest manifest;
+
+  manifest.set_minor_version(kFullPayloadMinorVersion);
+  manifest.set_max_timestamp(1);
+  fake_hardware_.SetBuildTimestamp(2);
+
+  RunManifestValidation(manifest,
+                        DeltaPerformer::kSupportedMajorPayloadVersion,
+                        InstallPayloadType::kFull,
+                        ErrorCode::kPayloadTimestampError);
+}
+
 TEST_F(DeltaPerformerTest, BrilloMetadataSignatureSizeTest) {
   EXPECT_TRUE(performer_.Write(kDeltaMagic, sizeof(kDeltaMagic)));
 
@@ -751,7 +767,7 @@
   install_plan_.hash_checks_mandatory = true;
   // Just set these value so that we can use ValidateMetadataSignature directly.
   performer_.major_payload_version_ = kBrilloMajorPayloadVersion;
-  performer_.metadata_size_ = install_plan_.metadata_size;
+  performer_.metadata_size_ = payload_.metadata_size;
   uint64_t signature_length;
   EXPECT_TRUE(PayloadSigner::SignatureBlobLength(
       {GetBuildArtifactsPath(kUnittestPrivateKeyPath)}, &signature_length));
@@ -832,8 +848,8 @@
 
   base::ScopedTempDir temp_dir;
   ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
-  string non_existing_file = temp_dir.path().Append("non-existing").value();
-  string existing_file = temp_dir.path().Append("existing").value();
+  string non_existing_file = temp_dir.GetPath().Append("non-existing").value();
+  string existing_file = temp_dir.GetPath().Append("existing").value();
   EXPECT_EQ(0, System(base::StringPrintf("touch %s", existing_file.c_str())));
 
   // Non-official build, non-existing public-key, key in response -> true
diff --git a/payload_consumer/download_action.cc b/payload_consumer/download_action.cc
index cf550f5..f1b6e33 100644
--- a/payload_consumer/download_action.cc
+++ b/payload_consumer/download_action.cc
@@ -28,6 +28,7 @@
 #include "update_engine/common/action_pipe.h"
 #include "update_engine/common/boot_control_interface.h"
 #include "update_engine/common/error_code_utils.h"
+#include "update_engine/common/multi_range_http_fetcher.h"
 #include "update_engine/common/utils.h"
 #include "update_engine/omaha_request_params.h"
 #include "update_engine/p2p_manager.h"
@@ -48,12 +49,11 @@
       boot_control_(boot_control),
       hardware_(hardware),
       system_state_(system_state),
-      http_fetcher_(http_fetcher),
+      http_fetcher_(new MultiRangeHttpFetcher(http_fetcher)),
       is_interactive_(is_interactive),
       writer_(nullptr),
       code_(ErrorCode::kSuccess),
       delegate_(nullptr),
-      bytes_received_(0),
       p2p_sharing_fd_(-1),
       p2p_visible_(true) {
   base::StatisticsRecorder::Initialize();
@@ -86,7 +86,7 @@
 bool DownloadAction::SetupP2PSharingFd() {
   P2PManager *p2p_manager = system_state_->p2p_manager();
 
-  if (!p2p_manager->FileShare(p2p_file_id_, install_plan_.payload_size)) {
+  if (!p2p_manager->FileShare(p2p_file_id_, payload_->size)) {
     LOG(ERROR) << "Unable to share file via p2p";
     CloseP2PSharingFd(true);  // delete p2p file
     return false;
@@ -174,10 +174,29 @@
   // Get the InstallPlan and read it
   CHECK(HasInputObject());
   install_plan_ = GetInputObject();
-  bytes_received_ = 0;
-
   install_plan_.Dump();
 
+  bytes_received_ = 0;
+  bytes_received_previous_payloads_ = 0;
+  bytes_total_ = 0;
+  for (const auto& payload : install_plan_.payloads)
+    bytes_total_ += payload.size;
+
+  if (install_plan_.is_resume) {
+    int64_t payload_index = 0;
+    if (prefs_->GetInt64(kPrefsUpdateStatePayloadIndex, &payload_index) &&
+        static_cast<size_t>(payload_index) < install_plan_.payloads.size()) {
+      // Save the index for the resume payload before downloading any previous
+      // payload, otherwise it will be overwritten.
+      resume_payload_index_ = payload_index;
+      for (int i = 0; i < payload_index; i++)
+        install_plan_.payloads[i].already_applied = true;
+    }
+  }
+  // TODO(senj): check that install plan has at least one payload.
+  if (!payload_)
+    payload_ = &install_plan_.payloads[0];
+
   LOG(INFO) << "Marking new slot as unbootable";
   if (!boot_control_->MarkSlotUnbootable(install_plan_.target_slot)) {
     LOG(WARNING) << "Unable to mark new slot "
@@ -185,7 +204,45 @@
                  << ". Proceeding with the update anyway.";
   }
 
-  if (writer_) {
+  StartDownloading();
+}
+
+void DownloadAction::StartDownloading() {
+  download_active_ = true;
+  http_fetcher_->ClearRanges();
+  if (install_plan_.is_resume &&
+      payload_ == &install_plan_.payloads[resume_payload_index_]) {
+    // Resuming an update so fetch the update manifest metadata first.
+    int64_t manifest_metadata_size = 0;
+    int64_t manifest_signature_size = 0;
+    prefs_->GetInt64(kPrefsManifestMetadataSize, &manifest_metadata_size);
+    prefs_->GetInt64(kPrefsManifestSignatureSize, &manifest_signature_size);
+    http_fetcher_->AddRange(base_offset_,
+                            manifest_metadata_size + manifest_signature_size);
+    // If there're remaining unprocessed data blobs, fetch them. Be careful not
+    // to request data beyond the end of the payload to avoid 416 HTTP response
+    // error codes.
+    int64_t next_data_offset = 0;
+    prefs_->GetInt64(kPrefsUpdateStateNextDataOffset, &next_data_offset);
+    uint64_t resume_offset =
+        manifest_metadata_size + manifest_signature_size + next_data_offset;
+    if (!payload_->size) {
+      http_fetcher_->AddRange(base_offset_ + resume_offset);
+    } else if (resume_offset < payload_->size) {
+      http_fetcher_->AddRange(base_offset_ + resume_offset,
+                              payload_->size - resume_offset);
+    }
+  } else {
+    if (payload_->size) {
+      http_fetcher_->AddRange(base_offset_, payload_->size);
+    } else {
+      // If no payload size is passed we assume we read until the end of the
+      // stream.
+      http_fetcher_->AddRange(base_offset_);
+    }
+  }
+
+  if (writer_ && writer_ != delta_performer_.get()) {
     LOG(INFO) << "Using writer for test.";
   } else {
     delta_performer_.reset(new DeltaPerformer(prefs_,
@@ -193,15 +250,13 @@
                                               hardware_,
                                               delegate_,
                                               &install_plan_,
+                                              payload_,
                                               is_interactive_));
     writer_ = delta_performer_.get();
   }
-  download_active_ = true;
-
   if (system_state_ != nullptr) {
     const PayloadStateInterface* payload_state = system_state_->payload_state();
-    string file_id = utils::CalculateP2PFileId(install_plan_.payload_hash,
-                                               install_plan_.payload_size);
+    string file_id = utils::CalculateP2PFileId(payload_->hash, payload_->size);
     if (payload_state->GetUsingP2PForSharing()) {
       // If we're sharing the update, store the file_id to convey
       // that we should write to the file.
@@ -272,14 +327,17 @@
   }
 
   bytes_received_ += length;
+  uint64_t bytes_downloaded_total =
+      bytes_received_previous_payloads_ + bytes_received_;
   if (delegate_ && download_active_) {
-    delegate_->BytesReceived(
-        length, bytes_received_, install_plan_.payload_size);
+    delegate_->BytesReceived(length, bytes_downloaded_total, bytes_total_);
   }
   if (writer_ && !writer_->Write(bytes, length, &code_)) {
-    LOG(ERROR) << "Error " << utils::ErrorCodeToString(code_) << " (" << code_
-               << ") in DeltaPerformer's Write method when "
-               << "processing the received payload -- Terminating processing";
+    if (code_ != ErrorCode::kSuccess) {
+      LOG(ERROR) << "Error " << utils::ErrorCodeToString(code_) << " (" << code_
+                 << ") in DeltaPerformer's Write method when "
+                 << "processing the received payload -- Terminating processing";
+    }
     // Delete p2p file, if applicable.
     if (!p2p_file_id_.empty())
       CloseP2PSharingFd(true);
@@ -303,15 +361,32 @@
 void DownloadAction::TransferComplete(HttpFetcher* fetcher, bool successful) {
   if (writer_) {
     LOG_IF(WARNING, writer_->Close() != 0) << "Error closing the writer.";
-    writer_ = nullptr;
+    if (delta_performer_.get() == writer_) {
+      // no delta_performer_ in tests, so leave the test writer in place
+      writer_ = nullptr;
+    }
   }
   download_active_ = false;
   ErrorCode code =
       successful ? ErrorCode::kSuccess : ErrorCode::kDownloadTransferError;
-  if (code == ErrorCode::kSuccess && delta_performer_.get()) {
-    code = delta_performer_->VerifyPayload(install_plan_.payload_hash,
-                                           install_plan_.payload_size);
+  if (code == ErrorCode::kSuccess) {
+    if (delta_performer_ && !payload_->already_applied)
+      code = delta_performer_->VerifyPayload(payload_->hash, payload_->size);
     if (code == ErrorCode::kSuccess) {
+      if (payload_ < &install_plan_.payloads.back() &&
+                 system_state_->payload_state()->NextPayload()) {
+        LOG(INFO) << "Incrementing to next payload";
+        // No need to reset if this payload was already applied.
+        if (delta_performer_ && !payload_->already_applied)
+          DeltaPerformer::ResetUpdateProgress(prefs_, false);
+        // Start downloading next payload.
+        bytes_received_previous_payloads_ += payload_->size;
+        payload_++;
+        install_plan_.download_url =
+            system_state_->payload_state()->GetCurrentUrl();
+        StartDownloading();
+        return;
+      }
       // Log UpdateEngine.DownloadAction.* histograms to help diagnose
       // long-blocking oeprations.
       std::string histogram_output;
@@ -333,9 +408,13 @@
   processor_->ActionComplete(this, code);
 }
 
-void DownloadAction::TransferTerminated(HttpFetcher *fetcher) {
+void DownloadAction::TransferTerminated(HttpFetcher* fetcher) {
   if (code_ != ErrorCode::kSuccess) {
     processor_->ActionComplete(this, code_);
+  } else if (payload_->already_applied) {
+    LOG(INFO) << "TransferTerminated with ErrorCode::kSuccess when the current "
+                 "payload has already applied, treating as TransferComplete.";
+    TransferComplete(fetcher, true);
   }
 }
 
diff --git a/payload_consumer/download_action.h b/payload_consumer/download_action.h
index 618a5b9..81d7333 100644
--- a/payload_consumer/download_action.h
+++ b/payload_consumer/download_action.h
@@ -27,6 +27,7 @@
 #include "update_engine/common/action.h"
 #include "update_engine/common/boot_control_interface.h"
 #include "update_engine/common/http_fetcher.h"
+#include "update_engine/common/multi_range_http_fetcher.h"
 #include "update_engine/payload_consumer/delta_performer.h"
 #include "update_engine/payload_consumer/install_plan.h"
 #include "update_engine/system_state.h"
@@ -107,6 +108,8 @@
     delegate_ = delegate;
   }
 
+  void set_base_offset(int64_t base_offset) { base_offset_ = base_offset; }
+
   HttpFetcher* http_fetcher() { return http_fetcher_.get(); }
 
   // Returns the p2p file id for the file being written or the empty
@@ -132,9 +135,15 @@
   // called or if CloseP2PSharingFd() has been called.
   void WriteToP2PFile(const void* data, size_t length, off_t file_offset);
 
+  // Start downloading the current payload using delta_performer.
+  void StartDownloading();
+
   // The InstallPlan passed in
   InstallPlan install_plan_;
 
+  // Pointer to the current payload in install_plan_.payloads.
+  InstallPlan::Payload* payload_{nullptr};
+
   // SystemState required pointers.
   PrefsInterface* prefs_;
   BootControlInterface* boot_control_;
@@ -143,8 +152,8 @@
   // Global context for the system.
   SystemState* system_state_;
 
-  // Pointer to the HttpFetcher that does the http work.
-  std::unique_ptr<HttpFetcher> http_fetcher_;
+  // Pointer to the MultiRangeHttpFetcher that does the http work.
+  std::unique_ptr<MultiRangeHttpFetcher> http_fetcher_;
 
   // If |true|, the update is user initiated (vs. periodic update checks). Hence
   // the |delta_performer_| can decide not to use O_DSYNC flag for faster
@@ -163,7 +172,9 @@
 
   // For reporting status to outsiders
   DownloadActionDelegate* delegate_;
-  uint64_t bytes_received_;
+  uint64_t bytes_received_{0};  // per file/range
+  uint64_t bytes_received_previous_payloads_{0};
+  uint64_t bytes_total_{0};
   bool download_active_{false};
 
   // The file-id for the file we're sharing or the empty string
@@ -177,6 +188,12 @@
   // Set to |false| if p2p file is not visible.
   bool p2p_visible_;
 
+  // Loaded from prefs before downloading any payload.
+  size_t resume_payload_index_{0};
+
+  // Offset of the payload in the download URL, used by UpdateAttempterAndroid.
+  int64_t base_offset_{0};
+
   DISALLOW_COPY_AND_ASSIGN(DownloadAction);
 };
 
diff --git a/payload_consumer/download_action_unittest.cc b/payload_consumer/download_action_unittest.cc
index 9d74116..21ce461 100644
--- a/payload_consumer/download_action_unittest.cc
+++ b/payload_consumer/download_action_unittest.cc
@@ -28,7 +28,6 @@
 #include <base/files/file_util.h>
 #include <base/location.h>
 #include <base/strings/stringprintf.h>
-#include <brillo/bind_lambda.h>
 #include <brillo/message_loops/fake_message_loop.h>
 #include <brillo/message_loops/message_loop.h>
 
@@ -40,6 +39,7 @@
 #include "update_engine/common/utils.h"
 #include "update_engine/fake_p2p_manager_configuration.h"
 #include "update_engine/fake_system_state.h"
+#include "update_engine/mock_file_writer.h"
 #include "update_engine/payload_consumer/mock_download_action.h"
 #include "update_engine/update_manager/fake_update_manager.h"
 
@@ -54,6 +54,7 @@
 using testing::AtLeast;
 using testing::InSequence;
 using testing::Return;
+using testing::SetArgPointee;
 using testing::_;
 
 class DownloadActionTest : public ::testing::Test { };
@@ -137,13 +138,13 @@
       0, writer.Open(output_temp_file.path().c_str(), O_WRONLY | O_CREAT, 0));
   writer.set_fail_write(fail_write);
 
-  // We pull off the first byte from data and seek past it.
-  string hash = HashCalculator::HashOfBytes(&data[1], data.size() - 1);
-  uint64_t size = data.size();
+  uint64_t size = data.size() - 1;
   InstallPlan install_plan;
-  install_plan.payload_type = InstallPayloadType::kDelta;
-  install_plan.payload_size = size;
-  install_plan.payload_hash = hash;
+  install_plan.payloads.push_back(
+      {.size = size, .type = InstallPayloadType::kDelta});
+  // We pull off the first byte from data and seek past it.
+  EXPECT_TRUE(HashCalculator::RawHashOfBytes(
+      &data[1], data.size() - 1, &install_plan.payloads[0].hash));
   install_plan.source_slot = 0;
   install_plan.target_slot = 1;
   // We mark both slots as bootable. Only the target slot should be unbootable
@@ -173,7 +174,7 @@
     download_action.set_delegate(&download_delegate);
     if (data.size() > kMockHttpFetcherChunkSize)
       EXPECT_CALL(download_delegate,
-                  BytesReceived(_, 1 + kMockHttpFetcherChunkSize, _));
+                  BytesReceived(_, kMockHttpFetcherChunkSize, _));
     EXPECT_CALL(download_delegate, BytesReceived(_, _, _)).Times(AtLeast(1));
   }
   ErrorCode expected_code = ErrorCode::kSuccess;
@@ -241,6 +242,93 @@
                false);  // use_download_delegate
 }
 
+TEST(DownloadActionTest, MultiPayloadProgressTest) {
+  std::vector<brillo::Blob> payload_datas;
+  // the first payload must be the largest, as it's the actual payload used by
+  // the MockHttpFetcher for all downloaded data.
+  payload_datas.emplace_back(4 * kMockHttpFetcherChunkSize + 256);
+  payload_datas.emplace_back(2 * kMockHttpFetcherChunkSize);
+  brillo::FakeMessageLoop loop(nullptr);
+  loop.SetAsCurrent();
+  FakeSystemState fake_system_state;
+  EXPECT_CALL(*fake_system_state.mock_payload_state(), NextPayload())
+      .WillOnce(Return(true));
+
+  MockFileWriter mock_file_writer;
+  EXPECT_CALL(mock_file_writer, Close()).WillRepeatedly(Return(0));
+  EXPECT_CALL(mock_file_writer, Write(_, _, _))
+      .WillRepeatedly(
+          DoAll(SetArgPointee<2>(ErrorCode::kSuccess), Return(true)));
+
+  InstallPlan install_plan;
+  uint64_t total_expected_download_size{0};
+  for (const auto& data : payload_datas) {
+    uint64_t size = data.size();
+    install_plan.payloads.push_back(
+        {.size = size, .type = InstallPayloadType::kFull});
+    total_expected_download_size += size;
+  }
+  ObjectFeederAction<InstallPlan> feeder_action;
+  feeder_action.set_obj(install_plan);
+  MockPrefs prefs;
+  MockHttpFetcher* http_fetcher = new MockHttpFetcher(
+      payload_datas[0].data(), payload_datas[0].size(), nullptr);
+  // takes ownership of passed in HttpFetcher
+  DownloadAction download_action(&prefs,
+                                 fake_system_state.boot_control(),
+                                 fake_system_state.hardware(),
+                                 &fake_system_state,
+                                 http_fetcher,
+                                 false /* is_interactive */);
+  download_action.SetTestFileWriter(&mock_file_writer);
+  BondActions(&feeder_action, &download_action);
+  MockDownloadActionDelegate download_delegate;
+  {
+    InSequence s;
+    download_action.set_delegate(&download_delegate);
+    // these are hand-computed based on the payloads specified above
+    EXPECT_CALL(download_delegate,
+                BytesReceived(kMockHttpFetcherChunkSize,
+                              kMockHttpFetcherChunkSize,
+                              total_expected_download_size));
+    EXPECT_CALL(download_delegate,
+                BytesReceived(kMockHttpFetcherChunkSize,
+                              kMockHttpFetcherChunkSize * 2,
+                              total_expected_download_size));
+    EXPECT_CALL(download_delegate,
+                BytesReceived(kMockHttpFetcherChunkSize,
+                              kMockHttpFetcherChunkSize * 3,
+                              total_expected_download_size));
+    EXPECT_CALL(download_delegate,
+                BytesReceived(kMockHttpFetcherChunkSize,
+                              kMockHttpFetcherChunkSize * 4,
+                              total_expected_download_size));
+    EXPECT_CALL(download_delegate,
+                BytesReceived(256,
+                              kMockHttpFetcherChunkSize * 4 + 256,
+                              total_expected_download_size));
+    EXPECT_CALL(download_delegate,
+                BytesReceived(kMockHttpFetcherChunkSize,
+                              kMockHttpFetcherChunkSize * 5 + 256,
+                              total_expected_download_size));
+    EXPECT_CALL(download_delegate,
+                BytesReceived(kMockHttpFetcherChunkSize,
+                              total_expected_download_size,
+                              total_expected_download_size));
+  }
+  ActionProcessor processor;
+  processor.EnqueueAction(&feeder_action);
+  processor.EnqueueAction(&download_action);
+
+  loop.PostTask(
+      FROM_HERE,
+      base::Bind(
+          [](ActionProcessor* processor) { processor->StartProcessing(); },
+          base::Unretained(&processor)));
+  loop.Run();
+  EXPECT_FALSE(loop.PendingTasks());
+}
+
 namespace {
 class TerminateEarlyTestProcessorDelegate : public ActionProcessorDelegate {
  public:
@@ -271,6 +359,7 @@
     // takes ownership of passed in HttpFetcher
     ObjectFeederAction<InstallPlan> feeder_action;
     InstallPlan install_plan;
+    install_plan.payloads.resize(1);
     feeder_action.set_obj(install_plan);
     FakeSystemState fake_system_state_;
     MockPrefs prefs;
@@ -370,8 +459,9 @@
 
   // takes ownership of passed in HttpFetcher
   InstallPlan install_plan;
-  install_plan.payload_size = 1;
-  install_plan.payload_hash = HashCalculator::HashOfString("x");
+  install_plan.payloads.push_back({.size = 1});
+  EXPECT_TRUE(
+      HashCalculator::RawHashOfData({'x'}, &install_plan.payloads[0].hash));
   ObjectFeederAction<InstallPlan> feeder_action;
   feeder_action.set_obj(install_plan);
   MockPrefs prefs;
@@ -456,8 +546,9 @@
     EXPECT_EQ(
         0, writer.Open(output_temp_file.path().c_str(), O_WRONLY | O_CREAT, 0));
     InstallPlan install_plan;
-    install_plan.payload_size = data_.length();
-    install_plan.payload_hash = "1234hash";
+    install_plan.payloads.push_back(
+        {.size = data_.length(),
+         .hash = {'1', '2', '3', '4', 'h', 'a', 's', 'h'}});
     ObjectFeederAction<InstallPlan> feeder_action;
     feeder_action.set_obj(install_plan);
     MockPrefs prefs;
@@ -571,7 +662,8 @@
 
   // Prepare the file with existing data before starting to write to
   // it via DownloadAction.
-  string file_id = utils::CalculateP2PFileId("1234hash", data_.length());
+  string file_id = utils::CalculateP2PFileId(
+      {'1', '2', '3', '4', 'h', 'a', 's', 'h'}, data_.length());
   ASSERT_TRUE(p2p_manager_->FileShare(file_id, data_.length()));
   string existing_data;
   for (unsigned int i = 0; i < 1000; i++)
@@ -608,7 +700,8 @@
 
   // Prepare the file with all existing data before starting to write
   // to it via DownloadAction.
-  string file_id = utils::CalculateP2PFileId("1234hash", data_.length());
+  string file_id = utils::CalculateP2PFileId(
+      {'1', '2', '3', '4', 'h', 'a', 's', 'h'}, data_.length());
   ASSERT_TRUE(p2p_manager_->FileShare(file_id, data_.length()));
   string existing_data;
   for (unsigned int i = 0; i < 1000; i++)
diff --git a/payload_consumer/extent_reader.cc b/payload_consumer/extent_reader.cc
index 428ecfe..96ea918 100644
--- a/payload_consumer/extent_reader.cc
+++ b/payload_consumer/extent_reader.cc
@@ -68,9 +68,10 @@
     if (cur_extent_ == extents_.end()) {
       TEST_AND_RETURN_FALSE(bytes_read == count);
     }
-    uint64_t bytes_to_read = std::min(
-        count - bytes_read,
-        cur_extent_->num_blocks() * block_size_ - cur_extent_bytes_read_);
+    uint64_t cur_extent_bytes_left =
+        cur_extent_->num_blocks() * block_size_ - cur_extent_bytes_read_;
+    uint64_t bytes_to_read =
+        std::min(count - bytes_read, cur_extent_bytes_left);
 
     ssize_t out_bytes_read;
     TEST_AND_RETURN_FALSE(utils::PReadAll(
diff --git a/payload_consumer/filesystem_verifier_action.cc b/payload_consumer/filesystem_verifier_action.cc
index 5156f96..5edde9e 100644
--- a/payload_consumer/filesystem_verifier_action.cc
+++ b/payload_consumer/filesystem_verifier_action.cc
@@ -34,16 +34,13 @@
 #include "update_engine/payload_consumer/delta_performer.h"
 #include "update_engine/payload_consumer/payload_constants.h"
 
+using brillo::data_encoding::Base64Encode;
 using std::string;
 
 namespace chromeos_update_engine {
 
 namespace {
 const off_t kReadFileBufferSize = 128 * 1024;
-
-string StringForHashBytes(const brillo::Blob& hash) {
-  return brillo::data_encoding::Base64Encode(hash.data(), hash.size());
-}
 }  // namespace
 
 void FilesystemVerifierAction::PerformAction() {
@@ -199,15 +196,18 @@
   }
   InstallPlan::Partition& partition =
       install_plan_.partitions[partition_index_];
-  LOG(INFO) << "Hash of " << partition.name << ": " << hasher_->hash();
+  LOG(INFO) << "Hash of " << partition.name << ": "
+            << Base64Encode(hasher_->raw_hash());
 
   switch (verifier_step_) {
     case VerifierStep::kVerifyTargetHash:
       if (partition.target_hash != hasher_->raw_hash()) {
         LOG(ERROR) << "New '" << partition.name
                    << "' partition verification failed.";
-        if (install_plan_.payload_type == InstallPayloadType::kFull)
+        if (partition.source_hash.empty()) {
+          // No need to verify source if it is a full payload.
           return Cleanup(ErrorCode::kNewRootfsVerificationError);
+        }
         // If we have not verified source partition yet, now that the target
         // partition does not match, and it's not a full payload, we need to
         // switch to kVerifySourceHash step to check if it's because the source
@@ -231,9 +231,9 @@
                       " means that the delta I've been given doesn't match my"
                       " existing system. The "
                    << partition.name << " partition I have has hash: "
-                   << StringForHashBytes(hasher_->raw_hash())
+                   << Base64Encode(hasher_->raw_hash())
                    << " but the update expected me to have "
-                   << StringForHashBytes(partition.source_hash) << " .";
+                   << Base64Encode(partition.source_hash) << " .";
         LOG(INFO) << "To get the checksum of the " << partition.name
                   << " partition run this command: dd if="
                   << partition.source_path
diff --git a/payload_consumer/filesystem_verifier_action_unittest.cc b/payload_consumer/filesystem_verifier_action_unittest.cc
index 2e1d95d..b4f7f7f 100644
--- a/payload_consumer/filesystem_verifier_action_unittest.cc
+++ b/payload_consumer/filesystem_verifier_action_unittest.cc
@@ -26,7 +26,6 @@
 #include <base/posix/eintr_wrapper.h>
 #include <base/strings/string_util.h>
 #include <base/strings/stringprintf.h>
-#include <brillo/bind_lambda.h>
 #include <brillo/message_loops/fake_message_loop.h>
 #include <brillo/message_loops/message_loop_utils.h>
 #include <gmock/gmock.h>
diff --git a/payload_consumer/install_plan.cc b/payload_consumer/install_plan.cc
index b04da74..45112d6 100644
--- a/payload_consumer/install_plan.cc
+++ b/payload_consumer/install_plan.cc
@@ -18,6 +18,7 @@
 
 #include <base/format_macros.h>
 #include <base/logging.h>
+#include <base/strings/string_number_conversions.h>
 #include <base/strings/stringprintf.h>
 
 #include "update_engine/common/utils.h"
@@ -41,15 +42,9 @@
 
 bool InstallPlan::operator==(const InstallPlan& that) const {
   return ((is_resume == that.is_resume) &&
-          (payload_type == that.payload_type) &&
-          (download_url == that.download_url) &&
-          (payload_size == that.payload_size) &&
-          (payload_hash == that.payload_hash) &&
-          (metadata_size == that.metadata_size) &&
-          (metadata_signature == that.metadata_signature) &&
+          (download_url == that.download_url) && (payloads == that.payloads) &&
           (source_slot == that.source_slot) &&
-          (target_slot == that.target_slot) &&
-          (partitions == that.partitions));
+          (target_slot == that.target_slot) && (partitions == that.partitions));
 }
 
 bool InstallPlan::operator!=(const InstallPlan& that) const {
@@ -67,21 +62,35 @@
                            partition.target_size,
                            utils::ToString(partition.run_postinstall).c_str());
   }
+  string payloads_str;
+  for (const auto& payload : payloads) {
+    payloads_str += base::StringPrintf(
+        ", payload: (size: %" PRIu64 ", metadata_size: %" PRIu64
+        ", metadata signature: %s, hash: %s, payload type: %s)",
+        payload.size,
+        payload.metadata_size,
+        payload.metadata_signature.c_str(),
+        base::HexEncode(payload.hash.data(), payload.hash.size()).c_str(),
+        InstallPayloadTypeToString(payload.type).c_str());
+  }
 
-  LOG(INFO) << "InstallPlan: "
-            << (is_resume ? "resume" : "new_update")
-            << ", payload type: " << InstallPayloadTypeToString(payload_type)
+  string version_str = base::StringPrintf(", version: %s", version.c_str());
+  if (!system_version.empty()) {
+    version_str +=
+        base::StringPrintf(", system_version: %s", system_version.c_str());
+  }
+
+  LOG(INFO) << "InstallPlan: " << (is_resume ? "resume" : "new_update")
+            << version_str
             << ", source_slot: " << BootControlInterface::SlotName(source_slot)
             << ", target_slot: " << BootControlInterface::SlotName(target_slot)
-            << ", url: " << download_url
-            << ", payload size: " << payload_size
-            << ", payload hash: " << payload_hash
-            << ", metadata size: " << metadata_size
-            << ", metadata signature: " << metadata_signature
-            << partitions_str
-            << ", hash_checks_mandatory: " << utils::ToString(
-                hash_checks_mandatory)
-            << ", powerwash_required: " << utils::ToString(powerwash_required);
+            << ", url: " << download_url << payloads_str << partitions_str
+            << ", hash_checks_mandatory: "
+            << utils::ToString(hash_checks_mandatory)
+            << ", powerwash_required: " << utils::ToString(powerwash_required)
+            << ", switch_slot_on_reboot: "
+            << utils::ToString(switch_slot_on_reboot)
+            << ", run_post_install: " << utils::ToString(run_post_install);
 }
 
 bool InstallPlan::LoadPartitionsFromSlots(BootControlInterface* boot_control) {
diff --git a/payload_consumer/install_plan.h b/payload_consumer/install_plan.h
index 3f0005c..5cdfbc1 100644
--- a/payload_consumer/install_plan.h
+++ b/payload_consumer/install_plan.h
@@ -52,14 +52,30 @@
   bool LoadPartitionsFromSlots(BootControlInterface* boot_control);
 
   bool is_resume{false};
-  InstallPayloadType payload_type{InstallPayloadType::kUnknown};
   std::string download_url;  // url to download from
   std::string version;       // version we are installing.
+  // system version, if present and separate from version
+  std::string system_version;
 
-  uint64_t payload_size{0};              // size of the payload
-  std::string payload_hash;              // SHA256 hash of the payload
-  uint64_t metadata_size{0};             // size of the metadata
-  std::string metadata_signature;        // signature of the  metadata
+  struct Payload {
+    uint64_t size = 0;               // size of the payload
+    uint64_t metadata_size = 0;      // size of the metadata
+    std::string metadata_signature;  // signature of the metadata in base64
+    brillo::Blob hash;               // SHA256 hash of the payload
+    InstallPayloadType type{InstallPayloadType::kUnknown};
+    // Only download manifest and fill in partitions in install plan without
+    // apply the payload if true. Will be set by DownloadAction when resuming
+    // multi-payload.
+    bool already_applied = false;
+
+    bool operator==(const Payload& that) const {
+      return size == that.size && metadata_size == that.metadata_size &&
+             metadata_signature == that.metadata_signature &&
+             hash == that.hash && type == that.type &&
+             already_applied == that.already_applied;
+    }
+  };
+  std::vector<Payload> payloads;
 
   // The partition slots used for the update.
   BootControlInterface::Slot source_slot{BootControlInterface::kInvalidSlot};
@@ -103,6 +119,14 @@
   // False otherwise.
   bool powerwash_required{false};
 
+  // True if the updated slot should be marked active on success.
+  // False otherwise.
+  bool switch_slot_on_reboot{true};
+
+  // True if the update should run its post-install step.
+  // False otherwise.
+  bool run_post_install{true};
+
   // If not blank, a base-64 encoded representation of the PEM-encoded
   // public key in the response.
   std::string public_key_rsa;
diff --git a/payload_consumer/postinstall_runner_action.cc b/payload_consumer/postinstall_runner_action.cc
index 27a9ed6..cedecda 100644
--- a/payload_consumer/postinstall_runner_action.cc
+++ b/payload_consumer/postinstall_runner_action.cc
@@ -82,6 +82,11 @@
 }
 
 void PostinstallRunnerAction::PerformPartitionPostinstall() {
+  if (!install_plan_.run_post_install) {
+    LOG(INFO) << "Skipping post-install according to install plan.";
+    return CompletePostinstall(ErrorCode::kSuccess);
+  }
+
   if (install_plan_.download_url.empty()) {
     LOG(INFO) << "Skipping post-install during rollback";
     return CompletePostinstall(ErrorCode::kSuccess);
@@ -331,15 +336,21 @@
 void PostinstallRunnerAction::CompletePostinstall(ErrorCode error_code) {
   // We only attempt to mark the new slot as active if all the postinstall
   // steps succeeded.
-  if (error_code == ErrorCode::kSuccess &&
-      !boot_control_->SetActiveBootSlot(install_plan_.target_slot)) {
-    error_code = ErrorCode::kPostinstallRunnerError;
+  if (error_code == ErrorCode::kSuccess) {
+    if (install_plan_.switch_slot_on_reboot) {
+      if (!boot_control_->SetActiveBootSlot(install_plan_.target_slot)) {
+        error_code = ErrorCode::kPostinstallRunnerError;
+      }
+    } else {
+      error_code = ErrorCode::kUpdatedButNotActive;
+    }
   }
 
   ScopedActionCompleter completer(processor_, this);
   completer.set_code(error_code);
 
-  if (error_code != ErrorCode::kSuccess) {
+  if (error_code != ErrorCode::kSuccess &&
+      error_code != ErrorCode::kUpdatedButNotActive) {
     LOG(ERROR) << "Postinstall action failed.";
 
     // Undo any changes done to trigger Powerwash.
diff --git a/payload_consumer/postinstall_runner_action_unittest.cc b/payload_consumer/postinstall_runner_action_unittest.cc
index a319299..f15171b 100644
--- a/payload_consumer/postinstall_runner_action_unittest.cc
+++ b/payload_consumer/postinstall_runner_action_unittest.cc
@@ -28,7 +28,6 @@
 #include <base/message_loop/message_loop.h>
 #include <base/strings/string_util.h>
 #include <base/strings/stringprintf.h>
-#include <brillo/bind_lambda.h>
 #include <brillo/message_loops/base_message_loop.h>
 #include <brillo/message_loops/message_loop_utils.h>
 #include <gmock/gmock.h>
diff --git a/payload_generator/delta_diff_utils.cc b/payload_generator/delta_diff_utils.cc
index fbb6066..bcbc3a5 100644
--- a/payload_generator/delta_diff_utils.cc
+++ b/payload_generator/delta_diff_utils.cc
@@ -38,6 +38,7 @@
 #include <base/strings/string_util.h>
 #include <base/strings/stringprintf.h>
 #include <base/threading/simple_thread.h>
+#include <brillo/data_encoding.h>
 #include <bsdiff/bsdiff.h>
 
 #include "update_engine/common/hash_calculator.h"
@@ -210,8 +211,8 @@
   void MergeOperation(vector<AnnotatedOperation>* aops);
 
  private:
-  const string old_part_;
-  const string new_part_;
+  const string& old_part_;
+  const string& new_part_;
   const PayloadVersion& version_;
 
   // The block ranges of the old/new file within the src/tgt image
@@ -883,7 +884,8 @@
   TEST_AND_RETURN_FALSE(hasher.Finalize());
   const brillo::Blob& hash = hasher.raw_hash();
   info->set_hash(hash.data(), hash.size());
-  LOG(INFO) << part.path << ": size=" << part.size << " hash=" << hasher.hash();
+  LOG(INFO) << part.path << ": size=" << part.size
+            << " hash=" << brillo::data_encoding::Base64Encode(hash);
   return true;
 }
 
diff --git a/payload_generator/generate_delta_main.cc b/payload_generator/generate_delta_main.cc
index 96915c1..2729bc4 100644
--- a/payload_generator/generate_delta_main.cc
+++ b/payload_generator/generate_delta_main.cc
@@ -167,15 +167,19 @@
   }
 }
 
-void VerifySignedPayload(const string& in_file,
-                         const string& public_key) {
+int VerifySignedPayload(const string& in_file, const string& public_key) {
   LOG(INFO) << "Verifying signed payload.";
   LOG_IF(FATAL, in_file.empty())
       << "Must pass --in_file to verify signed payload.";
   LOG_IF(FATAL, public_key.empty())
       << "Must pass --public_key to verify signed payload.";
-  CHECK(PayloadSigner::VerifySignedPayload(in_file, public_key));
+  if (!PayloadSigner::VerifySignedPayload(in_file, public_key)) {
+    LOG(INFO) << "VerifySignedPayload failed";
+    return 1;
+  }
+
   LOG(INFO) << "Done verifying signed payload.";
+  return 0;
 }
 
 // TODO(deymo): This function is likely broken for deltas minor version 2 or
@@ -190,10 +194,11 @@
   FakeHardware fake_hardware;
   MemoryPrefs prefs;
   InstallPlan install_plan;
+  InstallPlan::Payload payload;
   install_plan.source_slot =
       config.is_delta ? 0 : BootControlInterface::kInvalidSlot;
   install_plan.target_slot = 1;
-  install_plan.payload_type =
+  payload.type =
       config.is_delta ? InstallPayloadType::kDelta : InstallPayloadType::kFull;
 
   for (size_t i = 0; i < config.target.partitions.size(); i++) {
@@ -220,6 +225,7 @@
                            &fake_hardware,
                            nullptr,
                            &install_plan,
+                           &payload,
                            true);  // is_interactive
 
   brillo::Blob buf(1024 * 1024);
@@ -329,6 +335,10 @@
   DEFINE_string(properties_file, "",
                 "If passed, dumps the payload properties of the payload passed "
                 "in --in_file and exits.");
+  DEFINE_int64(max_timestamp,
+               0,
+               "The maximum timestamp of the OS allowed to apply this "
+               "payload.");
 
   DEFINE_string(old_channel, "",
                 "The channel for the old image. 'dev-channel', 'npo-channel', "
@@ -404,8 +414,7 @@
   if (!FLAGS_public_key.empty()) {
     LOG_IF(WARNING, FLAGS_public_key_version != -1)
         << "--public_key_version is deprecated and ignored.";
-    VerifySignedPayload(FLAGS_in_file, FLAGS_public_key);
-    return 0;
+    return VerifySignedPayload(FLAGS_in_file, FLAGS_public_key);
   }
   if (!FLAGS_properties_file.empty()) {
     return ExtractProperties(FLAGS_in_file, FLAGS_properties_file) ? 0 : 1;
@@ -571,6 +580,8 @@
     LOG(INFO) << "Using provided minor_version=" << FLAGS_minor_version;
   }
 
+  payload_config.max_timestamp = FLAGS_max_timestamp;
+
   LOG(INFO) << "Generating " << (payload_config.is_delta ? "delta" : "full")
             << " update";
 
diff --git a/payload_generator/payload_file.cc b/payload_generator/payload_file.cc
index 38aa0da..f48d2a2 100644
--- a/payload_generator/payload_file.cc
+++ b/payload_generator/payload_file.cc
@@ -73,6 +73,7 @@
     *(manifest_.mutable_new_image_info()) = config.target.image_info;
 
   manifest_.set_block_size(config.block_size);
+  manifest_.set_max_timestamp(config.max_timestamp);
   return true;
 }
 
@@ -315,10 +316,8 @@
 
 bool PayloadFile::AddOperationHash(InstallOperation* op,
                                    const brillo::Blob& buf) {
-  HashCalculator hasher;
-  TEST_AND_RETURN_FALSE(hasher.Update(buf.data(), buf.size()));
-  TEST_AND_RETURN_FALSE(hasher.Finalize());
-  const brillo::Blob& hash = hasher.raw_hash();
+  brillo::Blob hash;
+  TEST_AND_RETURN_FALSE(HashCalculator::RawHashOfData(buf, &hash));
   op->set_data_sha256_hash(hash.data(), hash.size());
   return true;
 }
diff --git a/payload_generator/payload_generation_config.h b/payload_generator/payload_generation_config.h
index ca6fb04..c553d29 100644
--- a/payload_generator/payload_generation_config.h
+++ b/payload_generator/payload_generation_config.h
@@ -186,6 +186,9 @@
 
   // The block size used for all the operations in the manifest.
   size_t block_size = 4096;
+
+  // The maximum timestamp of the OS allowed to apply this payload.
+  int64_t max_timestamp = 0;
 };
 
 }  // namespace chromeos_update_engine
diff --git a/payload_generator/squashfs_filesystem_unittest.cc b/payload_generator/squashfs_filesystem_unittest.cc
index 13b4dd3..29fcf1c 100644
--- a/payload_generator/squashfs_filesystem_unittest.cc
+++ b/payload_generator/squashfs_filesystem_unittest.cc
@@ -108,6 +108,8 @@
   }
 };
 
+// CreateFromFile() depends on unsquashfs -m, which only exists in Chrome OS.
+#ifdef __CHROMEOS__
 TEST_F(SquashfsFilesystemTest, EmptyFilesystemTest) {
   unique_ptr<SquashfsFilesystem> fs = SquashfsFilesystem::CreateFromFile(
       GetBuildArtifactsPath("gen/disk_sqfs_empty.img"), true);
@@ -146,6 +148,7 @@
   EXPECT_EQ(files[0].name, file.name);
   EXPECT_EQ(files[0].extents, file.extents);
 }
+#endif  // __CHROMEOS__
 
 TEST_F(SquashfsFilesystemTest, SimpleFileMapTest) {
   string filemap = R"(dir1/file1 96 4000
diff --git a/payload_state.cc b/payload_state.cc
index 846f901..4992606 100644
--- a/payload_state.cc
+++ b/payload_state.cc
@@ -32,6 +32,7 @@
 #include "update_engine/common/prefs.h"
 #include "update_engine/common/utils.h"
 #include "update_engine/connection_manager_interface.h"
+#include "update_engine/metrics_reporter_interface.h"
 #include "update_engine/metrics_utils.h"
 #include "update_engine/omaha_request_params.h"
 #include "update_engine/payload_consumer/install_plan.h"
@@ -44,6 +45,8 @@
 
 namespace chromeos_update_engine {
 
+using metrics_utils::GetPersistedValue;
+
 const TimeDelta PayloadState::kDurationSlack = TimeDelta::FromSeconds(600);
 
 // We want to upperbound backoffs to 16 days
@@ -122,11 +125,16 @@
     return;
   }
 
+  // Always start from payload index 0, even for resume, to download partition
+  // info from previous payloads.
+  payload_index_ = 0;
+
   // This is the earliest point at which we can validate whether the URL index
   // we loaded from the persisted state is a valid value. If the response
   // hasn't changed but the URL index is invalid, it's indicative of some
   // tampering of the persisted state.
-  if (static_cast<uint32_t>(url_index_) >= candidate_urls_.size()) {
+  if (payload_index_ >= candidate_urls_.size() ||
+      url_index_ >= candidate_urls_[payload_index_].size()) {
     LOG(INFO) << "Resetting all payload state as the url index seems to have "
                  "been tampered with";
     ResetPersistedState();
@@ -230,8 +238,8 @@
       break;
 
     case AttemptType::kRollback:
-      metrics::ReportRollbackMetrics(system_state_,
-                                     metrics::RollbackResult::kSuccess);
+      system_state_->metrics_reporter()->ReportRollbackMetrics(
+          metrics::RollbackResult::kSuccess);
       break;
   }
   attempt_error_code_ = ErrorCode::kSuccess;
@@ -239,8 +247,9 @@
   // Reset the number of responses seen since it counts from the last
   // successful update, e.g. now.
   SetNumResponsesSeen(0);
+  SetPayloadIndex(0);
 
-  CreateSystemUpdatedMarkerFile();
+  metrics_utils::SetSystemUpdatedMarker(system_state_->clock(), prefs_);
 }
 
 void PayloadState::UpdateFailed(ErrorCode error) {
@@ -264,8 +273,8 @@
       break;
 
     case AttemptType::kRollback:
-      metrics::ReportRollbackMetrics(system_state_,
-                                     metrics::RollbackResult::kFailed);
+      system_state_->metrics_reporter()->ReportRollbackMetrics(
+          metrics::RollbackResult::kFailed);
       break;
   }
 
@@ -296,6 +305,7 @@
     case ErrorCode::kPayloadMismatchedType:
     case ErrorCode::kUnsupportedMajorPayloadVersion:
     case ErrorCode::kUnsupportedMinorPayloadVersion:
+    case ErrorCode::kPayloadTimestampError:
       IncrementUrlIndex();
       break;
 
@@ -348,7 +358,7 @@
     case ErrorCode::kOmahaRequestXMLHasEntityDecl:
     case ErrorCode::kFilesystemVerifierError:
     case ErrorCode::kUserCanceled:
-    case ErrorCode::kOmahaUpdateIgnoredOverCellular:
+    case ErrorCode::kUpdatedButNotActive:
       LOG(INFO) << "Not incrementing URL index or failure count for this error";
       break;
 
@@ -386,14 +396,16 @@
     LOG(INFO) << "Payload backoff disabled for interactive update checks.";
     return false;
   }
-  if (response_.is_delta_payload) {
-    // If delta payloads fail, we want to fallback quickly to full payloads as
-    // they are more likely to succeed. Exponential backoffs would greatly
-    // slow down the fallback to full payloads.  So we don't backoff for delta
-    // payloads.
-    LOG(INFO) << "No backoffs for delta payloads. "
-              << "Can proceed with the download";
-    return false;
+  for (const auto& package : response_.packages) {
+    if (package.is_delta) {
+      // If delta payloads fail, we want to fallback quickly to full payloads as
+      // they are more likely to succeed. Exponential backoffs would greatly
+      // slow down the fallback to full payloads.  So we don't backoff for delta
+      // payloads.
+      LOG(INFO) << "No backoffs for delta payloads. "
+                << "Can proceed with the download";
+      return false;
+    }
   }
 
   if (!system_state_->hardware()->IsOfficialBuild()) {
@@ -434,7 +446,7 @@
 
 void PayloadState::IncrementFullPayloadAttemptNumber() {
   // Update the payload attempt number for full payloads and the backoff time.
-  if (response_.is_delta_payload) {
+  if (response_.packages[payload_index_].is_delta) {
     LOG(INFO) << "Not incrementing payload attempt number for delta payloads";
     return;
   }
@@ -445,21 +457,23 @@
 }
 
 void PayloadState::IncrementUrlIndex() {
-  uint32_t next_url_index = GetUrlIndex() + 1;
-  if (next_url_index < candidate_urls_.size()) {
+  size_t next_url_index = url_index_ + 1;
+  size_t max_url_size = 0;
+  for (const auto& urls : candidate_urls_)
+    max_url_size = std::max(max_url_size, urls.size());
+  if (next_url_index < max_url_size) {
     LOG(INFO) << "Incrementing the URL index for next attempt";
     SetUrlIndex(next_url_index);
   } else {
-    LOG(INFO) << "Resetting the current URL index (" << GetUrlIndex() << ") to "
-              << "0 as we only have " << candidate_urls_.size()
-              << " candidate URL(s)";
+    LOG(INFO) << "Resetting the current URL index (" << url_index_ << ") to "
+              << "0 as we only have " << max_url_size << " candidate URL(s)";
     SetUrlIndex(0);
     IncrementPayloadAttemptNumber();
     IncrementFullPayloadAttemptNumber();
   }
 
   // If we have multiple URLs, record that we just switched to another one
-  if (candidate_urls_.size() > 1)
+  if (max_url_size > 1)
     SetUrlSwitchCount(url_switch_count_ + 1);
 
   // Whenever we update the URL index, we should also clear the URL failure
@@ -520,12 +534,14 @@
 
   if (using_p2p_for_downloading_) {
     current_download_source_ = kDownloadSourceHttpPeer;
-  } else if (GetUrlIndex() < candidate_urls_.size())  {
-    string current_url = candidate_urls_[GetUrlIndex()];
-    if (base::StartsWith(current_url, "https://",
-                         base::CompareCase::INSENSITIVE_ASCII)) {
+  } else if (payload_index_ < candidate_urls_.size() &&
+             candidate_urls_[payload_index_].size() != 0) {
+    const string& current_url = candidate_urls_[payload_index_][GetUrlIndex()];
+    if (base::StartsWith(
+            current_url, "https://", base::CompareCase::INSENSITIVE_ASCII)) {
       current_download_source_ = kDownloadSourceHttpsServer;
-    } else if (base::StartsWith(current_url, "http://",
+    } else if (base::StartsWith(current_url,
+                                "http://",
                                 base::CompareCase::INSENSITIVE_ASCII)) {
       current_download_source_ = kDownloadSourceHttpServer;
     }
@@ -549,16 +565,17 @@
 }
 
 PayloadType PayloadState::CalculatePayloadType() {
-  PayloadType payload_type;
-  OmahaRequestParams* params = system_state_->request_params();
-  if (response_.is_delta_payload) {
-    payload_type = kPayloadTypeDelta;
-  } else if (params->delta_okay()) {
-    payload_type = kPayloadTypeFull;
-  } else {  // Full payload, delta was not allowed by request.
-    payload_type = kPayloadTypeForcedFull;
+  for (const auto& package : response_.packages) {
+    if (package.is_delta) {
+      return kPayloadTypeDelta;
+    }
   }
-  return payload_type;
+  OmahaRequestParams* params = system_state_->request_params();
+  if (params->delta_okay()) {
+    return kPayloadTypeFull;
+  }
+  // Full payload, delta was not allowed by request.
+  return kPayloadTypeForcedFull;
 }
 
 // TODO(zeuthen): Currently we don't report the UpdateEngine.Attempt.*
@@ -570,7 +587,7 @@
 
   PayloadType payload_type = CalculatePayloadType();
 
-  int64_t payload_size = response_.size;
+  int64_t payload_size = GetPayloadSize();
 
   int64_t payload_bytes_downloaded = attempt_num_bytes_downloaded_;
 
@@ -618,24 +635,28 @@
     case metrics::AttemptResult::kPostInstallFailed:
     case metrics::AttemptResult::kAbnormalTermination:
     case metrics::AttemptResult::kUpdateCanceled:
+    case metrics::AttemptResult::kUpdateSucceededNotActive:
     case metrics::AttemptResult::kNumConstants:
     case metrics::AttemptResult::kUnset:
       break;
   }
 
-  metrics::ReportUpdateAttemptMetrics(system_state_,
-                                      attempt_number,
-                                      payload_type,
-                                      duration,
-                                      duration_uptime,
-                                      payload_size,
-                                      payload_bytes_downloaded,
-                                      payload_download_speed_bps,
-                                      download_source,
-                                      attempt_result,
-                                      internal_error_code,
-                                      payload_download_error_code,
-                                      attempt_connection_type_);
+  system_state_->metrics_reporter()->ReportUpdateAttemptMetrics(
+      system_state_,
+      attempt_number,
+      payload_type,
+      duration,
+      duration_uptime,
+      payload_size,
+      attempt_result,
+      internal_error_code);
+
+  system_state_->metrics_reporter()->ReportUpdateAttemptDownloadMetrics(
+      payload_bytes_downloaded,
+      payload_download_speed_bps,
+      download_source,
+      payload_download_error_code,
+      attempt_connection_type_);
 }
 
 void PayloadState::PersistAttemptMetrics() {
@@ -660,7 +681,8 @@
   if (!attempt_in_progress)
     return;
 
-  metrics::ReportAbnormallyTerminatedUpdateAttemptMetrics(system_state_);
+  system_state_->metrics_reporter()
+      ->ReportAbnormallyTerminatedUpdateAttemptMetrics();
 
   ClearPersistedAttemptMetrics();
 }
@@ -716,22 +738,22 @@
 
   PayloadType payload_type = CalculatePayloadType();
 
-  int64_t payload_size = response_.size;
+  int64_t payload_size = GetPayloadSize();
 
   int attempt_count = GetPayloadAttemptNumber();
 
   int updates_abandoned_count = num_responses_seen_ - 1;
 
-  metrics::ReportSuccessfulUpdateMetrics(system_state_,
-                                         attempt_count,
-                                         updates_abandoned_count,
-                                         payload_type,
-                                         payload_size,
-                                         total_bytes_by_source,
-                                         download_overhead_percentage,
-                                         duration,
-                                         reboot_count,
-                                         url_switch_count);
+  system_state_->metrics_reporter()->ReportSuccessfulUpdateMetrics(
+      attempt_count,
+      updates_abandoned_count,
+      payload_type,
+      payload_size,
+      total_bytes_by_source,
+      download_overhead_percentage,
+      duration,
+      reboot_count,
+      url_switch_count);
 }
 
 void PayloadState::UpdateNumReboots() {
@@ -745,16 +767,14 @@
 }
 
 void PayloadState::SetNumReboots(uint32_t num_reboots) {
-  CHECK(prefs_);
   num_reboots_ = num_reboots;
-  prefs_->SetInt64(kPrefsNumReboots, num_reboots);
-  LOG(INFO) << "Number of Reboots during current update attempt = "
-            << num_reboots_;
+  metrics_utils::SetNumReboots(num_reboots, prefs_);
 }
 
 void PayloadState::ResetPersistedState() {
   SetPayloadAttemptNumber(0);
   SetFullPayloadAttemptNumber(0);
+  SetPayloadIndex(0);
   SetUrlIndex(0);
   SetUrlFailureCount(0);
   SetUrlSwitchCount(0);
@@ -785,46 +805,34 @@
   }
 }
 
-int64_t PayloadState::GetPersistedValue(const string& key) {
-  CHECK(prefs_);
-  if (!prefs_->Exists(key))
-    return 0;
+string PayloadState::CalculateResponseSignature() {
+  string response_sign;
+  for (size_t i = 0; i < response_.packages.size(); i++) {
+    const auto& package = response_.packages[i];
+    response_sign += base::StringPrintf(
+        "Payload %zu:\n"
+        "  Size = %ju\n"
+        "  Sha256 Hash = %s\n"
+        "  Metadata Size = %ju\n"
+        "  Metadata Signature = %s\n"
+        "  Is Delta = %d\n"
+        "  NumURLs = %zu\n",
+        i,
+        static_cast<uintmax_t>(package.size),
+        package.hash.c_str(),
+        static_cast<uintmax_t>(package.metadata_size),
+        package.metadata_signature.c_str(),
+        package.is_delta,
+        candidate_urls_[i].size());
 
-  int64_t stored_value;
-  if (!prefs_->GetInt64(key, &stored_value))
-    return 0;
-
-  if (stored_value < 0) {
-    LOG(ERROR) << key << ": Invalid value (" << stored_value
-               << ") in persisted state. Defaulting to 0";
-    return 0;
+    for (size_t j = 0; j < candidate_urls_[i].size(); j++)
+      response_sign += base::StringPrintf(
+          "  Candidate Url%zu = %s\n", j, candidate_urls_[i][j].c_str());
   }
 
-  return stored_value;
-}
-
-string PayloadState::CalculateResponseSignature() {
-  string response_sign = base::StringPrintf(
-      "NumURLs = %d\n", static_cast<int>(candidate_urls_.size()));
-
-  for (size_t i = 0; i < candidate_urls_.size(); i++)
-    response_sign += base::StringPrintf("Candidate Url%d = %s\n",
-                                        static_cast<int>(i),
-                                        candidate_urls_[i].c_str());
-
   response_sign += base::StringPrintf(
-      "Payload Size = %ju\n"
-      "Payload Sha256 Hash = %s\n"
-      "Metadata Size = %ju\n"
-      "Metadata Signature = %s\n"
-      "Is Delta Payload = %d\n"
       "Max Failure Count Per Url = %d\n"
       "Disable Payload Backoff = %d\n",
-      static_cast<uintmax_t>(response_.size),
-      response_.hash.c_str(),
-      static_cast<uintmax_t>(response_.metadata_size),
-      response_.metadata_signature.c_str(),
-      response_.is_delta_payload,
       response_.max_failure_count_per_url,
       response_.disable_payload_backoff);
   return response_sign;
@@ -847,19 +855,18 @@
 }
 
 void PayloadState::LoadPayloadAttemptNumber() {
-  SetPayloadAttemptNumber(GetPersistedValue(kPrefsPayloadAttemptNumber));
+  SetPayloadAttemptNumber(
+      GetPersistedValue(kPrefsPayloadAttemptNumber, prefs_));
 }
 
 void PayloadState::LoadFullPayloadAttemptNumber() {
-  SetFullPayloadAttemptNumber(GetPersistedValue(
-      kPrefsFullPayloadAttemptNumber));
+  SetFullPayloadAttemptNumber(
+      GetPersistedValue(kPrefsFullPayloadAttemptNumber, prefs_));
 }
 
 void PayloadState::SetPayloadAttemptNumber(int payload_attempt_number) {
-  CHECK(prefs_);
   payload_attempt_number_ = payload_attempt_number;
-  LOG(INFO) << "Payload Attempt Number = " << payload_attempt_number_;
-  prefs_->SetInt64(kPrefsPayloadAttemptNumber, payload_attempt_number_);
+  metrics_utils::SetPayloadAttemptNumber(payload_attempt_number, prefs_);
 }
 
 void PayloadState::SetFullPayloadAttemptNumber(
@@ -871,8 +878,22 @@
       full_payload_attempt_number_);
 }
 
+void PayloadState::SetPayloadIndex(size_t payload_index) {
+  CHECK(prefs_);
+  payload_index_ = payload_index;
+  LOG(INFO) << "Payload Index = " << payload_index_;
+  prefs_->SetInt64(kPrefsUpdateStatePayloadIndex, payload_index_);
+}
+
+bool PayloadState::NextPayload() {
+  if (payload_index_ + 1 >= candidate_urls_.size())
+    return false;
+  SetPayloadIndex(payload_index_ + 1);
+  return true;
+}
+
 void PayloadState::LoadUrlIndex() {
-  SetUrlIndex(GetPersistedValue(kPrefsCurrentUrlIndex));
+  SetUrlIndex(GetPersistedValue(kPrefsCurrentUrlIndex, prefs_));
 }
 
 void PayloadState::SetUrlIndex(uint32_t url_index) {
@@ -887,8 +908,8 @@
 }
 
 void PayloadState::LoadScatteringWaitPeriod() {
-  SetScatteringWaitPeriod(
-      TimeDelta::FromSeconds(GetPersistedValue(kPrefsWallClockWaitPeriod)));
+  SetScatteringWaitPeriod(TimeDelta::FromSeconds(
+      GetPersistedValue(kPrefsWallClockWaitPeriod, prefs_)));
 }
 
 void PayloadState::SetScatteringWaitPeriod(TimeDelta wait_period) {
@@ -905,7 +926,7 @@
 }
 
 void PayloadState::LoadUrlSwitchCount() {
-  SetUrlSwitchCount(GetPersistedValue(kPrefsUrlSwitchCount));
+  SetUrlSwitchCount(GetPersistedValue(kPrefsUrlSwitchCount, prefs_));
 }
 
 void PayloadState::SetUrlSwitchCount(uint32_t url_switch_count) {
@@ -916,7 +937,7 @@
 }
 
 void PayloadState::LoadUrlFailureCount() {
-  SetUrlFailureCount(GetPersistedValue(kPrefsCurrentUrlFailureCount));
+  SetUrlFailureCount(GetPersistedValue(kPrefsCurrentUrlFailureCount, prefs_));
 }
 
 void PayloadState::SetUrlFailureCount(uint32_t url_failure_count) {
@@ -999,12 +1020,8 @@
 }
 
 void PayloadState::SetUpdateTimestampStart(const Time& value) {
-  CHECK(prefs_);
   update_timestamp_start_ = value;
-  prefs_->SetInt64(kPrefsUpdateTimestampStart,
-                   update_timestamp_start_.ToInternalValue());
-  LOG(INFO) << "Update Timestamp Start = "
-            << utils::ToString(update_timestamp_start_);
+  metrics_utils::SetUpdateTimestampStart(value, prefs_);
 }
 
 void PayloadState::SetUpdateTimestampEnd(const Time& value) {
@@ -1050,7 +1067,7 @@
 }
 
 void PayloadState::LoadNumReboots() {
-  SetNumReboots(GetPersistedValue(kPrefsNumReboots));
+  SetNumReboots(GetPersistedValue(kPrefsNumReboots, prefs_));
 }
 
 void PayloadState::LoadRollbackVersion() {
@@ -1102,7 +1119,7 @@
 
 void PayloadState::LoadCurrentBytesDownloaded(DownloadSource source) {
   string key = GetPrefsKey(kPrefsCurrentBytesDownloaded, source);
-  SetCurrentBytesDownloaded(source, GetPersistedValue(key), true);
+  SetCurrentBytesDownloaded(source, GetPersistedValue(key, prefs_), true);
 }
 
 void PayloadState::SetCurrentBytesDownloaded(
@@ -1126,7 +1143,7 @@
 
 void PayloadState::LoadTotalBytesDownloaded(DownloadSource source) {
   string key = GetPrefsKey(kPrefsTotalBytesDownloaded, source);
-  SetTotalBytesDownloaded(source, GetPersistedValue(key), true);
+  SetTotalBytesDownloaded(source, GetPersistedValue(key, prefs_), true);
 }
 
 void PayloadState::SetTotalBytesDownloaded(
@@ -1150,7 +1167,7 @@
 }
 
 void PayloadState::LoadNumResponsesSeen() {
-  SetNumResponsesSeen(GetPersistedValue(kPrefsNumResponsesSeen));
+  SetNumResponsesSeen(GetPersistedValue(kPrefsNumResponsesSeen, prefs_));
 }
 
 void PayloadState::SetNumResponsesSeen(int num_responses_seen) {
@@ -1173,38 +1190,22 @@
   }
 
   candidate_urls_.clear();
-  for (size_t i = 0; i < response_.payload_urls.size(); i++) {
-    string candidate_url = response_.payload_urls[i];
-    if (base::StartsWith(candidate_url, "http://",
-                         base::CompareCase::INSENSITIVE_ASCII) &&
-        !http_url_ok) {
-      continue;
+  for (const auto& package : response_.packages) {
+    candidate_urls_.emplace_back();
+    for (const string& candidate_url : package.payload_urls) {
+      if (base::StartsWith(
+              candidate_url, "http://", base::CompareCase::INSENSITIVE_ASCII) &&
+          !http_url_ok) {
+        continue;
+      }
+      candidate_urls_.back().push_back(candidate_url);
+      LOG(INFO) << "Candidate Url" << (candidate_urls_.back().size() - 1)
+                << ": " << candidate_url;
     }
-    candidate_urls_.push_back(candidate_url);
-    LOG(INFO) << "Candidate Url" << (candidate_urls_.size() - 1)
-              << ": " << candidate_url;
+    LOG(INFO) << "Found " << candidate_urls_.back().size() << " candidate URLs "
+              << "out of " << package.payload_urls.size()
+              << " URLs supplied in package " << candidate_urls_.size() - 1;
   }
-
-  LOG(INFO) << "Found " << candidate_urls_.size() << " candidate URLs "
-            << "out of " << response_.payload_urls.size() << " URLs supplied";
-}
-
-void PayloadState::CreateSystemUpdatedMarkerFile() {
-  CHECK(prefs_);
-  int64_t value = system_state_->clock()->GetWallclockTime().ToInternalValue();
-  prefs_->SetInt64(kPrefsSystemUpdatedMarker, value);
-}
-
-void PayloadState::BootedIntoUpdate(TimeDelta time_to_reboot) {
-  // Send |time_to_reboot| as a UMA stat.
-  string metric = metrics::kMetricTimeToRebootMinutes;
-  system_state_->metrics_lib()->SendToUMA(metric,
-                                          time_to_reboot.InMinutes(),
-                                          0,         // min: 0 minute
-                                          30*24*60,  // max: 1 month (approx)
-                                          kNumDefaultUmaBuckets);
-  LOG(INFO) << "Uploading " << utils::FormatTimeDelta(time_to_reboot)
-            << " for metric " <<  metric;
 }
 
 void PayloadState::UpdateEngineStarted() {
@@ -1216,24 +1217,11 @@
   if (!system_state_->system_rebooted())
     return;
 
-  // Figure out if we just booted into a new update
-  if (prefs_->Exists(kPrefsSystemUpdatedMarker)) {
-    int64_t stored_value;
-    if (prefs_->GetInt64(kPrefsSystemUpdatedMarker, &stored_value)) {
-      Time system_updated_at = Time::FromInternalValue(stored_value);
-      if (!system_updated_at.is_null()) {
-        TimeDelta time_to_reboot =
-            system_state_->clock()->GetWallclockTime() - system_updated_at;
-        if (time_to_reboot.ToInternalValue() < 0) {
-          LOG(ERROR) << "time_to_reboot is negative - system_updated_at: "
-                     << utils::ToString(system_updated_at);
-        } else {
-          BootedIntoUpdate(time_to_reboot);
-        }
-      }
-    }
-    prefs_->Delete(kPrefsSystemUpdatedMarker);
-  }
+  // Report time_to_reboot if we booted into a new update.
+  metrics_utils::LoadAndReportTimeToReboot(
+      system_state_->metrics_reporter(), prefs_, system_state_->clock());
+  prefs_->Delete(kPrefsSystemUpdatedMarker);
+
   // Check if it is needed to send metrics about a failed reboot into a new
   // version.
   ReportFailedBootIfNeeded();
@@ -1267,15 +1255,8 @@
       }
 
       // Report the UMA metric of the current boot failure.
-      string metric = metrics::kMetricFailedUpdateCount;
-      LOG(INFO) << "Uploading " << target_attempt
-                << " (count) for metric " <<  metric;
-      system_state_->metrics_lib()->SendToUMA(
-           metric,
-           target_attempt,
-           1,    // min value
-           50,   // max value
-           kNumDefaultUmaBuckets);
+      system_state_->metrics_reporter()->ReportFailedUpdateCount(
+          target_attempt);
     } else {
       prefs_->Delete(kPrefsTargetVersionAttempt);
       prefs_->Delete(kPrefsTargetVersionUniqueId);
@@ -1332,7 +1313,7 @@
 }
 
 void PayloadState::LoadP2PNumAttempts() {
-  SetP2PNumAttempts(GetPersistedValue(kPrefsP2PNumAttempts));
+  SetP2PNumAttempts(GetPersistedValue(kPrefsP2PNumAttempts, prefs_));
 }
 
 Time PayloadState::GetP2PFirstAttemptTimestamp() {
@@ -1349,7 +1330,8 @@
 }
 
 void PayloadState::LoadP2PFirstAttemptTimestamp() {
-  int64_t stored_value = GetPersistedValue(kPrefsP2PFirstAttemptTimestamp);
+  int64_t stored_value =
+      GetPersistedValue(kPrefsP2PFirstAttemptTimestamp, prefs_);
   Time stored_time = Time::FromInternalValue(stored_value);
   SetP2PFirstAttemptTimestamp(stored_time);
 }
@@ -1395,4 +1377,11 @@
   return true;
 }
 
+int64_t PayloadState::GetPayloadSize() {
+  int64_t payload_size = 0;
+  for (const auto& package : response_.packages)
+    payload_size += package.size;
+  return payload_size;
+}
+
 }  // namespace chromeos_update_engine
diff --git a/payload_state.h b/payload_state.h
index 46711b6..24e9900 100644
--- a/payload_state.h
+++ b/payload_state.h
@@ -17,6 +17,7 @@
 #ifndef UPDATE_ENGINE_PAYLOAD_STATE_H_
 #define UPDATE_ENGINE_PAYLOAD_STATE_H_
 
+#include <algorithm>
 #include <string>
 #include <vector>
 
@@ -24,7 +25,7 @@
 #include <gtest/gtest_prod.h>  // for FRIEND_TEST
 
 #include "update_engine/common/prefs_interface.h"
-#include "update_engine/metrics.h"
+#include "update_engine/metrics_constants.h"
 #include "update_engine/payload_state_interface.h"
 
 namespace chromeos_update_engine {
@@ -79,7 +80,9 @@
   }
 
   inline std::string GetCurrentUrl() override {
-    return candidate_urls_.size() ? candidate_urls_[url_index_] : "";
+    return candidate_urls_.size() && candidate_urls_[payload_index_].size()
+               ? candidate_urls_[payload_index_][url_index_]
+               : "";
   }
 
   inline uint32_t GetUrlFailureCount() override {
@@ -151,6 +154,8 @@
     return attempt_error_code_;
   }
 
+  bool NextPayload() override;
+
  private:
   enum class AttemptType {
     kUpdate,
@@ -236,10 +241,6 @@
   // reset on a new update.
   void ResetDownloadSourcesOnNewUpdate();
 
-  // Returns the persisted value from prefs_ for the given key. It also
-  // validates that the value returned is non-negative.
-  int64_t GetPersistedValue(const std::string& key);
-
   // Calculates the response "signature", which is basically a string composed
   // of the subset of the fields in the current response that affect the
   // behavior of the PayloadState.
@@ -270,6 +271,11 @@
   // of a process restart.
   void SetFullPayloadAttemptNumber(int payload_attempt_number);
 
+  // Sets the current payload index to the given value. Also persists the value
+  // being set so that we resume from the same value in case of a process
+  // restart.
+  void SetPayloadIndex(size_t payload_index);
+
   // Initializes the current URL index from the persisted state.
   void LoadUrlIndex();
 
@@ -368,7 +374,9 @@
   void ResetRollbackVersion();
 
   inline uint32_t GetUrlIndex() {
-    return url_index_;
+    return url_index_ ? std::min(candidate_urls_[payload_index_].size() - 1,
+                                 url_index_)
+                      : 0;
   }
 
   // Computes the list of candidate URLs from the total list of payload URLs in
@@ -393,16 +401,7 @@
   // increments num_reboots.
   void UpdateNumReboots();
 
-  // Writes the current wall-clock time to the kPrefsSystemUpdatedMarker
-  // state variable.
-  void CreateSystemUpdatedMarkerFile();
 
-  // Called at program startup if the device booted into a new update.
-  // The |time_to_reboot| parameter contains the (wall-clock) duration
-  // from when the update successfully completed (the value written
-  // into the kPrefsSystemUpdatedMarker state variable) until the device
-  // was booted into the update (current wall-clock time).
-  void BootedIntoUpdate(base::TimeDelta time_to_reboot);
 
   // Loads the |kPrefsP2PFirstAttemptTimestamp| state variable from disk
   // into |p2p_first_attempt_timestamp_|.
@@ -420,6 +419,9 @@
   // Loads the persisted scattering wallclock-based wait period.
   void LoadScatteringWaitPeriod();
 
+  // Get the total size of all payloads.
+  int64_t GetPayloadSize();
+
   // The global state of the system.
   SystemState* system_state_;
 
@@ -468,12 +470,15 @@
   // we resume from the same value in case of a process restart.
   int full_payload_attempt_number_;
 
+  // The index of the current payload.
+  size_t payload_index_ = 0;
+
   // The index of the current URL.  This type is different from the one in the
   // accessor methods because PrefsInterface supports only int64_t but we want
   // to provide a stronger abstraction of uint32_t.  Each update to this value
   // is persisted so we resume from the same value in case of a process
   // restart.
-  int64_t url_index_;
+  size_t url_index_;
 
   // The count of failures encountered in the current attempt to download using
   // the current URL (specified by url_index_).  Each update to this value is
@@ -543,7 +548,7 @@
 
   // The ordered list of the subset of payload URL candidates which are
   // allowed as per device policy.
-  std::vector<std::string> candidate_urls_;
+  std::vector<std::vector<std::string>> candidate_urls_;
 
   // This stores a blacklisted version set as part of rollback. When we rollback
   // we store the version of the os from which we are rolling back from in order
diff --git a/payload_state_interface.h b/payload_state_interface.h
index 68798ee..4aa25e3 100644
--- a/payload_state_interface.h
+++ b/payload_state_interface.h
@@ -193,6 +193,9 @@
   virtual void SetP2PUrl(const std::string& url) = 0;
   virtual std::string GetP2PUrl() const = 0;
   virtual ErrorCode GetAttemptErrorCode() const = 0;
+
+  // Switch to next payload.
+  virtual bool NextPayload() = 0;
 };
 
 }  // namespace chromeos_update_engine
diff --git a/payload_state_unittest.cc b/payload_state_unittest.cc
index 1fa45c2..f1c3835 100644
--- a/payload_state_unittest.cc
+++ b/payload_state_unittest.cc
@@ -31,6 +31,7 @@
 #include "update_engine/common/test_utils.h"
 #include "update_engine/common/utils.h"
 #include "update_engine/fake_system_state.h"
+#include "update_engine/metrics_reporter_interface.h"
 #include "update_engine/omaha_request_action.h"
 
 using base::Time;
@@ -61,41 +62,44 @@
 
 static void SetupPayloadStateWith2Urls(string hash,
                                        bool http_enabled,
+                                       bool is_delta_payload,
                                        PayloadState* payload_state,
                                        OmahaResponse* response) {
-  response->payload_urls.clear();
-  response->payload_urls.push_back("http://test");
-  response->payload_urls.push_back("https://test");
-  response->size = 523456789;
-  response->hash = hash;
-  response->metadata_size = 558123;
-  response->metadata_signature = "metasign";
+  response->packages.clear();
+  response->packages.push_back({.payload_urls = {"http://test", "https://test"},
+                                .size = 523456789,
+                                .metadata_size = 558123,
+                                .metadata_signature = "metasign",
+                                .hash = hash,
+                                .is_delta = is_delta_payload});
   response->max_failure_count_per_url = 3;
   payload_state->SetResponse(*response);
   string stored_response_sign = payload_state->GetResponseSignature();
 
   string expected_url_https_only =
-      "NumURLs = 1\n"
-      "Candidate Url0 = https://test\n";
+      "  NumURLs = 1\n"
+      "  Candidate Url0 = https://test\n";
 
   string expected_urls_both =
-      "NumURLs = 2\n"
-      "Candidate Url0 = http://test\n"
-      "Candidate Url1 = https://test\n";
+      "  NumURLs = 2\n"
+      "  Candidate Url0 = http://test\n"
+      "  Candidate Url1 = https://test\n";
 
-  string expected_response_sign =
-      (http_enabled ? expected_urls_both : expected_url_https_only) +
-      base::StringPrintf("Payload Size = 523456789\n"
-                         "Payload Sha256 Hash = %s\n"
-                         "Metadata Size = 558123\n"
-                         "Metadata Signature = metasign\n"
-                         "Is Delta Payload = %d\n"
-                         "Max Failure Count Per Url = %d\n"
-                         "Disable Payload Backoff = %d\n",
-                         hash.c_str(),
-                         response->is_delta_payload,
-                         response->max_failure_count_per_url,
-                         response->disable_payload_backoff);
+  string expected_response_sign = base::StringPrintf(
+      "Payload 0:\n"
+      "  Size = 523456789\n"
+      "  Sha256 Hash = %s\n"
+      "  Metadata Size = 558123\n"
+      "  Metadata Signature = metasign\n"
+      "  Is Delta = %d\n"
+      "%s"
+      "Max Failure Count Per Url = %d\n"
+      "Disable Payload Backoff = %d\n",
+      hash.c_str(),
+      response->packages[0].is_delta,
+      (http_enabled ? expected_urls_both : expected_url_https_only).c_str(),
+      response->max_failure_count_per_url,
+      response->disable_payload_backoff);
   EXPECT_EQ(expected_response_sign, stored_response_sign);
 }
 
@@ -129,14 +133,9 @@
   EXPECT_TRUE(payload_state.Initialize(&fake_system_state));
   payload_state.SetResponse(response);
   string stored_response_sign = payload_state.GetResponseSignature();
-  string expected_response_sign = "NumURLs = 0\n"
-                                  "Payload Size = 0\n"
-                                  "Payload Sha256 Hash = \n"
-                                  "Metadata Size = 0\n"
-                                  "Metadata Signature = \n"
-                                  "Is Delta Payload = 0\n"
-                                  "Max Failure Count Per Url = 0\n"
-                                  "Disable Payload Backoff = 0\n";
+  string expected_response_sign =
+      "Max Failure Count Per Url = 0\n"
+      "Disable Payload Backoff = 0\n";
   EXPECT_EQ(expected_response_sign, stored_response_sign);
   EXPECT_EQ("", payload_state.GetCurrentUrl());
   EXPECT_EQ(0U, payload_state.GetUrlFailureCount());
@@ -146,11 +145,11 @@
 
 TEST(PayloadStateTest, SetResponseWorksWithSingleUrl) {
   OmahaResponse response;
-  response.payload_urls.push_back("https://single.url.test");
-  response.size = 123456789;
-  response.hash = "hash";
-  response.metadata_size = 58123;
-  response.metadata_signature = "msign";
+  response.packages.push_back({.payload_urls = {"https://single.url.test"},
+                               .size = 123456789,
+                               .metadata_size = 58123,
+                               .metadata_signature = "msign",
+                               .hash = "hash"});
   FakeSystemState fake_system_state;
   NiceMock<MockPrefs>* prefs = fake_system_state.mock_prefs();
   EXPECT_CALL(*prefs, SetInt64(_, _)).Times(AnyNumber());
@@ -180,15 +179,17 @@
   EXPECT_TRUE(payload_state.Initialize(&fake_system_state));
   payload_state.SetResponse(response);
   string stored_response_sign = payload_state.GetResponseSignature();
-  string expected_response_sign = "NumURLs = 1\n"
-                                  "Candidate Url0 = https://single.url.test\n"
-                                  "Payload Size = 123456789\n"
-                                  "Payload Sha256 Hash = hash\n"
-                                  "Metadata Size = 58123\n"
-                                  "Metadata Signature = msign\n"
-                                  "Is Delta Payload = 0\n"
-                                  "Max Failure Count Per Url = 0\n"
-                                  "Disable Payload Backoff = 0\n";
+  string expected_response_sign =
+      "Payload 0:\n"
+      "  Size = 123456789\n"
+      "  Sha256 Hash = hash\n"
+      "  Metadata Size = 58123\n"
+      "  Metadata Signature = msign\n"
+      "  Is Delta = 0\n"
+      "  NumURLs = 1\n"
+      "  Candidate Url0 = https://single.url.test\n"
+      "Max Failure Count Per Url = 0\n"
+      "Disable Payload Backoff = 0\n";
   EXPECT_EQ(expected_response_sign, stored_response_sign);
   EXPECT_EQ("https://single.url.test", payload_state.GetCurrentUrl());
   EXPECT_EQ(0U, payload_state.GetUrlFailureCount());
@@ -198,12 +199,12 @@
 
 TEST(PayloadStateTest, SetResponseWorksWithMultipleUrls) {
   OmahaResponse response;
-  response.payload_urls.push_back("http://multiple.url.test");
-  response.payload_urls.push_back("https://multiple.url.test");
-  response.size = 523456789;
-  response.hash = "rhash";
-  response.metadata_size = 558123;
-  response.metadata_signature = "metasign";
+  response.packages.push_back({.payload_urls = {"http://multiple.url.test",
+                                                "https://multiple.url.test"},
+                               .size = 523456789,
+                               .metadata_size = 558123,
+                               .metadata_signature = "metasign",
+                               .hash = "rhash"});
   FakeSystemState fake_system_state;
   NiceMock<MockPrefs>* prefs = fake_system_state.mock_prefs();
   EXPECT_CALL(*prefs, SetInt64(_, _)).Times(AnyNumber());
@@ -230,16 +231,18 @@
   EXPECT_TRUE(payload_state.Initialize(&fake_system_state));
   payload_state.SetResponse(response);
   string stored_response_sign = payload_state.GetResponseSignature();
-  string expected_response_sign = "NumURLs = 2\n"
-                                  "Candidate Url0 = http://multiple.url.test\n"
-                                  "Candidate Url1 = https://multiple.url.test\n"
-                                  "Payload Size = 523456789\n"
-                                  "Payload Sha256 Hash = rhash\n"
-                                  "Metadata Size = 558123\n"
-                                  "Metadata Signature = metasign\n"
-                                  "Is Delta Payload = 0\n"
-                                  "Max Failure Count Per Url = 0\n"
-                                  "Disable Payload Backoff = 0\n";
+  string expected_response_sign =
+      "Payload 0:\n"
+      "  Size = 523456789\n"
+      "  Sha256 Hash = rhash\n"
+      "  Metadata Size = 558123\n"
+      "  Metadata Signature = metasign\n"
+      "  Is Delta = 0\n"
+      "  NumURLs = 2\n"
+      "  Candidate Url0 = http://multiple.url.test\n"
+      "  Candidate Url1 = https://multiple.url.test\n"
+      "Max Failure Count Per Url = 0\n"
+      "Disable Payload Backoff = 0\n";
   EXPECT_EQ(expected_response_sign, stored_response_sign);
   EXPECT_EQ("http://multiple.url.test", payload_state.GetCurrentUrl());
   EXPECT_EQ(0U, payload_state.GetUrlFailureCount());
@@ -281,7 +284,8 @@
 
   // This does a SetResponse which causes all the states to be set to 0 for
   // the first time.
-  SetupPayloadStateWith2Urls("Hash1235", true, &payload_state, &response);
+  SetupPayloadStateWith2Urls(
+      "Hash1235", true, false, &payload_state, &response);
   EXPECT_EQ("http://test", payload_state.GetCurrentUrl());
 
   // Verify that on the first error, the URL index advances to 1.
@@ -308,13 +312,9 @@
 
   EXPECT_TRUE(payload_state.Initialize(&fake_system_state));
 
-  EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendToUMA(_, _, _, _, _))
-    .Times(AnyNumber());
-  EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendEnumToUMA(_, _, _))
-    .Times(AnyNumber());
-
   // Set the first response.
-  SetupPayloadStateWith2Urls("Hash5823", true, &payload_state, &response);
+  SetupPayloadStateWith2Urls(
+      "Hash5823", true, false, &payload_state, &response);
   EXPECT_EQ(1, payload_state.GetNumResponsesSeen());
 
   // Advance the URL index to 1 by faking an error.
@@ -324,7 +324,8 @@
   EXPECT_EQ(1U, payload_state.GetUrlSwitchCount());
 
   // Now, slightly change the response and set it again.
-  SetupPayloadStateWith2Urls("Hash8225", true, &payload_state, &response);
+  SetupPayloadStateWith2Urls(
+      "Hash8225", true, false, &payload_state, &response);
   EXPECT_EQ(2, payload_state.GetNumResponsesSeen());
 
   // Fake an error again.
@@ -333,7 +334,8 @@
   EXPECT_EQ(1U, payload_state.GetUrlSwitchCount());
 
   // Return a third different response.
-  SetupPayloadStateWith2Urls("Hash9999", true, &payload_state, &response);
+  SetupPayloadStateWith2Urls(
+      "Hash9999", true, false, &payload_state, &response);
   EXPECT_EQ(3, payload_state.GetNumResponsesSeen());
 
   // Make sure the url index was reset to 0 because of the new response.
@@ -404,7 +406,8 @@
 
   EXPECT_TRUE(payload_state.Initialize(&fake_system_state));
 
-  SetupPayloadStateWith2Urls("Hash5873", true, &payload_state, &response);
+  SetupPayloadStateWith2Urls(
+      "Hash5873", true, false, &payload_state, &response);
   EXPECT_EQ(1, payload_state.GetNumResponsesSeen());
 
   // This should advance the URL index.
@@ -483,7 +486,8 @@
   EXPECT_TRUE(payload_state.ShouldBackoffDownload());
 
   // Now, slightly change the response and set it again.
-  SetupPayloadStateWith2Urls("Hash8532", true, &payload_state, &response);
+  SetupPayloadStateWith2Urls(
+      "Hash8532", true, false, &payload_state, &response);
   EXPECT_EQ(2, payload_state.GetNumResponsesSeen());
 
   // Make sure the url index was reset to 0 because of the new response.
@@ -497,7 +501,6 @@
 
 TEST(PayloadStateTest, PayloadAttemptNumberIncreasesOnSuccessfulFullDownload) {
   OmahaResponse response;
-  response.is_delta_payload = false;
   PayloadState payload_state;
   FakeSystemState fake_system_state;
   NiceMock<MockPrefs>* prefs = fake_system_state.mock_prefs();
@@ -523,7 +526,8 @@
 
   EXPECT_TRUE(payload_state.Initialize(&fake_system_state));
 
-  SetupPayloadStateWith2Urls("Hash8593", true, &payload_state, &response);
+  SetupPayloadStateWith2Urls(
+      "Hash8593", true, false, &payload_state, &response);
 
   // This should just advance the payload attempt number;
   EXPECT_EQ(0, payload_state.GetPayloadAttemptNumber());
@@ -538,7 +542,6 @@
 
 TEST(PayloadStateTest, PayloadAttemptNumberIncreasesOnSuccessfulDeltaDownload) {
   OmahaResponse response;
-  response.is_delta_payload = true;
   PayloadState payload_state;
   FakeSystemState fake_system_state;
   NiceMock<MockPrefs>* prefs = fake_system_state.mock_prefs();
@@ -563,7 +566,7 @@
 
   EXPECT_TRUE(payload_state.Initialize(&fake_system_state));
 
-  SetupPayloadStateWith2Urls("Hash8593", true, &payload_state, &response);
+  SetupPayloadStateWith2Urls("Hash8593", true, true, &payload_state, &response);
 
   // This should just advance the payload attempt number;
   EXPECT_EQ(0, payload_state.GetPayloadAttemptNumber());
@@ -582,7 +585,8 @@
   FakeSystemState fake_system_state;
 
   EXPECT_TRUE(payload_state.Initialize(&fake_system_state));
-  SetupPayloadStateWith2Urls("Hash4427", true, &payload_state, &response);
+  SetupPayloadStateWith2Urls(
+      "Hash4427", true, false, &payload_state, &response);
 
   // Generate enough events to advance URL index, failure count and
   // payload attempt number all to 1.
@@ -618,7 +622,8 @@
   // response was different. We want to specifically test that even if the
   // response is same, we should reset the state if we find it corrupted.
   EXPECT_TRUE(payload_state.Initialize(&fake_system_state2));
-  SetupPayloadStateWith2Urls("Hash4427", true, &payload_state, &response);
+  SetupPayloadStateWith2Urls(
+      "Hash4427", true, false, &payload_state, &response);
 
   // Make sure all counters get reset to 0 because of the corrupted URL index
   // we supplied above.
@@ -631,7 +636,6 @@
 
 TEST(PayloadStateTest, NoBackoffInteractiveChecks) {
   OmahaResponse response;
-  response.is_delta_payload = false;
   PayloadState payload_state;
   FakeSystemState fake_system_state;
   OmahaRequestParams params(&fake_system_state);
@@ -639,7 +643,8 @@
   fake_system_state.set_request_params(&params);
 
   EXPECT_TRUE(payload_state.Initialize(&fake_system_state));
-  SetupPayloadStateWith2Urls("Hash6437", true, &payload_state, &response);
+  SetupPayloadStateWith2Urls(
+      "Hash6437", true, false, &payload_state, &response);
 
   // Simulate two failures (enough to cause payload backoff) and check
   // again that we're ready to re-download without any backoff as this is
@@ -654,7 +659,6 @@
 
 TEST(PayloadStateTest, NoBackoffForP2PUpdates) {
   OmahaResponse response;
-  response.is_delta_payload = false;
   PayloadState payload_state;
   FakeSystemState fake_system_state;
   OmahaRequestParams params(&fake_system_state);
@@ -662,7 +666,8 @@
   fake_system_state.set_request_params(&params);
 
   EXPECT_TRUE(payload_state.Initialize(&fake_system_state));
-  SetupPayloadStateWith2Urls("Hash6437", true, &payload_state, &response);
+  SetupPayloadStateWith2Urls(
+      "Hash6437", true, false, &payload_state, &response);
 
   // Simulate two failures (enough to cause payload backoff) and check
   // again that we're ready to re-download without any backoff as this is
@@ -685,12 +690,11 @@
 
 TEST(PayloadStateTest, NoBackoffForDeltaPayloads) {
   OmahaResponse response;
-  response.is_delta_payload = true;
   PayloadState payload_state;
   FakeSystemState fake_system_state;
 
   EXPECT_TRUE(payload_state.Initialize(&fake_system_state));
-  SetupPayloadStateWith2Urls("Hash6437", true, &payload_state, &response);
+  SetupPayloadStateWith2Urls("Hash6437", true, true, &payload_state, &response);
 
   // Simulate a successful download and see that we're ready to download
   // again without any backoff as this is a delta payload.
@@ -730,12 +734,12 @@
 
 TEST(PayloadStateTest, BackoffPeriodsAreInCorrectRange) {
   OmahaResponse response;
-  response.is_delta_payload = false;
   PayloadState payload_state;
   FakeSystemState fake_system_state;
 
   EXPECT_TRUE(payload_state.Initialize(&fake_system_state));
-  SetupPayloadStateWith2Urls("Hash8939", true, &payload_state, &response);
+  SetupPayloadStateWith2Urls(
+      "Hash8939", true, false, &payload_state, &response);
 
   CheckPayloadBackoffState(&payload_state, 1,  TimeDelta::FromDays(1));
   CheckPayloadBackoffState(&payload_state, 2,  TimeDelta::FromDays(2));
@@ -756,7 +760,8 @@
   FakeSystemState fake_system_state;
 
   EXPECT_TRUE(payload_state.Initialize(&fake_system_state));
-  SetupPayloadStateWith2Urls("Hash8939", true, &payload_state, &response);
+  SetupPayloadStateWith2Urls(
+      "Hash8939", true, false, &payload_state, &response);
 
   // Simulate a successful download and see that we are ready to download
   // again without any backoff.
@@ -784,7 +789,8 @@
   uint64_t http_total = 0;
 
   EXPECT_TRUE(payload_state.Initialize(&fake_system_state));
-  SetupPayloadStateWith2Urls("Hash3286", true, &payload_state, &response);
+  SetupPayloadStateWith2Urls(
+      "Hash3286", true, false, &payload_state, &response);
   EXPECT_EQ(1, payload_state.GetNumResponsesSeen());
 
   // Simulate a previous attempt with in order to set an initial non-zero value
@@ -801,7 +807,8 @@
 
   // Change the response hash so as to simulate a new response which will
   // reset the current bytes downloaded, but not the total bytes downloaded.
-  SetupPayloadStateWith2Urls("Hash9904", true, &payload_state, &response);
+  SetupPayloadStateWith2Urls(
+      "Hash9904", true, false, &payload_state, &response);
   EXPECT_EQ(2, payload_state.GetNumResponsesSeen());
 
   // First, simulate successful download of a few bytes over HTTP.
@@ -862,26 +869,9 @@
   EXPECT_EQ(p2p_total,
             payload_state.GetTotalBytesDownloaded(kDownloadSourceHttpPeer));
 
-  EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendToUMA(_, _, _, _, _))
-    .Times(AnyNumber());
-  EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendEnumToUMA(_, _, _))
-    .Times(AnyNumber());
-  EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendToUMA(
-      metrics::kMetricSuccessfulUpdateUrlSwitchCount,
-      3, _, _, _));
-  EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendToUMA(
-      metrics::kMetricSuccessfulUpdateTotalDurationMinutes,
-      _, _, _, _));
-  EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendToUMA(
-      metrics::kMetricSuccessfulUpdateDownloadOverheadPercentage,
-      314, _, _, _));
-  EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendEnumToUMA(
-      metrics::kMetricAttemptPayloadType, kPayloadTypeFull, kNumPayloadTypes));
-  EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendEnumToUMA(
-      metrics::kMetricSuccessfulUpdatePayloadType, kPayloadTypeFull,
-      kNumPayloadTypes));
-  EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendToUMA(
-      metrics::kMetricSuccessfulUpdateAttemptCount, 1, _, _, _));
+  EXPECT_CALL(*fake_system_state.mock_metrics_reporter(),
+              ReportSuccessfulUpdateMetrics(
+                  1, _, kPayloadTypeFull, _, _, 314, _, _, 3));
 
   payload_state.UpdateSucceeded();
 
@@ -903,7 +893,8 @@
   FakeSystemState fake_system_state;
 
   EXPECT_TRUE(payload_state.Initialize(&fake_system_state));
-  SetupPayloadStateWith2Urls("Hash3286", true, &payload_state, &response);
+  SetupPayloadStateWith2Urls(
+      "Hash3286", true, false, &payload_state, &response);
 
   // Simulate progress in order to mark HTTP as one of the sources used.
   uint64_t num_bytes = 42 * 1000 * 1000;
@@ -916,12 +907,21 @@
             payload_state.GetTotalBytesDownloaded(kDownloadSourceHttpServer));
 
   // Check that only HTTP is reported as a download source.
-  EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendToUMA(_, _, _, _, _))
-    .Times(AnyNumber());
-  EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendToUMA(
-      metrics::kMetricSuccessfulUpdateDownloadSourcesUsed,
-      (1 << kDownloadSourceHttpServer),
-      _, _, _));
+  int64_t total_bytes[kNumDownloadSources] = {};
+  total_bytes[kDownloadSourceHttpServer] = num_bytes;
+
+  EXPECT_CALL(*fake_system_state.mock_metrics_reporter(),
+              ReportSuccessfulUpdateMetrics(
+                  _,
+                  _,
+                  _,
+                  _,
+                  test_utils::DownloadSourceMatcher(total_bytes),
+                  _,
+                  _,
+                  _,
+                  _))
+      .Times(1);
 
   payload_state.UpdateSucceeded();
 }
@@ -934,7 +934,8 @@
   EXPECT_TRUE(payload_state.Initialize(&fake_system_state));
 
   // Set the first response.
-  SetupPayloadStateWith2Urls("Hash5823", true, &payload_state, &response);
+  SetupPayloadStateWith2Urls(
+      "Hash5823", true, false, &payload_state, &response);
 
   uint64_t num_bytes = 10000;
   payload_state.DownloadProgress(num_bytes);
@@ -1019,20 +1020,16 @@
 
   // Check that we report only UpdateEngine.Rollback.* metrics in
   // UpdateSucceeded().
-  EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendToUMA(_, _, _, _, _))
-    .Times(0);
-  EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendEnumToUMA(_, _, _))
-    .Times(0);
-  EXPECT_CALL(*fake_system_state.mock_metrics_lib(),
-              SendEnumToUMA(
-                  metrics::kMetricRollbackResult,
-                  static_cast<int>(metrics::RollbackResult::kSuccess),
-                  static_cast<int>(metrics::RollbackResult::kNumConstants)));
+  EXPECT_CALL(*fake_system_state.mock_metrics_reporter(),
+              ReportRollbackMetrics(metrics::RollbackResult::kSuccess))
+      .Times(1);
+
   payload_state.UpdateSucceeded();
 }
 
 TEST(PayloadStateTest, DurationsAreCorrect) {
   OmahaResponse response;
+  response.packages.resize(1);
   PayloadState payload_state;
   FakeSystemState fake_system_state;
   FakeClock fake_clock;
@@ -1050,7 +1047,8 @@
   // Check that durations are correct for a successful update where
   // time has advanced 7 seconds on the wall clock and 4 seconds on
   // the monotonic clock.
-  SetupPayloadStateWith2Urls("Hash8593", true, &payload_state, &response);
+  SetupPayloadStateWith2Urls(
+      "Hash8593", true, false, &payload_state, &response);
   fake_clock.SetWallclockTime(Time::FromInternalValue(8000000));
   fake_clock.SetMonotonicTime(Time::FromInternalValue(6000000));
   payload_state.UpdateSucceeded();
@@ -1058,7 +1056,8 @@
   EXPECT_EQ(payload_state.GetUpdateDurationUptime().InMicroseconds(), 4000000);
 
   // Check that durations are reset when a new response comes in.
-  SetupPayloadStateWith2Urls("Hash8594", true, &payload_state, &response);
+  SetupPayloadStateWith2Urls(
+      "Hash8594", true, false, &payload_state, &response);
   EXPECT_EQ(payload_state.GetUpdateDuration().InMicroseconds(), 0);
   EXPECT_EQ(payload_state.GetUpdateDurationUptime().InMicroseconds(), 0);
 
@@ -1076,6 +1075,7 @@
   fake_clock.SetMonotonicTime(Time::FromInternalValue(5000));
   PayloadState payload_state2;
   EXPECT_TRUE(payload_state2.Initialize(&fake_system_state));
+  payload_state2.SetResponse(response);
   EXPECT_EQ(payload_state2.GetUpdateDuration().InMicroseconds(), 10000000);
   EXPECT_EQ(payload_state2.GetUpdateDurationUptime().InMicroseconds(),
             10000000);
@@ -1098,15 +1098,16 @@
   FakePrefs fake_prefs;
 
   // Set the clock to a well-known time (t = 30 seconds).
-  fake_clock.SetWallclockTime(Time::FromInternalValue(
-      30 * Time::kMicrosecondsPerSecond));
+  fake_clock.SetMonotonicTime(
+      Time::FromInternalValue(30 * Time::kMicrosecondsPerSecond));
 
   fake_system_state.set_clock(&fake_clock);
   fake_system_state.set_prefs(&fake_prefs);
   EXPECT_TRUE(payload_state.Initialize(&fake_system_state));
 
   // Make the update succeed.
-  SetupPayloadStateWith2Urls("Hash8593", true, &payload_state, &response);
+  SetupPayloadStateWith2Urls(
+      "Hash8593", true, false, &payload_state, &response);
   payload_state.UpdateSucceeded();
 
   // Check that the marker was written.
@@ -1116,15 +1117,14 @@
   // (t = 500 seconds). We do this by using a new PayloadState object
   // and checking that it emits the right UMA metric with the right
   // value.
-  fake_clock.SetWallclockTime(Time::FromInternalValue(
-      500 * Time::kMicrosecondsPerSecond));
+  fake_clock.SetMonotonicTime(
+      Time::FromInternalValue(500 * Time::kMicrosecondsPerSecond));
   PayloadState payload_state2;
   EXPECT_TRUE(payload_state2.Initialize(&fake_system_state));
 
   // Expect 500 - 30 seconds = 470 seconds ~= 7 min 50 sec
-  EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendToUMA(
-      metrics::kMetricTimeToRebootMinutes,
-      7, _, _, _));
+  EXPECT_CALL(*fake_system_state.mock_metrics_reporter(),
+              ReportTimeToReboot(7));
   fake_system_state.set_system_rebooted(true);
 
   payload_state2.UpdateEngineStarted();
@@ -1136,6 +1136,8 @@
 TEST(PayloadStateTest, RestartAfterCrash) {
   PayloadState payload_state;
   FakeSystemState fake_system_state;
+  testing::StrictMock<MockMetricsReporter> mock_metrics_reporter;
+  fake_system_state.set_metrics_reporter(&mock_metrics_reporter);
   NiceMock<MockPrefs>* prefs = fake_system_state.mock_prefs();
 
   EXPECT_TRUE(payload_state.Initialize(&fake_system_state));
@@ -1150,10 +1152,6 @@
   EXPECT_CALL(*prefs, GetBoolean(_, _)).Times(0);
   EXPECT_CALL(*prefs, GetBoolean(kPrefsAttemptInProgress, _));
 
-  // No metrics are reported after a crash.
-  EXPECT_CALL(*fake_system_state.mock_metrics_lib(),
-              SendToUMA(_, _, _, _, _)).Times(0);
-
   // Simulate an update_engine restart without a reboot.
   fake_system_state.set_system_rebooted(false);
 
@@ -1166,11 +1164,9 @@
 
   // If there's no marker at startup, ensure we don't report a metric.
   EXPECT_TRUE(payload_state.Initialize(&fake_system_state));
-  EXPECT_CALL(*fake_system_state.mock_metrics_lib(),
-      SendEnumToUMA(
-          metrics::kMetricAttemptResult,
-          static_cast<int>(metrics::AttemptResult::kAbnormalTermination),
-          _)).Times(0);
+  EXPECT_CALL(*fake_system_state.mock_metrics_reporter(),
+              ReportAbnormallyTerminatedUpdateAttemptMetrics())
+      .Times(0);
   payload_state.UpdateEngineStarted();
 }
 
@@ -1186,11 +1182,9 @@
 
   EXPECT_TRUE(payload_state.Initialize(&fake_system_state));
 
-  EXPECT_CALL(*fake_system_state.mock_metrics_lib(),
-      SendEnumToUMA(
-          metrics::kMetricAttemptResult,
-          static_cast<int>(metrics::AttemptResult::kAbnormalTermination),
-          _)).Times(1);
+  EXPECT_CALL(*fake_system_state.mock_metrics_reporter(),
+              ReportAbnormallyTerminatedUpdateAttemptMetrics())
+      .Times(1);
   payload_state.UpdateEngineStarted();
 
   EXPECT_FALSE(fake_prefs.Exists(kPrefsAttemptInProgress));
@@ -1206,16 +1200,13 @@
   // abnormally).
   fake_system_state.set_prefs(&fake_prefs);
   EXPECT_TRUE(payload_state.Initialize(&fake_system_state));
+  OmahaResponse response;
+  response.packages.resize(1);
+  payload_state.SetResponse(response);
 
-  EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendToUMA(_, _, _, _, _))
-    .Times(AnyNumber());
-  EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendEnumToUMA(_, _, _))
-    .Times(AnyNumber());
-  EXPECT_CALL(*fake_system_state.mock_metrics_lib(),
-      SendEnumToUMA(
-          metrics::kMetricAttemptResult,
-          static_cast<int>(metrics::AttemptResult::kAbnormalTermination),
-          _)).Times(0);
+  EXPECT_CALL(*fake_system_state.mock_metrics_reporter(),
+              ReportAbnormallyTerminatedUpdateAttemptMetrics())
+      .Times(0);
 
   // Attempt not in progress, should be clear.
   EXPECT_FALSE(fake_prefs.Exists(kPrefsAttemptInProgress));
@@ -1245,7 +1236,8 @@
       .WillRepeatedly(Return(false));
 
   // Set the first response.
-  SetupPayloadStateWith2Urls("Hash8433", true, &payload_state, &response);
+  SetupPayloadStateWith2Urls(
+      "Hash8433", true, false, &payload_state, &response);
 
   // Check that we use the HTTP URL since there is no value set for allowing
   // http.
@@ -1256,7 +1248,8 @@
       .WillRepeatedly(DoAll(SetArgPointee<0>(false), Return(true)));
 
   // Reset state and set again.
-  SetupPayloadStateWith2Urls("Hash8433", false, &payload_state, &response);
+  SetupPayloadStateWith2Urls(
+      "Hash8433", false, false, &payload_state, &response);
 
   // Check that we skip the HTTP URL and use only the HTTPS url.
   EXPECT_EQ("https://test", payload_state.GetCurrentUrl());
@@ -1270,7 +1263,8 @@
   EXPECT_EQ(0U, payload_state.GetUrlSwitchCount());
 
   // Now, slightly change the response and set it again.
-  SetupPayloadStateWith2Urls("Hash2399", false, &payload_state, &response);
+  SetupPayloadStateWith2Urls(
+      "Hash2399", false, false, &payload_state, &response);
 
   // Check that we still skip the HTTP URL and use only the HTTPS url.
   EXPECT_EQ("https://test", payload_state.GetCurrentUrl());
@@ -1286,7 +1280,8 @@
   // so that we can test that the state is reset not because of the
   // hash but because of the policy change which results in candidate url
   // list change.
-  SetupPayloadStateWith2Urls("Hash2399", true, &payload_state, &response);
+  SetupPayloadStateWith2Urls(
+      "Hash2399", true, false, &payload_state, &response);
 
   // Check that we use the HTTP URL now and the failure count is reset.
   EXPECT_EQ("http://test", payload_state.GetCurrentUrl());
@@ -1302,23 +1297,17 @@
 
 TEST(PayloadStateTest, PayloadTypeMetricWhenTypeIsDelta) {
   OmahaResponse response;
-  response.is_delta_payload = true;
   PayloadState payload_state;
   FakeSystemState fake_system_state;
 
   EXPECT_TRUE(payload_state.Initialize(&fake_system_state));
-  SetupPayloadStateWith2Urls("Hash6437", true, &payload_state, &response);
+  SetupPayloadStateWith2Urls("Hash6437", true, true, &payload_state, &response);
 
   // Simulate a successful download and update.
   payload_state.DownloadComplete();
-
-  EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendEnumToUMA(_, _, _))
-    .Times(AnyNumber());
-  EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendEnumToUMA(
-      metrics::kMetricAttemptPayloadType, kPayloadTypeDelta, kNumPayloadTypes));
-  EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendEnumToUMA(
-      metrics::kMetricSuccessfulUpdatePayloadType, kPayloadTypeDelta,
-      kNumPayloadTypes));
+  EXPECT_CALL(
+      *fake_system_state.mock_metrics_reporter(),
+      ReportSuccessfulUpdateMetrics(_, _, kPayloadTypeDelta, _, _, _, _, _, _));
   payload_state.UpdateSucceeded();
 
   // Mock the request to a request where the delta was disabled but Omaha sends
@@ -1328,27 +1317,24 @@
   fake_system_state.set_request_params(&params);
 
   EXPECT_TRUE(payload_state.Initialize(&fake_system_state));
-  SetupPayloadStateWith2Urls("Hash6437", true, &payload_state, &response);
+  SetupPayloadStateWith2Urls("Hash6437", true, true, &payload_state, &response);
 
   payload_state.DownloadComplete();
 
-  EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendEnumToUMA(
-      metrics::kMetricAttemptPayloadType, kPayloadTypeDelta,
-      kNumPayloadTypes));
-  EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendEnumToUMA(
-      metrics::kMetricSuccessfulUpdatePayloadType, kPayloadTypeDelta,
-      kNumPayloadTypes));
+  EXPECT_CALL(
+      *fake_system_state.mock_metrics_reporter(),
+      ReportSuccessfulUpdateMetrics(_, _, kPayloadTypeDelta, _, _, _, _, _, _));
   payload_state.UpdateSucceeded();
 }
 
 TEST(PayloadStateTest, PayloadTypeMetricWhenTypeIsForcedFull) {
   OmahaResponse response;
-  response.is_delta_payload = false;
   PayloadState payload_state;
   FakeSystemState fake_system_state;
 
   EXPECT_TRUE(payload_state.Initialize(&fake_system_state));
-  SetupPayloadStateWith2Urls("Hash6437", true, &payload_state, &response);
+  SetupPayloadStateWith2Urls(
+      "Hash6437", true, false, &payload_state, &response);
 
   // Mock the request to a request where the delta was disabled.
   OmahaRequestParams params(&fake_system_state);
@@ -1358,25 +1344,20 @@
   // Simulate a successful download and update.
   payload_state.DownloadComplete();
 
-  EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendEnumToUMA(_, _, _))
-    .Times(AnyNumber());
-  EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendEnumToUMA(
-      metrics::kMetricAttemptPayloadType, kPayloadTypeForcedFull,
-      kNumPayloadTypes));
-  EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendEnumToUMA(
-      metrics::kMetricSuccessfulUpdatePayloadType, kPayloadTypeForcedFull,
-      kNumPayloadTypes));
+  EXPECT_CALL(*fake_system_state.mock_metrics_reporter(),
+              ReportSuccessfulUpdateMetrics(
+                  _, _, kPayloadTypeForcedFull, _, _, _, _, _, _));
   payload_state.UpdateSucceeded();
 }
 
 TEST(PayloadStateTest, PayloadTypeMetricWhenTypeIsFull) {
   OmahaResponse response;
-  response.is_delta_payload = false;
   PayloadState payload_state;
   FakeSystemState fake_system_state;
 
   EXPECT_TRUE(payload_state.Initialize(&fake_system_state));
-  SetupPayloadStateWith2Urls("Hash6437", true, &payload_state, &response);
+  SetupPayloadStateWith2Urls(
+      "Hash6437", true, false, &payload_state, &response);
 
   // Mock the request to a request where the delta is enabled, although the
   // result is full.
@@ -1387,14 +1368,9 @@
   // Simulate a successful download and update.
   payload_state.DownloadComplete();
 
-  EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendEnumToUMA(_, _, _))
-    .Times(AnyNumber());
-  EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendEnumToUMA(
-      metrics::kMetricAttemptPayloadType, kPayloadTypeFull,
-      kNumPayloadTypes));
-  EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendEnumToUMA(
-      metrics::kMetricSuccessfulUpdatePayloadType, kPayloadTypeFull,
-      kNumPayloadTypes));
+  EXPECT_CALL(
+      *fake_system_state.mock_metrics_reporter(),
+      ReportSuccessfulUpdateMetrics(_, _, kPayloadTypeFull, _, _, _, _, _, _));
   payload_state.UpdateSucceeded();
 }
 
@@ -1406,7 +1382,8 @@
   fake_system_state.set_prefs(&fake_prefs);
 
   EXPECT_TRUE(payload_state.Initialize(&fake_system_state));
-  SetupPayloadStateWith2Urls("Hash3141", true, &payload_state, &response);
+  SetupPayloadStateWith2Urls(
+      "Hash3141", true, false, &payload_state, &response);
 
   // Simulate a successful download and update.
   payload_state.DownloadComplete();
@@ -1414,27 +1391,27 @@
   payload_state.ExpectRebootInNewVersion("Version:12345678");
 
   // Reboot into the same environment to get an UMA metric with a value of 1.
-  EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendToUMA(
-      metrics::kMetricFailedUpdateCount, 1, _, _, _));
+  EXPECT_CALL(*fake_system_state.mock_metrics_reporter(),
+              ReportFailedUpdateCount(1));
   payload_state.ReportFailedBootIfNeeded();
-  Mock::VerifyAndClearExpectations(fake_system_state.mock_metrics_lib());
+  Mock::VerifyAndClearExpectations(fake_system_state.mock_metrics_reporter());
 
   // Simulate a second update and reboot into the same environment, this should
   // send a value of 2.
   payload_state.ExpectRebootInNewVersion("Version:12345678");
 
-  EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendToUMA(
-      metrics::kMetricFailedUpdateCount, 2, _, _, _));
+  EXPECT_CALL(*fake_system_state.mock_metrics_reporter(),
+              ReportFailedUpdateCount(2));
   payload_state.ReportFailedBootIfNeeded();
-  Mock::VerifyAndClearExpectations(fake_system_state.mock_metrics_lib());
+  Mock::VerifyAndClearExpectations(fake_system_state.mock_metrics_reporter());
 
   // Simulate a third failed reboot to new version, but this time for a
   // different payload. This should send a value of 1 this time.
   payload_state.ExpectRebootInNewVersion("Version:3141592");
-  EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendToUMA(
-      metrics::kMetricFailedUpdateCount, 1, _, _, _));
+  EXPECT_CALL(*fake_system_state.mock_metrics_reporter(),
+              ReportFailedUpdateCount(1));
   payload_state.ReportFailedBootIfNeeded();
-  Mock::VerifyAndClearExpectations(fake_system_state.mock_metrics_lib());
+  Mock::VerifyAndClearExpectations(fake_system_state.mock_metrics_reporter());
 }
 
 TEST(PayloadStateTest, RebootAfterUpdateSucceed) {
@@ -1448,7 +1425,8 @@
   fake_boot_control->SetCurrentSlot(0);
 
   EXPECT_TRUE(payload_state.Initialize(&fake_system_state));
-  SetupPayloadStateWith2Urls("Hash3141", true, &payload_state, &response);
+  SetupPayloadStateWith2Urls(
+      "Hash3141", true, false, &payload_state, &response);
 
   // Simulate a successful download and update.
   payload_state.DownloadComplete();
@@ -1458,8 +1436,8 @@
   // Change the BootDevice to a different one, no metric should be sent.
   fake_boot_control->SetCurrentSlot(1);
 
-  EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendToUMA(
-      metrics::kMetricFailedUpdateCount, _, _, _, _))
+  EXPECT_CALL(*fake_system_state.mock_metrics_reporter(),
+              ReportFailedUpdateCount(_))
       .Times(0);
   payload_state.ReportFailedBootIfNeeded();
 
@@ -1477,15 +1455,16 @@
 
   fake_system_state.set_prefs(&fake_prefs);
   EXPECT_TRUE(payload_state.Initialize(&fake_system_state));
-  SetupPayloadStateWith2Urls("Hash3141", true, &payload_state, &response);
+  SetupPayloadStateWith2Urls(
+      "Hash3141", true, false, &payload_state, &response);
 
   // Simulate a successful download and update.
   payload_state.DownloadComplete();
   payload_state.UpdateSucceeded();
   payload_state.ExpectRebootInNewVersion("Version:12345678");
 
-  EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendToUMA(
-      metrics::kMetricFailedUpdateCount, _, _, _, _))
+  EXPECT_CALL(*fake_system_state.mock_metrics_reporter(),
+              ReportFailedUpdateCount(_))
       .Times(0);
 
   // Cancel the applied update.
@@ -1503,8 +1482,8 @@
   fake_system_state.set_prefs(&fake_prefs);
   EXPECT_TRUE(payload_state.Initialize(&fake_system_state));
 
-  EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendToUMA(
-      metrics::kMetricFailedUpdateCount, _, _, _, _))
+  EXPECT_CALL(*fake_system_state.mock_metrics_reporter(),
+              ReportFailedUpdateCount(_))
       .Times(0);
 
   // Simulate a reboot in this environment.
@@ -1519,7 +1498,8 @@
   fake_system_state.set_prefs(&fake_prefs);
 
   EXPECT_TRUE(payload_state.Initialize(&fake_system_state));
-  SetupPayloadStateWith2Urls("Hash8593", true, &payload_state, &response);
+  SetupPayloadStateWith2Urls(
+      "Hash8593", true, false, &payload_state, &response);
 
   // Should allow exactly kMaxP2PAttempts...
   for (int n = 0; n < kMaxP2PAttempts; n++) {
@@ -1541,7 +1521,8 @@
   fake_system_state.set_clock(&fake_clock);
   fake_system_state.set_prefs(&fake_prefs);
   EXPECT_TRUE(payload_state.Initialize(&fake_system_state));
-  SetupPayloadStateWith2Urls("Hash8593", true, &payload_state, &response);
+  SetupPayloadStateWith2Urls(
+      "Hash8593", true, false, &payload_state, &response);
 
   // Set the clock to 1 second.
   Time epoch = Time::FromInternalValue(1000000);
@@ -1584,7 +1565,8 @@
 
   fake_system_state.set_prefs(&fake_prefs);
   EXPECT_TRUE(payload_state.Initialize(&fake_system_state));
-  SetupPayloadStateWith2Urls("Hash8593", true, &payload_state, &response);
+  SetupPayloadStateWith2Urls(
+      "Hash8593", true, false, &payload_state, &response);
 
   Time null_time = Time();
   EXPECT_EQ(null_time, payload_state.GetP2PFirstAttemptTimestamp());
@@ -1600,7 +1582,8 @@
   fake_system_state.set_clock(&fake_clock);
   fake_system_state.set_prefs(&fake_prefs);
   EXPECT_TRUE(payload_state.Initialize(&fake_system_state));
-  SetupPayloadStateWith2Urls("Hash8593", true, &payload_state, &response);
+  SetupPayloadStateWith2Urls(
+      "Hash8593", true, false, &payload_state, &response);
 
   // Set the clock to something known.
   Time time = Time::FromInternalValue(12345);
@@ -1629,7 +1612,8 @@
   fake_system_state.set_prefs(&fake_prefs);
 
   EXPECT_TRUE(payload_state.Initialize(&fake_system_state));
-  SetupPayloadStateWith2Urls("Hash8593", true, &payload_state, &response);
+  SetupPayloadStateWith2Urls(
+      "Hash8593", true, false, &payload_state, &response);
 
   // Set the clock to something known.
   Time time = Time::FromInternalValue(12345);
@@ -1641,7 +1625,8 @@
   EXPECT_EQ(time, payload_state.GetP2PFirstAttemptTimestamp());
 
   // Set a new response...
-  SetupPayloadStateWith2Urls("Hash9904", true, &payload_state, &response);
+  SetupPayloadStateWith2Urls(
+      "Hash9904", true, false, &payload_state, &response);
 
   // ... and check that it clears the P2P state vars.
   Time null_time = Time();
diff --git a/proxy_resolver_unittest.cc b/proxy_resolver_unittest.cc
index 070b361..484aae1 100644
--- a/proxy_resolver_unittest.cc
+++ b/proxy_resolver_unittest.cc
@@ -22,7 +22,6 @@
 #include <gtest/gtest.h>
 
 #include <base/bind.h>
-#include <brillo/bind_lambda.h>
 #include <brillo/message_loops/fake_message_loop.h>
 
 using std::deque;
diff --git a/real_system_state.cc b/real_system_state.cc
index 2a208ae..8e7ad51 100644
--- a/real_system_state.cc
+++ b/real_system_state.cc
@@ -33,6 +33,7 @@
 #include "update_engine/common/constants.h"
 #include "update_engine/common/hardware.h"
 #include "update_engine/common/utils.h"
+#include "update_engine/metrics_reporter_omaha.h"
 #if USE_DBUS
 #include "update_engine/dbus_connection.h"
 #endif  // USE_DBUS
@@ -50,7 +51,7 @@
 }
 
 bool RealSystemState::Initialize() {
-  metrics_lib_.Init();
+  metrics_reporter_.Initialize();
 
   boot_control_ = boot_control::CreateBootControl();
   if (!boot_control_) {
diff --git a/real_system_state.h b/real_system_state.h
index e25b34e..49f7c31 100644
--- a/real_system_state.h
+++ b/real_system_state.h
@@ -22,7 +22,6 @@
 #include <memory>
 #include <set>
 
-#include <metrics/metrics_library.h>
 #include <policy/device_policy.h>
 
 #if USE_CHROME_KIOSK_APP
@@ -36,6 +35,8 @@
 #include "update_engine/common/prefs.h"
 #include "update_engine/connection_manager_interface.h"
 #include "update_engine/daemon_state_interface.h"
+#include "update_engine/metrics_reporter_interface.h"
+#include "update_engine/metrics_reporter_omaha.h"
 #include "update_engine/p2p_manager.h"
 #include "update_engine/payload_state.h"
 #include "update_engine/power_manager_interface.h"
@@ -91,8 +92,8 @@
 
   inline HardwareInterface* hardware() override { return hardware_.get(); }
 
-  inline MetricsLibraryInterface* metrics_lib() override {
-    return &metrics_lib_;
+  inline MetricsReporterInterface* metrics_reporter() override {
+    return &metrics_reporter_;
   }
 
   inline PrefsInterface* prefs() override { return prefs_.get(); }
@@ -150,8 +151,8 @@
   // Interface for the hardware functions.
   std::unique_ptr<HardwareInterface> hardware_;
 
-  // The Metrics Library interface for reporting UMA stats.
-  MetricsLibrary metrics_lib_;
+  // The Metrics reporter for reporting UMA stats.
+  MetricsReporterOmaha metrics_reporter_;
 
   // Interface for persisted store.
   std::unique_ptr<PrefsInterface> prefs_;
diff --git a/scripts/brillo_update_payload b/scripts/brillo_update_payload
index 868e723..65c63f5 100755
--- a/scripts/brillo_update_payload
+++ b/scripts/brillo_update_payload
@@ -167,6 +167,10 @@
     "Optional: Path to a source image. If specified, this makes a delta update."
   DEFINE_string metadata_size_file "" \
     "Optional: Path to output metadata size."
+  DEFINE_string max_timestamp "" \
+    "Optional: The maximum unix timestamp of the OS allowed to apply this \
+payload, should be set to a number higher than the build timestamp of the \
+system running on the device, 0 if not specified."
 fi
 if [[ "${COMMAND}" == "hash" || "${COMMAND}" == "sign" ]]; then
   DEFINE_string unsigned_payload "" "Path to the input unsigned payload."
@@ -397,7 +401,8 @@
     if grep -v -E '^[a-zA-Z0-9_-]*$' "${ab_partitions_list}" >&2; then
       die "Invalid partition names found in the partition list."
     fi
-    partitions=($(cat "${ab_partitions_list}"))
+    # Get partition list without duplicates.
+    partitions=($(awk '!seen[$0]++' "${ab_partitions_list}"))
     if [[ ${#partitions[@]} -eq 0 ]]; then
       die "The list of partitions is empty. Can't generate a payload."
     fi
@@ -566,6 +571,10 @@
     GENERATOR_ARGS+=( --out_metadata_size_file="${FLAGS_metadata_size_file}" )
   fi
 
+  if [[ -n "${FLAGS_max_timestamp}" ]]; then
+    GENERATOR_ARGS+=( --max_timestamp="${FLAGS_max_timestamp}" )
+  fi
+
   if [[ -n "${POSTINSTALL_CONFIG_FILE}" ]]; then
     GENERATOR_ARGS+=(
       --new_postinstall_config_file="${POSTINSTALL_CONFIG_FILE}"
diff --git a/scripts/update_device.py b/scripts/update_device.py
index 63d0fc9..64cfbe3 100755
--- a/scripts/update_device.py
+++ b/scripts/update_device.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/python2
 #
 # Copyright (C) 2017 The Android Open Source Project
 #
@@ -19,18 +19,27 @@
 
 import argparse
 import BaseHTTPServer
+import hashlib
 import logging
 import os
 import socket
 import subprocess
 import sys
 import threading
+import xml.etree.ElementTree
 import zipfile
 
+import update_payload.payload
+
 
 # The path used to store the OTA package when applying the package from a file.
 OTA_PACKAGE_PATH = '/data/ota_package'
 
+# The path to the payload public key on the device.
+PAYLOAD_KEY_PATH = '/etc/update_engine/update-payload-key.pub.pem'
+
+# The port on the device that update_engine should connect to.
+DEVICE_PORT = 1234
 
 def CopyFileObjLength(fsrc, fdst, buffer_size=128 * 1024, copy_length=None):
   """Copy from a file object to another.
@@ -90,6 +99,7 @@
 
   Attributes:
     serving_payload: path to the only payload file we are serving.
+    serving_range: the start offset and size tuple of the payload.
   """
 
   @staticmethod
@@ -140,12 +150,12 @@
     else:
       self.send_response(200)
 
-    stat = os.fstat(f.fileno())
+    serving_start, serving_size = self.serving_range
     start_range, end_range = self._parse_range(self.headers.get('range'),
-                                               stat.st_size)
+                                               serving_size)
     logging.info('Serving request for %s from %s [%d, %d) length: %d',
-                 self.path, self.serving_payload, start_range, end_range,
-                 end_range - start_range)
+                 self.path, self.serving_payload, serving_start + start_range,
+                 serving_start + end_range, end_range - start_range)
 
     self.send_header('Accept-Ranges', 'bytes')
     self.send_header('Content-Range',
@@ -153,22 +163,99 @@
                      '/' + str(end_range - start_range))
     self.send_header('Content-Length', end_range - start_range)
 
+    stat = os.fstat(f.fileno())
     self.send_header('Last-Modified', self.date_time_string(stat.st_mtime))
     self.send_header('Content-type', 'application/octet-stream')
     self.end_headers()
 
-    f.seek(start_range)
+    f.seek(serving_start + start_range)
     CopyFileObjLength(f, self.wfile, copy_length=end_range - start_range)
 
 
+  def do_POST(self):  # pylint: disable=invalid-name
+    """Reply with the omaha response xml."""
+    if self.path != '/update':
+      self.send_error(404, 'Unknown request')
+      return
+
+    if not self.serving_payload:
+      self.send_error(500, 'No serving payload set')
+      return
+
+    try:
+      f = open(self.serving_payload, 'rb')
+    except IOError:
+      self.send_error(404, 'File not found')
+      return
+
+    content_length = int(self.headers.getheader('Content-Length'))
+    request_xml = self.rfile.read(content_length)
+    xml_root = xml.etree.ElementTree.fromstring(request_xml)
+    appid = None
+    for app in xml_root.iter('app'):
+      if 'appid' in app.attrib:
+        appid = app.attrib['appid']
+        break
+    if not appid:
+      self.send_error(400, 'No appid in Omaha request')
+      return
+
+    self.send_response(200)
+    self.send_header("Content-type", "text/xml")
+    self.end_headers()
+
+    serving_start, serving_size = self.serving_range
+    sha256 = hashlib.sha256()
+    f.seek(serving_start)
+    bytes_to_hash = serving_size
+    while bytes_to_hash:
+      buf = f.read(min(bytes_to_hash, 1024 * 1024))
+      if not buf:
+        self.send_error(500, 'Payload too small')
+        return
+      sha256.update(buf)
+      bytes_to_hash -= len(buf)
+
+    payload = update_payload.Payload(f, payload_file_offset=serving_start)
+    payload.Init()
+
+    response_xml = '''
+        <?xml version="1.0" encoding="UTF-8"?>
+        <response protocol="3.0">
+          <app appid="{appid}">
+            <updatecheck status="ok">
+              <urls>
+                <url codebase="http://127.0.0.1:{port}/"/>
+              </urls>
+              <manifest version="0.0.0.1">
+                <actions>
+                  <action event="install" run="payload"/>
+                  <action event="postinstall" MetadataSize="{metadata_size}"/>
+                </actions>
+                <packages>
+                  <package hash_sha256="{payload_hash}" name="payload" size="{payload_size}"/>
+                </packages>
+              </manifest>
+            </updatecheck>
+          </app>
+        </response>
+    '''.format(appid=appid, port=DEVICE_PORT,
+               metadata_size=payload.metadata_size,
+               payload_hash=sha256.hexdigest(),
+               payload_size=serving_size)
+    self.wfile.write(response_xml.strip())
+    return
+
+
 class ServerThread(threading.Thread):
   """A thread for serving HTTP requests."""
 
-  def __init__(self, ota_filename):
+  def __init__(self, ota_filename, serving_range):
     threading.Thread.__init__(self)
-    # serving_payload is a class attribute and the UpdateHandler class is
-    # instantiated with every request.
+    # serving_payload and serving_range are class attributes and the
+    # UpdateHandler class is instantiated with every request.
     UpdateHandler.serving_payload = ota_filename
+    UpdateHandler.serving_range = serving_range
     self._httpd = BaseHTTPServer.HTTPServer(('127.0.0.1', 0), UpdateHandler)
     self.port = self._httpd.server_port
 
@@ -183,26 +270,31 @@
     self._httpd.socket.close()
 
 
-def StartServer(ota_filename):
-  t = ServerThread(ota_filename)
+def StartServer(ota_filename, serving_range):
+  t = ServerThread(ota_filename, serving_range)
   t.start()
   return t
 
 
-def AndroidUpdateCommand(ota_filename, payload_url):
+def AndroidUpdateCommand(ota_filename, payload_url, extra_headers):
   """Return the command to run to start the update in the Android device."""
   ota = AndroidOTAPackage(ota_filename)
   headers = ota.properties
   headers += 'USER_AGENT=Dalvik (something, something)\n'
-
-  # headers += 'POWERWASH=1\n'
   headers += 'NETWORK_ID=0\n'
+  headers += extra_headers
 
   return ['update_engine_client', '--update', '--follow',
           '--payload=%s' % payload_url, '--offset=%d' % ota.offset,
           '--size=%d' % ota.size, '--headers="%s"' % headers]
 
 
+def OmahaUpdateCommand(omaha_url):
+  """Return the command to run to start the update in a device using Omaha."""
+  return ['update_engine_client', '--update', '--follow',
+          '--omaha_url=%s' % omaha_url]
+
+
 class AdbHost(object):
   """Represents a device connected via ADB."""
 
@@ -235,11 +327,28 @@
     p.wait()
     return p.returncode
 
+  def adb_output(self, command):
+    """Run an ADB command like "adb push" and return the output.
+
+    Args:
+      command: list of strings containing command and arguments to run
+
+    Returns:
+      the program's output as a string.
+
+    Raises:
+      subprocess.CalledProcessError on command exit != 0.
+    """
+    command = self._command_prefix + command
+    logging.info('Running: %s', ' '.join(str(x) for x in command))
+    return subprocess.check_output(command, universal_newlines=True)
+
 
 def main():
   parser = argparse.ArgumentParser(description='Android A/B OTA helper.')
-  parser.add_argument('otafile', metavar='ZIP', type=str,
-                      help='the OTA package file (a .zip file).')
+  parser.add_argument('otafile', metavar='PAYLOAD', type=str,
+                      help='the OTA package file (a .zip file) or raw payload \
+                      if device uses Omaha.')
   parser.add_argument('--file', action='store_true',
                       help='Push the file to the device before updating.')
   parser.add_argument('--no-push', action='store_true',
@@ -248,6 +357,10 @@
                       help='The specific device to use.')
   parser.add_argument('--no-verbose', action='store_true',
                       help='Less verbose output')
+  parser.add_argument('--public-key', type=str, default='',
+                      help='Override the public key used to verify payload.')
+  parser.add_argument('--extra-headers', type=str, default='',
+                      help='Extra headers to pass to the device.')
   args = parser.parse_args()
   logging.basicConfig(
       level=logging.WARNING if args.no_verbose else logging.INFO)
@@ -262,27 +375,57 @@
   # List of commands to perform the update.
   cmds = []
 
+  help_cmd = ['shell', 'su', '0', 'update_engine_client', '--help']
+  use_omaha = 'omaha' in dut.adb_output(help_cmd)
+
   if args.file:
     # Update via pushing a file to /data.
     device_ota_file = os.path.join(OTA_PACKAGE_PATH, 'debug.zip')
     payload_url = 'file://' + device_ota_file
     if not args.no_push:
-      cmds.append(['push', args.otafile, device_ota_file])
+      data_local_tmp_file = '/data/local/tmp/debug.zip'
+      cmds.append(['push', args.otafile, data_local_tmp_file])
+      cmds.append(['shell', 'su', '0', 'mv', data_local_tmp_file,
+                   device_ota_file])
+      cmds.append(['shell', 'su', '0', 'chcon',
+                   'u:object_r:ota_package_file:s0', device_ota_file])
     cmds.append(['shell', 'su', '0', 'chown', 'system:cache', device_ota_file])
     cmds.append(['shell', 'su', '0', 'chmod', '0660', device_ota_file])
   else:
     # Update via sending the payload over the network with an "adb reverse"
     # command.
-    device_port = 1234
-    payload_url = 'http://127.0.0.1:%d/payload' % device_port
-    server_thread = StartServer(args.otafile)
+    payload_url = 'http://127.0.0.1:%d/payload' % DEVICE_PORT
+    if use_omaha and zipfile.is_zipfile(args.otafile):
+      ota = AndroidOTAPackage(args.otafile)
+      serving_range = (ota.offset, ota.size)
+    else:
+      serving_range = (0, os.stat(args.otafile).st_size)
+    server_thread = StartServer(args.otafile, serving_range)
     cmds.append(
-        ['reverse', 'tcp:%d' % device_port, 'tcp:%d' % server_thread.port])
-    finalize_cmds.append(['reverse', '--remove', 'tcp:%d' % device_port])
+        ['reverse', 'tcp:%d' % DEVICE_PORT, 'tcp:%d' % server_thread.port])
+    finalize_cmds.append(['reverse', '--remove', 'tcp:%d' % DEVICE_PORT])
+
+  if args.public_key:
+    payload_key_dir = os.path.dirname(PAYLOAD_KEY_PATH)
+    cmds.append(
+        ['shell', 'su', '0', 'mount', '-t', 'tmpfs', 'tmpfs', payload_key_dir])
+    # Allow adb push to payload_key_dir
+    cmds.append(['shell', 'su', '0', 'chcon', 'u:object_r:shell_data_file:s0',
+                 payload_key_dir])
+    cmds.append(['push', args.public_key, PAYLOAD_KEY_PATH])
+    # Allow update_engine to read it.
+    cmds.append(['shell', 'su', '0', 'chcon', '-R', 'u:object_r:system_file:s0',
+                 payload_key_dir])
+    finalize_cmds.append(['shell', 'su', '0', 'umount', payload_key_dir])
 
   try:
     # The main update command using the configured payload_url.
-    update_cmd = AndroidUpdateCommand(args.otafile, payload_url)
+    if use_omaha:
+      update_cmd = \
+          OmahaUpdateCommand('http://127.0.0.1:%d/update' % DEVICE_PORT)
+    else:
+      update_cmd = \
+          AndroidUpdateCommand(args.otafile, payload_url, args.extra_headers)
     cmds.append(['shell', 'su', '0'] + update_cmd)
 
     for cmd in cmds:
diff --git a/scripts/update_payload/payload.py b/scripts/update_payload/payload.py
index 8e50c1f..8d9a20e 100644
--- a/scripts/update_payload/payload.py
+++ b/scripts/update_payload/payload.py
@@ -101,13 +101,15 @@
             hasher=hasher)
 
 
-  def __init__(self, payload_file):
+  def __init__(self, payload_file, payload_file_offset=0):
     """Initialize the payload object.
 
     Args:
       payload_file: update payload file object open for reading
+      payload_file_offset: the offset of the actual payload
     """
     self.payload_file = payload_file
+    self.payload_file_offset = payload_file_offset
     self.manifest_hasher = None
     self.is_init = False
     self.header = None
@@ -159,7 +161,8 @@
 
     return common.Read(
         self.payload_file, self.header.metadata_signature_len,
-        offset=self.header.size + self.header.manifest_len)
+        offset=self.payload_file_offset + self.header.size +
+        self.header.manifest_len)
 
   def ReadDataBlob(self, offset, length):
     """Reads and returns a single data blob from the update payload.
@@ -175,7 +178,8 @@
       PayloadError if a read error occurred.
     """
     return common.Read(self.payload_file, length,
-                       offset=self.data_offset + offset)
+                       offset=self.payload_file_offset + self.data_offset +
+                       offset)
 
   def Init(self):
     """Initializes the payload object.
@@ -192,6 +196,7 @@
     self.manifest_hasher = hashlib.sha256()
 
     # Read the file header.
+    self.payload_file.seek(self.payload_file_offset)
     self.header = self._ReadHeader()
 
     # Read the manifest.
@@ -242,7 +247,7 @@
 
   def ResetFile(self):
     """Resets the offset of the payload file to right past the manifest."""
-    self.payload_file.seek(self.data_offset)
+    self.payload_file.seek(self.payload_file_offset + self.data_offset)
 
   def IsDelta(self):
     """Returns True iff the payload appears to be a delta."""
diff --git a/service_observer_interface.h b/service_observer_interface.h
index 893df04..4edb0ac 100644
--- a/service_observer_interface.h
+++ b/service_observer_interface.h
@@ -31,11 +31,8 @@
 
   // Called whenever the value of these parameters changes. For |progress|
   // value changes, this method will be called only if it changes significantly.
-  virtual void SendStatusUpdate(int64_t last_checked_time,
-                                double progress,
-                                update_engine::UpdateStatus status,
-                                const std::string& new_version,
-                                int64_t new_size) = 0;
+  virtual void SendStatusUpdate(
+      const update_engine::UpdateEngineStatus& update_engine_status) = 0;
 
   // Called whenever an update attempt is completed.
   virtual void SendPayloadApplicationComplete(ErrorCode error_code) = 0;
diff --git a/sideload_main.cc b/sideload_main.cc
index 614d553..ddb312e 100644
--- a/sideload_main.cc
+++ b/sideload_main.cc
@@ -41,6 +41,7 @@
 using std::string;
 using std::vector;
 using update_engine::UpdateStatus;
+using update_engine::UpdateEngineStatus;
 
 namespace {
 // The root directory used for temporary files in update_engine_sideload.
@@ -80,11 +81,10 @@
   }
 
   // ServiceObserverInterface overrides.
-  void SendStatusUpdate(int64_t last_checked_time,
-                        double progress,
-                        UpdateStatus status,
-                        const string& new_version,
-                        int64_t new_size) override {
+  void SendStatusUpdate(
+      const UpdateEngineStatus& update_engine_status) override {
+    UpdateStatus status = update_engine_status.status;
+    double progress = update_engine_status.progress;
     if (status_ != status && (status == UpdateStatus::DOWNLOADING ||
                               status == UpdateStatus::FINALIZING)) {
       // Split the progress bar in two parts for the two stages DOWNLOADING and
diff --git a/system_state.h b/system_state.h
index d538427..1b0ad08 100644
--- a/system_state.h
+++ b/system_state.h
@@ -17,8 +17,6 @@
 #ifndef UPDATE_ENGINE_SYSTEM_STATE_H_
 #define UPDATE_ENGINE_SYSTEM_STATE_H_
 
-class MetricsLibraryInterface;
-
 namespace chromeos_update_manager {
 
 class UpdateManager;
@@ -40,6 +38,7 @@
 class ClockInterface;
 class ConnectionManagerInterface;
 class HardwareInterface;
+class MetricsReporterInterface;
 class OmahaRequestParams;
 class P2PManager;
 class PayloadStateInterface;
@@ -76,7 +75,7 @@
   virtual HardwareInterface* hardware() = 0;
 
   // Gets the Metrics Library interface for reporting UMA stats.
-  virtual MetricsLibraryInterface* metrics_lib() = 0;
+  virtual MetricsReporterInterface* metrics_reporter() = 0;
 
   // Gets the interface object for persisted store.
   virtual PrefsInterface* prefs() = 0;
diff --git a/test_http_server.cc b/test_http_server.cc
index 2955e79..93aa11c 100644
--- a/test_http_server.cc
+++ b/test_http_server.cc
@@ -98,9 +98,11 @@
   request->raw_headers = headers;
 
   // Break header into lines.
-  vector<string> lines;
-  base::SplitStringUsingSubstr(
-      headers.substr(0, headers.length() - strlen(EOL EOL)), EOL, &lines);
+  vector<string> lines = base::SplitStringUsingSubstr(
+      headers.substr(0, headers.length() - strlen(EOL EOL)),
+      EOL,
+      base::TRIM_WHITESPACE,
+      base::SPLIT_WANT_ALL);
 
   // Decode URL line.
   vector<string> terms = base::SplitString(lines[0], base::kWhitespaceASCII,
diff --git a/update_attempter.cc b/update_attempter.cc
index 4fef9ce..9cef154 100644
--- a/update_attempter.cc
+++ b/update_attempter.cc
@@ -31,7 +31,7 @@
 #include <base/rand_util.h>
 #include <base/strings/string_util.h>
 #include <base/strings/stringprintf.h>
-#include <brillo/bind_lambda.h>
+#include <brillo/data_encoding.h>
 #include <brillo/errors/error_codes.h>
 #include <brillo/message_loops/message_loop.h>
 #include <policy/device_policy.h>
@@ -43,14 +43,12 @@
 #include "update_engine/common/clock_interface.h"
 #include "update_engine/common/constants.h"
 #include "update_engine/common/hardware_interface.h"
-#include "update_engine/common/multi_range_http_fetcher.h"
 #include "update_engine/common/platform_constants.h"
 #include "update_engine/common/prefs_interface.h"
 #include "update_engine/common/subprocess.h"
 #include "update_engine/common/utils.h"
-#include "update_engine/connection_manager_interface.h"
 #include "update_engine/libcurl_http_fetcher.h"
-#include "update_engine/metrics.h"
+#include "update_engine/metrics_reporter_interface.h"
 #include "update_engine/omaha_request_action.h"
 #include "update_engine/omaha_request_params.h"
 #include "update_engine/omaha_response_handler_action.h"
@@ -78,6 +76,8 @@
 using std::shared_ptr;
 using std::string;
 using std::vector;
+using update_engine::UpdateAttemptFlags;
+using update_engine::UpdateEngineStatus;
 
 namespace chromeos_update_engine {
 
@@ -119,13 +119,11 @@
   return code;
 }
 
-UpdateAttempter::UpdateAttempter(
-    SystemState* system_state,
-    CertificateChecker* cert_checker)
+UpdateAttempter::UpdateAttempter(SystemState* system_state,
+                                 CertificateChecker* cert_checker)
     : processor_(new ActionProcessor()),
       system_state_(system_state),
-      cert_checker_(cert_checker) {
-}
+      cert_checker_(cert_checker) {}
 
 UpdateAttempter::~UpdateAttempter() {
   // CertificateChecker might not be initialized in unittests.
@@ -171,9 +169,8 @@
 
 void UpdateAttempter::CertificateChecked(ServerToCheck server_to_check,
                                          CertificateCheckResult result) {
-  metrics::ReportCertificateCheckMetrics(system_state_,
-                                         server_to_check,
-                                         result);
+  system_state_->metrics_reporter()->ReportCertificateCheckMetrics(
+      server_to_check, result);
 }
 
 bool UpdateAttempter::CheckAndReportDailyMetrics() {
@@ -234,7 +231,7 @@
     return;
   }
 
-  metrics::ReportDailyMetrics(system_state_, age);
+  system_state_->metrics_reporter()->ReportDailyMetrics(age);
 }
 
 void UpdateAttempter::Update(const string& app_version,
@@ -262,10 +259,11 @@
     // not performing an update check because of this.
     LOG(INFO) << "Not updating b/c we already updated and we're waiting for "
               << "reboot, we'll ping Omaha instead";
-    metrics::ReportUpdateCheckMetrics(system_state_,
-                                      metrics::CheckResult::kRebootPending,
-                                      metrics::CheckReaction::kUnset,
-                                      metrics::DownloadErrorCode::kUnset);
+    system_state_->metrics_reporter()->ReportUpdateCheckMetrics(
+        system_state_,
+        metrics::CheckResult::kRebootPending,
+        metrics::CheckReaction::kUnset,
+        metrics::DownloadErrorCode::kUnset);
     PingOmaha();
     return;
   }
@@ -598,24 +596,24 @@
   shared_ptr<OmahaResponseHandlerAction> response_handler_action(
       new OmahaResponseHandlerAction(system_state_));
 
-  shared_ptr<OmahaRequestAction> download_started_action(
-      new OmahaRequestAction(
-          system_state_,
-          new OmahaEvent(OmahaEvent::kTypeUpdateDownloadStarted),
-          std::make_unique<LibcurlHttpFetcher>(GetProxyResolver(),
-                                               system_state_->hardware()),
-          false));
+  shared_ptr<OmahaRequestAction> download_started_action(new OmahaRequestAction(
+      system_state_,
+      new OmahaEvent(OmahaEvent::kTypeUpdateDownloadStarted),
+      std::make_unique<LibcurlHttpFetcher>(GetProxyResolver(),
+                                           system_state_->hardware()),
+      false));
 
   LibcurlHttpFetcher* download_fetcher =
       new LibcurlHttpFetcher(GetProxyResolver(), system_state_->hardware());
   download_fetcher->set_server_to_check(ServerToCheck::kDownload);
+  if (interactive)
+    download_fetcher->set_max_retry_count(kDownloadMaxRetryCountInteractive);
   shared_ptr<DownloadAction> download_action(
       new DownloadAction(prefs_,
                          system_state_->boot_control(),
                          system_state_->hardware(),
                          system_state_,
-                         // passes ownership
-                         new MultiRangeHttpFetcher(download_fetcher),
+                         download_fetcher,  // passes ownership
                          interactive));
   shared_ptr<OmahaRequestAction> download_finished_action(
       new OmahaRequestAction(
@@ -627,12 +625,11 @@
   shared_ptr<FilesystemVerifierAction> filesystem_verifier_action(
       new FilesystemVerifierAction());
   shared_ptr<OmahaRequestAction> update_complete_action(
-      new OmahaRequestAction(
-          system_state_,
-          new OmahaEvent(OmahaEvent::kTypeUpdateComplete),
-          std::make_unique<LibcurlHttpFetcher>(GetProxyResolver(),
-                                               system_state_->hardware()),
-          false));
+      new OmahaRequestAction(system_state_,
+                             new OmahaEvent(OmahaEvent::kTypeUpdateComplete),
+                             std::make_unique<LibcurlHttpFetcher>(
+                                 GetProxyResolver(), system_state_->hardware()),
+                             false));
 
   download_action->set_delegate(this);
   response_handler_action_ = response_handler_action;
@@ -762,9 +759,20 @@
   return BootControlInterface::kInvalidSlot;
 }
 
-void UpdateAttempter::CheckForUpdate(const string& app_version,
+bool UpdateAttempter::CheckForUpdate(const string& app_version,
                                      const string& omaha_url,
-                                     bool interactive) {
+                                     UpdateAttemptFlags flags) {
+  bool interactive = !(flags & UpdateAttemptFlags::kFlagNonInteractive);
+
+  if (interactive && status_ != UpdateStatus::IDLE) {
+    // An update check is either in-progress, or an update has completed and the
+    // system is in UPDATED_NEED_REBOOT.  Either way, don't do an interactive
+    // update at this time
+    LOG(INFO) << "Refusing to do an interactive update with an update already "
+                 "in progress";
+    return false;
+  }
+
   LOG(INFO) << "Forced update check requested.";
   forced_app_version_.clear();
   forced_omaha_url_.clear();
@@ -786,15 +794,31 @@
     forced_omaha_url_ = constants::kOmahaDefaultAUTestURL;
   }
 
+  if (interactive) {
+    // Use the passed-in update attempt flags for this update attempt instead
+    // of the previously set ones.
+    current_update_attempt_flags_ = flags;
+    // Note: The caching for non-interactive update checks happens in
+    // OnUpdateScheduled().
+  }
+
   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, interactive);
   }
+
+  return true;
 }
 
 bool UpdateAttempter::RebootIfNeeded() {
+  if (status_ != UpdateStatus::UPDATED_NEED_REBOOT) {
+    LOG(INFO) << "Reboot requested, but status is "
+              << UpdateStatusToString(status_) << ", so not rebooting.";
+    return false;
+  }
+
   if (system_state_->power_manager()->RequestReboot())
     return true;
 
@@ -843,6 +867,15 @@
               << (params.is_interactive ? "interactive" : "periodic")
               << " update.";
 
+    if (!params.is_interactive) {
+      // Cache the update attempt flags that will be used by this update attempt
+      // so that they can't be changed mid-way through.
+      current_update_attempt_flags_ = update_attempt_flags_;
+    }
+
+    LOG(INFO) << "Update attempt flags in use = 0x" << std::hex
+              << current_update_attempt_flags_;
+
     Update(forced_app_version_, forced_omaha_url_, params.target_channel,
            params.target_version_prefix, false, params.is_interactive);
     // Always clear the forced app_version and omaha_url after an update attempt
@@ -876,6 +909,9 @@
   // Reset cpu shares back to normal.
   cpu_limiter_.StopLimiter();
 
+  // reset the state that's only valid for a single update pass
+  current_update_attempt_flags_ = UpdateAttemptFlags::kNone;
+
   if (status_ == UpdateStatus::REPORTING_ERROR_EVENT) {
     LOG(INFO) << "Error event sent.";
 
@@ -923,8 +959,12 @@
           response_handler_action_->install_plan();
 
       // Generate an unique payload identifier.
-      const string target_version_uid =
-          install_plan.payload_hash + ":" + install_plan.metadata_signature;
+      string target_version_uid;
+      for (const auto& payload : install_plan.payloads) {
+        target_version_uid +=
+            brillo::data_encoding::Base64Encode(payload.hash) + ":" +
+            payload.metadata_signature + ":";
+      }
 
       // Expect to reboot into the new version to send the proper metric during
       // next boot.
@@ -988,22 +1028,32 @@
         consecutive_failed_update_checks_ = 0;
       }
 
-      const OmahaResponse& omaha_response =
-          omaha_request_action->GetOutputObject();
       // Store the server-dictated poll interval, if any.
       server_dictated_poll_interval_ =
-          std::max(0, omaha_response.poll_interval);
-
-      // This update is ignored by omaha request action because update over
-      // cellular connection is not allowed. Needs to ask for user's permissions
-      // to update.
-      if (code == ErrorCode::kOmahaUpdateIgnoredOverCellular) {
-        new_version_ = omaha_response.version;
-        new_payload_size_ = omaha_response.size;
-        SetStatusAndNotify(UpdateStatus::NEED_PERMISSION_TO_UPDATE);
-      }
+          std::max(0, omaha_request_action->GetOutputObject().poll_interval);
+    }
+  } else if (type == OmahaResponseHandlerAction::StaticType()) {
+    // Depending on the returned error code, note that an update is available.
+    if (code == ErrorCode::kOmahaUpdateDeferredPerPolicy ||
+        code == ErrorCode::kSuccess) {
+      // Note that the status will be updated to DOWNLOADING when some bytes
+      // get actually downloaded from the server and the BytesReceived
+      // callback is invoked. This avoids notifying the user that a download
+      // has started in cases when the server and the client are unable to
+      // initiate the download.
+      CHECK(action == response_handler_action_.get());
+      auto plan = response_handler_action_->install_plan();
+      UpdateLastCheckedTime();
+      new_version_ = plan.version;
+      new_system_version_ = plan.system_version;
+      new_payload_size_ = 0;
+      for (const auto& payload : plan.payloads)
+        new_payload_size_ += payload.size;
+      cpu_limiter_.StartLimiter();
+      SetStatusAndNotify(UpdateStatus::UPDATE_AVAILABLE);
     }
   }
+  // General failure cases.
   if (code != ErrorCode::kSuccess) {
     // If the current state is at or past the download phase, count the failure
     // in case a switch to full update becomes necessary. Ignore network
@@ -1016,21 +1066,8 @@
     CreatePendingErrorEvent(action, code);
     return;
   }
-  // Find out which action completed.
-  if (type == OmahaResponseHandlerAction::StaticType()) {
-    // Note that the status will be updated to DOWNLOADING when some bytes get
-    // actually downloaded from the server and the BytesReceived callback is
-    // invoked. This avoids notifying the user that a download has started in
-    // cases when the server and the client are unable to initiate the download.
-    CHECK(action == response_handler_action_.get());
-    const InstallPlan& plan = response_handler_action_->install_plan();
-    UpdateLastCheckedTime();
-    new_version_ = plan.version;
-    new_payload_size_ = plan.payload_size;
-    SetupDownload();
-    cpu_limiter_.StartLimiter();
-    SetStatusAndNotify(UpdateStatus::UPDATE_AVAILABLE);
-  } else if (type == DownloadAction::StaticType()) {
+  // Find out which action completed (successfully).
+  if (type == DownloadAction::StaticType()) {
     SetStatusAndNotify(UpdateStatus::FINALIZING);
   } else if (type == FilesystemVerifierAction::StaticType()) {
     // Log the system properties before the postinst and after the file system
@@ -1127,16 +1164,15 @@
   }
 }
 
-bool UpdateAttempter::GetStatus(int64_t* last_checked_time,
-                                double* progress,
-                                string* current_operation,
-                                string* new_version,
-                                int64_t* new_payload_size) {
-  *last_checked_time = last_checked_time_;
-  *progress = download_progress_;
-  *current_operation = UpdateStatusToString(status_);
-  *new_version = new_version_;
-  *new_payload_size = new_payload_size_;
+bool UpdateAttempter::GetStatus(UpdateEngineStatus* out_status) {
+  out_status->last_checked_time = last_checked_time_;
+  out_status->status = status_;
+  out_status->current_version = omaha_request_params_->app_version();
+  out_status->current_system_version = omaha_request_params_->system_version();
+  out_status->progress = download_progress_;
+  out_status->new_size_bytes = new_payload_size_;
+  out_status->new_version = new_version_;
+  out_status->new_system_version = new_system_version_;
   return true;
 }
 
@@ -1173,12 +1209,12 @@
 }
 
 void UpdateAttempter::BroadcastStatus() {
+  UpdateEngineStatus broadcast_status;
+  // Use common method for generating the current status.
+  GetStatus(&broadcast_status);
+
   for (const auto& observer : service_observers_) {
-    observer->SendStatusUpdate(last_checked_time_,
-                               download_progress_,
-                               status_,
-                               new_version_,
-                               new_payload_size_);
+    observer->SendStatusUpdate(broadcast_status);
   }
   last_notify_time_ = TimeTicks::Now();
 }
@@ -1281,12 +1317,11 @@
   // Send it to Omaha.
   LOG(INFO) << "Reporting the error event";
   shared_ptr<OmahaRequestAction> error_event_action(
-      new OmahaRequestAction(
-          system_state_,
-          error_event_.release(),  // Pass ownership.
-          std::make_unique<LibcurlHttpFetcher>(GetProxyResolver(),
-                                               system_state_->hardware()),
-          false));
+      new OmahaRequestAction(system_state_,
+                             error_event_.release(),  // Pass ownership.
+                             std::make_unique<LibcurlHttpFetcher>(
+                                 GetProxyResolver(), system_state_->hardware()),
+                             false));
   actions_.push_back(shared_ptr<AbstractAction>(error_event_action));
   processor_->EnqueueAction(error_event_action.get());
   SetStatusAndNotify(UpdateStatus::REPORTING_ERROR_EVENT);
@@ -1324,32 +1359,6 @@
   prefs_->SetInt64(kPrefsDeltaUpdateFailures, ++delta_failures);
 }
 
-void UpdateAttempter::SetupDownload() {
-  MultiRangeHttpFetcher* fetcher =
-      static_cast<MultiRangeHttpFetcher*>(download_action_->http_fetcher());
-  fetcher->ClearRanges();
-  if (response_handler_action_->install_plan().is_resume) {
-    // Resuming an update so fetch the update manifest metadata first.
-    int64_t manifest_metadata_size = 0;
-    int64_t manifest_signature_size = 0;
-    prefs_->GetInt64(kPrefsManifestMetadataSize, &manifest_metadata_size);
-    prefs_->GetInt64(kPrefsManifestSignatureSize, &manifest_signature_size);
-    fetcher->AddRange(0, manifest_metadata_size + manifest_signature_size);
-    // If there're remaining unprocessed data blobs, fetch them. Be careful not
-    // to request data beyond the end of the payload to avoid 416 HTTP response
-    // error codes.
-    int64_t next_data_offset = 0;
-    prefs_->GetInt64(kPrefsUpdateStateNextDataOffset, &next_data_offset);
-    uint64_t resume_offset =
-        manifest_metadata_size + manifest_signature_size + next_data_offset;
-    if (resume_offset < response_handler_action_->install_plan().payload_size) {
-      fetcher->AddRange(resume_offset);
-    }
-  } else {
-    fetcher->AddRange(0);
-  }
-}
-
 void UpdateAttempter::PingOmaha() {
   if (!processor_->IsRunning()) {
     shared_ptr<OmahaRequestAction> ping_action(new OmahaRequestAction(
diff --git a/update_attempter.h b/update_attempter.h
index f0747e2..76e93a2 100644
--- a/update_attempter.h
+++ b/update_attempter.h
@@ -46,8 +46,6 @@
 #include "update_engine/update_manager/policy.h"
 #include "update_engine/update_manager/update_manager.h"
 
-class MetricsLibraryInterface;
-
 namespace policy {
 class PolicyProvider;
 }
@@ -62,6 +60,7 @@
                         public PostinstallRunnerAction::DelegateInterface {
  public:
   using UpdateStatus = update_engine::UpdateStatus;
+  using UpdateAttemptFlags = update_engine::UpdateAttemptFlags;
   static const int kMaxDeltaUpdateFailures;
 
   UpdateAttempter(SystemState* system_state, CertificateChecker* cert_checker);
@@ -105,12 +104,8 @@
   // for testing purposes.
   virtual bool ResetStatus();
 
-  // Returns the current status in the out params. Returns true on success.
-  virtual bool GetStatus(int64_t* last_checked_time,
-                         double* progress,
-                         std::string* current_operation,
-                         std::string* new_version,
-                         int64_t* new_size);
+  // Returns the current status in the out param. Returns true on success.
+  virtual bool GetStatus(update_engine::UpdateEngineStatus* out_status);
 
   // Runs chromeos-setgoodkernel, whose responsibility it is to mark the
   // currently booted partition has high priority/permanent/etc. The execution
@@ -127,12 +122,28 @@
   int http_response_code() const { return http_response_code_; }
   void set_http_response_code(int code) { http_response_code_ = code; }
 
+  // Set flags that influence how updates and checks are performed.  These
+  // influence all future checks and updates until changed or the device
+  // reboots.
+  void SetUpdateAttemptFlags(UpdateAttemptFlags flags) {
+    update_attempt_flags_ = flags;
+  }
+
+  // Returns the update attempt flags that are in place for the current update
+  // attempt.  These are cached at the start of an update attempt so that they
+  // remain constant throughout the process.
+  virtual UpdateAttemptFlags GetCurrentUpdateAttemptFlags() {
+    return current_update_attempt_flags_;
+  }
+
   // This is the internal entry point for going through an
   // update. If the current status is idle invokes Update.
   // This is called by the DBus implementation.
-  virtual void CheckForUpdate(const std::string& app_version,
+  // This returns true if an update check was started, false if a check or an
+  // update was already in progress.
+  virtual bool CheckForUpdate(const std::string& app_version,
                               const std::string& omaha_url,
-                              bool is_interactive);
+                              UpdateAttemptFlags flags);
 
   // 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
@@ -243,17 +254,23 @@
   FRIEND_TEST(UpdateAttempterTest, ActionCompletedDownloadTest);
   FRIEND_TEST(UpdateAttempterTest, ActionCompletedErrorTest);
   FRIEND_TEST(UpdateAttempterTest, ActionCompletedOmahaRequestTest);
+  FRIEND_TEST(UpdateAttempterTest, BootTimeInUpdateMarkerFile);
+  FRIEND_TEST(UpdateAttempterTest, BroadcastCompleteDownloadTest);
+  FRIEND_TEST(UpdateAttempterTest, ChangeToDownloadingOnReceivedBytesTest);
   FRIEND_TEST(UpdateAttempterTest, CreatePendingErrorEventTest);
   FRIEND_TEST(UpdateAttempterTest, CreatePendingErrorEventResumedTest);
   FRIEND_TEST(UpdateAttempterTest, DisableDeltaUpdateIfNeededTest);
+  FRIEND_TEST(UpdateAttempterTest, DownloadProgressAccumulationTest);
   FRIEND_TEST(UpdateAttempterTest, MarkDeltaUpdateFailureTest);
   FRIEND_TEST(UpdateAttempterTest, PingOmahaTest);
+  FRIEND_TEST(UpdateAttempterTest, ReportDailyMetrics);
   FRIEND_TEST(UpdateAttempterTest, ScheduleErrorEventActionNoEventTest);
   FRIEND_TEST(UpdateAttempterTest, ScheduleErrorEventActionTest);
-  FRIEND_TEST(UpdateAttempterTest, UpdateTest);
-  FRIEND_TEST(UpdateAttempterTest, ReportDailyMetrics);
-  FRIEND_TEST(UpdateAttempterTest, BootTimeInUpdateMarkerFile);
   FRIEND_TEST(UpdateAttempterTest, TargetVersionPrefixSetAndReset);
+  FRIEND_TEST(UpdateAttempterTest, UpdateAttemptFlagsCachedAtUpdateStart);
+  FRIEND_TEST(UpdateAttempterTest, UpdateDeferredByPolicyTest);
+  FRIEND_TEST(UpdateAttempterTest, UpdateIsNotRunningWhenUpdateAvailable);
+  FRIEND_TEST(UpdateAttempterTest, UpdateTest);
 
   // CertificateChecker::Observer method.
   // Report metrics about the certificate being checked.
@@ -272,9 +289,6 @@
   // Sets the status to the given status and notifies a status update over dbus.
   void SetStatusAndNotify(UpdateStatus status);
 
-  // Sets up the download parameters after receiving the update check response.
-  void SetupDownload();
-
   // Creates an error event object in |error_event_| to be included in an
   // OmahaRequestAction once the current action processor is done.
   void CreatePendingErrorEvent(AbstractAction* action, ErrorCode code);
@@ -432,7 +446,13 @@
   int64_t last_checked_time_ = 0;
   std::string prev_version_;
   std::string new_version_ = "0.0.0.0";
-  int64_t new_payload_size_ = 0;
+  std::string new_system_version_;
+  uint64_t new_payload_size_ = 0;
+  // Flags influencing all periodic update checks
+  UpdateAttemptFlags update_attempt_flags_ = UpdateAttemptFlags::kNone;
+  // Flags influencing the currently in-progress check (cached at the start of
+  // the update check).
+  UpdateAttemptFlags current_update_attempt_flags_ = UpdateAttemptFlags::kNone;
 
   // Common parameters for all Omaha requests.
   OmahaRequestParams* omaha_request_params_ = nullptr;
diff --git a/update_attempter_android.cc b/update_attempter_android.cc
index 9a765b9..aacb06b 100644
--- a/update_attempter_android.cc
+++ b/update_attempter_android.cc
@@ -18,20 +18,25 @@
 
 #include <algorithm>
 #include <map>
+#include <memory>
 #include <utility>
 
+#include <android-base/properties.h>
 #include <base/bind.h>
 #include <base/logging.h>
 #include <base/strings/string_number_conversions.h>
 #include <brillo/bind_lambda.h>
+#include <brillo/data_encoding.h>
 #include <brillo/message_loops/message_loop.h>
 #include <brillo/strings/string_utils.h>
+#include <log/log_safetynet.h>
 
 #include "update_engine/common/constants.h"
 #include "update_engine/common/file_fetcher.h"
-#include "update_engine/common/multi_range_http_fetcher.h"
 #include "update_engine/common/utils.h"
 #include "update_engine/daemon_state_interface.h"
+#include "update_engine/metrics_reporter_interface.h"
+#include "update_engine/metrics_utils.h"
 #include "update_engine/network_selector.h"
 #include "update_engine/payload_consumer/download_action.h"
 #include "update_engine/payload_consumer/filesystem_verifier_action.h"
@@ -45,11 +50,13 @@
 #endif
 
 using base::Bind;
+using base::Time;
 using base::TimeDelta;
 using base::TimeTicks;
 using std::shared_ptr;
 using std::string;
 using std::vector;
+using update_engine::UpdateEngineStatus;
 
 namespace chromeos_update_engine {
 
@@ -74,6 +81,13 @@
   return false;
 }
 
+bool GetHeaderAsBool(const string& header, bool default_value) {
+  int value = 0;
+  if (base::StringToInt(header, &value) && (value == 0 || value == 1))
+    return value == 1;
+  return default_value;
+}
+
 }  // namespace
 
 UpdateAttempterAndroid::UpdateAttempterAndroid(
@@ -85,7 +99,9 @@
       prefs_(prefs),
       boot_control_(boot_control),
       hardware_(hardware),
-      processor_(new ActionProcessor()) {
+      processor_(new ActionProcessor()),
+      clock_(new Clock()) {
+  metrics_reporter_ = metrics::CreateMetricsReporter();
   network_selector_ = network::CreateNetworkSelector();
 }
 
@@ -98,10 +114,12 @@
 void UpdateAttempterAndroid::Init() {
   // In case of update_engine restart without a reboot we need to restore the
   // reboot needed state.
-  if (UpdateCompletedOnThisBoot())
+  if (UpdateCompletedOnThisBoot()) {
     SetStatusAndNotify(UpdateStatus::UPDATED_NEED_REBOOT);
-  else
+  } else {
     SetStatusAndNotify(UpdateStatus::IDLE);
+    UpdatePrefsAndReportUpdateMetricsOnReboot();
+  }
 }
 
 bool UpdateAttempterAndroid::ApplyPayload(
@@ -144,19 +162,27 @@
   install_plan_.download_url = payload_url;
   install_plan_.version = "";
   base_offset_ = payload_offset;
-  install_plan_.payload_size = payload_size;
-  if (!install_plan_.payload_size) {
+  InstallPlan::Payload payload;
+  payload.size = payload_size;
+  if (!payload.size) {
     if (!base::StringToUint64(headers[kPayloadPropertyFileSize],
-                              &install_plan_.payload_size)) {
-      install_plan_.payload_size = 0;
+                              &payload.size)) {
+      payload.size = 0;
     }
   }
-  install_plan_.payload_hash = headers[kPayloadPropertyFileHash];
-  if (!base::StringToUint64(headers[kPayloadPropertyMetadataSize],
-                            &install_plan_.metadata_size)) {
-    install_plan_.metadata_size = 0;
+  if (!brillo::data_encoding::Base64Decode(headers[kPayloadPropertyFileHash],
+                                           &payload.hash)) {
+    LOG(WARNING) << "Unable to decode base64 file hash: "
+                 << headers[kPayloadPropertyFileHash];
   }
-  install_plan_.metadata_signature = "";
+  if (!base::StringToUint64(headers[kPayloadPropertyMetadataSize],
+                            &payload.metadata_size)) {
+    payload.metadata_size = 0;
+  }
+  // The |payload.type| is not used anymore since minor_version 3.
+  payload.type = InstallPayloadType::kUnknown;
+  install_plan_.payloads.push_back(payload);
+
   // The |public_key_rsa| key would override the public key stored on disk.
   install_plan_.public_key_rsa = "";
 
@@ -171,16 +197,28 @@
       LOG(WARNING) << "Unable to save the update check response hash.";
     }
   }
-  // The |payload_type| is not used anymore since minor_version 3.
-  install_plan_.payload_type = InstallPayloadType::kUnknown;
-
   install_plan_.source_slot = boot_control_->GetCurrentSlot();
   install_plan_.target_slot = install_plan_.source_slot == 0 ? 1 : 0;
 
-  int data_wipe = 0;
   install_plan_.powerwash_required =
-      base::StringToInt(headers[kPayloadPropertyPowerwash], &data_wipe) &&
-      data_wipe != 0;
+      GetHeaderAsBool(headers[kPayloadPropertyPowerwash], false);
+
+  install_plan_.switch_slot_on_reboot =
+      GetHeaderAsBool(headers[kPayloadPropertySwitchSlotOnReboot], true);
+
+  install_plan_.run_post_install = true;
+  // Optionally skip post install if and only if:
+  // a) we're resuming
+  // b) post install has already succeeded before
+  // c) RUN_POST_INSTALL is set to 0.
+  if (install_plan_.is_resume && prefs_->Exists(kPrefsPostInstallSucceeded)) {
+    bool post_install_succeeded = false;
+    prefs_->GetBoolean(kPrefsPostInstallSucceeded, &post_install_succeeded);
+    if (post_install_succeeded) {
+      install_plan_.run_post_install =
+          GetHeaderAsBool(headers[kPayloadPropertyRunPostInstall], true);
+    }
+  }
 
   NetworkId network_id = kDefaultNetworkId;
   if (!headers[kPayloadPropertyNetworkId].empty()) {
@@ -192,7 +230,10 @@
           "Invalid network_id: " + headers[kPayloadPropertyNetworkId]);
     }
     if (!network_selector_->SetProcessNetwork(network_id)) {
-      LOG(WARNING) << "Unable to set network_id, continuing with the update.";
+      return LogAndSetError(
+          error,
+          FROM_HERE,
+          "Unable to set network_id: " + headers[kPayloadPropertyNetworkId]);
     }
   }
 
@@ -200,7 +241,6 @@
   install_plan_.Dump();
 
   BuildUpdateActions(payload_url);
-  SetupDownload();
   // Setup extra headers.
   HttpFetcher* fetcher = download_action_->http_fetcher();
   if (!headers[kPayloadPropertyAuthorization].empty())
@@ -214,6 +254,10 @@
   // Just in case we didn't update boot flags yet, make sure they're updated
   // before any update processing starts. This will start the update process.
   UpdateBootFlags();
+
+  UpdatePrefsOnUpdateStart(install_plan_.is_resume);
+  // TODO(xunchang) report the metrics for unresumable updates
+
   return true;
 }
 
@@ -251,6 +295,7 @@
       // after resetting to idle state, it doesn't go back to
       // UpdateStatus::UPDATED_NEED_REBOOT state.
       bool ret_value = prefs_->Delete(kPrefsUpdateCompletedOnBootId);
+      ClearMetricsPrefs();
 
       // Update the boot flags so the current slot has higher priority.
       if (!boot_control_->SetActiveBootSlot(boot_control_->GetCurrentSlot()))
@@ -291,7 +336,6 @@
       // Update succeeded.
       WriteUpdateCompletedMarker();
       prefs_->SetInt64(kPrefsDeltaUpdateFailures, 0);
-      DeltaPerformer::ResetUpdateProgress(prefs_, false);
 
       LOG(INFO) << "Update successfully applied, waiting to reboot.";
       break;
@@ -307,6 +351,11 @@
       LOG(INFO) << "Resetting update progress.";
       break;
 
+    case ErrorCode::kPayloadTimestampError:
+      // SafetyNet logging, b/36232423
+      android_errorWriteLog(0x534e4554, "36232423");
+      break;
+
     default:
       // Ignore all other error codes.
       break;
@@ -329,6 +378,11 @@
   if (type == DownloadAction::StaticType()) {
     download_progress_ = 0;
   }
+  if (type == PostinstallRunnerAction::StaticType()) {
+    bool succeeded =
+        code == ErrorCode::kSuccess || code == ErrorCode::kUpdatedButNotActive;
+    prefs_->SetBoolean(kPrefsPostInstallSucceeded, succeeded);
+  }
   if (code != ErrorCode::kSuccess) {
     // If an action failed, the ActionProcessor will cancel the whole thing.
     return;
@@ -350,6 +404,16 @@
   } else {
     ProgressUpdate(progress);
   }
+
+  // Update the bytes downloaded in prefs.
+  int64_t current_bytes_downloaded =
+      metrics_utils::GetPersistedValue(kPrefsCurrentBytesDownloaded, prefs_);
+  int64_t total_bytes_downloaded =
+      metrics_utils::GetPersistedValue(kPrefsTotalBytesDownloaded, prefs_);
+  prefs_->SetInt64(kPrefsCurrentBytesDownloaded,
+                   current_bytes_downloaded + bytes_progressed);
+  prefs_->SetInt64(kPrefsTotalBytesDownloaded,
+                   total_bytes_downloaded + bytes_progressed);
 }
 
 bool UpdateAttempterAndroid::ShouldCancel(ErrorCode* cancel_reason) {
@@ -417,15 +481,34 @@
   SetStatusAndNotify(new_status);
   ongoing_update_ = false;
 
+  // The network id is only applicable to one download attempt and once it's
+  // done the network id should not be re-used anymore.
+  if (!network_selector_->SetProcessNetwork(kDefaultNetworkId)) {
+    LOG(WARNING) << "Unable to unbind network.";
+  }
+
   for (auto observer : daemon_state_->service_observers())
     observer->SendPayloadApplicationComplete(error_code);
+
+  CollectAndReportUpdateMetricsOnUpdateFinished(error_code);
+  ClearMetricsPrefs();
+  if (error_code == ErrorCode::kSuccess) {
+    metrics_utils::SetSystemUpdatedMarker(clock_.get(), prefs_);
+    // Clear the total bytes downloaded if and only if the update succeeds.
+    prefs_->SetInt64(kPrefsTotalBytesDownloaded, 0);
+  }
 }
 
 void UpdateAttempterAndroid::SetStatusAndNotify(UpdateStatus status) {
   status_ = status;
+  size_t payload_size =
+      install_plan_.payloads.empty() ? 0 : install_plan_.payloads[0].size;
+  UpdateEngineStatus status_to_send = {.status = status_,
+                                       .progress = download_progress_,
+                                       .new_size_bytes = payload_size};
+
   for (auto observer : daemon_state_->service_observers()) {
-    observer->SendStatusUpdate(
-        0, download_progress_, status_, "", install_plan_.payload_size);
+    observer->SendStatusUpdate(status_to_send);
   }
   last_notify_time_ = TimeTicks::Now();
 }
@@ -456,9 +539,8 @@
       new DownloadAction(prefs_,
                          boot_control_,
                          hardware_,
-                         nullptr,  // system_state, not used.
-                         // passes ownership
-                         new MultiRangeHttpFetcher(download_fetcher),
+                         nullptr,           // system_state, not used.
+                         download_fetcher,  // passes ownership
                          true /* is_interactive */));
   shared_ptr<FilesystemVerifierAction> filesystem_verifier_action(
       new FilesystemVerifierAction());
@@ -467,6 +549,7 @@
       new PostinstallRunnerAction(boot_control_, hardware_));
 
   download_action->set_delegate(this);
+  download_action->set_base_offset(base_offset_);
   download_action_ = download_action;
   postinstall_runner_action->set_delegate(this);
 
@@ -487,42 +570,6 @@
     processor_->EnqueueAction(action.get());
 }
 
-void UpdateAttempterAndroid::SetupDownload() {
-  MultiRangeHttpFetcher* fetcher =
-      static_cast<MultiRangeHttpFetcher*>(download_action_->http_fetcher());
-  fetcher->ClearRanges();
-  if (install_plan_.is_resume) {
-    // Resuming an update so fetch the update manifest metadata first.
-    int64_t manifest_metadata_size = 0;
-    int64_t manifest_signature_size = 0;
-    prefs_->GetInt64(kPrefsManifestMetadataSize, &manifest_metadata_size);
-    prefs_->GetInt64(kPrefsManifestSignatureSize, &manifest_signature_size);
-    fetcher->AddRange(base_offset_,
-                      manifest_metadata_size + manifest_signature_size);
-    // If there're remaining unprocessed data blobs, fetch them. Be careful not
-    // to request data beyond the end of the payload to avoid 416 HTTP response
-    // error codes.
-    int64_t next_data_offset = 0;
-    prefs_->GetInt64(kPrefsUpdateStateNextDataOffset, &next_data_offset);
-    uint64_t resume_offset =
-        manifest_metadata_size + manifest_signature_size + next_data_offset;
-    if (!install_plan_.payload_size) {
-      fetcher->AddRange(base_offset_ + resume_offset);
-    } else if (resume_offset < install_plan_.payload_size) {
-      fetcher->AddRange(base_offset_ + resume_offset,
-                        install_plan_.payload_size - resume_offset);
-    }
-  } else {
-    if (install_plan_.payload_size) {
-      fetcher->AddRange(base_offset_, install_plan_.payload_size);
-    } else {
-      // If no payload size is passed we assume we read until the end of the
-      // stream.
-      fetcher->AddRange(base_offset_);
-    }
-  }
-}
-
 bool UpdateAttempterAndroid::WriteUpdateCompletedMarker() {
   string boot_id;
   TEST_AND_RETURN_FALSE(utils::GetBootId(&boot_id));
@@ -544,4 +591,153 @@
           update_completed_on_boot_id == boot_id);
 }
 
+// Collect and report the android metrics when we terminate the update.
+void UpdateAttempterAndroid::CollectAndReportUpdateMetricsOnUpdateFinished(
+    ErrorCode error_code) {
+  int64_t attempt_number =
+      metrics_utils::GetPersistedValue(kPrefsPayloadAttemptNumber, prefs_);
+  PayloadType payload_type = kPayloadTypeFull;
+  int64_t payload_size = 0;
+  for (const auto& p : install_plan_.payloads) {
+    if (p.type == InstallPayloadType::kDelta)
+      payload_type = kPayloadTypeDelta;
+    payload_size += p.size;
+  }
+
+  metrics::AttemptResult attempt_result =
+      metrics_utils::GetAttemptResult(error_code);
+  Time attempt_start_time = Time::FromInternalValue(
+      metrics_utils::GetPersistedValue(kPrefsUpdateTimestampStart, prefs_));
+  TimeDelta duration = clock_->GetBootTime() - attempt_start_time;
+  TimeDelta duration_uptime = clock_->GetMonotonicTime() - attempt_start_time;
+
+  metrics_reporter_->ReportUpdateAttemptMetrics(
+      nullptr,  // system_state
+      static_cast<int>(attempt_number),
+      payload_type,
+      duration,
+      duration_uptime,
+      payload_size,
+      attempt_result,
+      error_code);
+
+  int64_t current_bytes_downloaded =
+      metrics_utils::GetPersistedValue(kPrefsCurrentBytesDownloaded, prefs_);
+  metrics_reporter_->ReportUpdateAttemptDownloadMetrics(
+      current_bytes_downloaded,
+      0,
+      DownloadSource::kNumDownloadSources,
+      metrics::DownloadErrorCode::kUnset,
+      metrics::ConnectionType::kUnset);
+
+  if (error_code == ErrorCode::kSuccess) {
+    int64_t reboot_count =
+        metrics_utils::GetPersistedValue(kPrefsNumReboots, prefs_);
+    string build_version;
+    prefs_->GetString(kPrefsPreviousVersion, &build_version);
+
+    // For android metrics, we only care about the total bytes downloaded
+    // for all sources; for now we assume the only download source is
+    // HttpsServer.
+    int64_t total_bytes_downloaded =
+        metrics_utils::GetPersistedValue(kPrefsTotalBytesDownloaded, prefs_);
+    int64_t num_bytes_downloaded[kNumDownloadSources] = {};
+    num_bytes_downloaded[DownloadSource::kDownloadSourceHttpsServer] =
+        total_bytes_downloaded;
+
+    int download_overhead_percentage = 0;
+    if (current_bytes_downloaded > 0) {
+      download_overhead_percentage =
+          (total_bytes_downloaded - current_bytes_downloaded) * 100ull /
+          current_bytes_downloaded;
+    }
+    metrics_reporter_->ReportSuccessfulUpdateMetrics(
+        static_cast<int>(attempt_number),
+        0,  // update abandoned count
+        payload_type,
+        payload_size,
+        num_bytes_downloaded,
+        download_overhead_percentage,
+        duration,
+        static_cast<int>(reboot_count),
+        0);  // url_switch_count
+  }
+}
+
+void UpdateAttempterAndroid::UpdatePrefsAndReportUpdateMetricsOnReboot() {
+  string current_boot_id;
+  TEST_AND_RETURN(utils::GetBootId(&current_boot_id));
+  // Example: [ro.build.version.incremental]: [4292972]
+  string current_version =
+      android::base::GetProperty("ro.build.version.incremental", "");
+  TEST_AND_RETURN(!current_version.empty());
+
+  // If there's no record of previous version (e.g. due to a data wipe), we
+  // save the info of current boot and skip the metrics report.
+  if (!prefs_->Exists(kPrefsPreviousVersion)) {
+    prefs_->SetString(kPrefsBootId, current_boot_id);
+    prefs_->SetString(kPrefsPreviousVersion, current_version);
+    ClearMetricsPrefs();
+    return;
+  }
+  string previous_version;
+  // update_engine restarted under the same build.
+  // TODO(xunchang) identify and report rollback by checking UpdateMarker.
+  if (prefs_->GetString(kPrefsPreviousVersion, &previous_version) &&
+      previous_version == current_version) {
+    string last_boot_id;
+    bool is_reboot = prefs_->Exists(kPrefsBootId) &&
+                     (prefs_->GetString(kPrefsBootId, &last_boot_id) &&
+                      last_boot_id != current_boot_id);
+    // Increment the reboot number if |kPrefsNumReboots| exists. That pref is
+    // set when we start a new update.
+    if (is_reboot && prefs_->Exists(kPrefsNumReboots)) {
+      prefs_->SetString(kPrefsBootId, current_boot_id);
+      int64_t reboot_count =
+          metrics_utils::GetPersistedValue(kPrefsNumReboots, prefs_);
+      metrics_utils::SetNumReboots(reboot_count + 1, prefs_);
+    }
+    return;
+  }
+
+  // Now that the build version changes, report the update metrics.
+  // TODO(xunchang) check the build version is larger than the previous one.
+  prefs_->SetString(kPrefsBootId, current_boot_id);
+  prefs_->SetString(kPrefsPreviousVersion, current_version);
+
+  bool previous_attempt_exists = prefs_->Exists(kPrefsPayloadAttemptNumber);
+  // |kPrefsPayloadAttemptNumber| should be cleared upon successful update.
+  if (previous_attempt_exists) {
+    metrics_reporter_->ReportAbnormallyTerminatedUpdateAttemptMetrics();
+  }
+
+  metrics_utils::LoadAndReportTimeToReboot(
+      metrics_reporter_.get(), prefs_, clock_.get());
+  ClearMetricsPrefs();
+}
+
+// Save the update start time. Reset the reboot count and attempt number if the
+// update isn't a resume; otherwise increment the attempt number.
+void UpdateAttempterAndroid::UpdatePrefsOnUpdateStart(bool is_resume) {
+  if (!is_resume) {
+    metrics_utils::SetNumReboots(0, prefs_);
+    metrics_utils::SetPayloadAttemptNumber(1, prefs_);
+  } else {
+    int64_t attempt_number =
+        metrics_utils::GetPersistedValue(kPrefsPayloadAttemptNumber, prefs_);
+    metrics_utils::SetPayloadAttemptNumber(attempt_number + 1, prefs_);
+  }
+  Time update_start_time = clock_->GetMonotonicTime();
+  metrics_utils::SetUpdateTimestampStart(update_start_time, prefs_);
+}
+
+void UpdateAttempterAndroid::ClearMetricsPrefs() {
+  CHECK(prefs_);
+  prefs_->Delete(kPrefsCurrentBytesDownloaded);
+  prefs_->Delete(kPrefsNumReboots);
+  prefs_->Delete(kPrefsPayloadAttemptNumber);
+  prefs_->Delete(kPrefsSystemUpdatedMarker);
+  prefs_->Delete(kPrefsUpdateTimestampStart);
+}
+
 }  // namespace chromeos_update_engine
diff --git a/update_attempter_android.h b/update_attempter_android.h
index 6a5c227..28bf90a 100644
--- a/update_attempter_android.h
+++ b/update_attempter_android.h
@@ -28,9 +28,12 @@
 #include "update_engine/client_library/include/update_engine/update_status.h"
 #include "update_engine/common/action_processor.h"
 #include "update_engine/common/boot_control_interface.h"
+#include "update_engine/common/clock.h"
 #include "update_engine/common/hardware_interface.h"
 #include "update_engine/common/prefs_interface.h"
 #include "update_engine/daemon_state_interface.h"
+#include "update_engine/metrics_reporter_interface.h"
+#include "update_engine/metrics_utils.h"
 #include "update_engine/network_selector_interface.h"
 #include "update_engine/payload_consumer/download_action.h"
 #include "update_engine/payload_consumer/postinstall_runner_action.h"
@@ -86,6 +89,8 @@
   void ProgressUpdate(double progress) override;
 
  private:
+  friend class UpdateAttempterAndroidTest;
+
   // Asynchronously marks the current slot as successful if needed. If already
   // marked as good, CompleteUpdateBootFlags() is called starting the action
   // processor.
@@ -110,10 +115,6 @@
   // applying an update from the given |url|.
   void BuildUpdateActions(const std::string& url);
 
-  // Sets up the download parameters based on the update requested on the
-  // |install_plan_|.
-  void SetupDownload();
-
   // Writes to the processing completed marker. Does nothing if
   // |update_completed_marker_| is empty.
   bool WriteUpdateCompletedMarker();
@@ -121,6 +122,41 @@
   // Returns whether an update was completed in the current boot.
   bool UpdateCompletedOnThisBoot();
 
+  // Prefs to use for metrics report
+  // |kPrefsPayloadAttemptNumber|: number of update attempts for the current
+  // payload_id.
+  // |KprefsNumReboots|: number of reboots when applying the current update.
+  // |kPrefsSystemUpdatedMarker|: end timestamp of the last successful update.
+  // |kPrefsUpdateTimestampStart|: start timestamp of the current update.
+  // |kPrefsCurrentBytesDownloaded|: number of bytes downloaded for the current
+  // payload_id.
+  // |kPrefsTotalBytesDownloaded|: number of bytes downloaded in total since
+  // the last successful update.
+
+  // Metrics report function to call:
+  //   |ReportUpdateAttemptMetrics|
+  //   |ReportSuccessfulUpdateMetrics|
+  // Prefs to update:
+  //   |kPrefsSystemUpdatedMarker|
+  void CollectAndReportUpdateMetricsOnUpdateFinished(ErrorCode error_code);
+
+  // Metrics report function to call:
+  //   |ReportAbnormallyTerminatedUpdateAttemptMetrics|
+  //   |ReportTimeToRebootMetrics|
+  // Prefs to update:
+  //   |kPrefsBootId|, |kPrefsPreviousVersion|
+  void UpdatePrefsAndReportUpdateMetricsOnReboot();
+
+  // Prefs to update:
+  //   |kPrefsPayloadAttemptNumber|, |kPrefsUpdateTimestampStart|
+  void UpdatePrefsOnUpdateStart(bool is_resume);
+
+  // Prefs to delete:
+  //   |kPrefsNumReboots|, |kPrefsPayloadAttemptNumber|,
+  //   |kPrefsSystemUpdatedMarker|, |kPrefsUpdateTimestampStart|,
+  //   |kPrefsCurrentBytesDownloaded|
+  void ClearMetricsPrefs();
+
   DaemonStateInterface* daemon_state_;
 
   // DaemonStateAndroid pointers.
@@ -166,6 +202,10 @@
   // before applying an update to the other slot.
   bool updated_boot_flags_ = false;
 
+  std::unique_ptr<ClockInterface> clock_;
+
+  std::unique_ptr<MetricsReporterInterface> metrics_reporter_;
+
   DISALLOW_COPY_AND_ASSIGN(UpdateAttempterAndroid);
 };
 
diff --git a/update_attempter_android_unittest.cc b/update_attempter_android_unittest.cc
new file mode 100644
index 0000000..94452df
--- /dev/null
+++ b/update_attempter_android_unittest.cc
@@ -0,0 +1,209 @@
+//
+// Copyright (C) 2017 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#include "update_engine/update_attempter_android.h"
+
+#include <memory>
+#include <string>
+
+#include <android-base/properties.h>
+#include <base/time/time.h>
+#include <gtest/gtest.h>
+
+#include "update_engine/common/fake_boot_control.h"
+#include "update_engine/common/fake_clock.h"
+#include "update_engine/common/fake_hardware.h"
+#include "update_engine/common/fake_prefs.h"
+#include "update_engine/common/mock_action_processor.h"
+#include "update_engine/common/test_utils.h"
+#include "update_engine/common/utils.h"
+#include "update_engine/daemon_state_android.h"
+#include "update_engine/mock_metrics_reporter.h"
+
+using base::Time;
+using base::TimeDelta;
+using testing::_;
+using update_engine::UpdateStatus;
+
+namespace chromeos_update_engine {
+
+class UpdateAttempterAndroidTest : public ::testing::Test {
+ protected:
+  UpdateAttempterAndroidTest() = default;
+
+  void SetUp() override {
+    clock_ = new FakeClock();
+    metrics_reporter_ = new testing::NiceMock<MockMetricsReporter>();
+    update_attempter_android_.metrics_reporter_.reset(metrics_reporter_);
+    update_attempter_android_.clock_.reset(clock_);
+    update_attempter_android_.processor_.reset(
+        new testing::NiceMock<MockActionProcessor>());
+  }
+
+  void SetUpdateStatus(update_engine::UpdateStatus status) {
+    update_attempter_android_.status_ = status;
+  }
+
+  UpdateAttempterAndroid update_attempter_android_{
+      &daemon_state_, &prefs_, &boot_control_, &hardware_};
+
+  DaemonStateAndroid daemon_state_;
+  FakePrefs prefs_;
+  FakeBootControl boot_control_;
+  FakeHardware hardware_;
+
+  FakeClock* clock_;
+  testing::NiceMock<MockMetricsReporter>* metrics_reporter_;
+};
+
+TEST_F(UpdateAttempterAndroidTest, UpdatePrefsSameBuildVersionOnInit) {
+  std::string build_version =
+      android::base::GetProperty("ro.build.version.incremental", "");
+  prefs_.SetString(kPrefsPreviousVersion, build_version);
+  prefs_.SetString(kPrefsBootId, "oldboot");
+  prefs_.SetInt64(kPrefsNumReboots, 1);
+
+  EXPECT_CALL(*metrics_reporter_, ReportTimeToReboot(_)).Times(0);
+  update_attempter_android_.Init();
+
+  // Check that the boot_id and reboot_count are updated.
+  std::string boot_id;
+  utils::GetBootId(&boot_id);
+  EXPECT_TRUE(prefs_.Exists(kPrefsBootId));
+  std::string prefs_boot_id;
+  EXPECT_TRUE(prefs_.GetString(kPrefsBootId, &prefs_boot_id));
+  EXPECT_EQ(boot_id, prefs_boot_id);
+
+  EXPECT_TRUE(prefs_.Exists(kPrefsNumReboots));
+  int64_t reboot_count;
+  EXPECT_TRUE(prefs_.GetInt64(kPrefsNumReboots, &reboot_count));
+  EXPECT_EQ(2, reboot_count);
+}
+
+TEST_F(UpdateAttempterAndroidTest, UpdatePrefsBuildVersionChangeOnInit) {
+  prefs_.SetString(kPrefsPreviousVersion, "00001");  // Set the fake version
+  prefs_.SetInt64(kPrefsPayloadAttemptNumber, 1);
+  prefs_.SetInt64(kPrefsSystemUpdatedMarker, 23456);
+
+  EXPECT_CALL(*metrics_reporter_,
+              ReportAbnormallyTerminatedUpdateAttemptMetrics())
+      .Times(1);
+
+  Time now = Time::FromInternalValue(34456);
+  clock_->SetMonotonicTime(now);
+  TimeDelta duration = now - Time::FromInternalValue(23456);
+  EXPECT_CALL(*metrics_reporter_, ReportTimeToReboot(duration.InMinutes()))
+      .Times(1);
+
+  update_attempter_android_.Init();
+  // Check that we reset the metric prefs.
+  EXPECT_FALSE(prefs_.Exists(kPrefsNumReboots));
+  EXPECT_FALSE(prefs_.Exists(kPrefsPayloadAttemptNumber));
+  EXPECT_FALSE(prefs_.Exists(kPrefsUpdateTimestampStart));
+  EXPECT_FALSE(prefs_.Exists(kPrefsSystemUpdatedMarker));
+}
+
+TEST_F(UpdateAttempterAndroidTest, ReportMetricsOnUpdateTerminated) {
+  prefs_.SetInt64(kPrefsNumReboots, 3);
+  prefs_.SetInt64(kPrefsPayloadAttemptNumber, 2);
+  prefs_.SetString(kPrefsPreviousVersion, "56789");
+  prefs_.SetInt64(kPrefsUpdateTimestampStart, 12345);
+
+  Time boot_time = Time::FromInternalValue(22345);
+  Time up_time = Time::FromInternalValue(21345);
+  clock_->SetBootTime(boot_time);
+  clock_->SetMonotonicTime(up_time);
+  TimeDelta duration = boot_time - Time::FromInternalValue(12345);
+  TimeDelta duration_uptime = up_time - Time::FromInternalValue(12345);
+  EXPECT_CALL(
+      *metrics_reporter_,
+      ReportUpdateAttemptMetrics(_,
+                                 2,
+                                 _,
+                                 duration,
+                                 duration_uptime,
+                                 _,
+                                 metrics::AttemptResult::kUpdateSucceeded,
+                                 ErrorCode::kSuccess))
+      .Times(1);
+  EXPECT_CALL(*metrics_reporter_,
+              ReportSuccessfulUpdateMetrics(2, 0, _, _, _, _, duration, 3, _))
+      .Times(1);
+
+  SetUpdateStatus(UpdateStatus::UPDATE_AVAILABLE);
+  update_attempter_android_.ProcessingDone(nullptr, ErrorCode::kSuccess);
+
+  EXPECT_FALSE(prefs_.Exists(kPrefsNumReboots));
+  EXPECT_FALSE(prefs_.Exists(kPrefsPayloadAttemptNumber));
+  EXPECT_FALSE(prefs_.Exists(kPrefsUpdateTimestampStart));
+  EXPECT_TRUE(prefs_.Exists(kPrefsSystemUpdatedMarker));
+}
+
+TEST_F(UpdateAttempterAndroidTest, ReportMetricsForBytesDownloaded) {
+  // Check both prefs are updated correctly.
+  update_attempter_android_.BytesReceived(20, 50, 200);
+  EXPECT_EQ(
+      20,
+      metrics_utils::GetPersistedValue(kPrefsCurrentBytesDownloaded, &prefs_));
+  EXPECT_EQ(
+      20,
+      metrics_utils::GetPersistedValue(kPrefsTotalBytesDownloaded, &prefs_));
+
+  EXPECT_CALL(*metrics_reporter_,
+              ReportUpdateAttemptDownloadMetrics(50, _, _, _, _))
+      .Times(1);
+  EXPECT_CALL(*metrics_reporter_,
+              ReportUpdateAttemptDownloadMetrics(40, _, _, _, _))
+      .Times(1);
+
+  int64_t total_bytes[kNumDownloadSources] = {};
+  total_bytes[kDownloadSourceHttpsServer] = 90;
+  EXPECT_CALL(*metrics_reporter_,
+              ReportSuccessfulUpdateMetrics(
+                  _,
+                  _,
+                  _,
+                  _,
+                  test_utils::DownloadSourceMatcher(total_bytes),
+                  125,
+                  _,
+                  _,
+                  _))
+      .Times(1);
+
+  // The first update fails after receving 50 bytes in total.
+  update_attempter_android_.BytesReceived(30, 50, 200);
+  update_attempter_android_.ProcessingDone(nullptr, ErrorCode::kError);
+  EXPECT_EQ(
+      0,
+      metrics_utils::GetPersistedValue(kPrefsCurrentBytesDownloaded, &prefs_));
+  EXPECT_EQ(
+      50,
+      metrics_utils::GetPersistedValue(kPrefsTotalBytesDownloaded, &prefs_));
+
+  // The second update succeeds after receiving 40 bytes, which leads to a
+  // overhead of 50 / 40 = 125%.
+  update_attempter_android_.BytesReceived(40, 40, 50);
+  update_attempter_android_.ProcessingDone(nullptr, ErrorCode::kSuccess);
+  // Both prefs should be cleared.
+  EXPECT_EQ(
+      0,
+      metrics_utils::GetPersistedValue(kPrefsCurrentBytesDownloaded, &prefs_));
+  EXPECT_EQ(
+      0, metrics_utils::GetPersistedValue(kPrefsTotalBytesDownloaded, &prefs_));
+}
+
+}  // namespace chromeos_update_engine
diff --git a/update_attempter_unittest.cc b/update_attempter_unittest.cc
index 06c82c9..240e4ec 100644
--- a/update_attempter_unittest.cc
+++ b/update_attempter_unittest.cc
@@ -22,7 +22,6 @@
 
 #include <base/files/file_util.h>
 #include <base/message_loop/message_loop.h>
-#include <brillo/bind_lambda.h>
 #include <brillo/message_loops/base_message_loop.h>
 #include <brillo/message_loops/message_loop.h>
 #include <brillo/message_loops/message_loop_utils.h>
@@ -43,6 +42,7 @@
 #include "update_engine/fake_system_state.h"
 #include "update_engine/mock_p2p_manager.h"
 #include "update_engine/mock_payload_state.h"
+#include "update_engine/mock_service_observer.h"
 #include "update_engine/payload_consumer/filesystem_verifier_action.h"
 #include "update_engine/payload_consumer/install_plan.h"
 #include "update_engine/payload_consumer/payload_constants.h"
@@ -50,9 +50,13 @@
 
 using base::Time;
 using base::TimeDelta;
+using chromeos_update_manager::EvalStatus;
+using chromeos_update_manager::UpdateCheckParams;
 using std::string;
 using std::unique_ptr;
+using testing::_;
 using testing::DoAll;
+using testing::Field;
 using testing::InSequence;
 using testing::Ne;
 using testing::NiceMock;
@@ -61,7 +65,8 @@
 using testing::ReturnPointee;
 using testing::SaveArg;
 using testing::SetArgPointee;
-using testing::_;
+using update_engine::UpdateAttemptFlags;
+using update_engine::UpdateEngineStatus;
 using update_engine::UpdateStatus;
 
 namespace chromeos_update_engine {
@@ -121,7 +126,7 @@
     EXPECT_EQ(0.0, attempter_.download_progress_);
     EXPECT_EQ(0, attempter_.last_checked_time_);
     EXPECT_EQ("0.0.0.0", attempter_.new_version_);
-    EXPECT_EQ(0, attempter_.new_payload_size_);
+    EXPECT_EQ(0ULL, attempter_.new_payload_size_);
     processor_ = new NiceMock<MockActionProcessor>();
     attempter_.processor_.reset(processor_);  // Transfers ownership.
     prefs_ = fake_system_state_.mock_prefs();
@@ -202,8 +207,8 @@
                         false /* is_interactive */);
   EXPECT_CALL(*prefs_, GetInt64(kPrefsDeltaUpdateFailures, _)).Times(0);
   attempter_.ActionCompleted(nullptr, &action, ErrorCode::kSuccess);
-  EXPECT_EQ(503, attempter_.http_response_code());
   EXPECT_EQ(UpdateStatus::FINALIZING, attempter_.status());
+  EXPECT_EQ(0.0, attempter_.download_progress_);
   ASSERT_EQ(nullptr, attempter_.error_event_.get());
 }
 
@@ -217,6 +222,86 @@
   ASSERT_NE(nullptr, attempter_.error_event_.get());
 }
 
+TEST_F(UpdateAttempterTest, DownloadProgressAccumulationTest) {
+  // Simple test case, where all the values match (nothing was skipped)
+  uint64_t bytes_progressed_1 = 1024 * 1024;  // 1MB
+  uint64_t bytes_progressed_2 = 1024 * 1024;  // 1MB
+  uint64_t bytes_received_1 = bytes_progressed_1;
+  uint64_t bytes_received_2 = bytes_received_1 + bytes_progressed_2;
+  uint64_t bytes_total = 20 * 1024 * 1024;  // 20MB
+
+  double progress_1 =
+      static_cast<double>(bytes_received_1) / static_cast<double>(bytes_total);
+  double progress_2 =
+      static_cast<double>(bytes_received_2) / static_cast<double>(bytes_total);
+
+  EXPECT_EQ(0.0, attempter_.download_progress_);
+  // This is set via inspecting the InstallPlan payloads when the
+  // OmahaResponseAction is completed
+  attempter_.new_payload_size_ = bytes_total;
+  NiceMock<MockServiceObserver> observer;
+  EXPECT_CALL(observer,
+              SendStatusUpdate(AllOf(
+                  Field(&UpdateEngineStatus::progress, progress_1),
+                  Field(&UpdateEngineStatus::status, UpdateStatus::DOWNLOADING),
+                  Field(&UpdateEngineStatus::new_size_bytes, bytes_total))));
+  EXPECT_CALL(observer,
+              SendStatusUpdate(AllOf(
+                  Field(&UpdateEngineStatus::progress, progress_2),
+                  Field(&UpdateEngineStatus::status, UpdateStatus::DOWNLOADING),
+                  Field(&UpdateEngineStatus::new_size_bytes, bytes_total))));
+  attempter_.AddObserver(&observer);
+  attempter_.BytesReceived(bytes_progressed_1, bytes_received_1, bytes_total);
+  EXPECT_EQ(progress_1, attempter_.download_progress_);
+  // This iteration validates that a later set of updates to the variables are
+  // properly handled (so that |getStatus()| will return the same progress info
+  // as the callback is receiving.
+  attempter_.BytesReceived(bytes_progressed_2, bytes_received_2, bytes_total);
+  EXPECT_EQ(progress_2, attempter_.download_progress_);
+}
+
+TEST_F(UpdateAttempterTest, ChangeToDownloadingOnReceivedBytesTest) {
+  // The transition into UpdateStatus::DOWNLOADING happens when the
+  // first bytes are received.
+  uint64_t bytes_progressed = 1024 * 1024;    // 1MB
+  uint64_t bytes_received = 2 * 1024 * 1024;  // 2MB
+  uint64_t bytes_total = 20 * 1024 * 1024;    // 300MB
+  attempter_.status_ = UpdateStatus::CHECKING_FOR_UPDATE;
+  // This is set via inspecting the InstallPlan payloads when the
+  // OmahaResponseAction is completed
+  attempter_.new_payload_size_ = bytes_total;
+  EXPECT_EQ(0.0, attempter_.download_progress_);
+  NiceMock<MockServiceObserver> observer;
+  EXPECT_CALL(observer,
+              SendStatusUpdate(AllOf(
+                  Field(&UpdateEngineStatus::status, UpdateStatus::DOWNLOADING),
+                  Field(&UpdateEngineStatus::new_size_bytes, bytes_total))));
+  attempter_.AddObserver(&observer);
+  attempter_.BytesReceived(bytes_progressed, bytes_received, bytes_total);
+  EXPECT_EQ(UpdateStatus::DOWNLOADING, attempter_.status_);
+}
+
+TEST_F(UpdateAttempterTest, BroadcastCompleteDownloadTest) {
+  // There is a special case to ensure that at 100% downloaded,
+  // download_progress_ is updated and that value broadcast. This test confirms
+  // that.
+  uint64_t bytes_progressed = 0;              // ignored
+  uint64_t bytes_received = 5 * 1024 * 1024;  // ignored
+  uint64_t bytes_total = 5 * 1024 * 1024;     // 300MB
+  attempter_.status_ = UpdateStatus::DOWNLOADING;
+  attempter_.new_payload_size_ = bytes_total;
+  EXPECT_EQ(0.0, attempter_.download_progress_);
+  NiceMock<MockServiceObserver> observer;
+  EXPECT_CALL(observer,
+              SendStatusUpdate(AllOf(
+                  Field(&UpdateEngineStatus::progress, 1.0),
+                  Field(&UpdateEngineStatus::status, UpdateStatus::DOWNLOADING),
+                  Field(&UpdateEngineStatus::new_size_bytes, bytes_total))));
+  attempter_.AddObserver(&observer);
+  attempter_.BytesReceived(bytes_progressed, bytes_received, bytes_total);
+  EXPECT_EQ(1.0, attempter_.download_progress_);
+}
+
 TEST_F(UpdateAttempterTest, ActionCompletedOmahaRequestTest) {
   unique_ptr<MockHttpFetcher> fetcher(new MockHttpFetcher("", 0, nullptr));
   fetcher->FailTransfer(500);  // Sets the HTTP response code.
@@ -323,8 +408,7 @@
       .Times(0);
   OmahaResponse response;
   string url1 = "http://url1";
-  response.payload_urls.push_back(url1);
-  response.payload_urls.push_back("https://url");
+  response.packages.push_back({.payload_urls = {url1, "https://url"}});
   EXPECT_CALL(*(fake_system_state_.mock_payload_state()), GetCurrentUrl())
       .WillRepeatedly(Return(url1));
   fake_system_state_.mock_payload_state()->SetResponse(response);
@@ -948,14 +1032,14 @@
 TEST_F(UpdateAttempterTest, CheckForUpdateAUTest) {
   fake_system_state_.fake_hardware()->SetIsOfficialBuild(true);
   fake_system_state_.fake_hardware()->SetAreDevFeaturesEnabled(false);
-  attempter_.CheckForUpdate("", "autest", true);
+  attempter_.CheckForUpdate("", "autest", UpdateAttemptFlags::kNone);
   EXPECT_EQ(constants::kOmahaDefaultAUTestURL, attempter_.forced_omaha_url());
 }
 
 TEST_F(UpdateAttempterTest, CheckForUpdateScheduledAUTest) {
   fake_system_state_.fake_hardware()->SetIsOfficialBuild(true);
   fake_system_state_.fake_hardware()->SetAreDevFeaturesEnabled(false);
-  attempter_.CheckForUpdate("", "autest-scheduled", true);
+  attempter_.CheckForUpdate("", "autest-scheduled", UpdateAttemptFlags::kNone);
   EXPECT_EQ(constants::kOmahaDefaultAUTestURL, attempter_.forced_omaha_url());
 }
 
@@ -969,4 +1053,90 @@
       fake_system_state_.request_params()->target_version_prefix().empty());
 }
 
+TEST_F(UpdateAttempterTest, UpdateDeferredByPolicyTest) {
+  // Construct an OmahaResponseHandlerAction that has processed an InstallPlan,
+  // but the update is being deferred by the Policy.
+  OmahaResponseHandlerAction* response_action =
+      new OmahaResponseHandlerAction(&fake_system_state_);
+  response_action->install_plan_.version = "a.b.c.d";
+  response_action->install_plan_.system_version = "b.c.d.e";
+  response_action->install_plan_.payloads.push_back(
+      {.size = 1234ULL, .type = InstallPayloadType::kFull});
+  attempter_.response_handler_action_.reset(response_action);
+  // Inform the UpdateAttempter that the OmahaResponseHandlerAction has
+  // completed, with the deferred-update error code.
+  attempter_.ActionCompleted(
+      nullptr, response_action, ErrorCode::kOmahaUpdateDeferredPerPolicy);
+  {
+    UpdateEngineStatus status;
+    attempter_.GetStatus(&status);
+    EXPECT_EQ(UpdateStatus::UPDATE_AVAILABLE, status.status);
+    EXPECT_EQ(response_action->install_plan_.version, status.new_version);
+    EXPECT_EQ(response_action->install_plan_.system_version,
+              status.new_system_version);
+    EXPECT_EQ(response_action->install_plan_.payloads[0].size,
+              status.new_size_bytes);
+  }
+  // An "error" event should have been created to tell Omaha that the update is
+  // being deferred.
+  EXPECT_TRUE(nullptr != attempter_.error_event_);
+  EXPECT_EQ(OmahaEvent::kTypeUpdateComplete, attempter_.error_event_->type);
+  EXPECT_EQ(OmahaEvent::kResultUpdateDeferred, attempter_.error_event_->result);
+  ErrorCode expected_code = static_cast<ErrorCode>(
+      static_cast<int>(ErrorCode::kOmahaUpdateDeferredPerPolicy) |
+      static_cast<int>(ErrorCode::kTestOmahaUrlFlag));
+  EXPECT_EQ(expected_code, attempter_.error_event_->error_code);
+  // End the processing
+  attempter_.ProcessingDone(nullptr, ErrorCode::kOmahaUpdateDeferredPerPolicy);
+  // Validate the state of the attempter.
+  {
+    UpdateEngineStatus status;
+    attempter_.GetStatus(&status);
+    EXPECT_EQ(UpdateStatus::REPORTING_ERROR_EVENT, status.status);
+    EXPECT_EQ(response_action->install_plan_.version, status.new_version);
+    EXPECT_EQ(response_action->install_plan_.system_version,
+              status.new_system_version);
+    EXPECT_EQ(response_action->install_plan_.payloads[0].size,
+              status.new_size_bytes);
+  }
+}
+
+TEST_F(UpdateAttempterTest, UpdateIsNotRunningWhenUpdateAvailable) {
+  EXPECT_FALSE(attempter_.IsUpdateRunningOrScheduled());
+  // Verify in-progress update with UPDATE_AVAILABLE is running
+  attempter_.status_ = UpdateStatus::UPDATE_AVAILABLE;
+  EXPECT_TRUE(attempter_.IsUpdateRunningOrScheduled());
+}
+
+TEST_F(UpdateAttempterTest, UpdateAttemptFlagsCachedAtUpdateStart) {
+  attempter_.SetUpdateAttemptFlags(UpdateAttemptFlags::kFlagRestrictDownload);
+
+  UpdateCheckParams params = {.updates_enabled = true};
+  attempter_.OnUpdateScheduled(EvalStatus::kSucceeded, params);
+
+  EXPECT_EQ(UpdateAttemptFlags::kFlagRestrictDownload,
+            attempter_.GetCurrentUpdateAttemptFlags());
+}
+
+TEST_F(UpdateAttempterTest, InteractiveUpdateUsesPassedRestrictions) {
+  attempter_.SetUpdateAttemptFlags(UpdateAttemptFlags::kFlagRestrictDownload);
+
+  attempter_.CheckForUpdate("", "", UpdateAttemptFlags::kNone);
+  EXPECT_EQ(UpdateAttemptFlags::kNone,
+            attempter_.GetCurrentUpdateAttemptFlags());
+}
+
+TEST_F(UpdateAttempterTest, NonInteractiveUpdateUsesSetRestrictions) {
+  attempter_.SetUpdateAttemptFlags(UpdateAttemptFlags::kNone);
+
+  // This tests that when CheckForUpdate() is called with the non-interactive
+  // flag set, that it doesn't change the current UpdateAttemptFlags.
+  attempter_.CheckForUpdate("",
+                            "",
+                            UpdateAttemptFlags::kFlagNonInteractive |
+                                UpdateAttemptFlags::kFlagRestrictDownload);
+  EXPECT_EQ(UpdateAttemptFlags::kNone,
+            attempter_.GetCurrentUpdateAttemptFlags());
+}
+
 }  // namespace chromeos_update_engine
diff --git a/update_engine.gyp b/update_engine.gyp
index 4da04d4..f312a1d 100644
--- a/update_engine.gyp
+++ b/update_engine.gyp
@@ -257,7 +257,7 @@
         'hardware_chromeos.cc',
         'image_properties_chromeos.cc',
         'libcurl_http_fetcher.cc',
-        'metrics.cc',
+        'metrics_reporter_omaha.cc',
         'metrics_utils.cc',
         'omaha_request_action.cc',
         'omaha_request_params.cc',
@@ -510,6 +510,7 @@
             'fake_system_state.cc',
             'hardware_chromeos_unittest.cc',
             'image_properties_chromeos_unittest.cc',
+            'metrics_reporter_omaha_unittest.cc',
             'metrics_utils_unittest.cc',
             'omaha_request_action_unittest.cc',
             'omaha_request_params_unittest.cc',
diff --git a/update_engine.rc b/update_engine.rc
index b6a706a..a7d6235 100644
--- a/update_engine.rc
+++ b/update_engine.rc
@@ -1,5 +1,9 @@
-service update_engine /system/bin/update_engine --logtostderr --foreground
+service update_engine /system/bin/update_engine --logtostderr --logtofile --foreground
     class late_start
     user root
     group root system wakelock inet cache
     writepid /dev/cpuset/system-background/tasks
+    disabled
+
+on property:ro.boot.slot_suffix=*
+    enable update_engine
diff --git a/update_engine_client.cc b/update_engine_client.cc
index ad6ded0..bb19632 100644
--- a/update_engine_client.cc
+++ b/update_engine_client.cc
@@ -79,7 +79,7 @@
 
     // We can't call QuitWithExitCode from OnInit(), so we delay the execution
     // of the ProcessFlags method after the Daemon initialization is done.
-    base::MessageLoop::current()->PostTask(
+    base::MessageLoop::current()->task_runner()->PostTask(
         FROM_HERE,
         base::Bind(&UpdateEngineClient::ProcessFlagsAndExit,
                    base::Unretained(this)));
diff --git a/update_engine_client_android.cc b/update_engine_client_android.cc
index 989a97e..267f6e9 100644
--- a/update_engine_client_android.cc
+++ b/update_engine_client_android.cc
@@ -97,7 +97,10 @@
   ErrorCode code = static_cast<ErrorCode>(error_code);
   LOG(INFO) << "onPayloadApplicationComplete(" << utils::ErrorCodeToString(code)
             << " (" << error_code << "))";
-  client_->ExitWhenIdle(code == ErrorCode::kSuccess ? EX_OK : 1);
+  client_->ExitWhenIdle(
+      (code == ErrorCode::kSuccess || code == ErrorCode::kUpdatedButNotActive)
+          ? EX_OK
+          : 1);
   return Status::ok();
 }
 
diff --git a/update_manager/android_things_policy.cc b/update_manager/android_things_policy.cc
new file mode 100644
index 0000000..5fbda46
--- /dev/null
+++ b/update_manager/android_things_policy.cc
@@ -0,0 +1,180 @@
+//
+// Copyright (C) 2017 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#include "update_engine/update_manager/android_things_policy.h"
+
+#include <string>
+#include <vector>
+
+#include <base/logging.h>
+#include <base/time/time.h>
+
+#include "update_engine/update_manager/api_restricted_downloads_policy_impl.h"
+#include "update_engine/update_manager/enough_slots_ab_updates_policy_impl.h"
+#include "update_engine/update_manager/interactive_update_policy_impl.h"
+#include "update_engine/update_manager/official_build_check_policy_impl.h"
+
+using base::Time;
+using chromeos_update_engine::ErrorCode;
+using std::string;
+using std::vector;
+
+namespace chromeos_update_manager {
+
+const NextUpdateCheckPolicyConstants
+    AndroidThingsPolicy::kNextUpdateCheckPolicyConstants = {
+        .timeout_initial_interval = 7 * 60,
+        .timeout_periodic_interval = 5 * 60 * 60,
+        .timeout_max_backoff_interval = 26 * 60 * 60,
+        .timeout_regular_fuzz = 10 * 60,
+        .attempt_backoff_max_interval_in_days = 16,
+        .attempt_backoff_fuzz_in_hours = 12,
+};
+
+EvalStatus AndroidThingsPolicy::UpdateCheckAllowed(
+    EvaluationContext* ec,
+    State* state,
+    string* error,
+    UpdateCheckParams* result) const {
+  // Set the default return values.
+  result->updates_enabled = true;
+  result->target_channel.clear();
+  result->target_version_prefix.clear();
+  result->is_interactive = false;
+
+  // Build a list of policies to consult.  Note that each policy may modify the
+  // result structure, even if it signals kContinue.
+  EnoughSlotsAbUpdatesPolicyImpl enough_slots_ab_updates_policy;
+  OnlyUpdateOfficialBuildsPolicyImpl only_update_official_builds_policy;
+  InteractiveUpdatePolicyImpl interactive_update_policy;
+  NextUpdateCheckTimePolicyImpl next_update_check_time_policy(
+      kNextUpdateCheckPolicyConstants);
+
+  vector<Policy const*> policies_to_consult = {
+      // Do not perform any updates if there are not enough slots to do
+      // A/B updates
+      &enough_slots_ab_updates_policy,
+
+      // Unofficial builds should not perform periodic update checks.
+      &only_update_official_builds_policy,
+
+      // Check to see if an interactive update was requested.
+      &interactive_update_policy,
+
+      // Ensure that periodic update checks are timed properly.
+      &next_update_check_time_policy,
+  };
+
+  // Now that the list of policy implementations, and the order to consult them,
+  // as been setup, do that.  If none of the policies make a definitive
+  // decisions about whether or not to check for updates, then allow the update
+  // check to happen.
+  EvalStatus status = ConsultPolicies(policies_to_consult,
+                                      &Policy::UpdateCheckAllowed,
+                                      ec,
+                                      state,
+                                      error,
+                                      result);
+  if (status != EvalStatus::kContinue) {
+    return status;
+  } else {
+    // It is time to check for an update.
+    LOG(INFO) << "Allowing update check.";
+    return EvalStatus::kSucceeded;
+  }
+}
+
+// Uses the |UpdateRestrictions| to determine if the download and apply can
+// occur at this time.
+EvalStatus AndroidThingsPolicy::UpdateCanBeApplied(
+    EvaluationContext* ec,
+    State* state,
+    string* error,
+    ErrorCode* result,
+    chromeos_update_engine::InstallPlan* install_plan) const {
+  // Build a list of policies to consult.  Note that each policy may modify the
+  // result structure, even if it signals kContinue.
+  ApiRestrictedDownloadsPolicyImpl api_restricted_downloads_policy;
+
+  vector<Policy const*> policies_to_consult = {
+      // Do not apply the update if all updates are restricted by the API.
+      &api_restricted_downloads_policy,
+  };
+
+  // Now that the list of policy implementations, and the order to consult them,
+  // as been setup, do that.  If none of the policies make a definitive
+  // decisions about whether or not to check for updates, then allow the update
+  // check to happen.
+  EvalStatus status = ConsultPolicies(policies_to_consult,
+                                      &Policy::UpdateCanBeApplied,
+                                      ec,
+                                      state,
+                                      error,
+                                      result,
+                                      install_plan);
+  if (EvalStatus::kContinue != status) {
+    return status;
+  } else {
+    // The update can proceed.
+    LOG(INFO) << "Allowing update to be applied.";
+    *result = ErrorCode::kSuccess;
+    return EvalStatus::kSucceeded;
+  }
+}
+
+// Always returns |EvalStatus::kSucceeded|
+EvalStatus AndroidThingsPolicy::UpdateCanStart(EvaluationContext* ec,
+                                               State* state,
+                                               string* error,
+                                               UpdateDownloadParams* result,
+                                               UpdateState update_state) const {
+  // Update is good to go.
+  result->update_can_start = true;
+  return EvalStatus::kSucceeded;
+}
+
+// Always returns |EvalStatus::kSucceeded|
+EvalStatus AndroidThingsPolicy::UpdateDownloadAllowed(EvaluationContext* ec,
+                                                      State* state,
+                                                      string* error,
+                                                      bool* result) const {
+  // By default, we allow updates.
+  *result = true;
+  return EvalStatus::kSucceeded;
+}
+
+// P2P is always disabled.  Returns |result|==|false| and
+// |EvalStatus::kSucceeded|
+EvalStatus AndroidThingsPolicy::P2PEnabled(EvaluationContext* ec,
+                                           State* state,
+                                           string* error,
+                                           bool* result) const {
+  *result = false;
+  return EvalStatus::kSucceeded;
+}
+
+// This will return immediately with |EvalStatus::kSucceeded| and set
+// |result|==|false|
+EvalStatus AndroidThingsPolicy::P2PEnabledChanged(EvaluationContext* ec,
+                                                  State* state,
+                                                  string* error,
+                                                  bool* result,
+                                                  bool prev_result) const {
+  *result = false;
+  return EvalStatus::kSucceeded;
+}
+
+}  // namespace chromeos_update_manager
diff --git a/update_manager/android_things_policy.h b/update_manager/android_things_policy.h
new file mode 100644
index 0000000..9fd8bc4
--- /dev/null
+++ b/update_manager/android_things_policy.h
@@ -0,0 +1,92 @@
+//
+// Copyright (C) 2017 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#ifndef UPDATE_ENGINE_UPDATE_MANAGER_ANDROID_THINGS_POLICY_H_
+#define UPDATE_ENGINE_UPDATE_MANAGER_ANDROID_THINGS_POLICY_H_
+
+#include <string>
+
+#include "update_engine/update_manager/next_update_check_policy_impl.h"
+#include "update_engine/update_manager/policy_utils.h"
+
+namespace chromeos_update_manager {
+
+// AndroidThingsPolicy implements the policy-related logic used in
+// AndroidThings.
+class AndroidThingsPolicy : public Policy {
+ public:
+  AndroidThingsPolicy() = default;
+  ~AndroidThingsPolicy() override = default;
+
+  // Policy overrides.
+  EvalStatus UpdateCheckAllowed(EvaluationContext* ec,
+                                State* state,
+                                std::string* error,
+                                UpdateCheckParams* result) const override;
+
+  // Uses the |UpdateRestrictions| to determine if the download and apply can
+  // occur at this time.
+  EvalStatus UpdateCanBeApplied(
+      EvaluationContext* ec,
+      State* state,
+      std::string* error,
+      chromeos_update_engine::ErrorCode* result,
+      chromeos_update_engine::InstallPlan* install_plan) const override;
+
+  // Always returns |EvalStatus::kSucceeded|
+  EvalStatus UpdateCanStart(EvaluationContext* ec,
+                            State* state,
+                            std::string* error,
+                            UpdateDownloadParams* result,
+                            UpdateState update_state) const override;
+
+  // Always returns |EvalStatus::kSucceeded|
+  EvalStatus UpdateDownloadAllowed(EvaluationContext* ec,
+                                   State* state,
+                                   std::string* error,
+                                   bool* result) const override;
+
+  // P2P is always disabled.  Returns |result|==|false| and
+  // |EvalStatus::kSucceeded|
+  EvalStatus P2PEnabled(EvaluationContext* ec,
+                        State* state,
+                        std::string* error,
+                        bool* result) const override;
+
+  // This will return immediately with |EvalStatus::kSucceeded| and set
+  // |result|==|false|
+  EvalStatus P2PEnabledChanged(EvaluationContext* ec,
+                               State* state,
+                               std::string* error,
+                               bool* result,
+                               bool prev_result) const override;
+
+ protected:
+  // Policy override.
+  std::string PolicyName() const override { return "AndroidThingsPolicy"; }
+
+ private:
+  friend class UmAndroidThingsPolicyTest;
+  FRIEND_TEST(UmAndroidThingsPolicyTest, UpdateCheckAllowedWaitsForTheTimeout);
+
+  static const NextUpdateCheckPolicyConstants kNextUpdateCheckPolicyConstants;
+
+  DISALLOW_COPY_AND_ASSIGN(AndroidThingsPolicy);
+};
+
+}  // namespace chromeos_update_manager
+
+#endif  // UPDATE_ENGINE_UPDATE_MANAGER_ANDROID_THINGS_POLICY_H_
diff --git a/update_manager/android_things_policy_unittest.cc b/update_manager/android_things_policy_unittest.cc
new file mode 100644
index 0000000..8a50bc2
--- /dev/null
+++ b/update_manager/android_things_policy_unittest.cc
@@ -0,0 +1,188 @@
+//
+// Copyright (C) 2017 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#include "update_engine/update_manager/android_things_policy.h"
+
+#include <memory>
+
+#include "update_engine/update_manager/next_update_check_policy_impl.h"
+#include "update_engine/update_manager/policy_test_utils.h"
+
+using base::Time;
+using base::TimeDelta;
+using chromeos_update_engine::ErrorCode;
+using chromeos_update_engine::InstallPlan;
+
+namespace chromeos_update_manager {
+
+class UmAndroidThingsPolicyTest : public UmPolicyTestBase {
+ protected:
+  UmAndroidThingsPolicyTest() {
+    policy_ = std::make_unique<AndroidThingsPolicy>();
+  }
+
+  void SetUpDefaultState() override {
+    UmPolicyTestBase::SetUpDefaultState();
+
+    // For the purpose of the tests, this is an official build
+    fake_state_.system_provider()->var_is_official_build()->reset(
+        new bool(true));
+    // NOLINTNEXTLINE(readability/casting)
+    fake_state_.system_provider()->var_num_slots()->reset(new unsigned int(2));
+  }
+
+  // Configures the policy to return a desired value from UpdateCheckAllowed by
+  // faking the current wall clock time as needed. Restores the default state.
+  // This is used when testing policies that depend on this one.
+  virtual void SetUpdateCheckAllowed(bool allow_check) {
+    Time next_update_check;
+    CallMethodWithContext(&NextUpdateCheckTimePolicyImpl::NextUpdateCheckTime,
+                          &next_update_check,
+                          AndroidThingsPolicy::kNextUpdateCheckPolicyConstants);
+    SetUpDefaultState();
+    Time curr_time = next_update_check;
+    if (allow_check)
+      curr_time += TimeDelta::FromSeconds(1);
+    else
+      curr_time -= TimeDelta::FromSeconds(1);
+    fake_clock_.SetWallclockTime(curr_time);
+  }
+};
+
+TEST_F(UmAndroidThingsPolicyTest, UpdateCheckAllowedWaitsForTheTimeout) {
+  // We get the next update_check timestamp from the policy's private method
+  // and then we check the public method respects that value on the normal
+  // case.
+  Time next_update_check;
+  Time last_checked_time =
+      fake_clock_.GetWallclockTime() + TimeDelta::FromMinutes(1234);
+
+  LOG(INFO) << "last_checked_time: " << last_checked_time;
+  fake_state_.updater_provider()->var_last_checked_time()->reset(
+      new Time(last_checked_time));
+  CallMethodWithContext(&NextUpdateCheckTimePolicyImpl::NextUpdateCheckTime,
+                        &next_update_check,
+                        AndroidThingsPolicy::kNextUpdateCheckPolicyConstants);
+  LOG(INFO) << "Next check allowed at: " << next_update_check;
+
+  // Check that the policy blocks until the next_update_check is reached.
+  SetUpDefaultClock();
+  SetUpDefaultState();
+  fake_state_.updater_provider()->var_last_checked_time()->reset(
+      new Time(last_checked_time));
+  fake_clock_.SetWallclockTime(next_update_check - TimeDelta::FromSeconds(1));
+
+  UpdateCheckParams result;
+  ExpectPolicyStatus(
+      EvalStatus::kAskMeAgainLater, &Policy::UpdateCheckAllowed, &result);
+
+  SetUpDefaultClock();
+  SetUpDefaultState();
+  fake_state_.updater_provider()->var_last_checked_time()->reset(
+      new Time(last_checked_time));
+  fake_clock_.SetWallclockTime(next_update_check + TimeDelta::FromSeconds(1));
+  ExpectPolicyStatus(
+      EvalStatus::kSucceeded, &Policy::UpdateCheckAllowed, &result);
+  EXPECT_TRUE(result.updates_enabled);
+  EXPECT_FALSE(result.is_interactive);
+}
+
+TEST_F(UmAndroidThingsPolicyTest,
+       UpdateCheckAllowedUpdatesDisabledForUnofficialBuilds) {
+  // UpdateCheckAllowed should return kAskMeAgainLater if this is an unofficial
+  // build; we don't want periodic update checks on developer images.
+
+  fake_state_.system_provider()->var_is_official_build()->reset(
+      new bool(false));
+
+  UpdateCheckParams result;
+  ExpectPolicyStatus(
+      EvalStatus::kAskMeAgainLater, &Policy::UpdateCheckAllowed, &result);
+}
+
+TEST_F(UmAndroidThingsPolicyTest,
+       UpdateCheckAllowedUpdatesDisabledWhenNotEnoughSlotsAbUpdates) {
+  // UpdateCheckAllowed should return false (kSucceeded) if the image booted
+  // without enough slots to do A/B updates.
+
+  // NOLINTNEXTLINE(readability/casting)
+  fake_state_.system_provider()->var_num_slots()->reset(new unsigned int(1));
+
+  UpdateCheckParams result;
+  ExpectPolicyStatus(
+      EvalStatus::kSucceeded, &Policy::UpdateCheckAllowed, &result);
+  EXPECT_FALSE(result.updates_enabled);
+}
+
+TEST_F(UmAndroidThingsPolicyTest,
+       UpdateCheckAllowedForcedUpdateRequestedInteractive) {
+  // UpdateCheckAllowed should return true because a forced update request was
+  // signaled for an interactive update.
+
+  SetUpdateCheckAllowed(true);
+  fake_state_.updater_provider()->var_forced_update_requested()->reset(
+      new UpdateRequestStatus(UpdateRequestStatus::kInteractive));
+
+  UpdateCheckParams result;
+  ExpectPolicyStatus(
+      EvalStatus::kSucceeded, &Policy::UpdateCheckAllowed, &result);
+  EXPECT_TRUE(result.updates_enabled);
+  EXPECT_TRUE(result.is_interactive);
+}
+
+TEST_F(UmAndroidThingsPolicyTest,
+       UpdateCheckAllowedForcedUpdateRequestedPeriodic) {
+  // UpdateCheckAllowed should return true because a forced update request was
+  // signaled for a periodic check.
+
+  SetUpdateCheckAllowed(true);
+  fake_state_.updater_provider()->var_forced_update_requested()->reset(
+      new UpdateRequestStatus(UpdateRequestStatus::kPeriodic));
+
+  UpdateCheckParams result;
+  ExpectPolicyStatus(
+      EvalStatus::kSucceeded, &Policy::UpdateCheckAllowed, &result);
+  EXPECT_TRUE(result.updates_enabled);
+  EXPECT_FALSE(result.is_interactive);
+}
+
+TEST_F(UmAndroidThingsPolicyTest, UpdateCanBeAppliedOk) {
+  // UpdateCanBeApplied should return kSucceeded in the base case
+
+  InstallPlan plan;
+  ErrorCode result;
+  ExpectPolicyStatus(
+      EvalStatus::kSucceeded, &Policy::UpdateCanBeApplied, &result, &plan);
+
+  EXPECT_EQ(ErrorCode::kSuccess, result);
+}
+
+TEST_F(UmAndroidThingsPolicyTest, UpdateCanBeAppliedRestricted) {
+  // UpdateCanBeApplied should return kOmahaUpdateDeferredPerPolicy in
+  // when the restricted flag is set in the Updater.
+
+  fake_state_.updater_provider()->var_update_restrictions()->reset(
+      new UpdateRestrictions(UpdateRestrictions::kRestrictDownloading));
+
+  InstallPlan plan;
+  ErrorCode result;
+  ExpectPolicyStatus(
+      EvalStatus::kSucceeded, &Policy::UpdateCanBeApplied, &result, &plan);
+
+  EXPECT_EQ(ErrorCode::kOmahaUpdateDeferredPerPolicy, result);
+}
+
+}  // namespace chromeos_update_manager
diff --git a/update_manager/api_restricted_downloads_policy_impl.cc b/update_manager/api_restricted_downloads_policy_impl.cc
new file mode 100644
index 0000000..d413cca
--- /dev/null
+++ b/update_manager/api_restricted_downloads_policy_impl.cc
@@ -0,0 +1,47 @@
+//
+// Copyright (C) 2017 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#include "update_engine/update_manager/api_restricted_downloads_policy_impl.h"
+
+using chromeos_update_engine::ErrorCode;
+using std::string;
+using std::vector;
+
+namespace chromeos_update_manager {
+
+// Allow the API to restrict the downloading of updates.
+EvalStatus ApiRestrictedDownloadsPolicyImpl::UpdateCanBeApplied(
+    EvaluationContext* ec,
+    State* state,
+    std::string* error,
+    ErrorCode* result,
+    chromeos_update_engine::InstallPlan* install_plan) const {
+  // Next, check to see if updates can be applied (in general).
+  const UpdateRestrictions* update_restrictions_p =
+      ec->GetValue(state->updater_provider()->var_update_restrictions());
+  if (update_restrictions_p) {
+    if (*update_restrictions_p & UpdateRestrictions::kRestrictDownloading) {
+      *result = ErrorCode::kOmahaUpdateDeferredPerPolicy;
+      return EvalStatus::kSucceeded;
+    }
+  }
+
+  // The API isn't restricting downloads, so implicitly allow them to happen
+  // but don't explicitly return success from this policy implementation.
+  return EvalStatus::kContinue;
+}
+
+}  // namespace chromeos_update_manager
diff --git a/update_manager/api_restricted_downloads_policy_impl.h b/update_manager/api_restricted_downloads_policy_impl.h
new file mode 100644
index 0000000..21457a5
--- /dev/null
+++ b/update_manager/api_restricted_downloads_policy_impl.h
@@ -0,0 +1,51 @@
+//
+// Copyright (C) 2017 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#ifndef UPDATE_ENGINE_UPDATE_MANAGER_API_RESTRICTED_DOWNLOADS_POLICY_IMPL_H_
+#define UPDATE_ENGINE_UPDATE_MANAGER_API_RESTRICTED_DOWNLOADS_POLICY_IMPL_H_
+
+#include <string>
+
+#include "update_engine/update_manager/policy_utils.h"
+
+namespace chromeos_update_manager {
+
+// Allow the API to restrict the downloading of updates.
+class ApiRestrictedDownloadsPolicyImpl : public PolicyImplBase {
+ public:
+  ApiRestrictedDownloadsPolicyImpl() = default;
+  ~ApiRestrictedDownloadsPolicyImpl() override = default;
+
+  // Policy overrides.
+  EvalStatus UpdateCanBeApplied(
+      EvaluationContext* ec,
+      State* state,
+      std::string* error,
+      chromeos_update_engine::ErrorCode* result,
+      chromeos_update_engine::InstallPlan* install_plan) const override;
+
+ protected:
+  std::string PolicyName() const override {
+    return "ApiRestrictedDownloadsPolicyImpl";
+  }
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(ApiRestrictedDownloadsPolicyImpl);
+};
+
+}  // namespace chromeos_update_manager
+
+#endif  // UPDATE_ENGINE_UPDATE_MANAGER_API_RESTRICTED_DOWNLOADS_POLICY_IMPL_H_
diff --git a/update_manager/boxed_value.cc b/update_manager/boxed_value.cc
index 9758d33..a437c02 100644
--- a/update_manager/boxed_value.cc
+++ b/update_manager/boxed_value.cc
@@ -176,4 +176,19 @@
   return "Unknown";
 }
 
+template <>
+string BoxedValue::ValuePrinter<UpdateRestrictions>(const void* value) {
+  const UpdateRestrictions* val =
+      reinterpret_cast<const UpdateRestrictions*>(value);
+
+  if (*val == UpdateRestrictions::kNone) {
+    return "None";
+  }
+  string retval = "Flags:";
+  if (*val & kRestrictDownloading) {
+    retval += " RestrictDownloading";
+  }
+  return retval;
+}
+
 }  // namespace chromeos_update_manager
diff --git a/update_manager/boxed_value_unittest.cc b/update_manager/boxed_value_unittest.cc
index 2a086a6..4aeaec8 100644
--- a/update_manager/boxed_value_unittest.cc
+++ b/update_manager/boxed_value_unittest.cc
@@ -21,6 +21,7 @@
 #include <map>
 #include <set>
 #include <string>
+#include <utility>
 
 #include <base/strings/stringprintf.h>
 #include <base/time/time.h>
@@ -231,4 +232,14 @@
   EXPECT_EQ("DeleterMarker:true", value.ToString());
 }
 
+TEST(UmBoxedValueTest, UpdateRestrictionsToString) {
+  EXPECT_EQ(
+      "None",
+      BoxedValue(new UpdateRestrictions(UpdateRestrictions::kNone)).ToString());
+  EXPECT_EQ("Flags: RestrictDownloading",
+            BoxedValue(new UpdateRestrictions(
+                           UpdateRestrictions::kRestrictDownloading))
+                .ToString());
+}
+
 }  // namespace chromeos_update_manager
diff --git a/update_manager/chromeos_policy.cc b/update_manager/chromeos_policy.cc
index e3893d3..b32b626 100644
--- a/update_manager/chromeos_policy.cc
+++ b/update_manager/chromeos_policy.cc
@@ -36,6 +36,7 @@
 using chromeos_update_engine::ConnectionTethering;
 using chromeos_update_engine::ConnectionType;
 using chromeos_update_engine::ErrorCode;
+using chromeos_update_engine::InstallPlan;
 using std::get;
 using std::max;
 using std::min;
@@ -78,6 +79,7 @@
     case ErrorCode::kPayloadMismatchedType:
     case ErrorCode::kUnsupportedMajorPayloadVersion:
     case ErrorCode::kUnsupportedMinorPayloadVersion:
+    case ErrorCode::kPayloadTimestampError:
       LOG(INFO) << "Advancing download URL due to error "
                 << chromeos_update_engine::utils::ErrorCodeToString(err_code)
                 << " (" << static_cast<int>(err_code) << ")";
@@ -134,7 +136,7 @@
     case ErrorCode::kOmahaRequestXMLHasEntityDecl:
     case ErrorCode::kFilesystemVerifierError:
     case ErrorCode::kUserCanceled:
-    case ErrorCode::kOmahaUpdateIgnoredOverCellular:
+    case ErrorCode::kUpdatedButNotActive:
       LOG(INFO) << "Not changing URL index or failure count due to error "
                 << chromeos_update_engine::utils::ErrorCodeToString(err_code)
                 << " (" << static_cast<int>(err_code) << ")";
@@ -324,6 +326,15 @@
   return EvalStatus::kSucceeded;
 }
 
+EvalStatus ChromeOSPolicy::UpdateCanBeApplied(EvaluationContext* ec,
+                                              State* state,
+                                              std::string* error,
+                                              ErrorCode* result,
+                                              InstallPlan* install_plan) const {
+  *result = ErrorCode::kSuccess;
+  return EvalStatus::kSucceeded;
+}
+
 EvalStatus ChromeOSPolicy::UpdateCanStart(
     EvaluationContext* ec,
     State* state,
diff --git a/update_manager/chromeos_policy.h b/update_manager/chromeos_policy.h
index b4370c4..283bedc 100644
--- a/update_manager/chromeos_policy.h
+++ b/update_manager/chromeos_policy.h
@@ -59,6 +59,13 @@
       EvaluationContext* ec, State* state, std::string* error,
       UpdateCheckParams* result) const override;
 
+  EvalStatus UpdateCanBeApplied(
+      EvaluationContext* ec,
+      State* state,
+      std::string* error,
+      chromeos_update_engine::ErrorCode* result,
+      chromeos_update_engine::InstallPlan* install_plan) const override;
+
   EvalStatus UpdateCanStart(
       EvaluationContext* ec,
       State* state,
diff --git a/update_manager/chromeos_policy_unittest.cc b/update_manager/chromeos_policy_unittest.cc
index 0c38700..63fa0f7 100644
--- a/update_manager/chromeos_policy_unittest.cc
+++ b/update_manager/chromeos_policy_unittest.cc
@@ -93,6 +93,7 @@
         new bool(true));
     fake_state_.system_provider()->var_is_oobe_complete()->reset(
         new bool(true));
+    // NOLINTNEXTLINE(readability/casting)
     fake_state_.system_provider()->var_num_slots()->reset(new unsigned int(2));
 
     // Connection is wifi, untethered.
@@ -418,10 +419,11 @@
 }
 
 TEST_F(UmChromeOSPolicyTest,
-       UpdateCheckAllowedUpdatesDisabledForRemovableBootDevice) {
+       UpdateCheckAllowedUpdatesDisabledWhenNotEnoughSlotsAbUpdates) {
   // UpdateCheckAllowed should return false (kSucceeded) if the image booted
-  // from a removable device.
+  // without enough slots to do A/B updates.
 
+  // NOLINTNEXTLINE(readability/casting)
   fake_state_.system_provider()->var_num_slots()->reset(new unsigned int(1));
 
   UpdateCheckParams result;
diff --git a/update_manager/default_policy.cc b/update_manager/default_policy.cc
index 9a5ce7e..5da1520 100644
--- a/update_manager/default_policy.cc
+++ b/update_manager/default_policy.cc
@@ -16,6 +16,9 @@
 
 #include "update_engine/update_manager/default_policy.h"
 
+using chromeos_update_engine::ErrorCode;
+using chromeos_update_engine::InstallPlan;
+
 namespace {
 
 // A fixed minimum interval between consecutive allowed update checks. This
@@ -53,6 +56,15 @@
   return EvalStatus::kAskMeAgainLater;
 }
 
+EvalStatus DefaultPolicy::UpdateCanBeApplied(EvaluationContext* ec,
+                                             State* state,
+                                             std::string* error,
+                                             ErrorCode* result,
+                                             InstallPlan* install_plan) const {
+  *result = ErrorCode::kSuccess;
+  return EvalStatus::kSucceeded;
+}
+
 EvalStatus DefaultPolicy::UpdateCanStart(
     EvaluationContext* ec,
     State* state,
diff --git a/update_manager/default_policy.h b/update_manager/default_policy.h
index 3f41178..136ca35 100644
--- a/update_manager/default_policy.h
+++ b/update_manager/default_policy.h
@@ -69,6 +69,13 @@
       EvaluationContext* ec, State* state, std::string* error,
       UpdateCheckParams* result) const override;
 
+  EvalStatus UpdateCanBeApplied(
+      EvaluationContext* ec,
+      State* state,
+      std::string* error,
+      chromeos_update_engine::ErrorCode* result,
+      chromeos_update_engine::InstallPlan* install_plan) const override;
+
   EvalStatus UpdateCanStart(
       EvaluationContext* ec, State* state, std::string* error,
       UpdateDownloadParams* result,
diff --git a/update_manager/enough_slots_ab_updates_policy_impl.cc b/update_manager/enough_slots_ab_updates_policy_impl.cc
new file mode 100644
index 0000000..70f15d4
--- /dev/null
+++ b/update_manager/enough_slots_ab_updates_policy_impl.cc
@@ -0,0 +1,38 @@
+//
+// Copyright (C) 2017 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#include "update_engine/update_manager/enough_slots_ab_updates_policy_impl.h"
+
+namespace chromeos_update_manager {
+
+// Do not perform any updates if booted from removable device. This decision
+// is final.
+EvalStatus EnoughSlotsAbUpdatesPolicyImpl::UpdateCheckAllowed(
+    EvaluationContext* ec,
+    State* state,
+    std::string* error,
+    UpdateCheckParams* result) const {
+  const auto* num_slots_p =
+      ec->GetValue(state->system_provider()->var_num_slots());
+  if (num_slots_p == nullptr || *num_slots_p < 2) {
+    LOG(INFO) << "Not enough slots for A/B updates, disabling update checks.";
+    result->updates_enabled = false;
+    return EvalStatus::kSucceeded;
+  }
+  return EvalStatus::kContinue;
+}
+
+}  // namespace chromeos_update_manager
diff --git a/update_manager/enough_slots_ab_updates_policy_impl.h b/update_manager/enough_slots_ab_updates_policy_impl.h
new file mode 100644
index 0000000..1d45389
--- /dev/null
+++ b/update_manager/enough_slots_ab_updates_policy_impl.h
@@ -0,0 +1,49 @@
+//
+// Copyright (C) 2017 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#ifndef UPDATE_ENGINE_UPDATE_MANAGER_ENOUGH_SLOTS_AB_UPDATES_POLICY_IMPL_H_
+#define UPDATE_ENGINE_UPDATE_MANAGER_ENOUGH_SLOTS_AB_UPDATES_POLICY_IMPL_H_
+
+#include <string>
+
+#include "update_engine/update_manager/policy_utils.h"
+
+namespace chromeos_update_manager {
+
+// Do not perform any updates if booted from removable device.
+class EnoughSlotsAbUpdatesPolicyImpl : public PolicyImplBase {
+ public:
+  EnoughSlotsAbUpdatesPolicyImpl() = default;
+  ~EnoughSlotsAbUpdatesPolicyImpl() override = default;
+
+  // Policy overrides.
+  EvalStatus UpdateCheckAllowed(EvaluationContext* ec,
+                                State* state,
+                                std::string* error,
+                                UpdateCheckParams* result) const override;
+
+ protected:
+  std::string PolicyName() const override {
+    return "EnoughSlotsAbUpdatesPolicyImpl";
+  }
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(EnoughSlotsAbUpdatesPolicyImpl);
+};
+
+}  // namespace chromeos_update_manager
+
+#endif  // UPDATE_ENGINE_UPDATE_MANAGER_ENOUGH_SLOTS_AB_UPDATES_POLICY_IMPL_H_
diff --git a/update_manager/fake_updater_provider.h b/update_manager/fake_updater_provider.h
index 44389f4..7295765 100644
--- a/update_manager/fake_updater_provider.h
+++ b/update_manager/fake_updater_provider.h
@@ -41,19 +41,15 @@
     return &var_update_completed_time_;
   }
 
-  FakeVariable<double>* var_progress() override {
-    return &var_progress_;
-  }
+  FakeVariable<double>* var_progress() override { return &var_progress_; }
 
-  FakeVariable<Stage>* var_stage() override {
-    return &var_stage_;
-  }
+  FakeVariable<Stage>* var_stage() override { return &var_stage_; }
 
   FakeVariable<std::string>* var_new_version() override {
     return &var_new_version_;
   }
 
-  FakeVariable<int64_t>* var_payload_size() override {
+  FakeVariable<uint64_t>* var_payload_size() override {
     return &var_payload_size_;
   }
 
@@ -65,9 +61,7 @@
     return &var_new_channel_;
   }
 
-  FakeVariable<bool>* var_p2p_enabled() override {
-    return &var_p2p_enabled_;
-  }
+  FakeVariable<bool>* var_p2p_enabled() override { return &var_p2p_enabled_; }
 
   FakeVariable<bool>* var_cellular_enabled() override {
     return &var_cellular_enabled_;
@@ -85,42 +79,35 @@
     return &var_forced_update_requested_;
   }
 
+  FakeVariable<UpdateRestrictions>* var_update_restrictions() override {
+    return &var_update_restrictions_;
+  }
+
  private:
-  FakeVariable<base::Time>
-      var_updater_started_time_{  // NOLINT(whitespace/braces)
-    "updater_started_time", kVariableModePoll};
-  FakeVariable<base::Time> var_last_checked_time_{  // NOLINT(whitespace/braces)
-    "last_checked_time", kVariableModePoll};
-  FakeVariable<base::Time>
-      var_update_completed_time_{  // NOLINT(whitespace/braces)
-    "update_completed_time", kVariableModePoll};
-  FakeVariable<double> var_progress_{  // NOLINT(whitespace/braces)
-    "progress", kVariableModePoll};
-  FakeVariable<Stage> var_stage_{  // NOLINT(whitespace/braces)
-    "stage", kVariableModePoll};
-  FakeVariable<std::string> var_new_version_{  // NOLINT(whitespace/braces)
-    "new_version", kVariableModePoll};
-  FakeVariable<int64_t> var_payload_size_{  // NOLINT(whitespace/braces)
-    "payload_size", kVariableModePoll};
-  FakeVariable<std::string> var_curr_channel_{  // NOLINT(whitespace/braces)
-    "curr_channel", kVariableModePoll};
-  FakeVariable<std::string> var_new_channel_{  // NOLINT(whitespace/braces)
-    "new_channel", kVariableModePoll};
-  FakeVariable<bool> var_p2p_enabled_{// NOLINT(whitespace/braces)
-                                      "p2p_enabled",
-                                      kVariableModeAsync};
-  FakeVariable<bool> var_cellular_enabled_{// NOLINT(whitespace/braces)
-                                           "cellular_enabled",
+  FakeVariable<base::Time> var_updater_started_time_{"updater_started_time",
+                                                     kVariableModePoll};
+  FakeVariable<base::Time> var_last_checked_time_{"last_checked_time",
+                                                  kVariableModePoll};
+  FakeVariable<base::Time> var_update_completed_time_{"update_completed_time",
+                                                      kVariableModePoll};
+  FakeVariable<double> var_progress_{"progress", kVariableModePoll};
+  FakeVariable<Stage> var_stage_{"stage", kVariableModePoll};
+  FakeVariable<std::string> var_new_version_{"new_version", kVariableModePoll};
+  FakeVariable<uint64_t> var_payload_size_{"payload_size", kVariableModePoll};
+  FakeVariable<std::string> var_curr_channel_{"curr_channel",
+                                              kVariableModePoll};
+  FakeVariable<std::string> var_new_channel_{"new_channel", kVariableModePoll};
+  FakeVariable<bool> var_p2p_enabled_{"p2p_enabled", kVariableModeAsync};
+  FakeVariable<bool> var_cellular_enabled_{"cellular_enabled",
                                            kVariableModeAsync};
-  FakeVariable<unsigned int>
-      var_consecutive_failed_update_checks_{  // NOLINT(whitespace/braces)
-    "consecutive_failed_update_checks", kVariableModePoll};
-  FakeVariable<unsigned int>
-      var_server_dictated_poll_interval_{  // NOLINT(whitespace/braces)
-    "server_dictated_poll_interval", kVariableModePoll};
-  FakeVariable<UpdateRequestStatus>
-      var_forced_update_requested_{  // NOLINT(whitespace/braces)
-    "forced_update_requested", kVariableModeAsync};
+  FakeVariable<unsigned int> var_consecutive_failed_update_checks_{
+      "consecutive_failed_update_checks", kVariableModePoll};
+  FakeVariable<unsigned int> var_server_dictated_poll_interval_{
+      "server_dictated_poll_interval", kVariableModePoll};
+  FakeVariable<UpdateRequestStatus> var_forced_update_requested_{
+      "forced_update_requested", kVariableModeAsync};
+  FakeVariable<UpdateRestrictions> var_update_restrictions_{
+      "update_restrictions", kVariableModePoll};
 
   DISALLOW_COPY_AND_ASSIGN(FakeUpdaterProvider);
 };
diff --git a/update_manager/interactive_update_policy_impl.cc b/update_manager/interactive_update_policy_impl.cc
new file mode 100644
index 0000000..df7f17b
--- /dev/null
+++ b/update_manager/interactive_update_policy_impl.cc
@@ -0,0 +1,44 @@
+//
+// Copyright (C) 2017 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#include "update_engine/update_manager/interactive_update_policy_impl.h"
+
+namespace chromeos_update_manager {
+
+// Check to see if an interactive update was requested.
+EvalStatus InteractiveUpdatePolicyImpl::UpdateCheckAllowed(
+    EvaluationContext* ec,
+    State* state,
+    std::string* error,
+    UpdateCheckParams* result) const {
+  UpdaterProvider* const updater_provider = state->updater_provider();
+
+  // First, check to see if an interactive update was requested.
+  const UpdateRequestStatus* forced_update_requested_p =
+      ec->GetValue(updater_provider->var_forced_update_requested());
+  if (forced_update_requested_p != nullptr &&
+      *forced_update_requested_p != UpdateRequestStatus::kNone) {
+    result->is_interactive =
+        (*forced_update_requested_p == UpdateRequestStatus::kInteractive);
+    LOG(INFO) << "Forced update signaled ("
+              << (result->is_interactive ? "interactive" : "periodic")
+              << "), allowing update check.";
+    return EvalStatus::kSucceeded;
+  }
+  return EvalStatus::kContinue;
+}
+
+}  // namespace chromeos_update_manager
diff --git a/update_manager/interactive_update_policy_impl.h b/update_manager/interactive_update_policy_impl.h
new file mode 100644
index 0000000..a431456
--- /dev/null
+++ b/update_manager/interactive_update_policy_impl.h
@@ -0,0 +1,49 @@
+//
+// Copyright (C) 2017 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#ifndef UPDATE_ENGINE_UPDATE_MANAGER_INTERACTIVE_UPDATE_POLICY_IMPL_H_
+#define UPDATE_ENGINE_UPDATE_MANAGER_INTERACTIVE_UPDATE_POLICY_IMPL_H_
+
+#include <string>
+
+#include "update_engine/update_manager/policy_utils.h"
+
+namespace chromeos_update_manager {
+
+// Check to see if an interactive update was requested.
+class InteractiveUpdatePolicyImpl : public PolicyImplBase {
+ public:
+  InteractiveUpdatePolicyImpl() = default;
+  ~InteractiveUpdatePolicyImpl() override = default;
+
+  // Policy overrides.
+  EvalStatus UpdateCheckAllowed(EvaluationContext* ec,
+                                State* state,
+                                std::string* error,
+                                UpdateCheckParams* result) const override;
+
+ protected:
+  std::string PolicyName() const override {
+    return "InteractiveUpdatePolicyImpl";
+  }
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(InteractiveUpdatePolicyImpl);
+};
+
+}  // namespace chromeos_update_manager
+
+#endif  // UPDATE_ENGINE_UPDATE_MANAGER_OFFICIAL_BUILD_CHECK_POLICY_IMPL_H_
diff --git a/update_manager/mock_policy.h b/update_manager/mock_policy.h
index 14470e9..8060bf8 100644
--- a/update_manager/mock_policy.h
+++ b/update_manager/mock_policy.h
@@ -36,6 +36,11 @@
                                       testing::_))
         .WillByDefault(testing::Invoke(
                 &default_policy_, &DefaultPolicy::UpdateCheckAllowed));
+    ON_CALL(*this,
+            UpdateCanBeApplied(
+                testing::_, testing::_, testing::_, testing::_, testing::_))
+        .WillByDefault(testing::Invoke(&default_policy_,
+                                       &DefaultPolicy::UpdateCanBeApplied));
     ON_CALL(*this, UpdateCanStart(testing::_, testing::_, testing::_,
                                   testing::_, testing::_))
         .WillByDefault(testing::Invoke(
@@ -61,6 +66,13 @@
                      EvalStatus(EvaluationContext*, State*, std::string*,
                                 UpdateCheckParams*));
 
+  MOCK_CONST_METHOD5(UpdateCanBeApplied,
+                     EvalStatus(EvaluationContext*,
+                                State*,
+                                std::string*,
+                                chromeos_update_engine::ErrorCode*,
+                                chromeos_update_engine::InstallPlan*));
+
   MOCK_CONST_METHOD5(UpdateCanStart,
                      EvalStatus(EvaluationContext*, State*, std::string*,
                                 UpdateDownloadParams*, UpdateState));
diff --git a/update_manager/next_update_check_policy_impl.cc b/update_manager/next_update_check_policy_impl.cc
new file mode 100644
index 0000000..6f9748e
--- /dev/null
+++ b/update_manager/next_update_check_policy_impl.cc
@@ -0,0 +1,150 @@
+//
+// Copyright (C) 2017 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#include "update_engine/update_manager/next_update_check_policy_impl.h"
+
+#include <algorithm>
+
+#include "update_engine/common/utils.h"
+
+using base::Time;
+using base::TimeDelta;
+using std::max;
+using std::string;
+
+namespace chromeos_update_manager {
+
+NextUpdateCheckTimePolicyImpl::NextUpdateCheckTimePolicyImpl(
+    const NextUpdateCheckPolicyConstants& constants)
+    : policy_constants_(constants) {}
+
+EvalStatus NextUpdateCheckTimePolicyImpl::UpdateCheckAllowed(
+    EvaluationContext* ec,
+    State* state,
+    string* error,
+    UpdateCheckParams* result) const {
+  // Ensure that periodic update checks are timed properly.
+  Time next_update_check;
+
+  if (NextUpdateCheckTime(
+          ec, state, error, &next_update_check, policy_constants_) !=
+      EvalStatus::kSucceeded) {
+    return EvalStatus::kFailed;
+  }
+  if (!ec->IsWallclockTimeGreaterThan(next_update_check)) {
+    LOG(INFO) << "Periodic check interval not satisfied, blocking until "
+              << chromeos_update_engine::utils::ToString(next_update_check);
+    return EvalStatus::kAskMeAgainLater;
+  }
+
+  return EvalStatus::kContinue;
+}
+
+EvalStatus NextUpdateCheckTimePolicyImpl::NextUpdateCheckTime(
+    EvaluationContext* ec,
+    State* state,
+    string* error,
+    Time* next_update_check,
+    const NextUpdateCheckPolicyConstants& constants) {
+  UpdaterProvider* const updater_provider = state->updater_provider();
+
+  // Don't check for updates too often. We limit the update checks to once every
+  // some interval. The interval is kTimeoutInitialInterval the first time and
+  // kTimeoutPeriodicInterval for the subsequent update checks. If the update
+  // check fails, we increase the interval between the update checks
+  // exponentially until kTimeoutMaxBackoffInterval. Finally, to avoid having
+  // many chromebooks running update checks at the exact same time, we add some
+  // fuzz to the interval.
+  const Time* updater_started_time =
+      ec->GetValue(updater_provider->var_updater_started_time());
+  POLICY_CHECK_VALUE_AND_FAIL(updater_started_time, error);
+
+  const Time* last_checked_time =
+      ec->GetValue(updater_provider->var_last_checked_time());
+
+  const auto* seed = ec->GetValue(state->random_provider()->var_seed());
+  POLICY_CHECK_VALUE_AND_FAIL(seed, error);
+
+  PRNG prng(*seed);
+
+  // If this is the first attempt, compute and return an initial value.
+  if (last_checked_time == nullptr ||
+      *last_checked_time < *updater_started_time) {
+    *next_update_check = *updater_started_time +
+                         FuzzedInterval(&prng,
+                                        constants.timeout_initial_interval,
+                                        constants.timeout_regular_fuzz);
+    return EvalStatus::kSucceeded;
+  }
+
+  // Check whether the server is enforcing a poll interval; if not, this value
+  // will be zero.
+  const unsigned int* server_dictated_poll_interval =
+      ec->GetValue(updater_provider->var_server_dictated_poll_interval());
+  POLICY_CHECK_VALUE_AND_FAIL(server_dictated_poll_interval, error);
+
+  int interval = *server_dictated_poll_interval;
+  int fuzz = 0;
+
+  // If no poll interval was dictated by server compute a back-off period,
+  // starting from a predetermined base periodic interval and increasing
+  // exponentially by the number of consecutive failed attempts.
+  if (interval == 0) {
+    const unsigned int* consecutive_failed_update_checks =
+        ec->GetValue(updater_provider->var_consecutive_failed_update_checks());
+    POLICY_CHECK_VALUE_AND_FAIL(consecutive_failed_update_checks, error);
+
+    interval = constants.timeout_periodic_interval;
+    unsigned int num_failures = *consecutive_failed_update_checks;
+    while (interval < constants.timeout_max_backoff_interval && num_failures) {
+      interval *= 2;
+      num_failures--;
+    }
+  }
+
+  // We cannot back off longer than the predetermined maximum interval.
+  if (interval > constants.timeout_max_backoff_interval)
+    interval = constants.timeout_max_backoff_interval;
+
+  // We cannot back off shorter than the predetermined periodic interval. Also,
+  // in this case set the fuzz to a predetermined regular value.
+  if (interval <= constants.timeout_periodic_interval) {
+    interval = constants.timeout_periodic_interval;
+    fuzz = constants.timeout_regular_fuzz;
+  }
+
+  // If not otherwise determined, defer to a fuzz of +/-(interval / 2).
+  if (fuzz == 0)
+    fuzz = interval;
+
+  *next_update_check =
+      *last_checked_time + FuzzedInterval(&prng, interval, fuzz);
+  return EvalStatus::kSucceeded;
+}
+
+TimeDelta NextUpdateCheckTimePolicyImpl::FuzzedInterval(PRNG* prng,
+                                                        int interval,
+                                                        int fuzz) {
+  DCHECK_GE(interval, 0);
+  DCHECK_GE(fuzz, 0);
+  int half_fuzz = fuzz / 2;
+  // This guarantees the output interval is non negative.
+  int interval_min = max(interval - half_fuzz, 0);
+  int interval_max = interval + half_fuzz;
+  return TimeDelta::FromSeconds(prng->RandMinMax(interval_min, interval_max));
+}
+
+}  // namespace chromeos_update_manager
diff --git a/update_manager/next_update_check_policy_impl.h b/update_manager/next_update_check_policy_impl.h
new file mode 100644
index 0000000..291ea0f
--- /dev/null
+++ b/update_manager/next_update_check_policy_impl.h
@@ -0,0 +1,98 @@
+//
+// Copyright (C) 2017 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#ifndef UPDATE_ENGINE_UPDATE_MANAGER_NEXT_UPDATE_CHECK_POLICY_IMPL_H_
+#define UPDATE_ENGINE_UPDATE_MANAGER_NEXT_UPDATE_CHECK_POLICY_IMPL_H_
+
+#include <string>
+
+#include <base/time/time.h>
+
+#include "update_engine/update_manager/policy_utils.h"
+#include "update_engine/update_manager/prng.h"
+
+namespace chromeos_update_manager {
+
+// Constants that are provided to the policy implementation.
+struct NextUpdateCheckPolicyConstants {
+  // Default update check timeout interval/fuzz values used to compute the
+  // NextUpdateCheckTime(), in seconds. Actual fuzz is within +/- half of the
+  // indicated value.
+  int timeout_initial_interval;
+  int timeout_periodic_interval;
+  int timeout_max_backoff_interval;
+  int timeout_regular_fuzz;
+
+  // Maximum update attempt backoff interval and fuzz.
+  int attempt_backoff_max_interval_in_days;
+  int attempt_backoff_fuzz_in_hours;
+};
+
+// Ensure that periodic update checks are timed properly.
+class NextUpdateCheckTimePolicyImpl : public PolicyImplBase {
+ public:
+  explicit NextUpdateCheckTimePolicyImpl(
+      const NextUpdateCheckPolicyConstants& constants);
+
+  // Policy overrides.
+  EvalStatus UpdateCheckAllowed(EvaluationContext* ec,
+                                State* state,
+                                std::string* error,
+                                UpdateCheckParams* result) const override;
+
+  // A private policy implementation returning the wallclock timestamp when
+  // the next update check should happen.
+  // TODO(garnold) We should probably change that to infer a monotonic
+  // timestamp, which will make the update check intervals more resilient to
+  // clock skews. Might require switching some of the variables exported by the
+  // UpdaterProvider to report monotonic time, as well.
+  //
+  // NOTE:
+  // Exposed as a public static so that it's logic can be used to test
+  // Policy implementations that utilize this fragment for their
+  // timing, without needing to list them all with FRIEND_TEST (so that
+  // those Policy implementations can exist without modifying this
+  // class's definition.
+  //
+  // The output value from this method (|next_update_check|), isn't
+  // available via the UpdateCheckParams |result| of the Policy
+  // method, and so this timing logic needs to be otherwise exposed.
+  static EvalStatus NextUpdateCheckTime(
+      EvaluationContext* ec,
+      State* state,
+      std::string* error,
+      base::Time* next_update_check,
+      const NextUpdateCheckPolicyConstants& constants);
+
+  // Returns a TimeDelta based on the provided |interval| seconds +/- half
+  // |fuzz| seconds. The return value is guaranteed to be a non-negative
+  // TimeDelta.
+  static base::TimeDelta FuzzedInterval(PRNG* prng, int interval, int fuzz);
+
+ protected:
+  std::string PolicyName() const override {
+    return "NextUpdateCheckTimePolicyImpl";
+  }
+
+ private:
+  const NextUpdateCheckPolicyConstants policy_constants_;
+
+  DISALLOW_COPY_AND_ASSIGN(NextUpdateCheckTimePolicyImpl);
+};
+
+}  // namespace chromeos_update_manager
+
+#endif  // UPDATE_ENGINE_UPDATE_MANAGER_NEXT_UPDATE_CHECK_POLICY_IMPL_H_
diff --git a/update_manager/next_update_check_policy_impl_unittest.cc b/update_manager/next_update_check_policy_impl_unittest.cc
new file mode 100644
index 0000000..58aff66
--- /dev/null
+++ b/update_manager/next_update_check_policy_impl_unittest.cc
@@ -0,0 +1,163 @@
+//
+// Copyright (C) 2017 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#include "update_engine/update_manager/next_update_check_policy_impl.h"
+
+#include <memory>
+
+#include "update_engine/update_manager/policy_test_utils.h"
+
+using base::Time;
+using base::TimeDelta;
+using std::string;
+
+namespace chromeos_update_manager {
+
+const NextUpdateCheckPolicyConstants policy_test_constants = {
+    // these are specifically NOT the values used by real Policy
+    // implementations.
+    .timeout_initial_interval = 3 * 60,
+    .timeout_periodic_interval = 2 * 60 * 60,
+    .timeout_max_backoff_interval = 8 * 60 * 60,
+    .timeout_regular_fuzz = 5 * 60,
+    .attempt_backoff_max_interval_in_days = 12,
+    .attempt_backoff_fuzz_in_hours = 10,
+};
+
+class UmNextUpdateCheckTimePolicyImplTest : public UmPolicyTestBase {
+ protected:
+  UmNextUpdateCheckTimePolicyImplTest() {
+    policy_ =
+        std::make_unique<NextUpdateCheckTimePolicyImpl>(policy_test_constants);
+  }
+};
+
+TEST_F(UmNextUpdateCheckTimePolicyImplTest,
+       FirstCheckIsAtMostInitialIntervalAfterStart) {
+  Time next_update_check;
+
+  // Set the last update time so it'll appear as if this is a first update check
+  // in the lifetime of the current updater.
+  fake_state_.updater_provider()->var_last_checked_time()->reset(
+      new Time(fake_clock_.GetWallclockTime() - TimeDelta::FromMinutes(10)));
+
+  CallMethodWithContext(&NextUpdateCheckTimePolicyImpl::NextUpdateCheckTime,
+                        &next_update_check,
+                        policy_test_constants);
+
+  EXPECT_LE(fake_clock_.GetWallclockTime(), next_update_check);
+  EXPECT_GE(fake_clock_.GetWallclockTime() +
+                TimeDelta::FromSeconds(
+                    policy_test_constants.timeout_initial_interval +
+                    policy_test_constants.timeout_regular_fuzz / 2),
+            next_update_check);
+}
+
+TEST_F(UmNextUpdateCheckTimePolicyImplTest, RecurringCheckBaseIntervalAndFuzz) {
+  // Ensure that we're using the correct interval (kPeriodicInterval) and fuzz
+  // (ktimeout_regular_fuzz) as base values for period updates.
+  Time next_update_check;
+
+  CallMethodWithContext(&NextUpdateCheckTimePolicyImpl::NextUpdateCheckTime,
+                        &next_update_check,
+                        policy_test_constants);
+
+  EXPECT_LE(fake_clock_.GetWallclockTime() +
+                TimeDelta::FromSeconds(
+                    policy_test_constants.timeout_periodic_interval -
+                    policy_test_constants.timeout_regular_fuzz / 2),
+            next_update_check);
+  EXPECT_GE(fake_clock_.GetWallclockTime() +
+                TimeDelta::FromSeconds(
+                    policy_test_constants.timeout_periodic_interval +
+                    policy_test_constants.timeout_regular_fuzz / 2),
+            next_update_check);
+}
+
+TEST_F(UmNextUpdateCheckTimePolicyImplTest,
+       RecurringCheckBackoffIntervalAndFuzz) {
+  // Ensure that we're properly backing off and fuzzing in the presence of
+  // failed updates attempts.
+  Time next_update_check;
+
+  fake_state_.updater_provider()->var_consecutive_failed_update_checks()->reset(
+      new unsigned int{2});
+
+  ExpectStatus(EvalStatus::kSucceeded,
+               NextUpdateCheckTimePolicyImpl::NextUpdateCheckTime,
+               &next_update_check,
+               policy_test_constants);
+
+  int expected_interval = policy_test_constants.timeout_periodic_interval * 4;
+  EXPECT_LE(
+      fake_clock_.GetWallclockTime() +
+          TimeDelta::FromSeconds(expected_interval - expected_interval / 2),
+      next_update_check);
+  EXPECT_GE(
+      fake_clock_.GetWallclockTime() +
+          TimeDelta::FromSeconds(expected_interval + expected_interval / 2),
+      next_update_check);
+}
+
+TEST_F(UmNextUpdateCheckTimePolicyImplTest,
+       RecurringCheckServerDictatedPollInterval) {
+  // Policy honors the server provided check poll interval.
+  Time next_update_check;
+
+  const auto kInterval = policy_test_constants.timeout_periodic_interval * 4;
+  fake_state_.updater_provider()->var_server_dictated_poll_interval()->reset(
+      new unsigned int(kInterval));  // NOLINT(readability/casting)
+  // We should not be backing off in this case.
+  fake_state_.updater_provider()->var_consecutive_failed_update_checks()->reset(
+      new unsigned int(2));  // NOLINT(readability/casting)
+
+  ExpectStatus(EvalStatus::kSucceeded,
+               &NextUpdateCheckTimePolicyImpl::NextUpdateCheckTime,
+               &next_update_check,
+               policy_test_constants);
+
+  EXPECT_LE(fake_clock_.GetWallclockTime() +
+                TimeDelta::FromSeconds(kInterval - kInterval / 2),
+            next_update_check);
+  EXPECT_GE(fake_clock_.GetWallclockTime() +
+                TimeDelta::FromSeconds(kInterval + kInterval / 2),
+            next_update_check);
+}
+
+TEST_F(UmNextUpdateCheckTimePolicyImplTest, ExponentialBackoffIsCapped) {
+  Time next_update_check;
+
+  fake_state_.updater_provider()->var_consecutive_failed_update_checks()->reset(
+      new unsigned int(100));  // NOLINT(readability/casting)
+
+  ExpectStatus(EvalStatus::kSucceeded,
+               &NextUpdateCheckTimePolicyImpl::NextUpdateCheckTime,
+               &next_update_check,
+               policy_test_constants);
+
+  EXPECT_LE(fake_clock_.GetWallclockTime() +
+                TimeDelta::FromSeconds(
+                    policy_test_constants.timeout_max_backoff_interval -
+                    policy_test_constants.timeout_max_backoff_interval / 2),
+            next_update_check);
+  EXPECT_GE(fake_clock_.GetWallclockTime() +
+                TimeDelta::FromSeconds(
+                    policy_test_constants.timeout_max_backoff_interval +
+                    policy_test_constants.timeout_max_backoff_interval / 2),
+            next_update_check);
+}
+
+}  // namespace chromeos_update_manager
diff --git a/update_manager/official_build_check_policy_impl.cc b/update_manager/official_build_check_policy_impl.cc
new file mode 100644
index 0000000..096f7bf
--- /dev/null
+++ b/update_manager/official_build_check_policy_impl.cc
@@ -0,0 +1,36 @@
+//
+// Copyright (C) 2017 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#include "update_engine/update_manager/official_build_check_policy_impl.h"
+
+namespace chromeos_update_manager {
+
+// Unofficial builds should not perform periodic update checks.
+EvalStatus OnlyUpdateOfficialBuildsPolicyImpl::UpdateCheckAllowed(
+    EvaluationContext* ec,
+    State* state,
+    std::string* error,
+    UpdateCheckParams* result) const {
+  const bool* is_official_build_p =
+      ec->GetValue(state->system_provider()->var_is_official_build());
+  if (is_official_build_p != nullptr && !(*is_official_build_p)) {
+    LOG(INFO) << "Unofficial build, blocking periodic update checks.";
+    return EvalStatus::kAskMeAgainLater;
+  }
+  return EvalStatus::kContinue;
+}
+
+}  // namespace chromeos_update_manager
diff --git a/update_manager/official_build_check_policy_impl.h b/update_manager/official_build_check_policy_impl.h
new file mode 100644
index 0000000..6257209
--- /dev/null
+++ b/update_manager/official_build_check_policy_impl.h
@@ -0,0 +1,49 @@
+//
+// Copyright (C) 2017 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#ifndef UPDATE_ENGINE_UPDATE_MANAGER_OFFICIAL_BUILD_CHECK_POLICY_IMPL_H_
+#define UPDATE_ENGINE_UPDATE_MANAGER_OFFICIAL_BUILD_CHECK_POLICY_IMPL_H_
+
+#include <string>
+
+#include "update_engine/update_manager/policy_utils.h"
+
+namespace chromeos_update_manager {
+
+// Unofficial builds should not perform periodic update checks.
+class OnlyUpdateOfficialBuildsPolicyImpl : public PolicyImplBase {
+ public:
+  OnlyUpdateOfficialBuildsPolicyImpl() = default;
+  ~OnlyUpdateOfficialBuildsPolicyImpl() override = default;
+
+  // Policy overrides.
+  EvalStatus UpdateCheckAllowed(EvaluationContext* ec,
+                                State* state,
+                                std::string* error,
+                                UpdateCheckParams* result) const override;
+
+ protected:
+  std::string PolicyName() const override {
+    return "OnlyUpdateOfficialBuildsPolicyImpl";
+  }
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(OnlyUpdateOfficialBuildsPolicyImpl);
+};
+
+}  // namespace chromeos_update_manager
+
+#endif  // UPDATE_ENGINE_UPDATE_MANAGER_OFFICIAL_BUILD_CHECK_POLICY_IMPL_H_
diff --git a/update_manager/policy.cc b/update_manager/policy.cc
index 151c225..5f79a68 100644
--- a/update_manager/policy.cc
+++ b/update_manager/policy.cc
@@ -30,6 +30,8 @@
       return "kSucceeded";
     case EvalStatus::kAskMeAgainLater:
       return "kAskMeAgainLater";
+    case EvalStatus::kContinue:
+      return "kContinue";
   }
   return "Invalid";
 }
diff --git a/update_manager/policy.h b/update_manager/policy.h
index fae1494..b60c4da 100644
--- a/update_manager/policy.h
+++ b/update_manager/policy.h
@@ -22,6 +22,7 @@
 #include <vector>
 
 #include "update_engine/common/error_code.h"
+#include "update_engine/payload_consumer/install_plan.h"
 #include "update_engine/update_manager/evaluation_context.h"
 #include "update_engine/update_manager/state.h"
 
@@ -32,6 +33,7 @@
   kFailed,
   kSucceeded,
   kAskMeAgainLater,
+  kContinue,
 };
 
 std::string ToString(EvalStatus status);
@@ -204,6 +206,9 @@
     if (reinterpret_cast<typeof(&Policy::UpdateCheckAllowed)>(
             policy_method) == &Policy::UpdateCheckAllowed)
       return class_name + "UpdateCheckAllowed";
+    if (reinterpret_cast<typeof(&Policy::UpdateCanBeApplied)>(policy_method) ==
+        &Policy::UpdateCanBeApplied)
+      return class_name + "UpdateCanBeApplied";
     if (reinterpret_cast<typeof(&Policy::UpdateCanStart)>(
             policy_method) == &Policy::UpdateCanStart)
       return class_name + "UpdateCanStart";
@@ -235,6 +240,17 @@
       EvaluationContext* ec, State* state, std::string* error,
       UpdateCheckParams* result) const = 0;
 
+  // UpdateCanBeApplied returns whether the given |install_plan| can be acted
+  // on at this time.  The reason for not applying is returned in |result|.
+  // The Policy may modify the passed-in |install_plan|, based on the
+  // implementation in the Policy and values provided by the EvaluationContext.
+  virtual EvalStatus UpdateCanBeApplied(
+      EvaluationContext* ec,
+      State* state,
+      std::string* error,
+      chromeos_update_engine::ErrorCode* result,
+      chromeos_update_engine::InstallPlan* install_plan) const = 0;
+
   // Returns EvalStatus::kSucceeded if either an update can start being
   // processed, or the attempt needs to be aborted. In cases where the update
   // needs to wait for some condition to be satisfied, but none of the values
diff --git a/update_manager/policy_test_utils.cc b/update_manager/policy_test_utils.cc
new file mode 100644
index 0000000..fbfcb82
--- /dev/null
+++ b/update_manager/policy_test_utils.cc
@@ -0,0 +1,110 @@
+//
+// Copyright (C) 2017 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#include "update_engine/update_manager/policy_test_utils.h"
+
+#include <memory>
+#include <tuple>
+#include <vector>
+
+#include "update_engine/update_manager/next_update_check_policy_impl.h"
+
+using base::Time;
+using base::TimeDelta;
+using chromeos_update_engine::ErrorCode;
+using std::string;
+using std::tuple;
+using std::vector;
+
+namespace chromeos_update_manager {
+
+void UmPolicyTestBase::SetUp() {
+  loop_.SetAsCurrent();
+  SetUpDefaultClock();
+  eval_ctx_ = new EvaluationContext(&fake_clock_, TimeDelta::FromSeconds(5));
+  SetUpDefaultState();
+}
+
+void UmPolicyTestBase::TearDown() {
+  EXPECT_FALSE(loop_.PendingTasks());
+}
+
+// Sets the clock to fixed values.
+void UmPolicyTestBase::SetUpDefaultClock() {
+  fake_clock_.SetMonotonicTime(Time::FromInternalValue(12345678L));
+  fake_clock_.SetWallclockTime(Time::FromInternalValue(12345678901234L));
+}
+
+void UmPolicyTestBase::SetUpDefaultState() {
+  fake_state_.updater_provider()->var_updater_started_time()->reset(
+      new Time(fake_clock_.GetWallclockTime()));
+  fake_state_.updater_provider()->var_last_checked_time()->reset(
+      new Time(fake_clock_.GetWallclockTime()));
+  fake_state_.updater_provider()->var_consecutive_failed_update_checks()->reset(
+      new unsigned int(0));  // NOLINT(readability/casting)
+  fake_state_.updater_provider()->var_server_dictated_poll_interval()->reset(
+      new unsigned int(0));  // NOLINT(readability/casting)
+  fake_state_.updater_provider()->var_forced_update_requested()->reset(
+      new UpdateRequestStatus{UpdateRequestStatus::kNone});
+
+  // Chosen by fair dice roll.  Guaranteed to be random.
+  fake_state_.random_provider()->var_seed()->reset(new uint64_t(4));
+}
+
+// Returns a default UpdateState structure:
+UpdateState UmPolicyTestBase::GetDefaultUpdateState(
+    TimeDelta first_seen_period) {
+  Time first_seen_time = fake_clock_.GetWallclockTime() - first_seen_period;
+  UpdateState update_state = UpdateState();
+
+  // This is a non-interactive check returning a delta payload, seen for the
+  // first time (|first_seen_period| ago). Clearly, there were no failed
+  // attempts so far.
+  update_state.is_interactive = false;
+  update_state.is_delta_payload = false;
+  update_state.first_seen = first_seen_time;
+  update_state.num_checks = 1;
+  update_state.num_failures = 0;
+  update_state.failures_last_updated = Time();  // Needs to be zero.
+  // There's a single HTTP download URL with a maximum of 10 retries.
+  update_state.download_urls = vector<string>{"http://fake/url/"};
+  update_state.download_errors_max = 10;
+  // Download was never attempted.
+  update_state.last_download_url_idx = -1;
+  update_state.last_download_url_num_errors = 0;
+  // There were no download errors.
+  update_state.download_errors = vector<tuple<int, ErrorCode, Time>>();
+  // P2P is not disabled by Omaha.
+  update_state.p2p_downloading_disabled = false;
+  update_state.p2p_sharing_disabled = false;
+  // P2P was not attempted.
+  update_state.p2p_num_attempts = 0;
+  update_state.p2p_first_attempted = Time();
+  // No active backoff period, backoff is not disabled by Omaha.
+  update_state.backoff_expiry = Time();
+  update_state.is_backoff_disabled = false;
+  // There is no active scattering wait period (max 7 days allowed) nor check
+  // threshold (none allowed).
+  update_state.scatter_wait_period = TimeDelta();
+  update_state.scatter_check_threshold = 0;
+  update_state.scatter_wait_period_max = TimeDelta::FromDays(7);
+  update_state.scatter_check_threshold_min = 0;
+  update_state.scatter_check_threshold_max = 0;
+
+  return update_state;
+}
+
+}  // namespace chromeos_update_manager
diff --git a/update_manager/policy_test_utils.h b/update_manager/policy_test_utils.h
new file mode 100644
index 0000000..5b93f7b
--- /dev/null
+++ b/update_manager/policy_test_utils.h
@@ -0,0 +1,99 @@
+//
+// Copyright (C) 2017 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#ifndef UPDATE_ENGINE_UPDATE_MANAGER_POLICY_TEST_UTILS_H_
+#define UPDATE_ENGINE_UPDATE_MANAGER_POLICY_TEST_UTILS_H_
+
+#include <memory>
+#include <string>
+
+#include <base/time/time.h>
+#include <brillo/message_loops/fake_message_loop.h>
+#include <gtest/gtest.h>
+
+#include "update_engine/common/fake_clock.h"
+#include "update_engine/update_manager/evaluation_context.h"
+#include "update_engine/update_manager/fake_state.h"
+#include "update_engine/update_manager/policy_utils.h"
+
+namespace chromeos_update_manager {
+
+class UmPolicyTestBase : public ::testing::Test {
+ protected:
+  UmPolicyTestBase() = default;
+
+  void SetUp() override;
+
+  void TearDown() override;
+
+  // Sets the clock to fixed values.
+  virtual void SetUpDefaultClock();
+
+  // Sets up the default state in fake_state_.  override to add Policy-specific
+  // items, but only after calling this class's implementation.
+  virtual void SetUpDefaultState();
+
+  // Returns a default UpdateState structure:
+  virtual UpdateState GetDefaultUpdateState(base::TimeDelta first_seen_period);
+
+  // Runs the passed |method| after resetting the EvaluationContext and expects
+  // it to return the |expected| return value.
+  template <typename T, typename R, typename... Args>
+  void ExpectStatus(EvalStatus expected, T method, R* result, Args... args) {
+    std::string error = "<None>";
+    eval_ctx_->ResetEvaluation();
+    EXPECT_EQ(expected,
+              (*method)(eval_ctx_.get(), &fake_state_, &error, result, args...))
+        << "Returned error: " << error
+        << "\nEvaluation context: " << eval_ctx_->DumpContext();
+  }
+
+  // Runs the passed |method| after resetting the EvaluationContext, in order
+  // to use the method to get a value for other testing (doesn't validate the
+  // return value, just returns it).
+  template <typename T, typename R, typename... Args>
+  EvalStatus CallMethodWithContext(T method, R* result, Args... args) {
+    std::string error = "<None>";
+    eval_ctx_->ResetEvaluation();
+    return (*method)(eval_ctx_.get(), &fake_state_, &error, result, args...);
+  }
+
+  // Runs the passed |policy_method| on the framework policy and expects it to
+  // return the |expected| return value.
+  template <typename T, typename R, typename... Args>
+  void ExpectPolicyStatus(EvalStatus expected,
+                          T policy_method,
+                          R* result,
+                          Args... args) {
+    std::string error = "<None>";
+    eval_ctx_->ResetEvaluation();
+    EXPECT_EQ(expected,
+              (policy_.get()->*policy_method)(
+                  eval_ctx_.get(), &fake_state_, &error, result, args...))
+        << "Returned error: " << error
+        << "\nEvaluation context: " << eval_ctx_->DumpContext();
+  }
+
+  brillo::FakeMessageLoop loop_{nullptr};
+  chromeos_update_engine::FakeClock fake_clock_;
+  FakeState fake_state_;
+  scoped_refptr<EvaluationContext> eval_ctx_;
+  std::unique_ptr<Policy> policy_;
+};
+
+}  // namespace chromeos_update_manager
+
+#endif  // UPDATE_ENGINE_UPDATE_MANAGER_POLICY_TEST_UTILS_H_
diff --git a/update_manager/policy_utils.h b/update_manager/policy_utils.h
index 960987e..eaf9ee9 100644
--- a/update_manager/policy_utils.h
+++ b/update_manager/policy_utils.h
@@ -17,6 +17,9 @@
 #ifndef UPDATE_ENGINE_UPDATE_MANAGER_POLICY_UTILS_H_
 #define UPDATE_ENGINE_UPDATE_MANAGER_POLICY_UTILS_H_
 
+#include <string>
+#include <vector>
+
 #include "update_engine/update_manager/policy.h"
 
 // Checks that the passed pointer value is not null, returning kFailed on the
@@ -35,4 +38,84 @@
       } \
     } while (false)
 
+namespace chromeos_update_manager {
+
+// Call the passed-in Policy method on a series of Policy implementations, until
+// one of them renders a decision by returning a value other than
+// |EvalStatus::kContinue|.
+template <typename T, typename R, typename... Args>
+EvalStatus ConsultPolicies(const std::vector<Policy const*> policies,
+                           T policy_method,
+                           EvaluationContext* ec,
+                           State* state,
+                           std::string* error,
+                           R* result,
+                           Args... args) {
+  for (auto policy : policies) {
+    EvalStatus status =
+        (policy->*policy_method)(ec, state, error, result, args...);
+    if (status != EvalStatus::kContinue) {
+      LOG(INFO) << "decision by " << policy->PolicyRequestName(policy_method);
+      return status;
+    }
+  }
+  return EvalStatus::kContinue;
+}
+
+// Base class implementation that returns |EvalStatus::kContinue| for all
+// decisions, to be used as a base-class for various Policy facets that only
+// pertain to certain situations. This might be better folded into Policy
+// instead of using pure-virtual methods on that class.
+class PolicyImplBase : public Policy {
+ public:
+  // Policy overrides.
+  EvalStatus UpdateCheckAllowed(EvaluationContext* ec,
+                                State* state,
+                                std::string* error,
+                                UpdateCheckParams* result) const override {
+    return EvalStatus::kContinue;
+  };
+
+  EvalStatus UpdateCanBeApplied(
+      EvaluationContext* ec,
+      State* state,
+      std::string* error,
+      chromeos_update_engine::ErrorCode* result,
+      chromeos_update_engine::InstallPlan* install_plan) const override {
+    return EvalStatus::kContinue;
+  };
+
+  EvalStatus UpdateCanStart(EvaluationContext* ec,
+                            State* state,
+                            std::string* error,
+                            UpdateDownloadParams* result,
+                            UpdateState update_state) const override {
+    return EvalStatus::kContinue;
+  };
+
+  EvalStatus UpdateDownloadAllowed(EvaluationContext* ec,
+                                   State* state,
+                                   std::string* error,
+                                   bool* result) const override {
+    return EvalStatus::kContinue;
+  };
+
+  EvalStatus P2PEnabled(EvaluationContext* ec,
+                        State* state,
+                        std::string* error,
+                        bool* result) const override {
+    return EvalStatus::kContinue;
+  };
+
+  EvalStatus P2PEnabledChanged(EvaluationContext* ec,
+                               State* state,
+                               std::string* error,
+                               bool* result,
+                               bool prev_result) const override {
+    return EvalStatus::kContinue;
+  };
+};
+
+}  // namespace chromeos_update_manager
+
 #endif  // UPDATE_ENGINE_UPDATE_MANAGER_POLICY_UTILS_H_
diff --git a/update_manager/real_shill_provider_unittest.cc b/update_manager/real_shill_provider_unittest.cc
index af674d0..6506923 100644
--- a/update_manager/real_shill_provider_unittest.cc
+++ b/update_manager/real_shill_provider_unittest.cc
@@ -94,7 +94,9 @@
     now_exp.minute = 5;
     now_exp.second = 33;
     now_exp.millisecond = 675;
-    return Time::FromLocalExploded(now_exp);
+    Time time;
+    ignore_result(Time::FromLocalExploded(now_exp, &time));
+    return time;
   }
 
   Time ConnChangedTime() {
diff --git a/update_manager/real_time_provider.cc b/update_manager/real_time_provider.cc
index ca3acad..db26816 100644
--- a/update_manager/real_time_provider.cc
+++ b/update_manager/real_time_provider.cc
@@ -43,7 +43,10 @@
     Time::Exploded now_exp;
     clock_->GetWallclockTime().LocalExplode(&now_exp);
     now_exp.hour = now_exp.minute = now_exp.second = now_exp.millisecond = 0;
-    return new Time(Time::FromLocalExploded(now_exp));
+    Time* now = new Time();
+    bool success = Time::FromLocalExploded(now_exp, now);
+    DCHECK(success);
+    return now;
   }
 
  private:
diff --git a/update_manager/real_time_provider_unittest.cc b/update_manager/real_time_provider_unittest.cc
index 0e1ef34..f8db30b 100644
--- a/update_manager/real_time_provider_unittest.cc
+++ b/update_manager/real_time_provider_unittest.cc
@@ -51,7 +51,9 @@
     now_exp.minute = 5;
     now_exp.second = 33;
     now_exp.millisecond = 675;
-    return Time::FromLocalExploded(now_exp);
+    Time time;
+    ignore_result(Time::FromLocalExploded(now_exp, &time));
+    return time;
   }
 
   FakeClock fake_clock_;
@@ -66,7 +68,8 @@
   exploded.minute = 0;
   exploded.second = 0;
   exploded.millisecond = 0;
-  const Time expected = Time::FromLocalExploded(exploded);
+  Time expected;
+  ignore_result(Time::FromLocalExploded(exploded, &expected));
 
   fake_clock_.SetWallclockTime(now);
   UmTestUtils::ExpectVariableHasValue(expected, provider_->var_curr_date());
diff --git a/update_manager/real_updater_provider.cc b/update_manager/real_updater_provider.cc
index 1a3e65a..050bd42 100644
--- a/update_manager/real_updater_provider.cc
+++ b/update_manager/real_updater_provider.cc
@@ -25,10 +25,12 @@
 #include <base/time/time.h>
 #include <update_engine/dbus-constants.h>
 
+#include "update_engine/client_library/include/update_engine/update_status.h"
 #include "update_engine/common/clock_interface.h"
 #include "update_engine/common/prefs.h"
 #include "update_engine/omaha_request_params.h"
 #include "update_engine/update_attempter.h"
+#include "update_engine/update_status_utils.h"
 
 using base::StringPrintf;
 using base::Time;
@@ -36,6 +38,8 @@
 using chromeos_update_engine::OmahaRequestParams;
 using chromeos_update_engine::SystemState;
 using std::string;
+using update_engine::UpdateAttemptFlags;
+using update_engine::UpdateEngineStatus;
 
 namespace chromeos_update_manager {
 
@@ -60,27 +64,32 @@
 class GetStatusHelper {
  public:
   GetStatusHelper(SystemState* system_state, string* errmsg) {
-    is_success_ = system_state->update_attempter()->GetStatus(
-        &last_checked_time_, &progress_, &update_status_, &new_version_,
-        &payload_size_);
-    if (!is_success_ && errmsg)
+    is_success_ =
+        system_state->update_attempter()->GetStatus(&update_engine_status_);
+    if (!is_success_ && errmsg) {
       *errmsg = "Failed to get a status update from the update engine";
+    }
   }
 
   inline bool is_success() { return is_success_; }
-  inline int64_t last_checked_time() { return last_checked_time_; }
-  inline double progress() { return progress_; }
-  inline const string& update_status() { return update_status_; }
-  inline const string& new_version() { return new_version_; }
-  inline int64_t payload_size() { return payload_size_; }
+  inline int64_t last_checked_time() {
+    return update_engine_status_.last_checked_time;
+  }
+  inline double progress() { return update_engine_status_.progress; }
+  inline const string update_status() {
+    return chromeos_update_engine::UpdateStatusToString(
+        update_engine_status_.status);
+  }
+  inline const string& new_version() {
+    return update_engine_status_.new_version;
+  }
+  inline uint64_t payload_size() {
+    return update_engine_status_.new_size_bytes;
+  }
 
  private:
   bool is_success_;
-  int64_t last_checked_time_;
-  double progress_;
-  string update_status_;
-  string new_version_;
-  int64_t payload_size_;
+  UpdateEngineStatus update_engine_status_;
 };
 
 // A variable reporting the time when a last update check was issued.
@@ -196,24 +205,18 @@
 };
 
 // A variable reporting the size of the update being processed in bytes.
-class PayloadSizeVariable : public UpdaterVariableBase<int64_t> {
+class PayloadSizeVariable : public UpdaterVariableBase<uint64_t> {
  public:
   PayloadSizeVariable(const string& name, SystemState* system_state)
-      : UpdaterVariableBase<int64_t>(name, kVariableModePoll, system_state) {}
+      : UpdaterVariableBase<uint64_t>(name, kVariableModePoll, system_state) {}
 
  private:
-  const int64_t* GetValue(TimeDelta /* timeout */, string* errmsg) override {
+  const uint64_t* GetValue(TimeDelta /* timeout */, string* errmsg) override {
     GetStatusHelper raw(system_state(), errmsg);
     if (!raw.is_success())
       return nullptr;
 
-    if (raw.payload_size() < 0) {
-      if (errmsg)
-        *errmsg = string("Invalid payload size: %" PRId64, raw.payload_size());
-      return nullptr;
-    }
-
-    return new int64_t(raw.payload_size());
+    return new uint64_t(raw.payload_size());
   }
 
   DISALLOW_COPY_AND_ASSIGN(PayloadSizeVariable);
@@ -414,40 +417,66 @@
   DISALLOW_COPY_AND_ASSIGN(ForcedUpdateRequestedVariable);
 };
 
+// A variable returning the current update restrictions that are in effect.
+class UpdateRestrictionsVariable
+    : public UpdaterVariableBase<UpdateRestrictions> {
+ public:
+  UpdateRestrictionsVariable(const string& name, SystemState* system_state)
+      : UpdaterVariableBase<UpdateRestrictions>(
+            name, kVariableModePoll, system_state) {}
+
+ private:
+  const UpdateRestrictions* GetValue(TimeDelta /* timeout */,
+                                     string* /* errmsg */) override {
+    UpdateAttemptFlags attempt_flags =
+        system_state()->update_attempter()->GetCurrentUpdateAttemptFlags();
+    UpdateRestrictions restriction_flags = UpdateRestrictions::kNone;
+    // Don't blindly copy the whole value, test and set bits that should
+    // transfer from one set of flags to the other.
+    if (attempt_flags & UpdateAttemptFlags::kFlagRestrictDownload) {
+      restriction_flags = static_cast<UpdateRestrictions>(
+          restriction_flags | UpdateRestrictions::kRestrictDownloading);
+    }
+
+    return new UpdateRestrictions(restriction_flags);
+  }
+
+  DISALLOW_COPY_AND_ASSIGN(UpdateRestrictionsVariable);
+};
+
 // RealUpdaterProvider methods.
 
 RealUpdaterProvider::RealUpdaterProvider(SystemState* system_state)
-  : system_state_(system_state),
-    var_updater_started_time_("updater_started_time",
-                              system_state->clock()->GetWallclockTime()),
-    var_last_checked_time_(
-        new LastCheckedTimeVariable("last_checked_time", system_state_)),
-    var_update_completed_time_(
-        new UpdateCompletedTimeVariable("update_completed_time",
-                                        system_state_)),
-    var_progress_(new ProgressVariable("progress", system_state_)),
-    var_stage_(new StageVariable("stage", system_state_)),
-    var_new_version_(new NewVersionVariable("new_version", system_state_)),
-    var_payload_size_(new PayloadSizeVariable("payload_size", system_state_)),
-    var_curr_channel_(new CurrChannelVariable("curr_channel", system_state_)),
-    var_new_channel_(new NewChannelVariable("new_channel", system_state_)),
-    var_p2p_enabled_(
-        new BooleanPrefVariable("p2p_enabled", system_state_->prefs(),
-                                chromeos_update_engine::kPrefsP2PEnabled,
-                                false)),
-    var_cellular_enabled_(
-        new BooleanPrefVariable(
-            "cellular_enabled", system_state_->prefs(),
-            chromeos_update_engine::kPrefsUpdateOverCellularPermission,
-            false)),
-    var_consecutive_failed_update_checks_(
-        new ConsecutiveFailedUpdateChecksVariable(
-            "consecutive_failed_update_checks", system_state_)),
-    var_server_dictated_poll_interval_(
-        new ServerDictatedPollIntervalVariable(
-            "server_dictated_poll_interval", system_state_)),
-    var_forced_update_requested_(
-        new ForcedUpdateRequestedVariable(
-            "forced_update_requested", system_state_)) {}
-
+    : system_state_(system_state),
+      var_updater_started_time_("updater_started_time",
+                                system_state->clock()->GetWallclockTime()),
+      var_last_checked_time_(
+          new LastCheckedTimeVariable("last_checked_time", system_state_)),
+      var_update_completed_time_(new UpdateCompletedTimeVariable(
+          "update_completed_time", system_state_)),
+      var_progress_(new ProgressVariable("progress", system_state_)),
+      var_stage_(new StageVariable("stage", system_state_)),
+      var_new_version_(new NewVersionVariable("new_version", system_state_)),
+      var_payload_size_(new PayloadSizeVariable("payload_size", system_state_)),
+      var_curr_channel_(new CurrChannelVariable("curr_channel", system_state_)),
+      var_new_channel_(new NewChannelVariable("new_channel", system_state_)),
+      var_p2p_enabled_(
+          new BooleanPrefVariable("p2p_enabled",
+                                  system_state_->prefs(),
+                                  chromeos_update_engine::kPrefsP2PEnabled,
+                                  false)),
+      var_cellular_enabled_(new BooleanPrefVariable(
+          "cellular_enabled",
+          system_state_->prefs(),
+          chromeos_update_engine::kPrefsUpdateOverCellularPermission,
+          false)),
+      var_consecutive_failed_update_checks_(
+          new ConsecutiveFailedUpdateChecksVariable(
+              "consecutive_failed_update_checks", system_state_)),
+      var_server_dictated_poll_interval_(new ServerDictatedPollIntervalVariable(
+          "server_dictated_poll_interval", system_state_)),
+      var_forced_update_requested_(new ForcedUpdateRequestedVariable(
+          "forced_update_requested", system_state_)),
+      var_update_restrictions_(new UpdateRestrictionsVariable(
+          "update_restrictions", system_state_)) {}
 }  // namespace chromeos_update_manager
diff --git a/update_manager/real_updater_provider.h b/update_manager/real_updater_provider.h
index b99bcc5..5e3e27b 100644
--- a/update_manager/real_updater_provider.h
+++ b/update_manager/real_updater_provider.h
@@ -64,7 +64,7 @@
     return var_new_version_.get();
   }
 
-  Variable<int64_t>* var_payload_size() override {
+  Variable<uint64_t>* var_payload_size() override {
     return var_payload_size_.get();
   }
 
@@ -96,6 +96,10 @@
     return var_forced_update_requested_.get();
   }
 
+  Variable<UpdateRestrictions>* var_update_restrictions() override {
+    return var_update_restrictions_.get();
+  }
+
  private:
   // A pointer to the update engine's system state aggregator.
   chromeos_update_engine::SystemState* system_state_;
@@ -107,7 +111,7 @@
   std::unique_ptr<Variable<double>> var_progress_;
   std::unique_ptr<Variable<Stage>> var_stage_;
   std::unique_ptr<Variable<std::string>> var_new_version_;
-  std::unique_ptr<Variable<int64_t>> var_payload_size_;
+  std::unique_ptr<Variable<uint64_t>> var_payload_size_;
   std::unique_ptr<Variable<std::string>> var_curr_channel_;
   std::unique_ptr<Variable<std::string>> var_new_channel_;
   std::unique_ptr<Variable<bool>> var_p2p_enabled_;
@@ -115,6 +119,7 @@
   std::unique_ptr<Variable<unsigned int>> var_consecutive_failed_update_checks_;
   std::unique_ptr<Variable<unsigned int>> var_server_dictated_poll_interval_;
   std::unique_ptr<Variable<UpdateRequestStatus>> var_forced_update_requested_;
+  std::unique_ptr<Variable<UpdateRestrictions>> var_update_restrictions_;
 
   DISALLOW_COPY_AND_ASSIGN(RealUpdaterProvider);
 };
diff --git a/update_manager/real_updater_provider_unittest.cc b/update_manager/real_updater_provider_unittest.cc
index 14eb30b..b653885 100644
--- a/update_manager/real_updater_provider_unittest.cc
+++ b/update_manager/real_updater_provider_unittest.cc
@@ -38,9 +38,11 @@
 using chromeos_update_engine::OmahaRequestParams;
 using std::string;
 using std::unique_ptr;
+using testing::_;
+using testing::DoAll;
 using testing::Return;
 using testing::SetArgPointee;
-using testing::_;
+using update_engine::UpdateAttemptFlags;
 
 namespace {
 
@@ -55,7 +57,9 @@
   now_exp.minute = 5;
   now_exp.second = 33;
   now_exp.millisecond = 675;
-  return Time::FromLocalExploded(now_exp);
+  Time time;
+  ignore_result(Time::FromLocalExploded(now_exp, &time));
+  return time;
 }
 
 // Rounds down a timestamp to the nearest second. This is useful when faking
@@ -64,7 +68,29 @@
   Time::Exploded exp;
   time.LocalExplode(&exp);
   exp.millisecond = 0;
-  return Time::FromLocalExploded(exp);
+  Time rounded_time;
+  ignore_result(Time::FromLocalExploded(exp, &rounded_time));
+  return rounded_time;
+}
+
+ACTION_P(ActionSetUpdateEngineStatusLastCheckedTime, time) {
+  arg0->last_checked_time = time;
+};
+
+ACTION_P(ActionSetUpdateEngineStatusProgress, progress) {
+  arg0->progress = progress;
+};
+
+ACTION_P(ActionSetUpdateEngineStatusStatus, status) {
+  arg0->status = status;
+}
+
+ACTION_P(ActionSetUpdateEngineStatusNewVersion, new_version) {
+  arg0->new_version = new_version;
+}
+
+ACTION_P(ActionSetUpdateEngineStatusNewSizeBytes, new_size_bytes) {
+  arg0->new_size_bytes = new_size_bytes;
 }
 
 }  // namespace
@@ -116,225 +142,189 @@
 }
 
 TEST_F(UmRealUpdaterProviderTest, GetLastCheckedTimeOkay) {
-  EXPECT_CALL(*fake_sys_state_.mock_update_attempter(),
-              GetStatus(_, _, _, _, _))
-      .WillOnce(DoAll(SetArgPointee<0>(FixedTime().ToTimeT()), Return(true)));
+  EXPECT_CALL(*fake_sys_state_.mock_update_attempter(), GetStatus(_))
+      .WillOnce(DoAll(
+          ActionSetUpdateEngineStatusLastCheckedTime(FixedTime().ToTimeT()),
+          Return(true)));
   UmTestUtils::ExpectVariableHasValue(RoundedToSecond(FixedTime()),
                                       provider_->var_last_checked_time());
 }
 
 TEST_F(UmRealUpdaterProviderTest, GetLastCheckedTimeFailNoValue) {
-  EXPECT_CALL(*fake_sys_state_.mock_update_attempter(),
-              GetStatus(_, _, _, _, _))
+  EXPECT_CALL(*fake_sys_state_.mock_update_attempter(), GetStatus(_))
       .WillOnce(Return(false));
   UmTestUtils::ExpectVariableNotSet(provider_->var_last_checked_time());
 }
 
 TEST_F(UmRealUpdaterProviderTest, GetProgressOkayMin) {
-  EXPECT_CALL(*fake_sys_state_.mock_update_attempter(),
-              GetStatus(_, _, _, _, _))
-      .WillOnce(DoAll(SetArgPointee<1>(0.0), Return(true)));
+  EXPECT_CALL(*fake_sys_state_.mock_update_attempter(), GetStatus(_))
+      .WillOnce(DoAll(ActionSetUpdateEngineStatusProgress(0.0), Return(true)));
   UmTestUtils::ExpectVariableHasValue(0.0, provider_->var_progress());
 }
 
 TEST_F(UmRealUpdaterProviderTest, GetProgressOkayMid) {
-  EXPECT_CALL(*fake_sys_state_.mock_update_attempter(),
-              GetStatus(_, _, _, _, _))
-      .WillOnce(DoAll(SetArgPointee<1>(0.3), Return(true)));
+  EXPECT_CALL(*fake_sys_state_.mock_update_attempter(), GetStatus(_))
+      .WillOnce(DoAll(ActionSetUpdateEngineStatusProgress(0.3), Return(true)));
   UmTestUtils::ExpectVariableHasValue(0.3, provider_->var_progress());
 }
 
 TEST_F(UmRealUpdaterProviderTest, GetProgressOkayMax) {
-  EXPECT_CALL(*fake_sys_state_.mock_update_attempter(),
-              GetStatus(_, _, _, _, _))
-      .WillOnce(DoAll(SetArgPointee<1>(1.0), Return(true)));
+  EXPECT_CALL(*fake_sys_state_.mock_update_attempter(), GetStatus(_))
+      .WillOnce(DoAll(ActionSetUpdateEngineStatusProgress(1.0), Return(true)));
   UmTestUtils::ExpectVariableHasValue(1.0, provider_->var_progress());
 }
 
 TEST_F(UmRealUpdaterProviderTest, GetProgressFailNoValue) {
-  EXPECT_CALL(*fake_sys_state_.mock_update_attempter(),
-              GetStatus(_, _, _, _, _))
+  EXPECT_CALL(*fake_sys_state_.mock_update_attempter(), GetStatus(_))
       .WillOnce(Return(false));
   UmTestUtils::ExpectVariableNotSet(provider_->var_progress());
 }
 
 TEST_F(UmRealUpdaterProviderTest, GetProgressFailTooSmall) {
-  EXPECT_CALL(*fake_sys_state_.mock_update_attempter(),
-              GetStatus(_, _, _, _, _))
-      .WillOnce(DoAll(SetArgPointee<1>(-2.0), Return(true)));
+  EXPECT_CALL(*fake_sys_state_.mock_update_attempter(), GetStatus(_))
+      .WillOnce(DoAll(ActionSetUpdateEngineStatusProgress(-2.0), Return(true)));
   UmTestUtils::ExpectVariableNotSet(provider_->var_progress());
 }
 
 TEST_F(UmRealUpdaterProviderTest, GetProgressFailTooBig) {
-  EXPECT_CALL(*fake_sys_state_.mock_update_attempter(),
-              GetStatus(_, _, _, _, _))
-      .WillOnce(DoAll(SetArgPointee<1>(2.0), Return(true)));
+  EXPECT_CALL(*fake_sys_state_.mock_update_attempter(), GetStatus(_))
+      .WillOnce(DoAll(ActionSetUpdateEngineStatusProgress(2.0), Return(true)));
   UmTestUtils::ExpectVariableNotSet(provider_->var_progress());
 }
 
 TEST_F(UmRealUpdaterProviderTest, GetStageOkayIdle) {
-  EXPECT_CALL(*fake_sys_state_.mock_update_attempter(),
-              GetStatus(_, _, _, _, _))
-      .WillOnce(DoAll(SetArgPointee<2>(update_engine::kUpdateStatusIdle),
-                      Return(true)));
+  EXPECT_CALL(*fake_sys_state_.mock_update_attempter(), GetStatus(_))
+      .WillOnce(DoAll(
+          ActionSetUpdateEngineStatusStatus(update_engine::UpdateStatus::IDLE),
+          Return(true)));
   UmTestUtils::ExpectVariableHasValue(Stage::kIdle, provider_->var_stage());
 }
 
 TEST_F(UmRealUpdaterProviderTest, GetStageOkayCheckingForUpdate) {
-  EXPECT_CALL(*fake_sys_state_.mock_update_attempter(),
-              GetStatus(_, _, _, _, _))
-      .WillOnce(DoAll(
-              SetArgPointee<2>(update_engine::kUpdateStatusCheckingForUpdate),
-              Return(true)));
+  EXPECT_CALL(*fake_sys_state_.mock_update_attempter(), GetStatus(_))
+      .WillOnce(DoAll(ActionSetUpdateEngineStatusStatus(
+                          update_engine::UpdateStatus::CHECKING_FOR_UPDATE),
+                      Return(true)));
   UmTestUtils::ExpectVariableHasValue(Stage::kCheckingForUpdate,
                                       provider_->var_stage());
 }
 
 TEST_F(UmRealUpdaterProviderTest, GetStageOkayUpdateAvailable) {
-  EXPECT_CALL(*fake_sys_state_.mock_update_attempter(),
-              GetStatus(_, _, _, _, _))
-      .WillOnce(DoAll(
-              SetArgPointee<2>(update_engine::kUpdateStatusUpdateAvailable),
-              Return(true)));
+  EXPECT_CALL(*fake_sys_state_.mock_update_attempter(), GetStatus(_))
+      .WillOnce(DoAll(ActionSetUpdateEngineStatusStatus(
+                          update_engine::UpdateStatus::UPDATE_AVAILABLE),
+                      Return(true)));
   UmTestUtils::ExpectVariableHasValue(Stage::kUpdateAvailable,
                                       provider_->var_stage());
 }
 
 TEST_F(UmRealUpdaterProviderTest, GetStageOkayDownloading) {
-  EXPECT_CALL(*fake_sys_state_.mock_update_attempter(),
-              GetStatus(_, _, _, _, _))
-      .WillOnce(DoAll(SetArgPointee<2>(update_engine::kUpdateStatusDownloading),
+  EXPECT_CALL(*fake_sys_state_.mock_update_attempter(), GetStatus(_))
+      .WillOnce(DoAll(ActionSetUpdateEngineStatusStatus(
+                          update_engine::UpdateStatus::DOWNLOADING),
                       Return(true)));
   UmTestUtils::ExpectVariableHasValue(Stage::kDownloading,
                                       provider_->var_stage());
 }
 
 TEST_F(UmRealUpdaterProviderTest, GetStageOkayVerifying) {
-  EXPECT_CALL(*fake_sys_state_.mock_update_attempter(),
-              GetStatus(_, _, _, _, _))
-      .WillOnce(DoAll(SetArgPointee<2>(update_engine::kUpdateStatusVerifying),
+  EXPECT_CALL(*fake_sys_state_.mock_update_attempter(), GetStatus(_))
+      .WillOnce(DoAll(ActionSetUpdateEngineStatusStatus(
+                          update_engine::UpdateStatus::VERIFYING),
                       Return(true)));
   UmTestUtils::ExpectVariableHasValue(Stage::kVerifying,
                                       provider_->var_stage());
 }
 
 TEST_F(UmRealUpdaterProviderTest, GetStageOkayFinalizing) {
-  EXPECT_CALL(*fake_sys_state_.mock_update_attempter(),
-              GetStatus(_, _, _, _, _))
-      .WillOnce(DoAll(SetArgPointee<2>(update_engine::kUpdateStatusFinalizing),
+  EXPECT_CALL(*fake_sys_state_.mock_update_attempter(), GetStatus(_))
+      .WillOnce(DoAll(ActionSetUpdateEngineStatusStatus(
+                          update_engine::UpdateStatus::FINALIZING),
                       Return(true)));
   UmTestUtils::ExpectVariableHasValue(Stage::kFinalizing,
                                       provider_->var_stage());
 }
 
 TEST_F(UmRealUpdaterProviderTest, GetStageOkayUpdatedNeedReboot) {
-  EXPECT_CALL(*fake_sys_state_.mock_update_attempter(),
-              GetStatus(_, _, _, _, _))
-      .WillOnce(DoAll(
-              SetArgPointee<2>(update_engine::kUpdateStatusUpdatedNeedReboot),
-              Return(true)));
+  EXPECT_CALL(*fake_sys_state_.mock_update_attempter(), GetStatus(_))
+      .WillOnce(DoAll(ActionSetUpdateEngineStatusStatus(
+                          update_engine::UpdateStatus::UPDATED_NEED_REBOOT),
+                      Return(true)));
   UmTestUtils::ExpectVariableHasValue(Stage::kUpdatedNeedReboot,
                                       provider_->var_stage());
 }
 
 TEST_F(UmRealUpdaterProviderTest, GetStageOkayReportingErrorEvent) {
-  EXPECT_CALL(*fake_sys_state_.mock_update_attempter(),
-              GetStatus(_, _, _, _, _))
-      .WillOnce(DoAll(
-              SetArgPointee<2>(update_engine::kUpdateStatusReportingErrorEvent),
-              Return(true)));
+  EXPECT_CALL(*fake_sys_state_.mock_update_attempter(), GetStatus(_))
+      .WillOnce(DoAll(ActionSetUpdateEngineStatusStatus(
+                          update_engine::UpdateStatus::REPORTING_ERROR_EVENT),
+                      Return(true)));
   UmTestUtils::ExpectVariableHasValue(Stage::kReportingErrorEvent,
                                       provider_->var_stage());
 }
 
 TEST_F(UmRealUpdaterProviderTest, GetStageOkayAttemptingRollback) {
-  EXPECT_CALL(*fake_sys_state_.mock_update_attempter(),
-              GetStatus(_, _, _, _, _))
-      .WillOnce(DoAll(
-              SetArgPointee<2>(update_engine::kUpdateStatusAttemptingRollback),
-              Return(true)));
+  EXPECT_CALL(*fake_sys_state_.mock_update_attempter(), GetStatus(_))
+      .WillOnce(DoAll(ActionSetUpdateEngineStatusStatus(
+                          update_engine::UpdateStatus::ATTEMPTING_ROLLBACK),
+                      Return(true)));
   UmTestUtils::ExpectVariableHasValue(Stage::kAttemptingRollback,
                                       provider_->var_stage());
 }
 
 TEST_F(UmRealUpdaterProviderTest, GetStageFailNoValue) {
-  EXPECT_CALL(*fake_sys_state_.mock_update_attempter(),
-              GetStatus(_, _, _, _, _))
+  EXPECT_CALL(*fake_sys_state_.mock_update_attempter(), GetStatus(_))
       .WillOnce(Return(false));
   UmTestUtils::ExpectVariableNotSet(provider_->var_stage());
 }
 
-TEST_F(UmRealUpdaterProviderTest, GetStageFailUnknown) {
-  EXPECT_CALL(*fake_sys_state_.mock_update_attempter(),
-              GetStatus(_, _, _, _, _))
-      .WillOnce(DoAll(SetArgPointee<2>("FooUpdateEngineState"),
-                      Return(true)));
-  UmTestUtils::ExpectVariableNotSet(provider_->var_stage());
-}
-
-TEST_F(UmRealUpdaterProviderTest, GetStageFailEmpty) {
-  EXPECT_CALL(*fake_sys_state_.mock_update_attempter(),
-              GetStatus(_, _, _, _, _))
-      .WillOnce(DoAll(SetArgPointee<2>(""), Return(true)));
-  UmTestUtils::ExpectVariableNotSet(provider_->var_stage());
-}
-
 TEST_F(UmRealUpdaterProviderTest, GetNewVersionOkay) {
-  EXPECT_CALL(*fake_sys_state_.mock_update_attempter(),
-              GetStatus(_, _, _, _, _))
-      .WillOnce(DoAll(SetArgPointee<3>("1.2.0"), Return(true)));
+  EXPECT_CALL(*fake_sys_state_.mock_update_attempter(), GetStatus(_))
+      .WillOnce(
+          DoAll(ActionSetUpdateEngineStatusNewVersion("1.2.0"), Return(true)));
   UmTestUtils::ExpectVariableHasValue(string("1.2.0"),
                                       provider_->var_new_version());
 }
 
 TEST_F(UmRealUpdaterProviderTest, GetNewVersionFailNoValue) {
-  EXPECT_CALL(*fake_sys_state_.mock_update_attempter(),
-              GetStatus(_, _, _, _, _))
+  EXPECT_CALL(*fake_sys_state_.mock_update_attempter(), GetStatus(_))
       .WillOnce(Return(false));
   UmTestUtils::ExpectVariableNotSet(provider_->var_new_version());
 }
 
 TEST_F(UmRealUpdaterProviderTest, GetPayloadSizeOkayZero) {
-  EXPECT_CALL(*fake_sys_state_.mock_update_attempter(),
-              GetStatus(_, _, _, _, _))
-      .WillOnce(DoAll(SetArgPointee<4>(static_cast<int64_t>(0)), Return(true)));
-  UmTestUtils::ExpectVariableHasValue(static_cast<int64_t>(0),
+  EXPECT_CALL(*fake_sys_state_.mock_update_attempter(), GetStatus(_))
+      .WillOnce(DoAll(
+          ActionSetUpdateEngineStatusNewSizeBytes(static_cast<uint64_t>(0)),
+          Return(true)));
+  UmTestUtils::ExpectVariableHasValue(static_cast<uint64_t>(0),
                                       provider_->var_payload_size());
 }
 
 TEST_F(UmRealUpdaterProviderTest, GetPayloadSizeOkayArbitrary) {
-  EXPECT_CALL(*fake_sys_state_.mock_update_attempter(),
-              GetStatus(_, _, _, _, _))
-      .WillOnce(DoAll(SetArgPointee<4>(static_cast<int64_t>(567890)),
+  EXPECT_CALL(*fake_sys_state_.mock_update_attempter(), GetStatus(_))
+      .WillOnce(DoAll(ActionSetUpdateEngineStatusNewSizeBytes(
+                          static_cast<uint64_t>(567890)),
                       Return(true)));
-  UmTestUtils::ExpectVariableHasValue(static_cast<int64_t>(567890),
+  UmTestUtils::ExpectVariableHasValue(static_cast<uint64_t>(567890),
                                       provider_->var_payload_size());
 }
 
 TEST_F(UmRealUpdaterProviderTest, GetPayloadSizeOkayTwoGigabytes) {
-  EXPECT_CALL(*fake_sys_state_.mock_update_attempter(),
-              GetStatus(_, _, _, _, _))
-      .WillOnce(DoAll(SetArgPointee<4>(static_cast<int64_t>(1) << 31),
+  EXPECT_CALL(*fake_sys_state_.mock_update_attempter(), GetStatus(_))
+      .WillOnce(DoAll(ActionSetUpdateEngineStatusNewSizeBytes(
+                          static_cast<uint64_t>(1) << 31),
                       Return(true)));
-  UmTestUtils::ExpectVariableHasValue(static_cast<int64_t>(1) << 31,
+  UmTestUtils::ExpectVariableHasValue(static_cast<uint64_t>(1) << 31,
                                       provider_->var_payload_size());
 }
 
 TEST_F(UmRealUpdaterProviderTest, GetPayloadSizeFailNoValue) {
-  EXPECT_CALL(*fake_sys_state_.mock_update_attempter(),
-              GetStatus(_, _, _, _, _))
+  EXPECT_CALL(*fake_sys_state_.mock_update_attempter(), GetStatus(_))
       .WillOnce(Return(false));
   UmTestUtils::ExpectVariableNotSet(provider_->var_payload_size());
 }
 
-TEST_F(UmRealUpdaterProviderTest, GetPayloadSizeFailNegative) {
-  EXPECT_CALL(*fake_sys_state_.mock_update_attempter(),
-              GetStatus(_, _, _, _, _))
-      .WillOnce(DoAll(SetArgPointee<4>(static_cast<int64_t>(-1024)),
-                      Return(true)));
-  UmTestUtils::ExpectVariableNotSet(provider_->var_payload_size());
-}
-
 TEST_F(UmRealUpdaterProviderTest, GetCurrChannelOkay) {
   const string kChannelName("foo-channel");
   OmahaRequestParams request_params(&fake_sys_state_);
@@ -440,4 +430,20 @@
       kPollInterval, provider_->var_server_dictated_poll_interval());
 }
 
+TEST_F(UmRealUpdaterProviderTest, GetUpdateRestrictions) {
+  EXPECT_CALL(*fake_sys_state_.mock_update_attempter(),
+              GetCurrentUpdateAttemptFlags())
+      .WillRepeatedly(Return(UpdateAttemptFlags::kFlagRestrictDownload |
+                             UpdateAttemptFlags::kFlagNonInteractive));
+  UmTestUtils::ExpectVariableHasValue(UpdateRestrictions::kRestrictDownloading,
+                                      provider_->var_update_restrictions());
+}
+
+TEST_F(UmRealUpdaterProviderTest, GetUpdateRestrictionsNone) {
+  EXPECT_CALL(*fake_sys_state_.mock_update_attempter(),
+              GetCurrentUpdateAttemptFlags())
+      .WillRepeatedly(Return(UpdateAttemptFlags::kNone));
+  UmTestUtils::ExpectVariableHasValue(UpdateRestrictions::kNone,
+                                      provider_->var_update_restrictions());
+}
 }  // namespace chromeos_update_manager
diff --git a/update_manager/update_manager.cc b/update_manager/update_manager.cc
index 8e9b221..25f3216 100644
--- a/update_manager/update_manager.cc
+++ b/update_manager/update_manager.cc
@@ -16,7 +16,11 @@
 
 #include "update_engine/update_manager/update_manager.h"
 
+#ifdef __ANDROID__
+#include "update_engine/update_manager/android_things_policy.h"
+#else
 #include "update_engine/update_manager/chromeos_policy.h"
+#endif  // __ANDROID__
 #include "update_engine/update_manager/state.h"
 
 namespace chromeos_update_manager {
@@ -28,9 +32,11 @@
         evaluation_timeout_(evaluation_timeout),
         expiration_timeout_(expiration_timeout),
         weak_ptr_factory_(this) {
-  // TODO(deymo): Make it possible to replace this policy with a different
-  // implementation with a build-time flag.
+#ifdef __ANDROID__
+  policy_.reset(new AndroidThingsPolicy());
+#else
   policy_.reset(new ChromeOSPolicy());
+#endif  // __ANDROID__
 }
 
 UpdateManager::~UpdateManager() {
diff --git a/update_manager/update_manager_unittest.cc b/update_manager/update_manager_unittest.cc
index 03f1610..c2766ea 100644
--- a/update_manager/update_manager_unittest.cc
+++ b/update_manager/update_manager_unittest.cc
@@ -67,7 +67,9 @@
   now_exp.minute = 5;
   now_exp.second = 33;
   now_exp.millisecond = 675;
-  return Time::FromLocalExploded(now_exp);
+  Time time;
+  ignore_result(Time::FromLocalExploded(now_exp, &time));
+  return time;
 }
 
 }  // namespace
diff --git a/update_manager/updater_provider.h b/update_manager/updater_provider.h
index 8048d38..cb62623 100644
--- a/update_manager/updater_provider.h
+++ b/update_manager/updater_provider.h
@@ -44,6 +44,12 @@
   kPeriodic,
 };
 
+// These enum values are a bit-field.
+enum UpdateRestrictions : int {
+  kNone,
+  kRestrictDownloading = (1 << 0),
+};
+
 // Provider for Chrome OS update related information.
 class UpdaterProvider : public Provider {
  public:
@@ -79,7 +85,7 @@
 
   // A variable returning the update payload size. The payload size is
   // guaranteed to be non-negative.
-  virtual Variable<int64_t>* var_payload_size() = 0;
+  virtual Variable<uint64_t>* var_payload_size() = 0;
 
   // A variable returning the current channel.
   virtual Variable<std::string>* var_curr_channel() = 0;
@@ -105,6 +111,10 @@
   // scheduled update.
   virtual Variable<UpdateRequestStatus>* var_forced_update_requested() = 0;
 
+  // A variable that returns the update restriction flags that are set
+  // for all updates.
+  virtual Variable<UpdateRestrictions>* var_update_restrictions() = 0;
+
  protected:
   UpdaterProvider() {}
 
diff --git a/update_metadata.proto b/update_metadata.proto
index ad377c7..b5d6c59 100644
--- a/update_metadata.proto
+++ b/update_metadata.proto
@@ -288,4 +288,8 @@
   // array can have more than two partitions if needed, and they are identified
   // by the partition name.
   repeated PartitionUpdate partitions = 13;
+
+  // The maximum timestamp of the OS allowed to apply this payload.
+  // Can be used to prevent downgrading the OS.
+  optional int64 max_timestamp = 14;
 }
diff --git a/update_status_utils.cc b/update_status_utils.cc
index 5de3381..ff039b8 100644
--- a/update_status_utils.cc
+++ b/update_status_utils.cc
@@ -30,8 +30,6 @@
       return update_engine::kUpdateStatusCheckingForUpdate;
     case UpdateStatus::UPDATE_AVAILABLE:
       return update_engine::kUpdateStatusUpdateAvailable;
-    case UpdateStatus::NEED_PERMISSION_TO_UPDATE:
-      return update_engine::kUpdateStatusNeedPermissionToUpdate;
     case UpdateStatus::DOWNLOADING:
       return update_engine::kUpdateStatusDownloading;
     case UpdateStatus::VERIFYING:
@@ -63,9 +61,6 @@
   } else if (s == update_engine::kUpdateStatusUpdateAvailable) {
     *status = UpdateStatus::UPDATE_AVAILABLE;
     return true;
-  } else if (s == update_engine::kUpdateStatusNeedPermissionToUpdate) {
-    *status = UpdateStatus::NEED_PERMISSION_TO_UPDATE;
-    return true;
   } else if (s == update_engine::kUpdateStatusDownloading) {
     *status = UpdateStatus::DOWNLOADING;
     return true;
diff --git a/utils_android.cc b/utils_android.cc
index 38d62ea..393e65a 100644
--- a/utils_android.cc
+++ b/utils_android.cc
@@ -16,7 +16,6 @@
 
 #include "update_engine/utils_android.h"
 
-#include <cutils/properties.h>
 #include <fs_mgr.h>
 
 using std::string;