Merge remote-tracking branch 'remotes/cros/update_engine_aosp_merge'

In the follow up CLs we have to Reland:
eaad5d0f17a1b9d5652a350e1c785c7c5368cb54 Do not merge to AOSP: Fixes the link to brillo-clang-format in CrOS
740efad439e57e71d8d459c366edea3a90b19576 Reboot even if a system update is not available.
4b0d6032cbb86ce488c03b31936cda31283f97e3 Add functions to allow update over cellular (including tethered connection)
840703a4cc77228e2606f45665ae3a4bd75ff7dd Fix update over cellular network on guest account
Because they have been reverted in cros/upstream branch.

Also we have to partially revert:
2b9d2417722cd4052b0e22494886f93c5b4ef042  update_engine: Update libchrome APIS to r456626.
Because we have not upreved libchrome

BUG=chromium:815356
TEST=unittests
TEST=cl with squashfs merge passes

Change-Id: I0ec7f3fe3fd278ef1214868b42d3eb126ebeefe2
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..d2a83bc 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 := \
@@ -136,6 +141,7 @@
     payload_consumer/file_writer.cc \
     payload_consumer/filesystem_verifier_action.cc \
     payload_consumer/install_plan.cc \
+    payload_consumer/mount_history.cc \
     payload_consumer/payload_constants.cc \
     payload_consumer/payload_verifier.cc \
     payload_consumer/postinstall_runner_action.cc \
@@ -147,7 +153,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 +176,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 +211,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 +268,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 +297,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 +309,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 +370,7 @@
     libbrillo-binder \
     libcutils \
     libcurl \
+    libmetricslogger \
     libssl \
     libutils
 
@@ -370,7 +378,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 +404,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 +424,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 +465,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 +480,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 +502,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 +535,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 +572,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,9 +611,13 @@
 # for any client code.
 ue_libpayload_generator_exported_static_libraries := \
     libbsdiff \
+    libdivsufsort \
+    libdivsufsort64 \
+    libbrotli \
     liblzma \
     libpayload_consumer \
     libpuffdiff \
+    libz \
     update_metadata-protos \
     $(ue_libpayload_consumer_exported_static_libraries) \
     $(ue_update_metadata_protos_exported_static_libraries)
@@ -656,13 +659,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 +688,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 +722,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 +747,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)
@@ -770,7 +774,6 @@
     $(eval include $(CLEAR_VARS)) \
     $(eval LOCAL_MODULE := ue_$(1).pem) \
     $(eval LOCAL_MODULE_CLASS := ETC) \
-    $(eval $(ifeq $(BRILLO), 1, LOCAL_MODULE_TAGS := eng)) \
     $(eval LOCAL_SRC_FILES := $(1).pem) \
     $(eval LOCAL_MODULE_PATH := \
         $(TARGET_OUT_DATA_NATIVE_TESTS)/update_engine_unittests) \
@@ -780,7 +783,6 @@
     $(eval include $(CLEAR_VARS)) \
     $(eval LOCAL_MODULE := ue_$(1).pub.pem) \
     $(eval LOCAL_MODULE_CLASS := ETC) \
-    $(eval $(ifeq $(BRILLO), 1, LOCAL_MODULE_TAGS := eng)) \
     $(eval LOCAL_MODULE_PATH := \
         $(TARGET_OUT_DATA_NATIVE_TESTS)/update_engine_unittests) \
     $(eval LOCAL_MODULE_STEM := $(1).pub.pem) \
@@ -802,7 +804,6 @@
     $(eval include $(CLEAR_VARS)) \
     $(eval LOCAL_MODULE := ue_unittest_$(1)) \
     $(eval LOCAL_MODULE_CLASS := EXECUTABLES) \
-    $(eval $(ifeq $(BRILLO), 1, LOCAL_MODULE_TAGS := eng)) \
     $(eval LOCAL_MODULE_PATH := \
         $(TARGET_OUT_DATA_NATIVE_TESTS)/update_engine_unittests/gen) \
     $(eval LOCAL_MODULE_STEM := $(1)) \
@@ -817,15 +818,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 +836,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 +855,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 +881,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 +962,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 +972,18 @@
     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/enterprise_device_policy_impl.cc \
     update_manager/evaluation_context_unittest.cc \
     update_manager/generic_variables_unittest.cc \
+    update_manager/next_update_check_policy_impl_unittest.cc \
+    update_manager/out_of_box_experience_policy_impl.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 +999,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 +1015,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 +1028,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/client_dbus.cc b/client_library/client_dbus.cc
index 426412c..1072836 100644
--- a/client_library/client_dbus.cc
+++ b/client_library/client_dbus.cc
@@ -49,7 +49,9 @@
   return proxy_->AttemptUpdateWithFlags(
       in_app_version,
       in_omaha_url,
-      (at_user_request) ? 0 : kAttemptUpdateFlagNonInteractive,
+      (at_user_request)
+          ? 0
+          : update_engine::UpdateAttemptFlags::kFlagNonInteractive,
       nullptr);
 }
 
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..adae391 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..983d004 100644
--- a/common/error_code_utils.cc
+++ b/common/error_code_utils.cc
@@ -144,10 +144,10 @@
       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::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/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/multi_range_http_fetcher.cc b/common/multi_range_http_fetcher.cc
index e696c36..dc4b7c1 100644
--- a/common/multi_range_http_fetcher.cc
+++ b/common/multi_range_http_fetcher.cc
@@ -101,6 +101,12 @@
   LOG_IF(WARNING, next_size <= 0) << "Asked to write length <= 0";
   if (delegate_) {
     delegate_->ReceivedBytes(this, bytes, next_size);
+    // If the transfer was already terminated by |delegate_|, return immediately
+    // to avoid calling TerminateTransfer() again.
+    if (!base_fetcher_active_) {
+      LOG(INFO) << "Delegate has terminated the transfer.";
+      return;
+    }
   }
   bytes_received_this_range_ += length;
   if (range.HasLength() && bytes_received_this_range_ >= range.length()) {
@@ -188,4 +194,27 @@
   return range_str;
 }
 
+void MultiRangeHttpFetcher::SetOffset(off_t offset) {
+  current_index_ = 0;
+  for (const Range& range : ranges_) {
+    if (!range.HasLength() || static_cast<size_t>(offset) < range.length()) {
+      bytes_received_this_range_ = offset;
+
+      base_fetcher_->SetOffset(range.offset() + offset);
+      if (range.HasLength())
+        base_fetcher_->SetLength(range.length());
+      else
+        base_fetcher_->UnsetLength();
+      if (delegate_)
+        delegate_->SeekToOffset(range.offset() + offset);
+      return;
+    }
+    current_index_++;
+    offset -= range.length();
+  }
+  if (offset > 0) {
+    LOG(ERROR) << "Offset too large.";
+  }
+}
+
 }  // namespace chromeos_update_engine
diff --git a/common/multi_range_http_fetcher.h b/common/multi_range_http_fetcher.h
index 8a91ead..54ddfbc 100644
--- a/common/multi_range_http_fetcher.h
+++ b/common/multi_range_http_fetcher.h
@@ -67,7 +67,7 @@
   }
 
   // HttpFetcher overrides.
-  void SetOffset(off_t offset) override {}  // for now, doesn't support this
+  void SetOffset(off_t offset) override;
 
   void SetLength(size_t length) override {}  // unsupported
   void UnsetLength() override {}
diff --git a/common/subprocess_unittest.cc b/common/subprocess_unittest.cc
index 10710e8..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>
diff --git a/common/test_utils.cc b/common/test_utils.cc
index 3447cdc..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;
 }
 
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 606e58d..c102a16 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 033702b..62f9f6c 100644
--- a/common/utils_unittest.cc
+++ b/common/utils_unittest.cc
@@ -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) {
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 ca173ef..be707bc 100644
--- a/connection_manager.cc
+++ b/connection_manager.cc
@@ -31,7 +31,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;
@@ -60,47 +59,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 (!base::ContainsKey(allowed_types, shill::kTypeCellular)) {
-          LOG(INFO) << "Disabling updates over cellular per device policy.";
+          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:
@@ -115,22 +121,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..47aeec7 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}) {
@@ -46,12 +47,17 @@
     int32_t in_flags_as_int) {
   update_engine::AttemptUpdateFlags flags =
       static_cast<update_engine::AttemptUpdateFlags>(in_flags_as_int);
-  bool interactive = !(flags &
-      update_engine::kAttemptUpdateFlagNonInteractive);
-
+  bool interactive = !(flags & update_engine::kAttemptUpdateFlagNonInteractive);
+  bool result;
   return common_->AttemptUpdate(
-      error, in_app_version, in_omaha_url,
-      interactive ? 0 : UpdateEngineService::kAttemptUpdateFlagNonInteractive);
+             error,
+             in_app_version,
+             in_omaha_url,
+             interactive
+                 ? 0
+                 : update_engine::UpdateAttemptFlags::kFlagNonInteractive,
+             &result) &&
+         result;
 }
 
 bool DBusUpdateEngineService::AttemptRollback(ErrorPtr* error,
@@ -74,12 +80,16 @@
                                         string* out_current_operation,
                                         string* out_new_version,
                                         int64_t* out_new_size) {
-  return common_->GetStatus(error,
-                            out_last_checked_time,
-                            out_progress,
-                            out_current_operation,
-                            out_new_version,
-                            out_new_size);
+  UpdateEngineStatus status;
+  if (!common_->GetStatus(error, &status)) {
+    return false;
+  }
+  *out_last_checked_time = status.last_checked_time;
+  *out_progress = status.progress;
+  *out_current_operation = UpdateStatusToString(status.status);
+  *out_new_version = status.new_version;
+  *out_new_size = status.new_size_bytes;
+  return true;
 }
 
 bool DBusUpdateEngineService::RebootIfNeeded(ErrorPtr* error) {
@@ -123,14 +133,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 +182,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_bytes);
 }
 
 }  // 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..58c68b6 100644
--- a/hardware_android.cc
+++ b/hardware_android.cc
@@ -25,15 +25,17 @@
 
 #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::GetProperty;
 using std::string;
 
 namespace chromeos_update_engine {
@@ -71,8 +73,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 +122,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 +130,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 +150,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 {
@@ -205,7 +199,7 @@
   return false;
 }
 
-void HardwareChromeOS::SetFirstActiveOmahaPingSent() {
+void HardwareAndroid::SetFirstActiveOmahaPingSent() {
   LOG(WARNING) << "STUB: Assuming first active omaha is never set.";
   return;
 }
diff --git a/image_properties.h b/image_properties.h
index e03b8dc..49fe82f 100644
--- a/image_properties.h
+++ b/image_properties.h
@@ -33,20 +33,35 @@
   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;
 
   // The release channel this image was obtained from.
   std::string current_channel;
 
+  // Whether we allow arbitrary channels instead of just the ones listed in
+  // kChannelsByStability.
+  bool allow_arbitrary_channels = false;
+
   // The Omaha URL this image should get updates from.
   std::string omaha_url;
 };
diff --git a/image_properties_android.cc b/image_properties_android.cc
index 765b4ba..4dc2c02 100644
--- a/image_properties_android.cc
+++ b/image_properties_android.cc
@@ -16,39 +16,60 @@
 
 #include "update_engine/image_properties.h"
 
+#include <fcntl.h>
+
 #include <string>
 
+#include <android-base/properties.h>
 #include <base/logging.h>
+#include <base/strings/string_util.h>
+#include <bootloader.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"
+#include "update_engine/utils_android.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";
 
-// Prefs used to store the target channel and powerwash settings.
-const char kPrefsImgPropChannelName[] = "img-prop-channel-name";
+// 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 powerwash settings.
 const char kPrefsImgPropPowerwashAllowed[] = "img-prop-powerwash-allowed";
 
 // 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;
+// Default channel from factory.prop
+const char kPropDefaultChannel[] = "ro.update.default_channel";
+
+// 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 "
@@ -56,45 +77,136 @@
   return default_value;
 }
 
+// Open misc partition for read or write and output the fd in |out_fd|.
+bool OpenMisc(bool write, int* out_fd) {
+  base::FilePath misc_device;
+  int flags = write ? O_WRONLY | O_SYNC : O_RDONLY;
+  if (root_prefix) {
+    // Use a file for unittest and create one if doesn't exist.
+    misc_device = base::FilePath(root_prefix).Append("misc");
+    if (write)
+      flags |= O_CREAT;
+  } else if (!utils::DeviceForMountPoint("/misc", &misc_device)) {
+    return false;
+  }
+
+  int fd = HANDLE_EINTR(open(misc_device.value().c_str(), flags, 0600));
+  if (fd < 0) {
+    PLOG(ERROR) << "Opening misc failed";
+    return false;
+  }
+  *out_fd = fd;
+  return true;
+}
+
+// The offset and size of the channel field in misc partition.
+constexpr size_t kChannelOffset =
+    BOOTLOADER_MESSAGE_OFFSET_IN_MISC +
+    offsetof(bootloader_message_ab, update_channel);
+constexpr size_t kChannelSize = sizeof(bootloader_message_ab::update_channel);
+
+// Read channel from misc partition to |out_channel|, return false if unable to
+// read misc or no channel is set in misc.
+bool ReadChannelFromMisc(string* out_channel) {
+  int fd;
+  TEST_AND_RETURN_FALSE(OpenMisc(false, &fd));
+  ScopedFdCloser fd_closer(&fd);
+  char channel[kChannelSize] = {0};
+  ssize_t bytes_read = 0;
+  if (!utils::PReadAll(
+          fd, channel, kChannelSize - 1, kChannelOffset, &bytes_read) ||
+      bytes_read != kChannelSize - 1) {
+    PLOG(ERROR) << "Reading update channel from misc failed";
+    return false;
+  }
+  if (channel[0] == '\0') {
+    LOG(INFO) << "No channel set in misc.";
+    return false;
+  }
+  if (!base::EndsWith(channel, "-channel", base::CompareCase::SENSITIVE)) {
+    LOG(ERROR) << "Channel " << channel << " doesn't end with -channel.";
+    return false;
+  }
+  out_channel->assign(channel);
+  return true;
+}
+
+// Write |in_channel| to misc partition, return false if failed to write.
+bool WriteChannelToMisc(const string& in_channel) {
+  int fd;
+  TEST_AND_RETURN_FALSE(OpenMisc(true, &fd));
+  ScopedFdCloser fd_closer(&fd);
+  if (in_channel.size() >= kChannelSize) {
+    LOG(ERROR) << "Channel name is too long: " << in_channel
+               << ", the maximum length is " << kChannelSize - 1;
+    return false;
+  }
+  char channel[kChannelSize] = {0};
+  memcpy(channel, in_channel.data(), in_channel.size());
+  if (!utils::PWriteAll(fd, channel, kChannelSize, kChannelOffset)) {
+    PLOG(ERROR) << "Writing update channel to misc failed";
+    return false;
+  }
+  return true;
+}
+
+string GetTargetChannel() {
+  string channel;
+  if (!ReadChannelFromMisc(&channel))
+    channel = GetProperty(kPropDefaultChannel, "stable-channel");
+  return channel;
+}
 }  // 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;
+  result.board = GetProperty(kPropProductName, "brillo");
+  result.build_fingerprint = GetProperty(kPropBuildFingerprint, "none");
+  result.build_type = GetProperty(kPropBuildType, "");
 
-  property_get(kPropBuildFingerprint, prop, "none");
-  result.build_fingerprint = prop;
-
-  // 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 =
+  // Android doesn't have channel information in system image, we try to read
+  // the channel of current slot from prefs and then fallback to use the
+  // persisted target channel as current channel.
+  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";
+    current_channel = GetTargetChannel();
   result.current_channel = current_channel;
+  result.allow_arbitrary_channels = true;
 
   // Brillo only supports the official omaha URL.
   result.omaha_url = constants::kOmahaDefaultProductionURL;
@@ -104,11 +216,9 @@
 
 MutableImageProperties LoadMutableImageProperties(SystemState* system_state) {
   MutableImageProperties result;
-  PrefsInterface* const prefs = system_state->prefs();
-  if (!prefs->GetString(kPrefsImgPropChannelName, &result.target_channel))
-    result.target_channel.clear();
-  if (!prefs->GetBoolean(kPrefsImgPropPowerwashAllowed,
-                         &result.is_powerwash_allowed)) {
+  result.target_channel = GetTargetChannel();
+  if (!system_state->prefs()->GetBoolean(kPrefsImgPropPowerwashAllowed,
+                                         &result.is_powerwash_allowed)) {
     result.is_powerwash_allowed = false;
   }
   return result;
@@ -116,11 +226,13 @@
 
 bool StoreMutableImageProperties(SystemState* system_state,
                                  const MutableImageProperties& properties) {
-  PrefsInterface* const prefs = system_state->prefs();
-  return (
-      prefs->SetString(kPrefsImgPropChannelName, properties.target_channel) &&
-      prefs->SetBoolean(kPrefsImgPropPowerwashAllowed,
-                        properties.is_powerwash_allowed));
+  bool ret = true;
+  if (!WriteChannelToMisc(properties.target_channel))
+    ret = false;
+  if (!system_state->prefs()->SetBoolean(kPrefsImgPropPowerwashAllowed,
+                                         properties.is_powerwash_allowed))
+    ret = false;
+  return ret;
 }
 
 void LogImageProperties() {
diff --git a/image_properties_android_unittest.cc b/image_properties_android_unittest.cc
new file mode 100644
index 0000000..607284a
--- /dev/null
+++ b/image_properties_android_unittest.cc
@@ -0,0 +1,123 @@
+//
+// 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/fake_prefs.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));
+  }
+
+  void WriteChannel(const string& channel) {
+    string misc(2080, '\0');
+    misc += channel;
+    misc.resize(4096);
+    ASSERT_TRUE(
+        WriteFileString(tempdir_.GetPath().Append("misc").value(), misc));
+  }
+
+  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);
+}
+
+TEST_F(ImagePropertiesTest, LoadChannelTest) {
+  WriteChannel("unittest-channel");
+  ImageProperties props = LoadImageProperties(&fake_system_state_);
+  EXPECT_EQ("unittest-channel", props.current_channel);
+}
+
+TEST_F(ImagePropertiesTest, DefaultStableChannelTest) {
+  WriteChannel("");
+  ImageProperties props = LoadImageProperties(&fake_system_state_);
+  EXPECT_EQ("stable-channel", props.current_channel);
+}
+
+TEST_F(ImagePropertiesTest, StoreLoadMutableChannelTest) {
+  FakePrefs prefs;
+  fake_system_state_.set_prefs(&prefs);
+  WriteChannel("previous-channel");
+  MutableImageProperties props;
+  props.target_channel = "new-channel";
+  EXPECT_TRUE(StoreMutableImageProperties(&fake_system_state_, props));
+  MutableImageProperties loaded_props =
+      LoadMutableImageProperties(&fake_system_state_);
+  EXPECT_EQ(props.target_channel, loaded_props.target_channel);
+}
+
+}  // namespace chromeos_update_engine
diff --git a/image_properties_chromeos.cc b/image_properties_chromeos.cc
index 39ddeb3..87c32f0 100644
--- a/image_properties_chromeos.cc
+++ b/image_properties_chromeos.cc
@@ -118,6 +118,7 @@
                            constants::kOmahaDefaultProductionURL);
   // Build fingerprint not used in Chrome OS.
   result.build_fingerprint = "";
+  result.allow_arbitrary_channels = false;
 
   return result;
 }
diff --git a/main.cc b/main.cc
index 4275bc1..0612c54 100644
--- a/main.cc
+++ b/main.cc
@@ -14,15 +14,19 @@
 // limitations under the License.
 //
 
+#include <inttypes.h>
 #include <sys/stat.h>
 #include <sys/types.h>
 #include <unistd.h>
 #include <xz.h>
 
+#include <algorithm>
 #include <string>
+#include <vector>
 
 #include <base/at_exit.h>
 #include <base/command_line.h>
+#include <base/files/dir_reader_posix.h>
 #include <base/files/file_util.h>
 #include <base/logging.h>
 #include <base/strings/string_util.h>
@@ -38,6 +42,65 @@
 namespace chromeos_update_engine {
 namespace {
 
+string GetTimeAsString(time_t utime) {
+  struct tm tm;
+  CHECK_EQ(localtime_r(&utime, &tm), &tm);
+  char str[16];
+  CHECK_EQ(strftime(str, sizeof(str), "%Y%m%d-%H%M%S", &tm), 15u);
+  return str;
+}
+
+#ifdef __ANDROID__
+constexpr char kSystemLogsRoot[] = "/data/misc/update_engine_log";
+constexpr size_t kLogCount = 5;
+
+// Keep the most recent |kLogCount| logs but remove the old ones in
+// "/data/misc/update_engine_log/".
+void DeleteOldLogs(const string& kLogsRoot) {
+  base::DirReaderPosix reader(kLogsRoot.c_str());
+  if (!reader.IsValid()) {
+    LOG(ERROR) << "Failed to read " << kLogsRoot;
+    return;
+  }
+
+  std::vector<string> old_logs;
+  while (reader.Next()) {
+    if (reader.name()[0] == '.')
+      continue;
+
+    // Log files are in format "update_engine.%Y%m%d-%H%M%S",
+    // e.g. update_engine.20090103-231425
+    uint64_t date;
+    uint64_t local_time;
+    if (sscanf(reader.name(),
+               "update_engine.%" PRIu64 "-%" PRIu64 "",
+               &date,
+               &local_time) == 2) {
+      old_logs.push_back(reader.name());
+    } else {
+      LOG(WARNING) << "Unrecognized log file " << reader.name();
+    }
+  }
+
+  std::sort(old_logs.begin(), old_logs.end(), std::greater<string>());
+  for (size_t i = kLogCount; i < old_logs.size(); i++) {
+    string log_path = kLogsRoot + "/" + old_logs[i];
+    if (unlink(log_path.c_str()) == -1) {
+      PLOG(WARNING) << "Failed to unlink " << log_path;
+    }
+  }
+}
+
+string SetupLogFile(const string& kLogsRoot) {
+  DeleteOldLogs(kLogsRoot);
+
+  return base::StringPrintf("%s/update_engine.%s",
+                            kLogsRoot.c_str(),
+                            GetTimeAsString(::time(nullptr)).c_str());
+}
+#else
+constexpr char kSystemLogsRoot[] = "/var/log";
+
 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
@@ -56,14 +119,6 @@
   }
 }
 
-string GetTimeAsString(time_t utime) {
-  struct tm tm;
-  CHECK_EQ(localtime_r(&utime, &tm), &tm);
-  char str[16];
-  CHECK_EQ(strftime(str, sizeof(str), "%Y%m%d-%H%M%S", &tm), 15u);
-  return str;
-}
-
 string SetupLogFile(const string& kLogsRoot) {
   const string kLogSymlink = kLogsRoot + "/update_engine.log";
   const string kLogsDir = kLogsRoot + "/update_engine";
@@ -75,30 +130,36 @@
   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 {
-    log_file = SetupLogFile("/var/log");
+  string log_file;
+  if (log_to_file) {
+    log_file = SetupLogFile(kSystemLogsRoot);
+    log_settings.delete_old = logging::APPEND_TO_OLD_LOG_FILE;
     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 +167,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..7e6b20f 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;
 
@@ -109,7 +113,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 +213,7 @@
     case ErrorCode::kOmahaRequestXMLHasEntityDecl:
     case ErrorCode::kFilesystemVerifierError:
     case ErrorCode::kUserCanceled:
-    case ErrorCode::kOmahaUpdateIgnoredOverCellular:
+    case ErrorCode::kUpdatedButNotActive:
       break;
 
     // Special flags. These can't happen (we mask them out above) but
@@ -307,5 +310,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_omaha_request_params.h b/mock_omaha_request_params.h
index 5d5d47b..6d8d3d8 100644
--- a/mock_omaha_request_params.h
+++ b/mock_omaha_request_params.h
@@ -32,9 +32,6 @@
     // Delegate all calls to the parent instance by default. This helps the
     // migration from tests using the real RequestParams when they should have
     // use a fake or mock.
-    ON_CALL(*this, to_more_stable_channel())
-        .WillByDefault(testing::Invoke(
-            this, &MockOmahaRequestParams::fake_to_more_stable_channel));
     ON_CALL(*this, GetAppId())
         .WillByDefault(testing::Invoke(
             this, &MockOmahaRequestParams::FakeGetAppId));
@@ -44,27 +41,22 @@
     ON_CALL(*this, UpdateDownloadChannel())
         .WillByDefault(testing::Invoke(
             this, &MockOmahaRequestParams::FakeUpdateDownloadChannel));
-    ON_CALL(*this, is_powerwash_allowed())
+    ON_CALL(*this, ShouldPowerwash())
         .WillByDefault(testing::Invoke(
-            this, &MockOmahaRequestParams::fake_is_powerwash_allowed));
+            this, &MockOmahaRequestParams::FakeShouldPowerwash));
   }
 
-  MOCK_CONST_METHOD0(to_more_stable_channel, bool(void));
   MOCK_CONST_METHOD0(GetAppId, std::string(void));
   MOCK_METHOD3(SetTargetChannel, bool(const std::string& channel,
                                       bool is_powerwash_allowed,
                                       std::string* error));
   MOCK_METHOD0(UpdateDownloadChannel, void(void));
-  MOCK_CONST_METHOD0(is_powerwash_allowed, bool(void));
   MOCK_CONST_METHOD0(IsUpdateUrlOfficial, bool(void));
+  MOCK_CONST_METHOD0(ShouldPowerwash, bool(void));
 
  private:
   // Wrappers to call the parent class and behave like the real object by
   // default. See "Delegating Calls to a Parent Class" in gmock's documentation.
-  bool fake_to_more_stable_channel() const {
-    return OmahaRequestParams::to_more_stable_channel();
-  }
-
   std::string FakeGetAppId() const {
     return OmahaRequestParams::GetAppId();
   }
@@ -81,8 +73,8 @@
     return OmahaRequestParams::UpdateDownloadChannel();
   }
 
-  bool fake_is_powerwash_allowed() const {
-    return OmahaRequestParams::is_powerwash_allowed();
+  bool FakeShouldPowerwash() const {
+    return OmahaRequestParams::ShouldPowerwash();
   }
 };
 
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..1becb8e 100644
--- a/omaha_request_action.cc
+++ b/omaha_request_action.cc
@@ -21,15 +21,18 @@
 #include <map>
 #include <sstream>
 #include <string>
+#include <utility>
 #include <vector>
 
 #include <base/bind.h>
 #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 +44,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,17 +73,16 @@
 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";
 
 static const char* kOmahaUpdaterVersion = "0.1.0.0";
 
-// X-GoogleUpdate headers.
-static const char* kXGoogleUpdateInteractivity = "X-GoogleUpdate-Interactivity";
-static const char* kXGoogleUpdateAppId = "X-GoogleUpdate-AppId";
-static const char* kXGoogleUpdateUpdater = "X-GoogleUpdate-Updater";
+// X-Goog-Update headers.
+static const char* kXGoogleUpdateInteractivity = "X-Goog-Update-Interactivity";
+static const char* kXGoogleUpdateAppId = "X-Goog-Update-AppId";
+static const char* kXGoogleUpdateUpdater = "X-Goog-Update-Updater";
 
 // updatecheck attributes (without the underscore prefix).
 static const char* kEolAttr = "eol";
@@ -204,10 +206,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,
@@ -219,17 +236,17 @@
                                system_state->prefs());
   string app_versions;
 
-  // If we are upgrading to a more stable channel and we are allowed to do
+  // If we are downgrading to a more stable channel and we are allowed to do
   // powerwash, then pass 0.0.0.0 as the version. This is needed to get the
   // highest-versioned payload on the destination channel.
-  if (params->to_more_stable_channel() && params->is_powerwash_allowed()) {
+  if (params->ShouldPowerwash()) {
     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();
@@ -260,16 +277,52 @@
 
   string fingerprint_arg;
   if (!params->os_build_fingerprint().empty()) {
-    fingerprint_arg =
-        "fingerprint=\"" + XmlEncodeWithDefault(params->os_build_fingerprint(), "") + "\" ";
+    fingerprint_arg = "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 (!params->ShouldPowerwash() && !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 +333,7 @@
       ">\n" +
          app_body +
       "    </app>\n";
-
+  // clang-format on
   return app_xml;
 }
 
@@ -306,9 +359,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 +422,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 +472,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 +506,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);
     }
   }
 }
@@ -666,7 +762,7 @@
                                     GetInstallDate(system_state_),
                                     system_state_));
 
-  // Set X-GoogleUpdate headers.
+  // Set X-Goog-Update headers.
   http_fetcher_->SetHeader(kXGoogleUpdateInteractivity,
                            params_->interactive() ? "fg" : "bg");
   http_fetcher_->SetHeader(kXGoogleUpdateAppId, params_->GetAppId());
@@ -731,15 +827,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 +971,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 +989,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 +1062,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 +1083,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 +1175,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 +1307,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 +1578,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 +1637,6 @@
     break;
 
   case ErrorCode::kOmahaUpdateIgnoredPerPolicy:
-  case ErrorCode::kOmahaUpdateIgnoredOverCellular:
     result = metrics::CheckResult::kUpdateAvailable;
     reaction = metrics::CheckReaction::kIgnored;
     break;
@@ -1508,12 +1666,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 +1679,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 +1698,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 +1708,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 5698556..6bc5ba2 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);
@@ -485,9 +748,9 @@
 
   // Check that the headers were set in the fetcher during the action. Note that
   // we set this request as "interactive".
-  EXPECT_EQ("fg", fetcher->GetHeader("X-GoogleUpdate-Interactivity"));
-  EXPECT_EQ(kTestAppId, fetcher->GetHeader("X-GoogleUpdate-AppId"));
-  EXPECT_NE("", fetcher->GetHeader("X-GoogleUpdate-Updater"));
+  EXPECT_EQ("fg", fetcher->GetHeader("X-Goog-Update-Interactivity"));
+  EXPECT_EQ(kTestAppId, fetcher->GetHeader("X-Goog-Update-AppId"));
+  EXPECT_NE("", fetcher->GetHeader("X-Goog-Update-Updater"));
 }
 
 TEST_F(OmahaRequestActionTest, ValidUpdateBlockedByConnection) {
@@ -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(senj): set better default value for metrics::checkresult in
+  // OmahaRequestAction::ActionCompleted.
   fake_system_state_.fake_hardware()->UnsetIsOOBEComplete();
-  ASSERT_FALSE(
-      TestUpdateCheck(nullptr,  // request_params
-                      fake_update_response_.GetUpdateResponse(),
-                      -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);
@@ -2064,11 +2184,11 @@
   params.set_root(tempdir.GetPath().value());
   params.set_app_id("{22222222-2222-2222-2222-222222222222}");
   params.set_app_version("1.2.3.4");
+  params.set_product_components("o.bundle=1");
   params.set_current_channel("canary-channel");
   EXPECT_TRUE(params.SetTargetChannel("stable-channel", true, nullptr));
   params.UpdateDownloadChannel();
-  EXPECT_TRUE(params.to_more_stable_channel());
-  EXPECT_TRUE(params.is_powerwash_allowed());
+  EXPECT_TRUE(params.ShouldPowerwash());
   ASSERT_FALSE(TestUpdateCheck(&params,
                                "invalid xml>",
                                -1,
@@ -2085,6 +2205,7 @@
       "appid=\"{22222222-2222-2222-2222-222222222222}\" "
       "version=\"0.0.0.0\" from_version=\"1.2.3.4\" "
       "track=\"stable-channel\" from_track=\"canary-channel\" "));
+  EXPECT_EQ(string::npos, post_str.find("o.bundle"));
 }
 
 TEST_F(OmahaRequestActionTest, TestChangingToLessStableChannel) {
@@ -2097,11 +2218,11 @@
   params.set_root(tempdir.GetPath().value());
   params.set_app_id("{11111111-1111-1111-1111-111111111111}");
   params.set_app_version("5.6.7.8");
+  params.set_product_components("o.bundle=1");
   params.set_current_channel("stable-channel");
   EXPECT_TRUE(params.SetTargetChannel("canary-channel", false, nullptr));
   params.UpdateDownloadChannel();
-  EXPECT_FALSE(params.to_more_stable_channel());
-  EXPECT_FALSE(params.is_powerwash_allowed());
+  EXPECT_FALSE(params.ShouldPowerwash());
   ASSERT_FALSE(TestUpdateCheck(&params,
                                "invalid xml>",
                                -1,
@@ -2119,6 +2240,7 @@
       "version=\"5.6.7.8\" "
       "track=\"canary-channel\" from_track=\"stable-channel\""));
   EXPECT_EQ(string::npos, post_str.find("from_version"));
+  EXPECT_NE(string::npos, post_str.find("o.bundle.version=\"1\""));
 }
 
 // Checks that the initial ping with a=-1 r=-1 is not send when the device
diff --git a/omaha_request_params.cc b/omaha_request_params.cc
index 3402451..97a2ed1 100644
--- a/omaha_request_params.cc
+++ b/omaha_request_params.cc
@@ -77,7 +77,14 @@
   LOG(INFO) << "Running from channel " << image_props_.current_channel;
 
   os_platform_ = constants::kOmahaPlatformName;
-  os_version_ = OmahaRequestParams::kOsVersion;
+  if (!image_props_.system_version.empty()) {
+    if (in_app_version == "ForcedUpdate") {
+      image_props_.system_version = in_app_version;
+    }
+    os_version_ = image_props_.system_version;
+  } else {
+    os_version_ = OmahaRequestParams::kOsVersion;
+  }
   if (!in_app_version.empty())
     image_props_.version = in_app_version;
 
@@ -147,16 +154,7 @@
             << ", existing target channel = "
             << mutable_image_props_.target_channel
             << ", download channel = " << download_channel_;
-  if (!IsValidChannel(new_target_channel)) {
-    string valid_channels = brillo::string_utils::JoinRange(
-        ", ",
-        std::begin(kChannelsByStability),
-        std::end(kChannelsByStability));
-    if (error_message) {
-      *error_message = base::StringPrintf(
-          "Invalid channel name \"%s\", valid names are: %s",
-          new_target_channel.c_str(), valid_channels.c_str());
-    }
+  if (!IsValidChannel(new_target_channel, error_message)) {
     return false;
   }
 
@@ -188,8 +186,31 @@
   return ret;
 }
 
-bool OmahaRequestParams::IsValidChannel(const string& channel) const {
-  return GetChannelIndex(channel) >= 0;
+bool OmahaRequestParams::IsValidChannel(const string& channel,
+                                        string* error_message) const {
+  if (image_props_.allow_arbitrary_channels) {
+    if (!base::EndsWith(channel, "-channel", base::CompareCase::SENSITIVE)) {
+      if (error_message) {
+        *error_message = base::StringPrintf(
+            "Invalid channel name \"%s\", must ends with -channel.",
+            channel.c_str());
+      }
+      return false;
+    }
+    return true;
+  }
+  if (GetChannelIndex(channel) < 0) {
+    string valid_channels = brillo::string_utils::JoinRange(
+        ", ", std::begin(kChannelsByStability), std::end(kChannelsByStability));
+    if (error_message) {
+      *error_message =
+          base::StringPrintf("Invalid channel name \"%s\", valid names are: %s",
+                             channel.c_str(),
+                             valid_channels.c_str());
+    }
+    return false;
+  }
+  return true;
 }
 
 void OmahaRequestParams::set_root(const string& root) {
@@ -205,13 +226,24 @@
   return -1;
 }
 
-bool OmahaRequestParams::to_more_stable_channel() const {
+bool OmahaRequestParams::ToMoreStableChannel() const {
   int current_channel_index = GetChannelIndex(image_props_.current_channel);
   int download_channel_index = GetChannelIndex(download_channel_);
 
   return download_channel_index > current_channel_index;
 }
 
+bool OmahaRequestParams::ShouldPowerwash() const {
+  if (!mutable_image_props_.is_powerwash_allowed)
+    return false;
+  // If arbitrary channels are allowed, always powerwash on channel change.
+  if (image_props_.allow_arbitrary_channels)
+    return image_props_.current_channel != download_channel_;
+  // Otherwise only powerwash if we are moving from less stable (higher version)
+  // to more stable channel (lower version).
+  return ToMoreStableChannel();
+}
+
 string OmahaRequestParams::GetAppId() const {
   return download_channel_ == "canary-channel" ? image_props_.canary_product_id
                                                : image_props_.product_id;
diff --git a/omaha_request_params.h b/omaha_request_params.h
index 3a28ed1..60619f9 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,15 @@
     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 void set_product_components(const std::string& product_components) {
+    image_props_.product_components = product_components;
+  }
 
   inline std::string current_channel() const {
     return image_props_.current_channel;
@@ -184,10 +198,6 @@
     return max_update_checks_allowed_;
   }
 
-  // True if we're trying to update to a more stable channel.
-  // i.e. index(target_channel) > index(current_channel).
-  virtual bool to_more_stable_channel() const;
-
   // Returns the app id corresponding to the current value of the
   // download channel.
   virtual std::string GetAppId() const;
@@ -224,9 +234,8 @@
   // or Init is called again.
   virtual void UpdateDownloadChannel();
 
-  virtual bool is_powerwash_allowed() const {
-    return mutable_image_props_.is_powerwash_allowed;
-  }
+  // Returns whether we should powerwash for this update.
+  virtual bool ShouldPowerwash() const;
 
   // Check if the provided update URL is official, meaning either the default
   // autoupdate server or the autoupdate autotest server.
@@ -242,17 +251,30 @@
   }
 
  private:
-  FRIEND_TEST(OmahaRequestParamsTest, IsValidChannelTest);
   FRIEND_TEST(OmahaRequestParamsTest, ChannelIndexTest);
-  FRIEND_TEST(OmahaRequestParamsTest, ToMoreStableChannelFlagTest);
   FRIEND_TEST(OmahaRequestParamsTest, CollectECFWVersionsTest);
+  FRIEND_TEST(OmahaRequestParamsTest, IsValidChannelTest);
+  FRIEND_TEST(OmahaRequestParamsTest, SetIsPowerwashAllowedTest);
+  FRIEND_TEST(OmahaRequestParamsTest, SetTargetChannelInvalidTest);
+  FRIEND_TEST(OmahaRequestParamsTest, SetTargetChannelTest);
+  FRIEND_TEST(OmahaRequestParamsTest, ShouldPowerwashTest);
+  FRIEND_TEST(OmahaRequestParamsTest, ToMoreStableChannelFlagTest);
 
-  // Returns true if |channel| is a valid channel, false otherwise.
-  bool IsValidChannel(const std::string& channel) const;
+  // Returns true if |channel| is a valid channel, otherwise write error to
+  // |error_message| if passed and return false.
+  bool IsValidChannel(const std::string& channel,
+                      std::string* error_message) const;
+  bool IsValidChannel(const std::string& channel) const {
+    return IsValidChannel(channel, nullptr);
+  }
 
   // Returns the index of the given channel.
   int GetChannelIndex(const std::string& channel) const;
 
+  // True if we're trying to update to a more stable channel.
+  // i.e. index(target_channel) > index(current_channel).
+  bool ToMoreStableChannel() const;
+
   // Returns True if we should store the fw/ec versions based on our hwid_.
   // Compares hwid to a set of whitelisted prefixes.
   bool CollectECFWVersions() const;
diff --git a/omaha_request_params_unittest.cc b/omaha_request_params_unittest.cc
index 57ecf24..ce77f31 100644
--- a/omaha_request_params_unittest.cc
+++ b/omaha_request_params_unittest.cc
@@ -117,12 +117,12 @@
     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());
+    EXPECT_FALSE(params.mutable_image_props_.is_powerwash_allowed);
   }
   params_.set_root(tempdir_.GetPath().value());
   EXPECT_TRUE(params_.Init("", "", false));
   EXPECT_EQ("canary-channel", params_.target_channel());
-  EXPECT_FALSE(params_.is_powerwash_allowed());
+  EXPECT_FALSE(params_.mutable_image_props_.is_powerwash_allowed);
 }
 
 TEST_F(OmahaRequestParamsTest, SetIsPowerwashAllowedTest) {
@@ -131,12 +131,12 @@
     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());
+    EXPECT_TRUE(params.mutable_image_props_.is_powerwash_allowed);
   }
   params_.set_root(tempdir_.GetPath().value());
   EXPECT_TRUE(params_.Init("", "", false));
   EXPECT_EQ("canary-channel", params_.target_channel());
-  EXPECT_TRUE(params_.is_powerwash_allowed());
+  EXPECT_TRUE(params_.mutable_image_props_.is_powerwash_allowed);
 }
 
 TEST_F(OmahaRequestParamsTest, SetTargetChannelInvalidTest) {
@@ -145,17 +145,18 @@
     params.set_root(tempdir_.GetPath().value());
     SetLockDown(true);
     EXPECT_TRUE(params.Init("", "", false));
+    params.image_props_.allow_arbitrary_channels = false;
     string error_message;
     EXPECT_FALSE(
         params.SetTargetChannel("dogfood-channel", true, &error_message));
     // The error message should include a message about the valid channels.
     EXPECT_NE(string::npos, error_message.find("stable-channel"));
-    EXPECT_FALSE(params.is_powerwash_allowed());
+    EXPECT_FALSE(params.mutable_image_props_.is_powerwash_allowed);
   }
   params_.set_root(tempdir_.GetPath().value());
   EXPECT_TRUE(params_.Init("", "", false));
   EXPECT_EQ("stable-channel", params_.target_channel());
-  EXPECT_FALSE(params_.is_powerwash_allowed());
+  EXPECT_FALSE(params_.mutable_image_props_.is_powerwash_allowed);
 }
 
 TEST_F(OmahaRequestParamsTest, IsValidChannelTest) {
@@ -167,6 +168,10 @@
   EXPECT_FALSE(params_.IsValidChannel("dogfood-channel"));
   EXPECT_FALSE(params_.IsValidChannel("some-channel"));
   EXPECT_FALSE(params_.IsValidChannel(""));
+  params_.image_props_.allow_arbitrary_channels = true;
+  EXPECT_TRUE(params_.IsValidChannel("some-channel"));
+  EXPECT_FALSE(params_.IsValidChannel("wrong-suffix"));
+  EXPECT_FALSE(params_.IsValidChannel(""));
 }
 
 TEST_F(OmahaRequestParamsTest, SetTargetChannelWorks) {
@@ -227,7 +232,25 @@
 TEST_F(OmahaRequestParamsTest, ToMoreStableChannelFlagTest) {
   params_.image_props_.current_channel = "canary-channel";
   params_.download_channel_ = "stable-channel";
-  EXPECT_TRUE(params_.to_more_stable_channel());
+  EXPECT_TRUE(params_.ToMoreStableChannel());
+  params_.image_props_.current_channel = "stable-channel";
+  EXPECT_FALSE(params_.ToMoreStableChannel());
+  params_.download_channel_ = "beta-channel";
+  EXPECT_FALSE(params_.ToMoreStableChannel());
+}
+
+TEST_F(OmahaRequestParamsTest, ShouldPowerwashTest) {
+  params_.mutable_image_props_.is_powerwash_allowed = false;
+  EXPECT_FALSE(params_.ShouldPowerwash());
+  params_.mutable_image_props_.is_powerwash_allowed = true;
+  params_.image_props_.allow_arbitrary_channels = true;
+  params_.image_props_.current_channel = "foo-channel";
+  params_.download_channel_ = "bar-channel";
+  EXPECT_TRUE(params_.ShouldPowerwash());
+  params_.image_props_.allow_arbitrary_channels = false;
+  params_.image_props_.current_channel = "canary-channel";
+  params_.download_channel_ = "stable-channel";
+  EXPECT_TRUE(params_.ShouldPowerwash());
 }
 
 TEST_F(OmahaRequestParamsTest, CollectECFWVersionsTest) {
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..2d6105a 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;
@@ -120,7 +139,7 @@
   system_state_->prefs()->SetString(current_channel_key,
                                     params->download_channel());
 
-  if (params->to_more_stable_channel() && params->is_powerwash_allowed())
+  if (params->ShouldPowerwash())
     install_plan_.powerwash_required = true;
 
   TEST_AND_RETURN(HasOutputPipe());
@@ -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 13e2e93..9e2cdd1 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;
@@ -338,15 +388,12 @@
   params.set_current_channel("canary-channel");
   // The ImageProperties in Android uses prefs to store MutableImageProperties.
 #ifdef __ANDROID__
-  EXPECT_CALL(*fake_system_state_.mock_prefs(), SetString(_, "stable-channel"))
-      .WillOnce(Return(true));
   EXPECT_CALL(*fake_system_state_.mock_prefs(), SetBoolean(_, true))
       .WillOnce(Return(true));
 #endif  // __ANDROID__
   EXPECT_TRUE(params.SetTargetChannel("stable-channel", true, nullptr));
   params.UpdateDownloadChannel();
-  EXPECT_TRUE(params.to_more_stable_channel());
-  EXPECT_TRUE(params.is_powerwash_allowed());
+  EXPECT_TRUE(params.ShouldPowerwash());
 
   fake_system_state_.set_request_params(&params);
   InstallPlan install_plan;
@@ -358,10 +405,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;
@@ -373,15 +420,12 @@
   params.set_current_channel("stable-channel");
   // The ImageProperties in Android uses prefs to store MutableImageProperties.
 #ifdef __ANDROID__
-  EXPECT_CALL(*fake_system_state_.mock_prefs(), SetString(_, "canary-channel"))
-      .WillOnce(Return(true));
   EXPECT_CALL(*fake_system_state_.mock_prefs(), SetBoolean(_, false))
       .WillOnce(Return(true));
 #endif  // __ANDROID__
   EXPECT_TRUE(params.SetTargetChannel("canary-channel", false, nullptr));
   params.UpdateDownloadChannel();
-  EXPECT_FALSE(params.to_more_stable_channel());
-  EXPECT_FALSE(params.is_powerwash_allowed());
+  EXPECT_FALSE(params.ShouldPowerwash());
 
   fake_system_state_.set_request_params(&params);
   InstallPlan install_plan;
@@ -393,10 +437,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 +457,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 2a63153..0a12801 100644
--- a/payload_consumer/delta_performer.cc
+++ b/payload_consumer/delta_performer.cc
@@ -50,6 +50,7 @@
 #include "update_engine/payload_consumer/extent_reader.h"
 #include "update_engine/payload_consumer/extent_writer.h"
 #include "update_engine/payload_consumer/file_descriptor_utils.h"
+#include "update_engine/payload_consumer/mount_history.h"
 #if USE_MTD
 #include "update_engine/payload_consumer/mtd_file_descriptor.h"
 #endif
@@ -203,7 +204,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 +239,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 +288,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 +353,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 +372,7 @@
     }
   }
 
-  target_path_ = install_plan_->partitions[current_partition_].target_path;
+  target_path_ = install_part.target_path;
   int err;
 
   int flags = O_RDWR;
@@ -390,8 +396,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;
 }
@@ -406,8 +411,10 @@
 
 void LogPartitionInfo(const vector<PartitionUpdate>& partitions) {
   for (const PartitionUpdate& partition : partitions) {
-    LogPartitionInfoHash(partition.old_partition_info(),
-                         "old " + partition.partition_name());
+    if (partition.has_old_partition_info()) {
+      LogPartitionInfoHash(partition.old_partition_info(),
+                           "old " + partition.partition_name());
+    }
     LogPartitionInfoHash(partition.new_partition_info(),
                          "new " + partition.partition_name());
   }
@@ -452,7 +459,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 +549,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 +568,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 +662,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 +732,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 +889,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();
@@ -1000,9 +1012,9 @@
       if (target_fd_->BlkIoctl(request, start, length, &result) && result == 0)
         continue;
       attempt_ioctl = false;
-      zeros.resize(16 * block_size_);
     }
     // In case of failure, we fall back to writing 0 to the selected region.
+    zeros.resize(16 * block_size_);
     for (uint64_t offset = 0; offset < length; offset += zeros.size()) {
       uint64_t chunk_length = min(length - offset,
                                   static_cast<uint64_t>(zeros.size()));
@@ -1068,8 +1080,10 @@
 
 // Compare |calculated_hash| with source hash in |operation|, return false and
 // dump hash and set |error| if don't match.
+// |source_fd| is the file descriptor of the source partition.
 bool ValidateSourceHash(const brillo::Blob& calculated_hash,
                         const InstallOperation& operation,
+                        const FileDescriptorPtr source_fd,
                         ErrorCode* error) {
   brillo::Blob expected_source_hash(operation.src_sha256_hash().begin(),
                                     operation.src_sha256_hash().end());
@@ -1096,6 +1110,9 @@
     LOG(ERROR) << "Operation source (offset:size) in blocks: "
                << base::JoinString(source_extents, ",");
 
+    // Log remount history if this device is an ext4 partition.
+    LogMountHistory(source_fd);
+
     *error = ErrorCode::kDownloadStateInitializationError;
     return false;
   }
@@ -1120,7 +1137,8 @@
                                                      &source_hash));
 
   if (operation.has_src_sha256_hash()) {
-    TEST_AND_RETURN_FALSE(ValidateSourceHash(source_hash, operation, error));
+    TEST_AND_RETURN_FALSE(
+        ValidateSourceHash(source_hash, operation, source_fd_, error));
   }
 
   return true;
@@ -1189,28 +1207,6 @@
   return true;
 }
 
-bool DeltaPerformer::CalculateAndValidateSourceHash(
-    const InstallOperation& operation, ErrorCode* error) {
-  const uint64_t kMaxBlocksToRead = 256;  // 1MB if block size is 4KB
-  auto total_blocks = utils::BlocksInExtents(operation.src_extents());
-  brillo::Blob buf(std::min(kMaxBlocksToRead, total_blocks) * block_size_);
-  DirectExtentReader reader;
-  TEST_AND_RETURN_FALSE(
-      reader.Init(source_fd_, operation.src_extents(), block_size_));
-  HashCalculator source_hasher;
-  while (total_blocks > 0) {
-    auto read_blocks = std::min(total_blocks, kMaxBlocksToRead);
-    TEST_AND_RETURN_FALSE(reader.Read(buf.data(), read_blocks * block_size_));
-    TEST_AND_RETURN_FALSE(
-        source_hasher.Update(buf.data(), read_blocks * block_size_));
-    total_blocks -= read_blocks;
-  }
-  TEST_AND_RETURN_FALSE(source_hasher.Finalize());
-  TEST_AND_RETURN_FALSE(
-      ValidateSourceHash(source_hasher.raw_hash(), operation, error));
-  return true;
-}
-
 namespace {
 
 class BsdiffExtentFile : public bsdiff::FileInterface {
@@ -1291,7 +1287,11 @@
     TEST_AND_RETURN_FALSE(operation.dst_length() % block_size_ == 0);
 
   if (operation.has_src_sha256_hash()) {
-    TEST_AND_RETURN_FALSE(CalculateAndValidateSourceHash(operation, error));
+    brillo::Blob source_hash;
+    TEST_AND_RETURN_FALSE(fd_utils::ReadAndHashExtents(
+        source_fd_, operation.src_extents(), block_size_, &source_hash));
+    TEST_AND_RETURN_FALSE(
+        ValidateSourceHash(source_hash, operation, source_fd_, error));
   }
 
   auto reader = std::make_unique<DirectExtentReader>();
@@ -1404,7 +1404,11 @@
   TEST_AND_RETURN_FALSE(buffer_.size() >= operation.data_length());
 
   if (operation.has_src_sha256_hash()) {
-    TEST_AND_RETURN_FALSE(CalculateAndValidateSourceHash(operation, error));
+    brillo::Blob source_hash;
+    TEST_AND_RETURN_FALSE(fd_utils::ReadAndHashExtents(
+        source_fd_, operation.src_extents(), block_size_, &source_hash));
+    TEST_AND_RETURN_FALSE(
+        ValidateSourceHash(source_hash, operation, source_fd_, error));
   }
 
   auto reader = std::make_unique<DirectExtentReader>();
@@ -1491,18 +1495,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 +1533,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 +1591,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.";
@@ -1683,15 +1686,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 +1716,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 +1738,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 +1843,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 +1851,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..d5ca799 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
@@ -243,10 +245,6 @@
   // buffer.
   ErrorCode ValidateMetadataSignature(const brillo::Blob& payload);
 
-  // Calculates and validates the source hash of the operation |operation|.
-  bool CalculateAndValidateSourceHash(const InstallOperation& operation,
-                                      ErrorCode* error);
-
   // Returns true on success.
   bool PerformInstallOperation(const InstallOperation& operation);
 
@@ -312,6 +310,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 aa53347..bad7bf0 100644
--- a/payload_consumer/delta_performer_unittest.cc
+++ b/payload_consumer/delta_performer_unittest.cc
@@ -159,7 +159,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);
@@ -220,7 +220,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));
@@ -287,7 +287,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);
@@ -324,13 +324,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;
@@ -342,10 +342,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;
@@ -373,7 +373,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) {
@@ -381,6 +381,7 @@
   }
   FakePrefs prefs_;
   InstallPlan install_plan_;
+  InstallPlan::Payload payload_;
   FakeBootControl fake_boot_control_;
   FakeHardware fake_hardware_;
   MockDownloadActionDelegate mock_delegate_;
@@ -389,11 +390,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
@@ -412,7 +414,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
@@ -753,7 +755,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));
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..7ec7e0e 100644
--- a/payload_consumer/download_action_unittest.cc
+++ b/payload_consumer/download_action_unittest.cc
@@ -22,13 +22,13 @@
 #include <memory>
 #include <string>
 #include <utility>
+#include <vector>
 
 #include <base/bind.h>
 #include <base/files/file_path.h>
 #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 +40,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 +55,7 @@
 using testing::AtLeast;
 using testing::InSequence;
 using testing::Return;
+using testing::SetArgPointee;
 using testing::_;
 
 class DownloadActionTest : public ::testing::Test { };
@@ -137,13 +139,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 +175,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 +243,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 +360,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 +460,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 +547,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;
@@ -512,7 +604,7 @@
   // Callback used in StartDownload() method.
   void StartProcessorInRunLoopForP2P() {
     processor_.StartProcessing();
-    http_fetcher_->SetOffset(start_at_offset_);
+    download_action_->http_fetcher()->SetOffset(start_at_offset_);
   }
 
   // The requested starting offset passed to SetupDownload().
@@ -571,7 +663,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 +701,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/file_descriptor_utils.cc b/payload_consumer/file_descriptor_utils.cc
index 73f86df..b1902de 100644
--- a/payload_consumer/file_descriptor_utils.cc
+++ b/payload_consumer/file_descriptor_utils.cc
@@ -33,7 +33,44 @@
 namespace {
 
 // Size of the buffer used to copy blocks.
-const int kMaxCopyBufferSize = 1024 * 1024;
+const uint64_t kMaxCopyBufferSize = 1024 * 1024;
+
+bool CommonHashExtents(FileDescriptorPtr source,
+                       const RepeatedPtrField<Extent>& src_extents,
+                       DirectExtentWriter* writer,
+                       uint64_t block_size,
+                       brillo::Blob* hash_out) {
+  auto total_blocks = utils::BlocksInExtents(src_extents);
+  auto buffer_blocks = kMaxCopyBufferSize / block_size;
+  // Ensure we copy at least one block at a time.
+  if (buffer_blocks < 1)
+    buffer_blocks = 1;
+  brillo::Blob buf(buffer_blocks * block_size);
+
+  DirectExtentReader reader;
+  TEST_AND_RETURN_FALSE(reader.Init(source, src_extents, block_size));
+
+  HashCalculator source_hasher;
+  while (total_blocks > 0) {
+    auto read_blocks = std::min(total_blocks, buffer_blocks);
+    TEST_AND_RETURN_FALSE(reader.Read(buf.data(), read_blocks * block_size));
+    if (hash_out != nullptr) {
+      TEST_AND_RETURN_FALSE(
+          source_hasher.Update(buf.data(), read_blocks * block_size));
+    }
+    if (writer) {
+      TEST_AND_RETURN_FALSE(
+          writer->Write(buf.data(), read_blocks * block_size));
+    }
+    total_blocks -= read_blocks;
+  }
+
+  if (hash_out != nullptr) {
+    TEST_AND_RETURN_FALSE(source_hasher.Finalize());
+    *hash_out = source_hasher.raw_hash();
+  }
+  return true;
+}
 
 }  // namespace
 
@@ -43,40 +80,25 @@
                         const RepeatedPtrField<Extent>& src_extents,
                         FileDescriptorPtr target,
                         const RepeatedPtrField<Extent>& tgt_extents,
-                        uint32_t block_size,
+                        uint64_t block_size,
                         brillo::Blob* hash_out) {
-  uint64_t total_blocks = utils::BlocksInExtents(src_extents);
-  TEST_AND_RETURN_FALSE(total_blocks == utils::BlocksInExtents(tgt_extents));
-
-  DirectExtentReader reader;
-  TEST_AND_RETURN_FALSE(reader.Init(source, src_extents, block_size));
   DirectExtentWriter writer;
   TEST_AND_RETURN_FALSE(writer.Init(target, tgt_extents, block_size));
-
-  uint64_t buffer_blocks = kMaxCopyBufferSize / block_size;
-  // Ensure we copy at least one block at a time.
-  if (buffer_blocks < 1)
-    buffer_blocks = 1;
-  brillo::Blob buf(buffer_blocks * block_size);
-
-  HashCalculator source_hasher;
-  uint64_t blocks_left = total_blocks;
-  while (blocks_left > 0) {
-    uint64_t read_blocks = std::min(blocks_left, buffer_blocks);
-    TEST_AND_RETURN_FALSE(reader.Read(buf.data(), read_blocks * block_size));
-    if (hash_out) {
-      TEST_AND_RETURN_FALSE(
-          source_hasher.Update(buf.data(), read_blocks * block_size));
-    }
-    TEST_AND_RETURN_FALSE(writer.Write(buf.data(), read_blocks * block_size));
-    blocks_left -= read_blocks;
-  }
+  TEST_AND_RETURN_FALSE(utils::BlocksInExtents(src_extents) ==
+                        utils::BlocksInExtents(tgt_extents));
+  TEST_AND_RETURN_FALSE(
+      CommonHashExtents(source, src_extents, &writer, block_size, hash_out));
   TEST_AND_RETURN_FALSE(writer.End());
+  return true;
+}
 
-  if (hash_out) {
-    TEST_AND_RETURN_FALSE(source_hasher.Finalize());
-    *hash_out = source_hasher.raw_hash();
-  }
+bool ReadAndHashExtents(FileDescriptorPtr source,
+                        const RepeatedPtrField<Extent>& extents,
+                        uint64_t block_size,
+                        brillo::Blob* hash_out) {
+  TEST_AND_RETURN_FALSE(hash_out != nullptr);
+  TEST_AND_RETURN_FALSE(
+      CommonHashExtents(source, extents, nullptr, block_size, hash_out));
   return true;
 }
 
diff --git a/payload_consumer/file_descriptor_utils.h b/payload_consumer/file_descriptor_utils.h
index d1289d6..397c35e 100644
--- a/payload_consumer/file_descriptor_utils.h
+++ b/payload_consumer/file_descriptor_utils.h
@@ -39,7 +39,17 @@
     const google::protobuf::RepeatedPtrField<Extent>& src_extents,
     FileDescriptorPtr target,
     const google::protobuf::RepeatedPtrField<Extent>& tgt_extents,
-    uint32_t block_size,
+    uint64_t block_size,
+    brillo::Blob* hash_out);
+
+// Reads blocks from |source| and caculates the hash. The blocks to read are
+// specified by |extents|. Stores the hash in |hash_out| if it is not null. The
+// block sizes are passed as |block_size|. In case of error reading, it returns
+// false and the value pointed by |hash_out| is undefined.
+bool ReadAndHashExtents(
+    FileDescriptorPtr source,
+    const google::protobuf::RepeatedPtrField<Extent>& extents,
+    uint64_t block_size,
     brillo::Blob* hash_out);
 
 }  // namespace fd_utils
diff --git a/payload_consumer/file_descriptor_utils_unittest.cc b/payload_consumer/file_descriptor_utils_unittest.cc
index 8ba8ce6..79d2184 100644
--- a/payload_consumer/file_descriptor_utils_unittest.cc
+++ b/payload_consumer/file_descriptor_utils_unittest.cc
@@ -167,4 +167,32 @@
   EXPECT_EQ(expected_hash, hash_out);
 }
 
+// Failing to read from the source should fail the hash calculation.
+TEST_F(FileDescriptorUtilsTest, ReadAndHashExtentsReadFailureTest) {
+  auto extents = CreateExtentList({{0, 5}});
+  fake_source_->AddFailureRange(10, 5);
+  brillo::Blob hash_out;
+  EXPECT_FALSE(fd_utils::ReadAndHashExtents(source_, extents, 4, &hash_out));
+}
+
+// Test that if hash_out is null, then it should fail.
+TEST_F(FileDescriptorUtilsTest, ReadAndHashExtentsWithoutHashingTest) {
+  auto extents = CreateExtentList({{0, 5}});
+  EXPECT_FALSE(fd_utils::ReadAndHashExtents(source_, extents, 4, nullptr));
+}
+
+// Tests that it can calculate the hash properly.
+TEST_F(FileDescriptorUtilsTest, ReadAndHashExtentsTest) {
+  // Reorder the input as 1 4 2 3 0.
+  auto extents = CreateExtentList({{1, 1}, {4, 1}, {2, 2}, {0, 1}});
+  brillo::Blob hash_out;
+  EXPECT_TRUE(fd_utils::ReadAndHashExtents(source_, extents, 4, &hash_out));
+
+  const char kExpectedResult[] = "00010004000200030000";
+  brillo::Blob expected_hash;
+  EXPECT_TRUE(HashCalculator::RawHashOfBytes(
+      kExpectedResult, strlen(kExpectedResult), &expected_hash));
+  EXPECT_EQ(expected_hash, hash_out);
+}
+
 }  // namespace chromeos_update_engine
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/mount_history.cc b/payload_consumer/mount_history.cc
new file mode 100644
index 0000000..43a75b3
--- /dev/null
+++ b/payload_consumer/mount_history.cc
@@ -0,0 +1,72 @@
+//
+// Copyright (C) 2018 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#include "update_engine/payload_consumer/mount_history.h"
+
+#include <inttypes.h>
+
+#include <string>
+#include <vector>
+
+#include <base/logging.h>
+#include <base/time/time.h>
+
+#include "update_engine/common/utils.h"
+
+namespace chromeos_update_engine {
+void LogMountHistory(const FileDescriptorPtr blockdevice_fd) {
+  static constexpr ssize_t kBlockSize = 4096;
+
+  if (blockdevice_fd == nullptr) {
+    return;
+  }
+
+  brillo::Blob block0_buffer(kBlockSize);
+  ssize_t bytes_read;
+
+  if (!utils::PReadAll(
+          blockdevice_fd, block0_buffer.data(), kBlockSize, 0, &bytes_read)) {
+    LOG(WARNING) << "PReadAll failed";
+    return;
+  }
+
+  if (bytes_read != kBlockSize) {
+    LOG(WARNING) << "Could not read an entire block";
+    return;
+  }
+
+  // https://ext4.wiki.kernel.org/index.php/Ext4_Disk_Layout
+  // Super block starts from block 0, offset 0x400
+  //   0x2C: len32 Mount time
+  //   0x30: len32 Write time
+  //   0x34: len16 Number of mounts since the last fsck
+  //   0x38: len16 Magic signature 0xEF53
+
+  time_t mount_time =
+      *reinterpret_cast<uint32_t*>(&block0_buffer[0x400 + 0x2C]);
+  uint16_t mount_count =
+      *reinterpret_cast<uint16_t*>(&block0_buffer[0x400 + 0x34]);
+  uint16_t magic = *reinterpret_cast<uint16_t*>(&block0_buffer[0x400 + 0x38]);
+
+  if (magic == 0xEF53) {
+    if (mount_count > 0) {
+      LOG(WARNING) << "Device was remounted R/W " << mount_count << " times. "
+                   << "Last remount happened on "
+                   << base::Time::FromTimeT(mount_time) << ".";
+    }
+  }
+}
+}  // namespace chromeos_update_engine
diff --git a/payload_consumer/mount_history.h b/payload_consumer/mount_history.h
new file mode 100644
index 0000000..ba0c65d
--- /dev/null
+++ b/payload_consumer/mount_history.h
@@ -0,0 +1,28 @@
+//
+// Copyright (C) 2018 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#ifndef UPDATE_ENGINE_PAYLOAD_CONSUMER_MOUNT_HISTORY_H_
+#define UPDATE_ENGINE_PAYLOAD_CONSUMER_MOUNT_HISTORY_H_
+
+#include "update_engine/payload_consumer/file_descriptor.h"
+
+namespace chromeos_update_engine {
+// Try to parse an ext4 from the partition specified by |blockdevice_fd|.
+// If ext4 header exists and remount is detected, log mount count and date.
+void LogMountHistory(const FileDescriptorPtr blockdevice_fd);
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_PAYLOAD_CONSUMER_MOUNT_HISTORY_H_
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/deflate_utils.cc b/payload_generator/deflate_utils.cc
index 88e42e0..e331142 100644
--- a/payload_generator/deflate_utils.cc
+++ b/payload_generator/deflate_utils.cc
@@ -255,7 +255,7 @@
   part.fs_interface->GetFiles(&tmp_files);
   result_files->reserve(tmp_files.size());
 
-  for (const auto& file : tmp_files) {
+  for (auto& file : tmp_files) {
     if (IsSquashfsImage(part.path, file)) {
       // Read the image into a file.
       base::FilePath path;
@@ -285,7 +285,34 @@
                      << " was a Squashfs file, but it was not.";
       }
     }
-    // TODO(ahassani): Process other types of files like apk, zip, etc.
+
+    // Search for deflates if the file is in zip format.
+    bool is_zip =
+        base::EndsWith(
+            file.name, ".apk", base::CompareCase::INSENSITIVE_ASCII) ||
+        base::EndsWith(
+            file.name, ".zip", base::CompareCase::INSENSITIVE_ASCII) ||
+        base::EndsWith(file.name, ".jar", base::CompareCase::INSENSITIVE_ASCII);
+
+    if (is_zip && extract_deflates) {
+      brillo::Blob data;
+      TEST_AND_RETURN_FALSE(
+          utils::ReadExtents(part.path,
+                             file.extents,
+                             &data,
+                             kBlockSize * utils::BlocksInExtents(file.extents),
+                             kBlockSize));
+      std::vector<puffin::BitExtent> deflates_sub_blocks;
+      TEST_AND_RETURN_FALSE(puffin::LocateDeflateSubBlocksInZipArchive(
+          data, &deflates_sub_blocks));
+      // Shift the deflate's extent to the offset starting from the beginning
+      // of the current partition; and the delta processor will align the
+      // extents in a continuous buffer later.
+      TEST_AND_RETURN_FALSE(
+          ShiftBitExtentsOverExtents(file.extents, &deflates_sub_blocks));
+      file.deflates = std::move(deflates_sub_blocks);
+    }
+
     result_files->push_back(file);
   }
   return true;
diff --git a/payload_generator/delta_diff_utils.cc b/payload_generator/delta_diff_utils.cc
index fbb6066..877e13f 100644
--- a/payload_generator/delta_diff_utils.cc
+++ b/payload_generator/delta_diff_utils.cc
@@ -38,7 +38,9 @@
 #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 <bsdiff/patch_writer_factory.h>
 
 #include "update_engine/common/hash_calculator.h"
 #include "update_engine/common/subprocess.h"
@@ -72,6 +74,8 @@
 // memory intensive, so we limit these operations to 150 MiB.
 const uint64_t kMaxPuffdiffDestinationSize = 150 * 1024 * 1024;  // bytes
 
+const int kBrotliCompressionQuality = 11;
+
 // Process a range of blocks from |range_start| to |range_end| in the extent at
 // position |*idx_p| of |extents|. If |do_remove| is true, this range will be
 // removed, which may cause the extent to be trimmed, split or removed entirely.
@@ -210,8 +214,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
@@ -739,21 +743,33 @@
         TEST_AND_RETURN_FALSE(base::CreateTemporaryFile(&patch));
         ScopedPathUnlinker unlinker(patch.value());
 
+        std::unique_ptr<bsdiff::PatchWriterInterface> bsdiff_patch_writer;
+        InstallOperation_Type operation_type = InstallOperation::BSDIFF;
+        if (version.OperationAllowed(InstallOperation::BROTLI_BSDIFF)) {
+          bsdiff_patch_writer =
+              bsdiff::CreateBSDF2PatchWriter(patch.value(),
+                                             bsdiff::CompressorType::kBrotli,
+                                             kBrotliCompressionQuality);
+          operation_type = InstallOperation::BROTLI_BSDIFF;
+        } else {
+          bsdiff_patch_writer = bsdiff::CreateBsdiffPatchWriter(patch.value());
+          if (version.OperationAllowed(InstallOperation::SOURCE_BSDIFF)) {
+            operation_type = InstallOperation::SOURCE_BSDIFF;
+          }
+        }
+
         brillo::Blob bsdiff_delta;
         TEST_AND_RETURN_FALSE(0 == bsdiff::bsdiff(old_data.data(),
                                                   old_data.size(),
                                                   new_data.data(),
                                                   new_data.size(),
-                                                  patch.value().c_str(),
+                                                  bsdiff_patch_writer.get(),
                                                   nullptr));
 
         TEST_AND_RETURN_FALSE(utils::ReadFile(patch.value(), &bsdiff_delta));
         CHECK_GT(bsdiff_delta.size(), static_cast<brillo::Blob::size_type>(0));
         if (bsdiff_delta.size() < data_blob.size()) {
-          operation.set_type(
-              version.OperationAllowed(InstallOperation::SOURCE_BSDIFF)
-                  ? InstallOperation::SOURCE_BSDIFF
-                  : InstallOperation::BSDIFF);
+          operation.set_type(operation_type);
           data_blob = std::move(bsdiff_delta);
         }
       }
@@ -883,7 +899,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..a20a567 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);
@@ -404,8 +410,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;
diff --git a/payload_generator/payload_file.cc b/payload_generator/payload_file.cc
index 38aa0da..941b640 100644
--- a/payload_generator/payload_file.cc
+++ b/payload_generator/payload_file.cc
@@ -315,10 +315,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/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..cff02b1 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;
   }
 
@@ -348,7 +357,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 +395,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 +445,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 +456,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 +533,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 +564,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 +586,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 +634,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 +680,8 @@
   if (!attempt_in_progress)
     return;
 
-  metrics::ReportAbnormallyTerminatedUpdateAttemptMetrics(system_state_);
+  system_state_->metrics_reporter()
+      ->ReportAbnormallyTerminatedUpdateAttemptMetrics();
 
   ClearPersistedAttemptMetrics();
 }
@@ -716,22 +737,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 +766,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 +804,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 +854,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 +877,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 +907,8 @@
 }
 
 void PayloadState::LoadScatteringWaitPeriod() {
-  SetScatteringWaitPeriod(
-      TimeDelta::FromSeconds(GetPersistedValue(kPrefsWallClockWaitPeriod)));
+  SetScatteringWaitPeriod(TimeDelta::FromSeconds(
+      GetPersistedValue(kPrefsWallClockWaitPeriod, prefs_)));
 }
 
 void PayloadState::SetScatteringWaitPeriod(TimeDelta wait_period) {
@@ -905,7 +925,7 @@
 }
 
 void PayloadState::LoadUrlSwitchCount() {
-  SetUrlSwitchCount(GetPersistedValue(kPrefsUrlSwitchCount));
+  SetUrlSwitchCount(GetPersistedValue(kPrefsUrlSwitchCount, prefs_));
 }
 
 void PayloadState::SetUrlSwitchCount(uint32_t url_switch_count) {
@@ -916,7 +936,7 @@
 }
 
 void PayloadState::LoadUrlFailureCount() {
-  SetUrlFailureCount(GetPersistedValue(kPrefsCurrentUrlFailureCount));
+  SetUrlFailureCount(GetPersistedValue(kPrefsCurrentUrlFailureCount, prefs_));
 }
 
 void PayloadState::SetUrlFailureCount(uint32_t url_failure_count) {
@@ -999,12 +1019,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 +1066,7 @@
 }
 
 void PayloadState::LoadNumReboots() {
-  SetNumReboots(GetPersistedValue(kPrefsNumReboots));
+  SetNumReboots(GetPersistedValue(kPrefsNumReboots, prefs_));
 }
 
 void PayloadState::LoadRollbackVersion() {
@@ -1102,7 +1118,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 +1142,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 +1166,7 @@
 }
 
 void PayloadState::LoadNumResponsesSeen() {
-  SetNumResponsesSeen(GetPersistedValue(kPrefsNumResponsesSeen));
+  SetNumResponsesSeen(GetPersistedValue(kPrefsNumResponsesSeen, prefs_));
 }
 
 void PayloadState::SetNumResponsesSeen(int num_responses_seen) {
@@ -1173,38 +1189,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 +1216,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 +1254,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 +1312,7 @@
 }
 
 void PayloadState::LoadP2PNumAttempts() {
-  SetP2PNumAttempts(GetPersistedValue(kPrefsP2PNumAttempts));
+  SetP2PNumAttempts(GetPersistedValue(kPrefsP2PNumAttempts, prefs_));
 }
 
 Time PayloadState::GetP2PFirstAttemptTimestamp() {
@@ -1349,7 +1329,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 +1376,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..61ecadf 100755
--- a/scripts/brillo_update_payload
+++ b/scripts/brillo_update_payload
@@ -397,7 +397,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
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 52d2b4b..380d6d0 100644
--- a/scripts/update_payload/payload.py
+++ b/scripts/update_payload/payload.py
@@ -112,13 +112,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
@@ -170,7 +172,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.
@@ -186,7 +189,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.
@@ -203,6 +207,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.
@@ -253,7 +258,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 39a12ed..cf15672 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/testrunner.cc b/testrunner.cc
index 934ea91..81d4548 100644
--- a/testrunner.cc
+++ b/testrunner.cc
@@ -16,18 +16,14 @@
 
 // based on pam_google_testrunner.cc
 
-#include <string>
-
 #include <xz.h>
 
 #include <base/at_exit.h>
 #include <base/command_line.h>
-#include <base/environment.h>
 #include <brillo/test_helpers.h>
 #include <gtest/gtest.h>
 
 #include "update_engine/common/terminator.h"
-#include "update_engine/common/test_utils.h"
 #include "update_engine/payload_generator/xz.h"
 
 int main(int argc, char **argv) {
@@ -44,19 +40,12 @@
   // the default exit status of 1.  Corresponding reverts are necessary in
   // terminator_unittest.cc.
   chromeos_update_engine::Terminator::Init(2);
-  // In Android bsdiff is located in update_engine_unittests, add it to PATH.
-#ifdef __ANDROID__
-  std::unique_ptr<base::Environment> env(base::Environment::Create());
-  std::string path_env;
-  CHECK(env->GetVar("PATH", &path_env));
-  path_env +=
-      ":" + chromeos_update_engine::test_utils::GetBuildArtifactsPath().value();
-  CHECK(env->SetVar("PATH", path_env));
-#endif
   LOG(INFO) << "parsing command line arguments";
   base::CommandLine::Init(argc, argv);
   LOG(INFO) << "initializing gtest";
   SetUpTests(&argc, argv, true);
+  // Logging to string is not thread safe.
+  brillo::LogToString(false);
   LOG(INFO) << "running unit tests";
   int test_result = RUN_ALL_TESTS();
   LOG(INFO) << "unittest return value: " << test_result;
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..5ee0584 100644
--- a/update_attempter_android.cc
+++ b/update_attempter_android.cc
@@ -18,20 +18,24 @@
 
 #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 "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 +49,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 +80,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 +98,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 +113,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 +161,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 +196,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 +229,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 +240,6 @@
   install_plan_.Dump();
 
   BuildUpdateActions(payload_url);
-  SetupDownload();
   // Setup extra headers.
   HttpFetcher* fetcher = download_action_->http_fetcher();
   if (!headers[kPayloadPropertyAuthorization].empty())
@@ -214,6 +253,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 +294,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 +335,6 @@
       // Update succeeded.
       WriteUpdateCompletedMarker();
       prefs_->SetInt64(kPrefsDeltaUpdateFailures, 0);
-      DeltaPerformer::ResetUpdateProgress(prefs_, false);
 
       LOG(INFO) << "Update successfully applied, waiting to reboot.";
       break;
@@ -329,6 +372,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 +398,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 +475,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 +533,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 +543,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 +564,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 +585,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 41f4422..dc47da4 100644
--- a/update_engine.gyp
+++ b/update_engine.gyp
@@ -181,6 +181,7 @@
         'payload_consumer/file_writer.cc',
         'payload_consumer/filesystem_verifier_action.cc',
         'payload_consumer/install_plan.cc',
+        'payload_consumer/mount_history.cc',
         'payload_consumer/payload_constants.cc',
         'payload_consumer/payload_verifier.cc',
         'payload_consumer/postinstall_runner_action.cc',
@@ -257,7 +258,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',
@@ -273,8 +274,15 @@
         '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/enterprise_device_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/out_of_box_experience_policy_impl.cc',
         'update_manager/policy.cc',
+        'update_manager/policy_test_utils.cc',
         'update_manager/real_config_provider.cc',
         'update_manager/real_device_policy_provider.cc',
         'update_manager/real_random_provider.cc',
@@ -511,6 +519,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_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..842839a 100644
--- a/update_manager/chromeos_policy.cc
+++ b/update_manager/chromeos_policy.cc
@@ -19,6 +19,7 @@
 #include <algorithm>
 #include <set>
 #include <string>
+#include <vector>
 
 #include <base/logging.h>
 #include <base/strings/string_util.h>
@@ -28,6 +29,11 @@
 #include "update_engine/common/error_code_utils.h"
 #include "update_engine/common/utils.h"
 #include "update_engine/update_manager/device_policy_provider.h"
+#include "update_engine/update_manager/enough_slots_ab_updates_policy_impl.h"
+#include "update_engine/update_manager/enterprise_device_policy_impl.h"
+#include "update_engine/update_manager/interactive_update_policy_impl.h"
+#include "update_engine/update_manager/official_build_check_policy_impl.h"
+#include "update_engine/update_manager/out_of_box_experience_policy_impl.h"
 #include "update_engine/update_manager/policy_utils.h"
 #include "update_engine/update_manager/shill_provider.h"
 
@@ -36,11 +42,12 @@
 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;
 using std::set;
 using std::string;
+using std::vector;
 
 namespace {
 
@@ -134,7 +141,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) << ")";
@@ -171,21 +178,16 @@
 
 namespace chromeos_update_manager {
 
-const int ChromeOSPolicy::kTimeoutInitialInterval =  7 * 60;
+const NextUpdateCheckPolicyConstants
+    ChromeOSPolicy::kNextUpdateCheckPolicyConstants = {
+        .timeout_initial_interval = 7 * 60,
+        .timeout_periodic_interval = 45 * 60,
+        .timeout_max_backoff_interval = 4 * 60 * 60,
+        .timeout_regular_fuzz = 10 * 60,
+        .attempt_backoff_max_interval_in_days = 16,
+        .attempt_backoff_fuzz_in_hours = 12,
+};
 
-// TODO(deymo): Split the update_manager policies for Brillo and ChromeOS and
-// make the update check periodic interval configurable.
-#ifdef __ANDROID__
-const int ChromeOSPolicy::kTimeoutPeriodicInterval = 5 * 60 * 60;
-const int ChromeOSPolicy::kTimeoutMaxBackoffInterval = 26 * 60 * 60;
-#else
-const int ChromeOSPolicy::kTimeoutPeriodicInterval = 45 * 60;
-const int ChromeOSPolicy::kTimeoutMaxBackoffInterval = 4 * 60 * 60;
-#endif  // __ANDROID__
-
-const int ChromeOSPolicy::kTimeoutRegularFuzz = 10 * 60;
-const int ChromeOSPolicy::kAttemptBackoffMaxIntervalInDays = 16;
-const int ChromeOSPolicy::kAttemptBackoffFuzzInHours = 12;
 const int ChromeOSPolicy::kMaxP2PAttempts = 10;
 const int ChromeOSPolicy::kMaxP2PAttemptsPeriodInSeconds = 5 * 24 * 60 * 60;
 
@@ -198,129 +200,61 @@
   result->target_version_prefix.clear();
   result->is_interactive = false;
 
-  DevicePolicyProvider* const dp_provider = state->device_policy_provider();
-  UpdaterProvider* const updater_provider = state->updater_provider();
-  SystemProvider* const system_provider = state->system_provider();
+  EnoughSlotsAbUpdatesPolicyImpl enough_slots_ab_updates_policy;
+  EnterpriseDevicePolicyImpl enterprise_device_policy;
+  OnlyUpdateOfficialBuildsPolicyImpl only_update_official_builds_policy;
+  InteractiveUpdatePolicyImpl interactive_update_policy;
+  OobePolicyImpl oobe_policy;
+  NextUpdateCheckTimePolicyImpl next_update_check_time_policy(
+      kNextUpdateCheckPolicyConstants);
 
-  // Do not perform any updates if booted from removable device. This decision
-  // is final.
-  const unsigned int* num_slots_p = ec->GetValue(
-      system_provider->var_num_slots());
-  if (!num_slots_p || *num_slots_p < 2) {
-    LOG(INFO) << "Not enough slots for A/B updates, disabling update checks.";
-    result->updates_enabled = false;
+  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,
+
+      // Check to see if Enterprise-managed (has DevicePolicy) and/or
+      // Kiosk-mode.  If so, then defer to those settings.
+      &enterprise_device_policy,
+
+      // Check to see if an interactive update was requested.
+      &interactive_update_policy,
+
+      // Unofficial builds should not perform periodic update checks.
+      &only_update_official_builds_policy,
+
+      // If OOBE is enabled, wait until it is completed.
+      &oobe_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,
+  // has been setup, consult the policies. 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 (EvalStatus::kContinue != status) {
+    return status;
+  } else {
+    // It is time to check for an update.
+    LOG(INFO) << "Allowing update check.";
     return EvalStatus::kSucceeded;
   }
+}
 
-  const bool* device_policy_is_loaded_p = ec->GetValue(
-      dp_provider->var_device_policy_is_loaded());
-  if (device_policy_is_loaded_p && *device_policy_is_loaded_p) {
-    bool kiosk_app_control_chrome_version = false;
-
-    // Check whether updates are disabled by policy.
-    const bool* update_disabled_p = ec->GetValue(
-        dp_provider->var_update_disabled());
-    if (update_disabled_p && *update_disabled_p) {
-      // Check whether allow kiosk app to control chrome version policy. This
-      // policy is only effective when AU is disabled by admin.
-      const bool* allow_kiosk_app_control_chrome_version_p = ec->GetValue(
-          dp_provider->var_allow_kiosk_app_control_chrome_version());
-      kiosk_app_control_chrome_version =
-          allow_kiosk_app_control_chrome_version_p &&
-          *allow_kiosk_app_control_chrome_version_p;
-      if (!kiosk_app_control_chrome_version) {
-        // No kiosk pin chrome version policy. AU is really disabled.
-        LOG(INFO) << "Updates disabled by policy, blocking update checks.";
-        return EvalStatus::kAskMeAgainLater;
-      }
-    }
-
-    if (kiosk_app_control_chrome_version) {
-      // Get the required platform version from Chrome.
-      const string* kiosk_required_platform_version_p =
-          ec->GetValue(system_provider->var_kiosk_required_platform_version());
-      if (!kiosk_required_platform_version_p) {
-        LOG(INFO) << "Kiosk app required platform version is not fetched, "
-                     "blocking update checks";
-        return EvalStatus::kAskMeAgainLater;
-      }
-
-      result->target_version_prefix = *kiosk_required_platform_version_p;
-      LOG(INFO) << "Allow kiosk app to control Chrome version policy is set, "
-                << "target version is "
-                << (!kiosk_required_platform_version_p->empty()
-                        ? *kiosk_required_platform_version_p
-                        : std::string("latest"));
-    } else {
-      // Determine whether a target version prefix is dictated by policy.
-      const string* target_version_prefix_p = ec->GetValue(
-          dp_provider->var_target_version_prefix());
-      if (target_version_prefix_p)
-        result->target_version_prefix = *target_version_prefix_p;
-    }
-
-    // Determine whether a target channel is dictated by policy.
-    const bool* release_channel_delegated_p = ec->GetValue(
-        dp_provider->var_release_channel_delegated());
-    if (release_channel_delegated_p && !(*release_channel_delegated_p)) {
-      const string* release_channel_p = ec->GetValue(
-          dp_provider->var_release_channel());
-      if (release_channel_p)
-        result->target_channel = *release_channel_p;
-    }
-  }
-
-  // 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 &&
-      *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;
-  }
-
-  // The logic thereafter applies to periodic updates. Bear in mind that we
-  // should not return a final "no" if any of these criteria are not satisfied,
-  // because the system may still update due to an interactive update request.
-
-  // Unofficial builds should not perform periodic update checks.
-  const bool* is_official_build_p = ec->GetValue(
-      system_provider->var_is_official_build());
-  if (is_official_build_p && !(*is_official_build_p)) {
-    LOG(INFO) << "Unofficial build, blocking periodic update checks.";
-    return EvalStatus::kAskMeAgainLater;
-  }
-
-  // If OOBE is enabled, wait until it is completed.
-  const bool* is_oobe_enabled_p = ec->GetValue(
-      state->config_provider()->var_is_oobe_enabled());
-  if (is_oobe_enabled_p && *is_oobe_enabled_p) {
-    const bool* is_oobe_complete_p = ec->GetValue(
-        system_provider->var_is_oobe_complete());
-    if (is_oobe_complete_p && !(*is_oobe_complete_p)) {
-      LOG(INFO) << "OOBE not completed, blocking update checks.";
-      return EvalStatus::kAskMeAgainLater;
-    }
-  }
-
-  // Ensure that periodic update checks are timed properly.
-  Time next_update_check;
-  if (NextUpdateCheckTime(ec, state, error, &next_update_check) !=
-      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;
-  }
-
-  // It is time to check for an update.
-  LOG(INFO) << "Allowing update check.";
+EvalStatus ChromeOSPolicy::UpdateCanBeApplied(EvaluationContext* ec,
+                                              State* state,
+                                              std::string* error,
+                                              ErrorCode* result,
+                                              InstallPlan* install_plan) const {
+  *result = ErrorCode::kSuccess;
   return EvalStatus::kSucceeded;
 }
 
@@ -616,92 +550,6 @@
   return status;
 }
 
-EvalStatus ChromeOSPolicy::NextUpdateCheckTime(EvaluationContext* ec,
-                                               State* state, string* error,
-                                               Time* next_update_check) const {
-  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 uint64_t* 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 || *last_checked_time < *updater_started_time) {
-    *next_update_check = *updater_started_time + FuzzedInterval(
-        &prng, kTimeoutInitialInterval, kTimeoutRegularFuzz);
-    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 = kTimeoutPeriodicInterval;
-    unsigned int num_failures = *consecutive_failed_update_checks;
-    while (interval < kTimeoutMaxBackoffInterval && num_failures) {
-      interval *= 2;
-      num_failures--;
-    }
-  }
-
-  // We cannot back off longer than the predetermined maximum interval.
-  if (interval > kTimeoutMaxBackoffInterval)
-    interval = kTimeoutMaxBackoffInterval;
-
-  // We cannot back off shorter than the predetermined periodic interval. Also,
-  // in this case set the fuzz to a predetermined regular value.
-  if (interval <= kTimeoutPeriodicInterval) {
-    interval = kTimeoutPeriodicInterval;
-    fuzz = kTimeoutRegularFuzz;
-  }
-
-  // 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 ChromeOSPolicy::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));
-}
-
 EvalStatus ChromeOSPolicy::UpdateBackoffAndDownloadUrl(
     EvaluationContext* ec, State* state, string* error,
     UpdateBackoffAndDownloadUrlResult* result,
@@ -873,11 +721,13 @@
     PRNG prng(*seed);
     int exp = min(update_state.num_failures,
                        static_cast<int>(sizeof(int)) * 8 - 2);
-    TimeDelta backoff_interval = TimeDelta::FromDays(
-        min(1 << exp, kAttemptBackoffMaxIntervalInDays));
-    TimeDelta backoff_fuzz = TimeDelta::FromHours(kAttemptBackoffFuzzInHours);
-    TimeDelta wait_period = FuzzedInterval(&prng, backoff_interval.InSeconds(),
-                                           backoff_fuzz.InSeconds());
+    TimeDelta backoff_interval = TimeDelta::FromDays(min(
+        1 << exp,
+        kNextUpdateCheckPolicyConstants.attempt_backoff_max_interval_in_days));
+    TimeDelta backoff_fuzz = TimeDelta::FromHours(
+        kNextUpdateCheckPolicyConstants.attempt_backoff_fuzz_in_hours);
+    TimeDelta wait_period = NextUpdateCheckTimePolicyImpl::FuzzedInterval(
+        &prng, backoff_interval.InSeconds(), backoff_fuzz.InSeconds());
     backoff_expiry = err_time + wait_period;
 
     // If the newly computed backoff already expired, nullify it.
diff --git a/update_manager/chromeos_policy.h b/update_manager/chromeos_policy.h
index b4370c4..67c0d15 100644
--- a/update_manager/chromeos_policy.h
+++ b/update_manager/chromeos_policy.h
@@ -20,10 +20,9 @@
 #include <string>
 
 #include <base/time/time.h>
-#include <gtest/gtest_prod.h>  // for FRIEND_TEST
 
-#include "update_engine/update_manager/policy.h"
-#include "update_engine/update_manager/prng.h"
+#include "update_engine/update_manager/next_update_check_policy_impl.h"
+#include "update_engine/update_manager/policy_utils.h"
 
 namespace chromeos_update_manager {
 
@@ -59,6 +58,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,
@@ -91,12 +97,6 @@
 
  private:
   friend class UmChromeOSPolicyTest;
-  FRIEND_TEST(UmChromeOSPolicyTest,
-              FirstCheckIsAtMostInitialIntervalAfterStart);
-  FRIEND_TEST(UmChromeOSPolicyTest, RecurringCheckBaseIntervalAndFuzz);
-  FRIEND_TEST(UmChromeOSPolicyTest, RecurringCheckBackoffIntervalAndFuzz);
-  FRIEND_TEST(UmChromeOSPolicyTest, RecurringCheckServerDictatedPollInterval);
-  FRIEND_TEST(UmChromeOSPolicyTest, ExponentialBackoffIsCapped);
   FRIEND_TEST(UmChromeOSPolicyTest, UpdateCheckAllowedWaitsForTheTimeout);
   FRIEND_TEST(UmChromeOSPolicyTest, UpdateCheckAllowedWaitsForOOBE);
   FRIEND_TEST(UmChromeOSPolicyTest,
@@ -118,38 +118,13 @@
   // Auxiliary constant (zero by default).
   const base::TimeDelta kZeroInterval;
 
-  // Default update check timeout interval/fuzz values used to compute the
-  // NextUpdateCheckTime(), in seconds. Actual fuzz is within +/- half of the
-  // indicated value.
-  static const int kTimeoutInitialInterval;
-  static const int kTimeoutPeriodicInterval;
-  static const int kTimeoutMaxBackoffInterval;
-  static const int kTimeoutRegularFuzz;
-
-  // Maximum update attempt backoff interval and fuzz.
-  static const int kAttemptBackoffMaxIntervalInDays;
-  static const int kAttemptBackoffFuzzInHours;
+  static const NextUpdateCheckPolicyConstants kNextUpdateCheckPolicyConstants;
 
   // Maximum number of times we'll allow using P2P for the same update payload.
   static const int kMaxP2PAttempts;
   // Maximum period of time allowed for download a payload via P2P, in seconds.
   static const int kMaxP2PAttemptsPeriodInSeconds;
 
-  // 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.
-  EvalStatus NextUpdateCheckTime(EvaluationContext* ec, State* state,
-                                 std::string* error,
-                                 base::Time* next_update_check) const;
-
-  // 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);
-
   // A private policy for determining backoff and the download URL to use.
   // Within |update_state|, |backoff_expiry| and |is_backoff_disabled| are used
   // for determining whether backoff is still in effect; if not,
diff --git a/update_manager/chromeos_policy_unittest.cc b/update_manager/chromeos_policy_unittest.cc
index 0c38700..df29e8c 100644
--- a/update_manager/chromeos_policy_unittest.cc
+++ b/update_manager/chromeos_policy_unittest.cc
@@ -16,72 +16,35 @@
 
 #include "update_engine/update_manager/chromeos_policy.h"
 
+#include <memory>
 #include <set>
-#include <string>
-#include <tuple>
-#include <vector>
 
-#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/umtest_utils.h"
+#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::ConnectionTethering;
 using chromeos_update_engine::ConnectionType;
 using chromeos_update_engine::ErrorCode;
-using chromeos_update_engine::FakeClock;
 using std::set;
 using std::string;
-using std::tuple;
-using std::vector;
 
 namespace chromeos_update_manager {
 
-class UmChromeOSPolicyTest : public ::testing::Test {
+class UmChromeOSPolicyTest : public UmPolicyTestBase {
  protected:
+  UmChromeOSPolicyTest() : UmPolicyTestBase() {
+    policy_ = std::make_unique<ChromeOSPolicy>();
+  }
+
   void SetUp() override {
-    loop_.SetAsCurrent();
-    SetUpDefaultClock();
-    eval_ctx_ = new EvaluationContext(&fake_clock_, TimeDelta::FromSeconds(5));
-    SetUpDefaultState();
+    UmPolicyTestBase::SetUp();
     SetUpDefaultDevicePolicy();
   }
 
-  void TearDown() override {
-    EXPECT_FALSE(loop_.PendingTasks());
-  }
-
-  // Sets the clock to fixed values.
-  void SetUpDefaultClock() {
-    fake_clock_.SetMonotonicTime(Time::FromInternalValue(12345678L));
-    fake_clock_.SetWallclockTime(Time::FromInternalValue(12345678901234L));
-  }
-
-  void 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});
-    fake_state_.updater_provider()->var_server_dictated_poll_interval()->
-        reset(new unsigned int{0});
-    fake_state_.updater_provider()->var_forced_update_requested()->
-        reset(new UpdateRequestStatus{UpdateRequestStatus::kNone});
-
-    fake_state_.random_provider()->var_seed()->reset(
-        new uint64_t(4));  // chosen by fair dice roll.
-                           // guaranteed to be random.
-
-    // No device policy loaded by default.
-    fake_state_.device_policy_provider()->var_device_policy_is_loaded()->reset(
-        new bool(false));
+  void SetUpDefaultState() override {
+    UmPolicyTestBase::SetUpDefaultState();
 
     // OOBE is enabled by default.
     fake_state_.config_provider()->var_is_oobe_enabled()->reset(
@@ -93,6 +56,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.
@@ -121,14 +85,17 @@
         reset(new bool(true));
   }
 
-  // Configures the UpdateCheckAllowed policy to return a desired value by
+  // 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.
-  void SetUpdateCheckAllowed(bool allow_check) {
+  //
+  // Note that the default implementation relies on NextUpdateCheckPolicyImpl to
+  // set the FakeClock to the appropriate time.
+  virtual void SetUpdateCheckAllowed(bool allow_check) {
     Time next_update_check;
-    ExpectPolicyStatus(EvalStatus::kSucceeded,
-                       &ChromeOSPolicy::NextUpdateCheckTime,
-                       &next_update_check);
+    CallMethodWithContext(&NextUpdateCheckTimePolicyImpl::NextUpdateCheckTime,
+                          &next_update_check,
+                          ChromeOSPolicy::kNextUpdateCheckPolicyConstants);
     SetUpDefaultState();
     SetUpDefaultDevicePolicy();
     Time curr_time = next_update_check;
@@ -138,178 +105,8 @@
       curr_time -= TimeDelta::FromSeconds(1);
     fake_clock_.SetWallclockTime(curr_time);
   }
-
-  // Returns a default UpdateState structure:
-  UpdateState 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;
-  }
-
-  // Runs the passed |policy_method| 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) {
-    string error = "<None>";
-    eval_ctx_->ResetEvaluation();
-    EXPECT_EQ(expected,
-              (policy_.*policy_method)(eval_ctx_.get(), &fake_state_, &error,
-                                       result, args...))
-        << "Returned error: " << error
-        << "\nEvaluation context: " << eval_ctx_->DumpContext();
-  }
-
-  brillo::FakeMessageLoop loop_{nullptr};
-  FakeClock fake_clock_;
-  FakeState fake_state_;
-  scoped_refptr<EvaluationContext> eval_ctx_;
-  ChromeOSPolicy policy_;  // ChromeOSPolicy under test.
 };
 
-TEST_F(UmChromeOSPolicyTest, 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)));
-
-  ExpectPolicyStatus(EvalStatus::kSucceeded,
-                     &ChromeOSPolicy::NextUpdateCheckTime, &next_update_check);
-
-  EXPECT_LE(fake_clock_.GetWallclockTime(), next_update_check);
-  EXPECT_GE(
-      fake_clock_.GetWallclockTime() + TimeDelta::FromSeconds(
-          ChromeOSPolicy::kTimeoutInitialInterval +
-          ChromeOSPolicy::kTimeoutRegularFuzz / 2),
-      next_update_check);
-}
-
-TEST_F(UmChromeOSPolicyTest, RecurringCheckBaseIntervalAndFuzz) {
-  // Ensure that we're using the correct interval (kPeriodicInterval) and fuzz
-  // (kTimeoutRegularFuzz) as base values for period updates.
-  Time next_update_check;
-
-  ExpectPolicyStatus(EvalStatus::kSucceeded,
-                     &ChromeOSPolicy::NextUpdateCheckTime, &next_update_check);
-
-  EXPECT_LE(
-      fake_clock_.GetWallclockTime() + TimeDelta::FromSeconds(
-          ChromeOSPolicy::kTimeoutPeriodicInterval -
-          ChromeOSPolicy::kTimeoutRegularFuzz / 2),
-      next_update_check);
-  EXPECT_GE(
-      fake_clock_.GetWallclockTime() + TimeDelta::FromSeconds(
-          ChromeOSPolicy::kTimeoutPeriodicInterval +
-          ChromeOSPolicy::kTimeoutRegularFuzz / 2),
-      next_update_check);
-}
-
-TEST_F(UmChromeOSPolicyTest, 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});
-
-  ExpectPolicyStatus(EvalStatus::kSucceeded,
-                     &ChromeOSPolicy::NextUpdateCheckTime, &next_update_check);
-
-  int expected_interval = ChromeOSPolicy::kTimeoutPeriodicInterval * 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(UmChromeOSPolicyTest, RecurringCheckServerDictatedPollInterval) {
-  // Policy honors the server provided check poll interval.
-  Time next_update_check;
-
-  const unsigned int kInterval = ChromeOSPolicy::kTimeoutPeriodicInterval * 4;
-  fake_state_.updater_provider()->var_server_dictated_poll_interval()->
-      reset(new unsigned int{kInterval});
-  // We should not be backing off in this case.
-  fake_state_.updater_provider()->var_consecutive_failed_update_checks()->
-      reset(new unsigned int{2});
-
-  ExpectPolicyStatus(EvalStatus::kSucceeded,
-                     &ChromeOSPolicy::NextUpdateCheckTime, &next_update_check);
-
-  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(UmChromeOSPolicyTest, ExponentialBackoffIsCapped) {
-  Time next_update_check;
-
-  fake_state_.updater_provider()->var_consecutive_failed_update_checks()->
-      reset(new unsigned int{100});
-
-  ExpectPolicyStatus(EvalStatus::kSucceeded,
-                     &ChromeOSPolicy::NextUpdateCheckTime, &next_update_check);
-
-  EXPECT_LE(
-      fake_clock_.GetWallclockTime() + TimeDelta::FromSeconds(
-          ChromeOSPolicy::kTimeoutMaxBackoffInterval -
-          ChromeOSPolicy::kTimeoutMaxBackoffInterval / 2),
-      next_update_check);
-  EXPECT_GE(
-      fake_clock_.GetWallclockTime() + TimeDelta::FromSeconds(
-          ChromeOSPolicy::kTimeoutMaxBackoffInterval +
-          ChromeOSPolicy::kTimeoutMaxBackoffInterval /2),
-      next_update_check);
-}
-
 TEST_F(UmChromeOSPolicyTest, 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
@@ -320,8 +117,9 @@
 
   fake_state_.updater_provider()->var_last_checked_time()->reset(
       new Time(last_checked_time));
-  ExpectPolicyStatus(EvalStatus::kSucceeded,
-                     &ChromeOSPolicy::NextUpdateCheckTime, &next_update_check);
+  CallMethodWithContext(&NextUpdateCheckTimePolicyImpl::NextUpdateCheckTime,
+                        &next_update_check,
+                        ChromeOSPolicy::kNextUpdateCheckPolicyConstants);
 
   UpdateCheckParams result;
 
@@ -355,8 +153,9 @@
 
   fake_state_.updater_provider()->var_last_checked_time()->reset(
       new Time(last_checked_time));
-  ExpectPolicyStatus(EvalStatus::kSucceeded,
-                     &ChromeOSPolicy::NextUpdateCheckTime, &next_update_check);
+  CallMethodWithContext(&NextUpdateCheckTimePolicyImpl::NextUpdateCheckTime,
+                        &next_update_check,
+                        ChromeOSPolicy::kNextUpdateCheckPolicyConstants);
 
   SetUpDefaultClock();
   SetUpDefaultState();
@@ -418,10 +217,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/enterprise_device_policy_impl.cc b/update_manager/enterprise_device_policy_impl.cc
new file mode 100644
index 0000000..94518a1
--- /dev/null
+++ b/update_manager/enterprise_device_policy_impl.cc
@@ -0,0 +1,95 @@
+//
+// 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/enterprise_device_policy_impl.h"
+
+#include "update_engine/common/utils.h"
+
+using std::string;
+
+namespace chromeos_update_manager {
+
+// Check to see if Enterprise-managed (has DevicePolicy) and/or Kiosk-mode.  If
+// so, then defer to those settings.
+EvalStatus EnterpriseDevicePolicyImpl::UpdateCheckAllowed(
+    EvaluationContext* ec,
+    State* state,
+    std::string* error,
+    UpdateCheckParams* result) const {
+  DevicePolicyProvider* const dp_provider = state->device_policy_provider();
+  SystemProvider* const system_provider = state->system_provider();
+
+  const bool* device_policy_is_loaded_p =
+      ec->GetValue(dp_provider->var_device_policy_is_loaded());
+  if (device_policy_is_loaded_p && *device_policy_is_loaded_p) {
+    bool kiosk_app_control_chrome_version = false;
+
+    // Check whether updates are disabled by policy.
+    const bool* update_disabled_p =
+        ec->GetValue(dp_provider->var_update_disabled());
+    if (update_disabled_p && *update_disabled_p) {
+      // Check whether allow kiosk app to control chrome version policy. This
+      // policy is only effective when AU is disabled by admin.
+      const bool* allow_kiosk_app_control_chrome_version_p = ec->GetValue(
+          dp_provider->var_allow_kiosk_app_control_chrome_version());
+      kiosk_app_control_chrome_version =
+          allow_kiosk_app_control_chrome_version_p &&
+          *allow_kiosk_app_control_chrome_version_p;
+      if (!kiosk_app_control_chrome_version) {
+        // No kiosk pin chrome version policy. AU is really disabled.
+        LOG(INFO) << "Updates disabled by policy, blocking update checks.";
+        return EvalStatus::kAskMeAgainLater;
+      }
+    }
+
+    if (kiosk_app_control_chrome_version) {
+      // Get the required platform version from Chrome.
+      const string* kiosk_required_platform_version_p =
+          ec->GetValue(system_provider->var_kiosk_required_platform_version());
+      if (!kiosk_required_platform_version_p) {
+        LOG(INFO) << "Kiosk app required platform version is not fetched, "
+                     "blocking update checks";
+        return EvalStatus::kAskMeAgainLater;
+      }
+
+      result->target_version_prefix = *kiosk_required_platform_version_p;
+      LOG(INFO) << "Allow kiosk app to control Chrome version policy is set,"
+                << ", target version is "
+                << (kiosk_required_platform_version_p
+                        ? *kiosk_required_platform_version_p
+                        : std::string("latest"));
+    } else {
+      // Determine whether a target version prefix is dictated by policy.
+      const string* target_version_prefix_p =
+          ec->GetValue(dp_provider->var_target_version_prefix());
+      if (target_version_prefix_p)
+        result->target_version_prefix = *target_version_prefix_p;
+    }
+
+    // Determine whether a target channel is dictated by policy.
+    const bool* release_channel_delegated_p =
+        ec->GetValue(dp_provider->var_release_channel_delegated());
+    if (release_channel_delegated_p && !(*release_channel_delegated_p)) {
+      const string* release_channel_p =
+          ec->GetValue(dp_provider->var_release_channel());
+      if (release_channel_p)
+        result->target_channel = *release_channel_p;
+    }
+  }
+  return EvalStatus::kContinue;
+}
+
+}  // namespace chromeos_update_manager
diff --git a/update_manager/enterprise_device_policy_impl.h b/update_manager/enterprise_device_policy_impl.h
new file mode 100644
index 0000000..4b97fda
--- /dev/null
+++ b/update_manager/enterprise_device_policy_impl.h
@@ -0,0 +1,48 @@
+//
+// 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_ENTERPRISE_DEVICE_POLICY_IMPL_H_
+#define UPDATE_ENGINE_UPDATE_MANAGER_ENTERPRISE_DEVICE_POLICY_IMPL_H_
+
+#include <string>
+
+#include "update_engine/update_manager/policy_utils.h"
+
+namespace chromeos_update_manager {
+
+// Check to see if Enterprise-managed (has DevicePolicy) and/or Kiosk-mode.  If
+// so, then defer to those settings.
+class EnterpriseDevicePolicyImpl : public PolicyImplBase {
+ public:
+  EnterpriseDevicePolicyImpl() = default;
+  ~EnterpriseDevicePolicyImpl() override = default;
+
+  std::string PolicyName() const override {
+    return "EnterpriseDevicePolicyImpl";
+  }
+  // Policy overrides.
+  EvalStatus UpdateCheckAllowed(EvaluationContext* ec,
+                                State* state,
+                                std::string* error,
+                                UpdateCheckParams* result) const override;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(EnterpriseDevicePolicyImpl);
+};
+
+}  // namespace chromeos_update_manager
+
+#endif  // UPDATE_ENGINE_UPDATE_MANAGER_ENTERPRISE_DEVICE_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/out_of_box_experience_policy_impl.cc b/update_manager/out_of_box_experience_policy_impl.cc
new file mode 100644
index 0000000..8dcb560
--- /dev/null
+++ b/update_manager/out_of_box_experience_policy_impl.cc
@@ -0,0 +1,43 @@
+//
+// 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/out_of_box_experience_policy_impl.h"
+
+#include "update_engine/common/utils.h"
+
+namespace chromeos_update_manager {
+
+EvalStatus OobePolicyImpl::UpdateCheckAllowed(EvaluationContext* ec,
+                                              State* state,
+                                              std::string* error,
+                                              UpdateCheckParams* result) const {
+  SystemProvider* const system_provider = state->system_provider();
+
+  // If OOBE is enabled, wait until it is completed.
+  const bool* is_oobe_enabled_p =
+      ec->GetValue(state->config_provider()->var_is_oobe_enabled());
+  if (is_oobe_enabled_p && *is_oobe_enabled_p) {
+    const bool* is_oobe_complete_p =
+        ec->GetValue(system_provider->var_is_oobe_complete());
+    if (is_oobe_complete_p && !(*is_oobe_complete_p)) {
+      LOG(INFO) << "OOBE not completed, blocking update checks.";
+      return EvalStatus::kAskMeAgainLater;
+    }
+  }
+  return EvalStatus::kContinue;
+}
+
+}  // namespace chromeos_update_manager
diff --git a/update_manager/out_of_box_experience_policy_impl.h b/update_manager/out_of_box_experience_policy_impl.h
new file mode 100644
index 0000000..ec1997b
--- /dev/null
+++ b/update_manager/out_of_box_experience_policy_impl.h
@@ -0,0 +1,46 @@
+//
+// 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_OUT_OF_BOX_EXPERIENCE_POLICY_IMPL_H_
+#define UPDATE_ENGINE_UPDATE_MANAGER_OUT_OF_BOX_EXPERIENCE_POLICY_IMPL_H_
+
+#include <string>
+
+#include "update_engine/update_manager/policy_utils.h"
+
+namespace chromeos_update_manager {
+
+// If OOBE is enabled, wait until it is completed.
+class OobePolicyImpl : public PolicyImplBase {
+ public:
+  OobePolicyImpl() = default;
+  ~OobePolicyImpl() override = default;
+
+  std::string PolicyName() const override { return "OobePolicyImpl"; }
+
+  // Policy overrides.
+  EvalStatus UpdateCheckAllowed(EvaluationContext* ec,
+                                State* state,
+                                std::string* error,
+                                UpdateCheckParams* result) const override;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(OobePolicyImpl);
+};
+
+}  // namespace chromeos_update_manager
+
+#endif  // UPDATE_ENGINE_UPDATE_MANAGER_OUT_OF_BOX_EXPERIENCE_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_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;