Fix update_engine libchrome log tag. am: c3fd86a8be am: 84875e0c9b

Original change: https://googleplex-android-review.googlesource.com/c/platform/system/update_engine/+/11893835

Change-Id: I1d0b9dabcd3e6facc868eed3b595ff76a8a3ecfe
diff --git a/Android.bp b/Android.bp
index 3287b7b..a5223c7 100644
--- a/Android.bp
+++ b/Android.bp
@@ -123,15 +123,16 @@
         "libbz",
         "libbspatch",
         "libbrotli",
+        "libc++fs",
         "libfec_rs",
         "libpuffpatch",
         "libverity_tree",
     ],
     shared_libs: [
-        "libziparchive",
         "libbase",
         "libcrypto",
         "libfec",
+        "libziparchive",
     ],
 }
 
@@ -184,6 +185,7 @@
         "payload_consumer/verity_writer_android.cc",
         "payload_consumer/xz_extent_writer.cc",
         "payload_consumer/fec_file_descriptor.cc",
+        "payload_consumer/partition_update_generator_android.cc",
     ],
 }
 
@@ -730,6 +732,7 @@
         "payload_consumer/file_descriptor_utils_unittest.cc",
         "payload_consumer/file_writer_unittest.cc",
         "payload_consumer/filesystem_verifier_action_unittest.cc",
+        "payload_consumer/partition_update_generator_android_unittest.cc",
         "payload_consumer/postinstall_runner_action_unittest.cc",
         "payload_consumer/verity_writer_android_unittest.cc",
         "payload_consumer/xz_extent_writer_unittest.cc",
diff --git a/METADATA b/METADATA
new file mode 100644
index 0000000..d97975c
--- /dev/null
+++ b/METADATA
@@ -0,0 +1,3 @@
+third_party {
+  license_type: NOTICE
+}
diff --git a/boot_control_android.cc b/boot_control_android.cc
index ec2ca0f..dee5fa8 100644
--- a/boot_control_android.cc
+++ b/boot_control_android.cc
@@ -82,12 +82,24 @@
   return module_->getCurrentSlot();
 }
 
+bool BootControlAndroid::GetPartitionDevice(const std::string& partition_name,
+                                            BootControlInterface::Slot slot,
+                                            bool not_in_payload,
+                                            std::string* device,
+                                            bool* is_dynamic) const {
+  return dynamic_control_->GetPartitionDevice(partition_name,
+                                              slot,
+                                              GetCurrentSlot(),
+                                              not_in_payload,
+                                              device,
+                                              is_dynamic);
+}
 
 bool BootControlAndroid::GetPartitionDevice(const string& partition_name,
-                                            Slot slot,
+                                            BootControlInterface::Slot slot,
                                             string* device) const {
-  return dynamic_control_->GetPartitionDevice(
-      partition_name, slot, GetCurrentSlot(), device);
+  return GetPartitionDevice(
+      partition_name, slot, false /* not_in_payload */, device, nullptr);
 }
 
 bool BootControlAndroid::IsSlotBootable(Slot slot) const {
diff --git a/boot_control_android.h b/boot_control_android.h
index 0b042e3..5009dbd 100644
--- a/boot_control_android.h
+++ b/boot_control_android.h
@@ -46,6 +46,11 @@
   BootControlInterface::Slot GetCurrentSlot() const override;
   bool GetPartitionDevice(const std::string& partition_name,
                           BootControlInterface::Slot slot,
+                          bool not_in_payload,
+                          std::string* device,
+                          bool* is_dynamic) const override;
+  bool GetPartitionDevice(const std::string& partition_name,
+                          BootControlInterface::Slot slot,
                           std::string* device) const override;
   bool IsSlotBootable(BootControlInterface::Slot slot) const override;
   bool MarkSlotUnbootable(BootControlInterface::Slot slot) override;
diff --git a/boot_control_chromeos.cc b/boot_control_chromeos.cc
index 0f47169..da84e99 100644
--- a/boot_control_chromeos.cc
+++ b/boot_control_chromeos.cc
@@ -148,9 +148,11 @@
   return current_slot_;
 }
 
-bool BootControlChromeOS::GetPartitionDevice(const string& partition_name,
-                                             unsigned int slot,
-                                             string* device) const {
+bool BootControlChromeOS::GetPartitionDevice(const std::string& partition_name,
+                                             BootControlInterface::Slot slot,
+                                             bool not_in_payload,
+                                             std::string* device,
+                                             bool* is_dynamic) const {
   // Partition name prefixed with |kPartitionNamePrefixDlc| is a DLC module.
   if (base::StartsWith(partition_name,
                        kPartitionNamePrefixDlc,
@@ -180,9 +182,18 @@
     return false;
 
   *device = part_device;
+  if (is_dynamic) {
+    *is_dynamic = false;
+  }
   return true;
 }
 
+bool BootControlChromeOS::GetPartitionDevice(const string& partition_name,
+                                             BootControlInterface::Slot slot,
+                                             string* device) const {
+  return GetPartitionDevice(partition_name, slot, false, device, nullptr);
+}
+
 bool BootControlChromeOS::IsSlotBootable(Slot slot) const {
   int partition_num = GetPartitionNumber(kChromeOSPartitionNameKernel, slot);
   if (partition_num < 0)
diff --git a/boot_control_chromeos.h b/boot_control_chromeos.h
index 0209052..6edc148 100644
--- a/boot_control_chromeos.h
+++ b/boot_control_chromeos.h
@@ -47,6 +47,11 @@
   BootControlInterface::Slot GetCurrentSlot() const override;
   bool GetPartitionDevice(const std::string& partition_name,
                           BootControlInterface::Slot slot,
+                          bool not_in_payload,
+                          std::string* device,
+                          bool* is_dynamic) const override;
+  bool GetPartitionDevice(const std::string& partition_name,
+                          BootControlInterface::Slot slot,
                           std::string* device) const override;
   bool IsSlotBootable(BootControlInterface::Slot slot) const override;
   bool MarkSlotUnbootable(BootControlInterface::Slot slot) override;
diff --git a/cleanup_previous_update_action.cc b/cleanup_previous_update_action.cc
index 88dbc57..1a2476f 100644
--- a/cleanup_previous_update_action.cc
+++ b/cleanup_previous_update_action.cc
@@ -31,7 +31,7 @@
 #include "update_engine/payload_consumer/delta_performer.h"
 
 using android::base::GetBoolProperty;
-using android::snapshot::SnapshotManager;
+using android::snapshot::ISnapshotManager;
 using android::snapshot::SnapshotMergeStats;
 using android::snapshot::UpdateState;
 using brillo::MessageLoop;
@@ -56,7 +56,7 @@
 CleanupPreviousUpdateAction::CleanupPreviousUpdateAction(
     PrefsInterface* prefs,
     BootControlInterface* boot_control,
-    android::snapshot::SnapshotManager* snapshot,
+    android::snapshot::ISnapshotManager* snapshot,
     CleanupPreviousUpdateActionDelegateInterface* delegate)
     : prefs_(prefs),
       boot_control_(boot_control),
@@ -65,7 +65,7 @@
       running_(false),
       cancel_failed_(false),
       last_percentage_(0),
-      merge_stats_(SnapshotMergeStats::GetInstance(*snapshot)) {}
+      merge_stats_(nullptr) {}
 
 void CleanupPreviousUpdateAction::PerformAction() {
   ResumeAction();
@@ -111,8 +111,10 @@
     processor_->ActionComplete(this, ErrorCode::kSuccess);
     return;
   }
-  // SnapshotManager is only available on VAB devices.
-  CHECK(snapshot_);
+  // SnapshotManager must be available on VAB devices.
+  CHECK(snapshot_ != nullptr);
+  merge_stats_ = snapshot_->GetSnapshotMergeStatsInstance();
+  CHECK(merge_stats_ != nullptr);
   WaitBootCompletedOrSchedule();
 }
 
diff --git a/cleanup_previous_update_action.h b/cleanup_previous_update_action.h
index 91e08b0..6f6ce07 100644
--- a/cleanup_previous_update_action.h
+++ b/cleanup_previous_update_action.h
@@ -49,7 +49,7 @@
   CleanupPreviousUpdateAction(
       PrefsInterface* prefs,
       BootControlInterface* boot_control,
-      android::snapshot::SnapshotManager* snapshot,
+      android::snapshot::ISnapshotManager* snapshot,
       CleanupPreviousUpdateActionDelegateInterface* delegate);
 
   void PerformAction() override;
@@ -67,13 +67,13 @@
  private:
   PrefsInterface* prefs_;
   BootControlInterface* boot_control_;
-  android::snapshot::SnapshotManager* snapshot_;
+  android::snapshot::ISnapshotManager* snapshot_;
   CleanupPreviousUpdateActionDelegateInterface* delegate_;
   std::unique_ptr<android::snapshot::AutoDevice> metadata_device_;
   bool running_{false};
   bool cancel_failed_{false};
   unsigned int last_percentage_{0};
-  android::snapshot::SnapshotMergeStats* merge_stats_;
+  android::snapshot::ISnapshotMergeStats* merge_stats_;
 
   void StartActionInternal();
   void ScheduleWaitBootCompleted();
diff --git a/common/boot_control_interface.h b/common/boot_control_interface.h
index 3906e2f..c93de5c 100644
--- a/common/boot_control_interface.h
+++ b/common/boot_control_interface.h
@@ -59,8 +59,18 @@
   // every slot. In order to access the dynamic partitions in the target slot,
   // GetDynamicPartitionControl()->PreparePartitionsForUpdate() must be called
   // (with |update| == true for the first time for a payload, and |false| for
-  // for the rest of the times) prior to calling this function. On success,
-  // returns true and stores the block device in |device|.
+  // for the rest of the times) prior to calling this function.
+  // The handling may be different based on whether the partition is included
+  // in the update payload. On success, returns true; and stores the block
+  // device in |device|, if the partition is dynamic in |is_dynamic|.
+  virtual bool GetPartitionDevice(const std::string& partition_name,
+                                  Slot slot,
+                                  bool not_in_payload,
+                                  std::string* device,
+                                  bool* is_dynamic) const = 0;
+
+  // Overload of the above function. We assume the partition is always included
+  // in the payload.
   virtual bool GetPartitionDevice(const std::string& partition_name,
                                   Slot slot,
                                   std::string* device) const = 0;
diff --git a/common/boot_control_stub.cc b/common/boot_control_stub.cc
index 2eb9211..907f670 100644
--- a/common/boot_control_stub.cc
+++ b/common/boot_control_stub.cc
@@ -35,6 +35,15 @@
   return 0;
 }
 
+bool BootControlStub::GetPartitionDevice(const std::string& partition_name,
+                                         BootControlInterface::Slot slot,
+                                         bool not_in_payload,
+                                         std::string* device,
+                                         bool* is_dynamic) const {
+  LOG(ERROR) << __FUNCTION__ << " should never be called.";
+  return false;
+}
+
 bool BootControlStub::GetPartitionDevice(const string& partition_name,
                                          Slot slot,
                                          string* device) const {
diff --git a/common/boot_control_stub.h b/common/boot_control_stub.h
index cc16190..a1bdb96 100644
--- a/common/boot_control_stub.h
+++ b/common/boot_control_stub.h
@@ -41,6 +41,11 @@
   unsigned int GetNumSlots() const override;
   BootControlInterface::Slot GetCurrentSlot() const override;
   bool GetPartitionDevice(const std::string& partition_name,
+                          Slot slot,
+                          bool not_in_payload,
+                          std::string* device,
+                          bool* is_dynamic) const override;
+  bool GetPartitionDevice(const std::string& partition_name,
                           BootControlInterface::Slot slot,
                           std::string* device) const override;
   bool IsSlotBootable(BootControlInterface::Slot slot) const override;
diff --git a/common/dynamic_partition_control_interface.h b/common/dynamic_partition_control_interface.h
index 58ebfe4..7289dee 100644
--- a/common/dynamic_partition_control_interface.h
+++ b/common/dynamic_partition_control_interface.h
@@ -21,6 +21,7 @@
 
 #include <memory>
 #include <string>
+#include <vector>
 
 #include "update_engine/common/action.h"
 #include "update_engine/common/cleanup_previous_update_action_delegate.h"
@@ -118,6 +119,17 @@
   // progress, while ResetUpdate() forcefully free previously
   // allocated space for snapshot updates.
   virtual bool ResetUpdate(PrefsInterface* prefs) = 0;
+
+  // Reads the dynamic partitions metadata from the current slot, and puts the
+  // name of the dynamic partitions with the current suffix to |partitions|.
+  // Returns true on success.
+  virtual bool ListDynamicPartitionsForSlot(
+      uint32_t current_slot, std::vector<std::string>* partitions) = 0;
+
+  // Finds a possible location that list all block devices by name; and puts
+  // the result in |path|. Returns true on success.
+  // Sample result: /dev/block/by-name/
+  virtual bool GetDeviceDir(std::string* path) = 0;
 };
 
 }  // namespace chromeos_update_engine
diff --git a/common/dynamic_partition_control_stub.cc b/common/dynamic_partition_control_stub.cc
index 903b7ee..cde36af 100644
--- a/common/dynamic_partition_control_stub.cc
+++ b/common/dynamic_partition_control_stub.cc
@@ -67,4 +67,13 @@
   return false;
 }
 
+bool DynamicPartitionControlStub::ListDynamicPartitionsForSlot(
+    uint32_t current_slot, std::vector<std::string>* partitions) {
+  return true;
+}
+
+bool DynamicPartitionControlStub::GetDeviceDir(std::string* path) {
+  return true;
+}
+
 }  // namespace chromeos_update_engine
diff --git a/common/dynamic_partition_control_stub.h b/common/dynamic_partition_control_stub.h
index d8e254e..28e3e6a 100644
--- a/common/dynamic_partition_control_stub.h
+++ b/common/dynamic_partition_control_stub.h
@@ -21,6 +21,7 @@
 
 #include <memory>
 #include <string>
+#include <vector>
 
 #include "update_engine/common/dynamic_partition_control_interface.h"
 
@@ -46,6 +47,10 @@
       PrefsInterface* prefs,
       CleanupPreviousUpdateActionDelegateInterface* delegate) override;
   bool ResetUpdate(PrefsInterface* prefs) override;
+
+  bool ListDynamicPartitionsForSlot(
+      uint32_t current_slot, std::vector<std::string>* partitions) override;
+  bool GetDeviceDir(std::string* path) override;
 };
 
 }  // namespace chromeos_update_engine
diff --git a/common/fake_boot_control.h b/common/fake_boot_control.h
index bd9d9ca..adbacd6 100644
--- a/common/fake_boot_control.h
+++ b/common/fake_boot_control.h
@@ -48,7 +48,9 @@
 
   bool GetPartitionDevice(const std::string& partition_name,
                           BootControlInterface::Slot slot,
-                          std::string* device) const override {
+                          bool not_in_payload,
+                          std::string* device,
+                          bool* is_dynamic) const override {
     if (slot >= num_slots_)
       return false;
     auto part_it = devices_[slot].find(partition_name);
@@ -58,6 +60,12 @@
     return true;
   }
 
+  bool GetPartitionDevice(const std::string& partition_name,
+                          BootControlInterface::Slot slot,
+                          std::string* device) const override {
+    return GetPartitionDevice(partition_name, slot, false, device, nullptr);
+  }
+
   bool IsSlotBootable(BootControlInterface::Slot slot) const override {
     return slot < num_slots_ && is_bootable_[slot];
   }
diff --git a/dynamic_partition_control_android.cc b/dynamic_partition_control_android.cc
index ecd6252..6817c21 100644
--- a/dynamic_partition_control_android.cc
+++ b/dynamic_partition_control_android.cc
@@ -21,6 +21,8 @@
 #include <memory>
 #include <set>
 #include <string>
+#include <string_view>
+#include <utility>
 #include <vector>
 
 #include <android-base/properties.h>
@@ -35,6 +37,7 @@
 #include <libavb/libavb.h>
 #include <libdm/dm.h>
 #include <libsnapshot/snapshot.h>
+#include <libsnapshot/snapshot_stub.h>
 
 #include "update_engine/cleanup_previous_update_action.h"
 #include "update_engine/common/boot_control_interface.h"
@@ -58,6 +61,7 @@
 using android::snapshot::OptimizeSourceCopyOperation;
 using android::snapshot::Return;
 using android::snapshot::SnapshotManager;
+using android::snapshot::SnapshotManagerStub;
 using android::snapshot::UpdateState;
 
 namespace chromeos_update_engine {
@@ -108,8 +112,10 @@
       virtual_ab_(GetFeatureFlag(kVirtualAbEnabled, kVirtualAbRetrofit)) {
   if (GetVirtualAbFeatureFlag().IsEnabled()) {
     snapshot_ = SnapshotManager::New();
-    CHECK(snapshot_ != nullptr) << "Cannot initialize SnapshotManager.";
+  } else {
+    snapshot_ = SnapshotManagerStub::New();
   }
+  CHECK(snapshot_ != nullptr) << "Cannot initialize SnapshotManager.";
 }
 
 FeatureFlag DynamicPartitionControlAndroid::GetDynamicPartitionsFeatureFlag() {
@@ -489,8 +495,13 @@
     }
   }
 
-  return PrepareDynamicPartitionsForUpdate(
-      source_slot, target_slot, manifest, delete_source);
+  TEST_AND_RETURN_FALSE(PrepareDynamicPartitionsForUpdate(
+      source_slot, target_slot, manifest, delete_source));
+
+  if (required_size != nullptr) {
+    *required_size = 0;
+  }
+  return true;
 }
 
 namespace {
@@ -874,22 +885,35 @@
     const std::string& partition_name,
     uint32_t slot,
     uint32_t current_slot,
-    std::string* device) {
+    bool not_in_payload,
+    std::string* device,
+    bool* is_dynamic) {
   const auto& partition_name_suffix =
       partition_name + SlotSuffixForSlotNumber(slot);
   std::string device_dir_str;
   TEST_AND_RETURN_FALSE(GetDeviceDir(&device_dir_str));
   base::FilePath device_dir(device_dir_str);
 
+  if (is_dynamic) {
+    *is_dynamic = false;
+  }
+
   // When looking up target partition devices, treat them as static if the
   // current payload doesn't encode them as dynamic partitions. This may happen
   // when applying a retrofit update on top of a dynamic-partitions-enabled
   // build.
   if (GetDynamicPartitionsFeatureFlag().IsEnabled() &&
       (slot == current_slot || is_target_dynamic_)) {
-    switch (GetDynamicPartitionDevice(
-        device_dir, partition_name_suffix, slot, current_slot, device)) {
+    switch (GetDynamicPartitionDevice(device_dir,
+                                      partition_name_suffix,
+                                      slot,
+                                      current_slot,
+                                      not_in_payload,
+                                      device)) {
       case DynamicPartitionDeviceStatus::SUCCESS:
+        if (is_dynamic) {
+          *is_dynamic = true;
+        }
         return true;
       case DynamicPartitionDeviceStatus::TRY_STATIC:
         break;
@@ -908,6 +932,15 @@
   return true;
 }
 
+bool DynamicPartitionControlAndroid::GetPartitionDevice(
+    const std::string& partition_name,
+    uint32_t slot,
+    uint32_t current_slot,
+    std::string* device) {
+  return GetPartitionDevice(
+      partition_name, slot, current_slot, false, device, nullptr);
+}
+
 bool DynamicPartitionControlAndroid::IsSuperBlockDevice(
     const base::FilePath& device_dir,
     uint32_t current_slot,
@@ -924,6 +957,7 @@
     const std::string& partition_name_suffix,
     uint32_t slot,
     uint32_t current_slot,
+    bool not_in_payload,
     std::string* device) {
   std::string super_device =
       device_dir.Append(GetSuperPartitionName(slot)).value();
@@ -963,7 +997,7 @@
     }
   }
 
-  bool force_writable = slot != current_slot;
+  bool force_writable = (slot != current_slot) && !not_in_payload;
   if (MapPartitionOnDeviceMapper(
           super_device, partition_name_suffix, slot, force_writable, device)) {
     return DynamicPartitionDeviceStatus::SUCCESS;
@@ -1049,6 +1083,36 @@
   return true;
 }
 
+bool DynamicPartitionControlAndroid::ListDynamicPartitionsForSlot(
+    uint32_t current_slot, std::vector<std::string>* partitions) {
+  if (!GetDynamicPartitionsFeatureFlag().IsEnabled()) {
+    LOG(ERROR) << "Dynamic partition is not enabled";
+    return false;
+  }
+
+  std::string device_dir_str;
+  TEST_AND_RETURN_FALSE(GetDeviceDir(&device_dir_str));
+  base::FilePath device_dir(device_dir_str);
+  auto super_device =
+      device_dir.Append(GetSuperPartitionName(current_slot)).value();
+  auto builder = LoadMetadataBuilder(super_device, current_slot);
+  TEST_AND_RETURN_FALSE(builder != nullptr);
+
+  std::vector<std::string> result;
+  auto suffix = SlotSuffixForSlotNumber(current_slot);
+  for (const auto& group : builder->ListGroups()) {
+    for (const auto& partition : builder->ListPartitionsInGroup(group)) {
+      std::string_view partition_name = partition->name();
+      if (!android::base::ConsumeSuffix(&partition_name, suffix)) {
+        continue;
+      }
+      result.emplace_back(partition_name);
+    }
+  }
+  *partitions = std::move(result);
+  return true;
+}
+
 bool DynamicPartitionControlAndroid::ExpectMetadataMounted() {
   // No need to mount metadata for non-Virtual A/B devices.
   if (!GetVirtualAbFeatureFlag().IsEnabled()) {
diff --git a/dynamic_partition_control_android.h b/dynamic_partition_control_android.h
index 8ad7593..69026a4 100644
--- a/dynamic_partition_control_android.h
+++ b/dynamic_partition_control_android.h
@@ -20,6 +20,7 @@
 #include <memory>
 #include <set>
 #include <string>
+#include <vector>
 
 #include <base/files/file_util.h>
 #include <libsnapshot/auto_device.h>
@@ -53,6 +54,11 @@
 
   bool ResetUpdate(PrefsInterface* prefs) override;
 
+  bool ListDynamicPartitionsForSlot(
+      uint32_t current_slot, std::vector<std::string>* partitions) override;
+
+  bool GetDeviceDir(std::string* path) override;
+
   // Return the device for partition |partition_name| at slot |slot|.
   // |current_slot| should be set to the current active slot.
   // Note: this function is only used by BootControl*::GetPartitionDevice.
@@ -61,6 +67,13 @@
   bool GetPartitionDevice(const std::string& partition_name,
                           uint32_t slot,
                           uint32_t current_slot,
+                          bool not_in_payload,
+                          std::string* device,
+                          bool* is_dynamic);
+
+  bool GetPartitionDevice(const std::string& partition_name,
+                          uint32_t slot,
+                          uint32_t current_slot,
                           std::string* device);
 
  protected:
@@ -124,9 +137,6 @@
   virtual std::unique_ptr<android::fs_mgr::MetadataBuilder> LoadMetadataBuilder(
       const std::string& super_device, uint32_t source_slot);
 
-  // Return a possible location for devices listed by name.
-  virtual bool GetDeviceDir(std::string* path);
-
   // Return the name of the super partition (which stores super partition
   // metadata) for a given slot.
   virtual std::string GetSuperPartitionName(uint32_t slot);
@@ -173,8 +183,19 @@
   virtual bool EraseSystemOtherAvbFooter(uint32_t source_slot,
                                          uint32_t target_slot);
 
+  // Helper for PreparePartitionsForUpdate. Used for devices with dynamic
+  // partitions updating without snapshots.
+  // If |delete_source| is set, source partitions are deleted before resizing
+  // target partitions (using DeleteSourcePartitions).
+  virtual bool PrepareDynamicPartitionsForUpdate(
+      uint32_t source_slot,
+      uint32_t target_slot,
+      const DeltaArchiveManifest& manifest,
+      bool delete_source);
+
  private:
   friend class DynamicPartitionControlAndroidTest;
+  friend class SnapshotPartitionTestP;
 
   void UnmapAllPartitions();
   bool MapPartitionInternal(const std::string& super_device,
@@ -189,15 +210,6 @@
                                uint32_t target_slot,
                                const DeltaArchiveManifest& manifest);
 
-  // Helper for PreparePartitionsForUpdate. Used for devices with dynamic
-  // partitions updating without snapshots.
-  // If |delete_source| is set, source partitions are deleted before resizing
-  // target partitions (using DeleteSourcePartitions).
-  bool PrepareDynamicPartitionsForUpdate(uint32_t source_slot,
-                                         uint32_t target_slot,
-                                         const DeltaArchiveManifest& manifest,
-                                         bool delete_source);
-
   // Helper for PreparePartitionsForUpdate. Used for snapshotted partitions for
   // Virtual A/B update.
   bool PrepareSnapshotPartitionsForUpdate(uint32_t source_slot,
@@ -220,6 +232,7 @@
       const std::string& partition_name_suffix,
       uint32_t slot,
       uint32_t current_slot,
+      bool not_in_payload,
       std::string* device);
 
   // Return true if |partition_name_suffix| is a block device of
@@ -258,7 +271,7 @@
   std::set<std::string> mapped_devices_;
   const FeatureFlag dynamic_partitions_;
   const FeatureFlag virtual_ab_;
-  std::unique_ptr<android::snapshot::SnapshotManager> snapshot_;
+  std::unique_ptr<android::snapshot::ISnapshotManager> snapshot_;
   std::unique_ptr<android::snapshot::AutoDevice> metadata_device_;
   bool target_supports_snapshot_ = false;
   // Whether the target partitions should be loaded as dynamic partitions. Set
diff --git a/dynamic_partition_control_android_unittest.cc b/dynamic_partition_control_android_unittest.cc
index 2081918..3738170 100644
--- a/dynamic_partition_control_android_unittest.cc
+++ b/dynamic_partition_control_android_unittest.cc
@@ -24,6 +24,7 @@
 #include <gmock/gmock.h>
 #include <gtest/gtest.h>
 #include <libavb/libavb.h>
+#include <libsnapshot/mock_snapshot.h>
 
 #include "update_engine/common/mock_prefs.h"
 #include "update_engine/common/test_utils.h"
@@ -31,6 +32,7 @@
 #include "update_engine/mock_dynamic_partition_control.h"
 
 using android::dm::DmDeviceState;
+using android::snapshot::MockSnapshotManager;
 using chromeos_update_engine::test_utils::ScopedLoopbackDeviceBinder;
 using chromeos_update_engine::test_utils::ScopedTempFile;
 using std::string;
@@ -72,6 +74,17 @@
 
     ON_CALL(dynamicControl(), EraseSystemOtherAvbFooter(_, _))
         .WillByDefault(Return(true));
+
+    ON_CALL(dynamicControl(), IsRecovery()).WillByDefault(Return(false));
+
+    ON_CALL(dynamicControl(), PrepareDynamicPartitionsForUpdate(_, _, _, _))
+        .WillByDefault(Invoke([&](uint32_t source_slot,
+                                  uint32_t target_slot,
+                                  const DeltaArchiveManifest& manifest,
+                                  bool delete_source) {
+          return dynamicControl().RealPrepareDynamicPartitionsForUpdate(
+              source_slot, target_slot, manifest, delete_source);
+        }));
   }
 
   // Return the mocked DynamicPartitionControlInterface.
@@ -892,4 +905,112 @@
   ASSERT_EQ(new_expected, device_content);
 }
 
+class FakeAutoDevice : public android::snapshot::AutoDevice {
+ public:
+  FakeAutoDevice() : AutoDevice("") {}
+};
+
+class SnapshotPartitionTestP : public DynamicPartitionControlAndroidTestP {
+ public:
+  void SetUp() override {
+    DynamicPartitionControlAndroidTestP::SetUp();
+    ON_CALL(dynamicControl(), GetVirtualAbFeatureFlag())
+        .WillByDefault(Return(FeatureFlag(FeatureFlag::Value::LAUNCH)));
+
+    snapshot_ = new NiceMock<MockSnapshotManager>();
+    dynamicControl().snapshot_.reset(snapshot_);  // takes ownership
+    EXPECT_CALL(*snapshot_, BeginUpdate()).WillOnce(Return(true));
+    EXPECT_CALL(*snapshot_, EnsureMetadataMounted())
+        .WillRepeatedly(
+            Invoke([]() { return std::make_unique<FakeAutoDevice>(); }));
+
+    manifest_ =
+        PartitionSizesToManifest({{"system", 3_GiB}, {"vendor", 1_GiB}});
+  }
+  void ExpectCreateUpdateSnapshots(android::snapshot::Return val) {
+    manifest_.mutable_dynamic_partition_metadata()->set_snapshot_enabled(true);
+    EXPECT_CALL(*snapshot_, CreateUpdateSnapshots(_))
+        .WillRepeatedly(Invoke([&, val](const auto& manifest) {
+          // Deep comparison requires full protobuf library. Comparing the
+          // pointers are sufficient.
+          EXPECT_EQ(&manifest_, &manifest);
+          LOG(WARNING) << "CreateUpdateSnapshots returning " << val.string();
+          return val;
+        }));
+  }
+  bool PreparePartitionsForUpdate(uint64_t* required_size) {
+    return dynamicControl().PreparePartitionsForUpdate(
+        source(), target(), manifest_, true /* update */, required_size);
+  }
+  MockSnapshotManager* snapshot_ = nullptr;
+  DeltaArchiveManifest manifest_;
+};
+
+// Test happy path of PreparePartitionsForUpdate on a Virtual A/B device.
+TEST_P(SnapshotPartitionTestP, PreparePartitions) {
+  ExpectCreateUpdateSnapshots(android::snapshot::Return::Ok());
+  uint64_t required_size = 0;
+  EXPECT_TRUE(PreparePartitionsForUpdate(&required_size));
+  EXPECT_EQ(0u, required_size);
+}
+
+// Test that if not enough space, required size returned by SnapshotManager is
+// passed up.
+TEST_P(SnapshotPartitionTestP, PreparePartitionsNoSpace) {
+  ExpectCreateUpdateSnapshots(android::snapshot::Return::NoSpace(1_GiB));
+  uint64_t required_size = 0;
+  EXPECT_FALSE(PreparePartitionsForUpdate(&required_size));
+  EXPECT_EQ(1_GiB, required_size);
+}
+
+// Test that in recovery, use empty space in super partition for a snapshot
+// update first.
+TEST_P(SnapshotPartitionTestP, RecoveryUseSuperEmpty) {
+  ExpectCreateUpdateSnapshots(android::snapshot::Return::Ok());
+  EXPECT_CALL(dynamicControl(), IsRecovery()).WillRepeatedly(Return(true));
+  // Must not call PrepareDynamicPartitionsForUpdate if
+  // PrepareSnapshotPartitionsForUpdate succeeds.
+  EXPECT_CALL(dynamicControl(), PrepareDynamicPartitionsForUpdate(_, _, _, _))
+      .Times(0);
+  uint64_t required_size = 0;
+  EXPECT_TRUE(PreparePartitionsForUpdate(&required_size));
+  EXPECT_EQ(0u, required_size);
+}
+
+// Test that in recovery, if CreateUpdateSnapshots throws an error, try
+// the flashing path for full updates.
+TEST_P(SnapshotPartitionTestP, RecoveryErrorShouldDeleteSource) {
+  // Expectation on PreparePartitionsForUpdate
+  ExpectCreateUpdateSnapshots(android::snapshot::Return::NoSpace(1_GiB));
+  EXPECT_CALL(dynamicControl(), IsRecovery()).WillRepeatedly(Return(true));
+  EXPECT_CALL(*snapshot_, CancelUpdate()).WillOnce(Return(true));
+  EXPECT_CALL(dynamicControl(), PrepareDynamicPartitionsForUpdate(_, _, _, _))
+      .WillRepeatedly(Invoke([&](auto source_slot,
+                                 auto target_slot,
+                                 const auto& manifest,
+                                 auto delete_source) {
+        EXPECT_EQ(source(), source_slot);
+        EXPECT_EQ(target(), target_slot);
+        // Deep comparison requires full protobuf library. Comparing the
+        // pointers are sufficient.
+        EXPECT_EQ(&manifest_, &manifest);
+        EXPECT_TRUE(delete_source);
+        return dynamicControl().RealPrepareDynamicPartitionsForUpdate(
+            source_slot, target_slot, manifest, delete_source);
+      }));
+  // Expectation on PrepareDynamicPartitionsForUpdate
+  SetMetadata(source(), {{S("system"), 2_GiB}, {S("vendor"), 1_GiB}});
+  ExpectUnmap({T("system"), T("vendor")});
+  // Expect that the source partitions aren't present in target super metadata.
+  ExpectStoreMetadata({{T("system"), 3_GiB}, {T("vendor"), 1_GiB}});
+
+  uint64_t required_size = 0;
+  EXPECT_TRUE(PreparePartitionsForUpdate(&required_size));
+  EXPECT_EQ(0u, required_size);
+}
+
+INSTANTIATE_TEST_CASE_P(DynamicPartitionControlAndroidTest,
+                        SnapshotPartitionTestP,
+                        testing::Values(TestParam{0, 1}, TestParam{1, 0}));
+
 }  // namespace chromeos_update_engine
diff --git a/mock_dynamic_partition_control.h b/mock_dynamic_partition_control.h
index 1e4e5fd..1aaebd8 100644
--- a/mock_dynamic_partition_control.h
+++ b/mock_dynamic_partition_control.h
@@ -28,60 +28,55 @@
 
 namespace chromeos_update_engine {
 
-class MockDynamicPartitionControl : public DynamicPartitionControlInterface {
- public:
-  MOCK_METHOD5(MapPartitionOnDeviceMapper,
-               bool(const std::string&,
-                    const std::string&,
-                    uint32_t,
-                    bool,
-                    std::string*));
-  MOCK_METHOD0(Cleanup, void());
-  MOCK_METHOD0(GetDynamicPartitionsFeatureFlag, FeatureFlag());
-  MOCK_METHOD5(
-      PreparePartitionsForUpdate,
-      bool(uint32_t, uint32_t, const DeltaArchiveManifest&, bool, uint64_t*));
-  MOCK_METHOD0(GetVirtualAbFeatureFlag, FeatureFlag());
-  MOCK_METHOD1(FinishUpdate, bool(bool));
-  MOCK_METHOD0(CleanupSuccessfulUpdate, ErrorCode());
-  MOCK_METHOD3(GetCleanupPreviousUpdateAction,
-               std::unique_ptr<AbstractAction>(
-                   BootControlInterface*,
-                   PrefsInterface*,
-                   CleanupPreviousUpdateActionDelegateInterface*));
-};
-
 class MockDynamicPartitionControlAndroid
     : public DynamicPartitionControlAndroid {
  public:
-  MOCK_METHOD5(MapPartitionOnDeviceMapper,
-               bool(const std::string&,
-                    const std::string&,
-                    uint32_t,
-                    bool,
-                    std::string*));
-  MOCK_METHOD1(UnmapPartitionOnDeviceMapper, bool(const std::string&));
-  MOCK_METHOD0(Cleanup, void());
-  MOCK_METHOD1(DeviceExists, bool(const std::string&));
-  MOCK_METHOD1(GetState, ::android::dm::DmDeviceState(const std::string&));
-  MOCK_METHOD2(GetDmDevicePathByName, bool(const std::string&, std::string*));
-  MOCK_METHOD3(LoadMetadataBuilder,
-               std::unique_ptr<::android::fs_mgr::MetadataBuilder>(
-                   const std::string&, uint32_t, uint32_t));
-  MOCK_METHOD3(StoreMetadata,
-               bool(const std::string&,
-                    android::fs_mgr::MetadataBuilder*,
-                    uint32_t));
-  MOCK_METHOD1(GetDeviceDir, bool(std::string*));
-  MOCK_METHOD0(GetDynamicPartitionsFeatureFlag, FeatureFlag());
-  MOCK_METHOD1(GetSuperPartitionName, std::string(uint32_t));
-  MOCK_METHOD0(GetVirtualAbFeatureFlag, FeatureFlag());
-  MOCK_METHOD1(FinishUpdate, bool(bool));
-  MOCK_METHOD5(
-      GetSystemOtherPath,
-      bool(uint32_t, uint32_t, const std::string&, std::string*, bool*));
-  MOCK_METHOD2(EraseSystemOtherAvbFooter, bool(uint32_t, uint32_t));
-  MOCK_METHOD0(IsAvbEnabledOnSystemOther, std::optional<bool>());
+  MOCK_METHOD(
+      bool,
+      MapPartitionOnDeviceMapper,
+      (const std::string&, const std::string&, uint32_t, bool, std::string*),
+      (override));
+  MOCK_METHOD(bool,
+              UnmapPartitionOnDeviceMapper,
+              (const std::string&),
+              (override));
+  MOCK_METHOD(void, Cleanup, (), (override));
+  MOCK_METHOD(bool, DeviceExists, (const std::string&), (override));
+  MOCK_METHOD(::android::dm::DmDeviceState,
+              GetState,
+              (const std::string&),
+              (override));
+  MOCK_METHOD(bool,
+              GetDmDevicePathByName,
+              (const std::string&, std::string*),
+              (override));
+  MOCK_METHOD(std::unique_ptr<::android::fs_mgr::MetadataBuilder>,
+              LoadMetadataBuilder,
+              (const std::string&, uint32_t, uint32_t),
+              (override));
+  MOCK_METHOD(bool,
+              StoreMetadata,
+              (const std::string&, android::fs_mgr::MetadataBuilder*, uint32_t),
+              (override));
+  MOCK_METHOD(bool, GetDeviceDir, (std::string*), (override));
+  MOCK_METHOD(FeatureFlag, GetDynamicPartitionsFeatureFlag, (), (override));
+  MOCK_METHOD(std::string, GetSuperPartitionName, (uint32_t), (override));
+  MOCK_METHOD(FeatureFlag, GetVirtualAbFeatureFlag, (), (override));
+  MOCK_METHOD(bool, FinishUpdate, (bool), (override));
+  MOCK_METHOD(bool,
+              GetSystemOtherPath,
+              (uint32_t, uint32_t, const std::string&, std::string*, bool*),
+              (override));
+  MOCK_METHOD(bool,
+              EraseSystemOtherAvbFooter,
+              (uint32_t, uint32_t),
+              (override));
+  MOCK_METHOD(std::optional<bool>, IsAvbEnabledOnSystemOther, (), (override));
+  MOCK_METHOD(bool, IsRecovery, (), (override));
+  MOCK_METHOD(bool,
+              PrepareDynamicPartitionsForUpdate,
+              (uint32_t, uint32_t, const DeltaArchiveManifest&, bool),
+              (override));
 
   void set_fake_mapped_devices(const std::set<std::string>& fake) override {
     DynamicPartitionControlAndroid::set_fake_mapped_devices(fake);
@@ -105,6 +100,15 @@
   std::optional<bool> RealIsAvbEnabledInFstab(const std::string& path) {
     return DynamicPartitionControlAndroid::IsAvbEnabledInFstab(path);
   }
+
+  bool RealPrepareDynamicPartitionsForUpdate(
+      uint32_t source_slot,
+      uint32_t target_slot,
+      const DeltaArchiveManifest& manifest,
+      bool delete_source) {
+    return DynamicPartitionControlAndroid::PrepareDynamicPartitionsForUpdate(
+        source_slot, target_slot, manifest, delete_source);
+  }
 };
 
 }  // namespace chromeos_update_engine
diff --git a/payload_consumer/delta_performer.cc b/payload_consumer/delta_performer.cc
index 4c4ff04..d9b739d 100644
--- a/payload_consumer/delta_performer.cc
+++ b/payload_consumer/delta_performer.cc
@@ -23,6 +23,7 @@
 #include <cstring>
 #include <map>
 #include <memory>
+#include <set>
 #include <string>
 #include <utility>
 #include <vector>
@@ -50,6 +51,7 @@
 #include "update_engine/payload_consumer/download_action.h"
 #include "update_engine/payload_consumer/extent_reader.h"
 #include "update_engine/payload_consumer/extent_writer.h"
+#include "update_engine/payload_consumer/partition_update_generator_interface.h"
 #if USE_FEC
 #include "update_engine/payload_consumer/fec_file_descriptor.h"
 #endif  // USE_FEC
@@ -357,12 +359,15 @@
       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 (payload_->type == InstallPayloadType::kDelta &&
-      GetMinorVersion() != kInPlaceMinorPayloadVersion &&
-      // With dynamic partitions we could create a new partition in a
-      // delta payload, and we shouldn't open source partition in that case.
-      install_part.source_size > 0) {
+  // Open source fds if we have a delta payload with minor version >= 2, or for
+  // partitions in the partial update.
+  bool source_may_exist = manifest_.partial_update() ||
+                          (payload_->type == InstallPayloadType::kDelta &&
+                           GetMinorVersion() != kInPlaceMinorPayloadVersion);
+  // We shouldn't open the source partition in certain cases, e.g. some dynamic
+  // partitions in delta payload, partitions included in the full payload for
+  // partial updates. Use the source size as the indicator.
+  if (source_may_exist && install_part.source_size > 0) {
     source_path_ = install_part.source_path;
     int err;
     source_fd_ = OpenFile(source_path_.c_str(), O_RDONLY, false, &err);
@@ -851,6 +856,42 @@
     partitions_.push_back(std::move(kern_part));
   }
 
+  // For VAB and partial updates, the partition preparation will copy the
+  // dynamic partitions metadata to the target metadata slot, and rename the
+  // slot suffix of the partitions in the metadata.
+  if (install_plan_->target_slot != BootControlInterface::kInvalidSlot) {
+    uint64_t required_size = 0;
+    if (!PreparePartitionsForUpdate(&required_size)) {
+      if (required_size > 0) {
+        *error = ErrorCode::kNotEnoughSpace;
+      } else {
+        *error = ErrorCode::kInstallDeviceOpenError;
+      }
+      return false;
+    }
+  }
+
+  // TODO(xunchang) TBD: allow partial update only on devices with dynamic
+  // partition.
+  if (manifest_.partial_update()) {
+    std::set<std::string> touched_partitions;
+    for (const auto& partition_update : partitions_) {
+      touched_partitions.insert(partition_update.partition_name());
+    }
+
+    auto generator = partition_update_generator::Create(boot_control_,
+                                                        manifest_.block_size());
+    std::vector<PartitionUpdate> other_partitions;
+    TEST_AND_RETURN_FALSE(
+        generator->GenerateOperationsForPartitionsNotInPayload(
+            install_plan_->source_slot,
+            install_plan_->target_slot,
+            touched_partitions,
+            &other_partitions));
+    partitions_.insert(
+        partitions_.end(), other_partitions.begin(), other_partitions.end());
+  }
+
   // Fill in the InstallPlan::partitions based on the partitions from the
   // payload.
   for (const auto& partition : partitions_) {
@@ -924,22 +965,13 @@
     install_plan_->partitions.push_back(install_part);
   }
 
-  if (install_plan_->target_slot != BootControlInterface::kInvalidSlot) {
-    uint64_t required_size = 0;
-    if (!PreparePartitionsForUpdate(&required_size)) {
-      if (required_size > 0) {
-        *error = ErrorCode::kNotEnoughSpace;
-      } else {
-        *error = ErrorCode::kInstallDeviceOpenError;
-      }
-      return false;
-    }
-  }
-
   if (major_payload_version_ == kBrilloMajorPayloadVersion) {
     manifest_.clear_partitions();
   }
 
+  // TODO(xunchang) only need to load the partitions for those in payload.
+  // Because we have already loaded the other once when generating SOURCE_COPY
+  // operations.
   if (!install_plan_->LoadPartitionsFromSlots(boot_control_)) {
     LOG(ERROR) << "Unable to determine all the partition devices.";
     *error = ErrorCode::kInstallDeviceOpenError;
@@ -1712,7 +1744,6 @@
 ErrorCode DeltaPerformer::ValidateManifest() {
   // Perform assorted checks to sanity check the manifest, make sure it
   // matches data from other sources, and that it is a supported version.
-
   bool has_old_fields =
       (manifest_.has_old_kernel_info() || manifest_.has_old_rootfs_info());
   for (const PartitionUpdate& partition : manifest_.partitions()) {
@@ -1737,8 +1768,8 @@
                << "' payload.";
     return ErrorCode::kPayloadMismatchedType;
   }
-
   // Check that the minor version is compatible.
+  // TODO(xunchang) increment minor version & add check for partial update
   if (actual_payload_type == InstallPayloadType::kFull) {
     if (manifest_.minor_version() != kFullPayloadMinorVersion) {
       LOG(ERROR) << "Manifest contains minor version "
diff --git a/payload_consumer/partition_update_generator_android.cc b/payload_consumer/partition_update_generator_android.cc
new file mode 100644
index 0000000..aa3f2e5
--- /dev/null
+++ b/payload_consumer/partition_update_generator_android.cc
@@ -0,0 +1,257 @@
+//
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#include "update_engine/payload_consumer/partition_update_generator_android.h"
+
+#include <filesystem>
+#include <memory>
+#include <set>
+#include <string_view>
+#include <utility>
+
+#include <android-base/strings.h>
+#include <base/logging.h>
+
+#include "update_engine/common/hash_calculator.h"
+#include "update_engine/common/utils.h"
+
+namespace {
+// TODO(xunchang) use definition in fs_mgr, e.g. fs_mgr_get_slot_suffix
+const char* SUFFIX_A = "_a";
+const char* SUFFIX_B = "_b";
+}  // namespace
+
+namespace chromeos_update_engine {
+
+PartitionUpdateGeneratorAndroid::PartitionUpdateGeneratorAndroid(
+    BootControlInterface* boot_control,
+    std::string device_dir,
+    size_t block_size)
+    : boot_control_(boot_control),
+      block_device_dir_(std::move(device_dir)),
+      block_size_(block_size) {}
+
+bool PartitionUpdateGeneratorAndroid::
+    GenerateOperationsForPartitionsNotInPayload(
+        BootControlInterface::Slot source_slot,
+        BootControlInterface::Slot target_slot,
+        const std::set<std::string>& partitions_in_payload,
+        std::vector<PartitionUpdate>* update_list) {
+  auto ret = GetStaticAbPartitionsOnDevice();
+  if (!ret.has_value()) {
+    LOG(ERROR) << "Failed to load static a/b partitions";
+    return false;
+  }
+  auto ab_partitions = ret.value();
+
+  // Add the dynamic partitions.
+  auto dynamic_control = boot_control_->GetDynamicPartitionControl();
+  std::vector<std::string> dynamic_partitions;
+  if (!dynamic_control->ListDynamicPartitionsForSlot(source_slot,
+                                                     &dynamic_partitions)) {
+    LOG(ERROR) << "Failed to load dynamic partitions from slot " << source_slot;
+    return false;
+  }
+  ab_partitions.insert(dynamic_partitions.begin(), dynamic_partitions.end());
+
+  std::vector<PartitionUpdate> partition_updates;
+  for (const auto& partition_name : ab_partitions) {
+    if (partitions_in_payload.find(partition_name) !=
+        partitions_in_payload.end()) {
+      LOG(INFO) << partition_name << " has included in payload";
+      continue;
+    }
+
+    auto partition_update =
+        CreatePartitionUpdate(partition_name, source_slot, target_slot);
+    if (!partition_update.has_value()) {
+      LOG(ERROR) << "Failed to create partition update for " << partition_name;
+      return false;
+    }
+    partition_updates.push_back(partition_update.value());
+  }
+  *update_list = std::move(partition_updates);
+  return true;
+}
+
+std::optional<std::set<std::string>>
+PartitionUpdateGeneratorAndroid::GetStaticAbPartitionsOnDevice() {
+  if (std::error_code error_code;
+      !std::filesystem::exists(block_device_dir_, error_code) || error_code) {
+    LOG(ERROR) << "Failed to find " << block_device_dir_ << " "
+               << error_code.message();
+    return std::nullopt;
+  }
+
+  std::error_code error_code;
+  auto it = std::filesystem::directory_iterator(block_device_dir_, error_code);
+  if (error_code) {
+    LOG(ERROR) << "Failed to iterate " << block_device_dir_ << " "
+               << error_code.message();
+    return std::nullopt;
+  }
+
+  std::set<std::string> partitions_with_suffix;
+  for (const auto& entry : it) {
+    auto partition_name = entry.path().filename().string();
+    if (android::base::EndsWith(partition_name, SUFFIX_A) ||
+        android::base::EndsWith(partition_name, SUFFIX_B)) {
+      partitions_with_suffix.insert(partition_name);
+    }
+  }
+
+  // Second iteration to add the partition name without suffixes.
+  std::set<std::string> ab_partitions;
+  for (std::string_view name : partitions_with_suffix) {
+    if (!android::base::ConsumeSuffix(&name, SUFFIX_A)) {
+      continue;
+    }
+
+    // Add to the output list if the partition exist for both slot a and b.
+    auto base_name = std::string(name);
+    if (partitions_with_suffix.find(base_name + SUFFIX_B) !=
+        partitions_with_suffix.end()) {
+      ab_partitions.insert(base_name);
+    } else {
+      LOG(WARNING) << "Failed to find the b partition for " << base_name;
+    }
+  }
+
+  return ab_partitions;
+}
+
+std::optional<PartitionUpdate>
+PartitionUpdateGeneratorAndroid::CreatePartitionUpdate(
+    const std::string& partition_name,
+    BootControlInterface::Slot source_slot,
+    BootControlInterface::Slot target_slot) {
+  bool is_source_dynamic = false;
+  std::string source_device;
+  if (!boot_control_->GetPartitionDevice(partition_name,
+                                         source_slot,
+                                         true, /* not_in_payload */
+                                         &source_device,
+                                         &is_source_dynamic)) {
+    LOG(ERROR) << "Failed to load source " << partition_name;
+    return std::nullopt;
+  }
+  bool is_target_dynamic = false;
+  std::string target_device;
+  if (!boot_control_->GetPartitionDevice(partition_name,
+                                         target_slot,
+                                         true,
+                                         &target_device,
+                                         &is_target_dynamic)) {
+    LOG(ERROR) << "Failed to load target " << partition_name;
+    return std::nullopt;
+  }
+
+  if (is_source_dynamic != is_target_dynamic) {
+    LOG(ERROR) << "Source slot " << source_slot << " for partition "
+               << partition_name << " is " << (is_source_dynamic ? "" : "not")
+               << " dynamic, but target slot " << target_slot << " is "
+               << (is_target_dynamic ? "" : "not") << " dynamic.";
+    return std::nullopt;
+  }
+  auto source_size = utils::FileSize(source_device);
+  auto target_size = utils::FileSize(target_device);
+  if (source_size == -1 || target_size == -1 || source_size != target_size ||
+      source_size % block_size_ != 0) {
+    LOG(ERROR) << "Invalid partition size. source size " << source_size
+               << ", target size " << target_size;
+    return std::nullopt;
+  }
+
+  return CreatePartitionUpdate(partition_name,
+                               source_device,
+                               target_device,
+                               source_size,
+                               is_source_dynamic);
+}
+
+std::optional<PartitionUpdate>
+PartitionUpdateGeneratorAndroid::CreatePartitionUpdate(
+    const std::string& partition_name,
+    const std::string& source_device,
+    const std::string& target_device,
+    int64_t partition_size,
+    bool is_dynamic) {
+  PartitionUpdate partition_update;
+  partition_update.set_partition_name(partition_name);
+  auto old_partition_info = partition_update.mutable_old_partition_info();
+  old_partition_info->set_size(partition_size);
+
+  auto raw_hash = CalculateHashForPartition(source_device, partition_size);
+  if (!raw_hash.has_value()) {
+    return {};
+  }
+  old_partition_info->set_hash(raw_hash->data(), raw_hash->size());
+  auto new_partition_info = partition_update.mutable_new_partition_info();
+  new_partition_info->set_size(partition_size);
+  new_partition_info->set_hash(raw_hash->data(), raw_hash->size());
+  // TODO(xunchang) TBD, should we skip hashing and verification of the
+  // dynamic partitions not in payload?
+  if (!is_dynamic) {
+    auto copy_operation = partition_update.add_operations();
+    copy_operation->set_type(InstallOperation::SOURCE_COPY);
+    Extent copy_extent;
+    copy_extent.set_start_block(0);
+    copy_extent.set_num_blocks(partition_size / block_size_);
+
+    *copy_operation->add_src_extents() = copy_extent;
+    *copy_operation->add_dst_extents() = copy_extent;
+  }
+
+  return partition_update;
+}
+
+std::optional<brillo::Blob>
+PartitionUpdateGeneratorAndroid::CalculateHashForPartition(
+    const std::string& block_device, int64_t partition_size) {
+  // TODO(xunchang) compute the hash with ecc partitions first, the hashing
+  // behavior should match the one in SOURCE_COPY. Also, we don't have the
+  // correct hash for source partition.
+  // An alternative way is to verify the written bytes match the read bytes
+  // during filesystem verification. This could probably save us a read of
+  // partitions here.
+  brillo::Blob raw_hash;
+  if (HashCalculator::RawHashOfFile(block_device, partition_size, &raw_hash) !=
+      partition_size) {
+    LOG(ERROR) << "Failed to calculate hash for " << block_device;
+    return std::nullopt;
+  }
+
+  return raw_hash;
+}
+
+namespace partition_update_generator {
+std::unique_ptr<PartitionUpdateGeneratorInterface> Create(
+    BootControlInterface* boot_control, size_t block_size) {
+  CHECK(boot_control);
+  auto dynamic_control = boot_control->GetDynamicPartitionControl();
+  CHECK(dynamic_control);
+  std::string dir_path;
+  if (!dynamic_control->GetDeviceDir(&dir_path)) {
+    return nullptr;
+  }
+
+  return std::unique_ptr<PartitionUpdateGeneratorInterface>(
+      new PartitionUpdateGeneratorAndroid(
+          boot_control, std::move(dir_path), block_size));
+}
+}  // namespace partition_update_generator
+
+}  // namespace chromeos_update_engine
diff --git a/payload_consumer/partition_update_generator_android.h b/payload_consumer/partition_update_generator_android.h
new file mode 100644
index 0000000..8f33077
--- /dev/null
+++ b/payload_consumer/partition_update_generator_android.h
@@ -0,0 +1,78 @@
+//
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#ifndef UPDATE_ENGINE_PAYLOAD_CONSUMER_PARTITION_UPDATE_GENERATOR_ANDROID_H_
+#define UPDATE_ENGINE_PAYLOAD_CONSUMER_PARTITION_UPDATE_GENERATOR_ANDROID_H_
+
+#include <optional>
+#include <set>
+#include <string>
+#include <vector>
+
+#include <brillo/secure_blob.h>
+#include <gtest/gtest_prod.h>  // for FRIEND_TEST
+
+#include "update_engine/common/boot_control_interface.h"
+#include "update_engine/payload_consumer/partition_update_generator_interface.h"
+
+namespace chromeos_update_engine {
+class PartitionUpdateGeneratorAndroid
+    : public PartitionUpdateGeneratorInterface {
+ public:
+  PartitionUpdateGeneratorAndroid(BootControlInterface* boot_control,
+                                  std::string device_dir,
+                                  size_t block_size);
+
+  bool GenerateOperationsForPartitionsNotInPayload(
+      BootControlInterface::Slot source_slot,
+      BootControlInterface::Slot target_slot,
+      const std::set<std::string>& partitions_in_payload,
+      std::vector<PartitionUpdate>* update_list) override;
+
+ private:
+  friend class PartitionUpdateGeneratorAndroidTest;
+  FRIEND_TEST(PartitionUpdateGeneratorAndroidTest, GetStaticPartitions);
+  FRIEND_TEST(PartitionUpdateGeneratorAndroidTest, CreatePartitionUpdate);
+
+  // Gets the name of the static a/b partitions on the device.
+  std::optional<std::set<std::string>> GetStaticAbPartitionsOnDevice();
+
+  // Creates a PartitionUpdate object for a given partition to update from
+  // source to target. Returns std::nullopt on failure.
+  std::optional<PartitionUpdate> CreatePartitionUpdate(
+      const std::string& partition_name,
+      const std::string& source_device,
+      const std::string& target_device,
+      int64_t partition_size,
+      bool is_dynamic);
+
+  std::optional<PartitionUpdate> CreatePartitionUpdate(
+      const std::string& partition_name,
+      BootControlInterface::Slot source_slot,
+      BootControlInterface::Slot target_slot);
+
+  std::optional<brillo::Blob> CalculateHashForPartition(
+      const std::string& block_device, int64_t partition_size);
+
+  BootControlInterface* boot_control_;
+  // Path to look for a/b partitions
+  std::string block_device_dir_;
+  size_t block_size_;
+};
+
+}  // namespace chromeos_update_engine
+
+#endif
diff --git a/payload_consumer/partition_update_generator_android_unittest.cc b/payload_consumer/partition_update_generator_android_unittest.cc
new file mode 100644
index 0000000..c3be9db
--- /dev/null
+++ b/payload_consumer/partition_update_generator_android_unittest.cc
@@ -0,0 +1,162 @@
+//
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#include "update_engine/payload_consumer/partition_update_generator_android.h"
+
+#include <map>
+#include <memory>
+#include <set>
+#include <vector>
+
+#include <android-base/strings.h>
+#include <brillo/secure_blob.h>
+#include <gtest/gtest.h>
+
+#include "update_engine/common/fake_boot_control.h"
+#include "update_engine/common/hash_calculator.h"
+#include "update_engine/common/test_utils.h"
+#include "update_engine/common/utils.h"
+
+namespace chromeos_update_engine {
+
+class PartitionUpdateGeneratorAndroidTest : public ::testing::Test {
+ protected:
+  void SetUp() override {
+    ASSERT_TRUE(device_dir_.CreateUniqueTempDir());
+    boot_control_ = std::make_unique<FakeBootControl>();
+    boot_control_->SetNumSlots(2);
+    auto generator =
+        partition_update_generator::Create(boot_control_.get(), 4096);
+    generator_.reset(
+        static_cast<PartitionUpdateGeneratorAndroid*>(generator.release()));
+    ASSERT_TRUE(boot_control_);
+    ASSERT_TRUE(generator_);
+    generator_->block_device_dir_ = device_dir_.GetPath().value();
+  }
+
+  std::unique_ptr<PartitionUpdateGeneratorAndroid> generator_;
+  std::unique_ptr<FakeBootControl> boot_control_;
+
+  base::ScopedTempDir device_dir_;
+
+  void SetUpBlockDevice(const std::map<std::string, std::string>& contents) {
+    for (const auto& [name, content] : contents) {
+      auto path = generator_->block_device_dir_ + "/" + name;
+      ASSERT_TRUE(
+          utils::WriteFile(path.c_str(), content.data(), content.size()));
+
+      if (android::base::EndsWith(name, "_a")) {
+        boot_control_->SetPartitionDevice(
+            name.substr(0, name.size() - 2), 0, path);
+      } else if (android::base::EndsWith(name, "_b")) {
+        boot_control_->SetPartitionDevice(
+            name.substr(0, name.size() - 2), 1, path);
+      }
+    }
+  }
+
+  void CheckPartitionUpdate(const std::string& name,
+                            const std::string& content,
+                            const PartitionUpdate& partition_update) {
+    ASSERT_EQ(name, partition_update.partition_name());
+
+    brillo::Blob out_hash;
+    ASSERT_TRUE(HashCalculator::RawHashOfBytes(
+        content.data(), content.size(), &out_hash));
+    ASSERT_EQ(std::string(out_hash.begin(), out_hash.end()),
+              partition_update.old_partition_info().hash());
+    ASSERT_EQ(std::string(out_hash.begin(), out_hash.end()),
+              partition_update.new_partition_info().hash());
+
+    ASSERT_EQ(1, partition_update.operations_size());
+    const auto& operation = partition_update.operations(0);
+    ASSERT_EQ(InstallOperation::SOURCE_COPY, operation.type());
+
+    ASSERT_EQ(1, operation.src_extents_size());
+    ASSERT_EQ(0u, operation.src_extents(0).start_block());
+    ASSERT_EQ(content.size() / 4096, operation.src_extents(0).num_blocks());
+
+    ASSERT_EQ(1, operation.dst_extents_size());
+    ASSERT_EQ(0u, operation.dst_extents(0).start_block());
+    ASSERT_EQ(content.size() / 4096, operation.dst_extents(0).num_blocks());
+  }
+};
+
+TEST_F(PartitionUpdateGeneratorAndroidTest, GetStaticPartitions) {
+  std::map<std::string, std::string> contents = {
+      {"system_a", ""},
+      {"system_b", ""},
+      {"vendor_a", ""},
+      {"vendor_b", ""},
+      {"persist", ""},
+      {"vbmeta_a", ""},
+      {"vbmeta_b", ""},
+      {"boot_a", ""},
+      {"boot_b", ""},
+  };
+
+  SetUpBlockDevice(contents);
+  auto partitions = generator_->GetStaticAbPartitionsOnDevice();
+  ASSERT_EQ(std::set<std::string>({"system", "vendor", "vbmeta", "boot"}),
+            partitions);
+}
+
+TEST_F(PartitionUpdateGeneratorAndroidTest, CreatePartitionUpdate) {
+  auto system_contents = std::string(4096 * 2, '1');
+  auto boot_contents = std::string(4096 * 5, 'b');
+  std::map<std::string, std::string> contents = {
+      {"system_a", system_contents},
+      {"system_b", std::string(4096 * 2, 0)},
+      {"boot_a", boot_contents},
+      {"boot_b", std::string(4096 * 5, 0)},
+  };
+  SetUpBlockDevice(contents);
+
+  auto system_partition_update =
+      generator_->CreatePartitionUpdate("system", 0, 1);
+  ASSERT_TRUE(system_partition_update.has_value());
+  CheckPartitionUpdate(
+      "system", system_contents, system_partition_update.value());
+
+  auto boot_partition_update = generator_->CreatePartitionUpdate("boot", 0, 1);
+  ASSERT_TRUE(boot_partition_update.has_value());
+  CheckPartitionUpdate("boot", boot_contents, boot_partition_update.value());
+}
+
+TEST_F(PartitionUpdateGeneratorAndroidTest, GenerateOperations) {
+  auto system_contents = std::string(4096 * 10, '2');
+  auto boot_contents = std::string(4096 * 5, 'b');
+  std::map<std::string, std::string> contents = {
+      {"system_a", system_contents},
+      {"system_b", std::string(4096 * 10, 0)},
+      {"boot_a", boot_contents},
+      {"boot_b", std::string(4096 * 5, 0)},
+      {"vendor_a", ""},
+      {"vendor_b", ""},
+      {"persist", ""},
+  };
+  SetUpBlockDevice(contents);
+
+  std::vector<PartitionUpdate> update_list;
+  ASSERT_TRUE(generator_->GenerateOperationsForPartitionsNotInPayload(
+      0, 1, std::set<std::string>{"vendor"}, &update_list));
+
+  ASSERT_EQ(2u, update_list.size());
+  CheckPartitionUpdate("boot", boot_contents, update_list[0]);
+  CheckPartitionUpdate("system", system_contents, update_list[1]);
+}
+
+}  // namespace chromeos_update_engine
diff --git a/payload_consumer/partition_update_generator_interface.h b/payload_consumer/partition_update_generator_interface.h
new file mode 100644
index 0000000..3fa3dfb
--- /dev/null
+++ b/payload_consumer/partition_update_generator_interface.h
@@ -0,0 +1,55 @@
+//
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#ifndef UPDATE_ENGINE_PAYLOAD_CONSUMER_PARTITION_UPDATE_GENERATOR_INTERFACE_H_
+#define UPDATE_ENGINE_PAYLOAD_CONSUMER_PARTITION_UPDATE_GENERATOR_INTERFACE_H_
+
+#include <memory>
+#include <set>
+#include <string>
+#include <vector>
+
+#include "update_engine/common/boot_control_interface.h"
+
+namespace chromeos_update_engine {
+class PartitionUpdate;
+
+// This class parses the partitions that are not included in the payload of a
+// partial A/B update. And it generates additional operations for these
+// partitions to make the update complete.
+class PartitionUpdateGeneratorInterface {
+ public:
+  virtual ~PartitionUpdateGeneratorInterface() = default;
+
+  // Adds PartitionUpdate for partitions not included in the payload. For static
+  // partitions, it generates SOURCE_COPY operations to copy the bytes from the
+  // source slot to target slot. For dynamic partitions, it only calculates the
+  // partition hash for the filesystem verification later.
+  virtual bool GenerateOperationsForPartitionsNotInPayload(
+      BootControlInterface::Slot source_slot,
+      BootControlInterface::Slot target_slot,
+      const std::set<std::string>& partitions_in_payload,
+      std::vector<PartitionUpdate>* update_list) = 0;
+};
+
+namespace partition_update_generator {
+std::unique_ptr<PartitionUpdateGeneratorInterface> Create(
+    BootControlInterface* boot_control, size_t block_size);
+}
+
+}  // namespace chromeos_update_engine
+
+#endif
diff --git a/payload_consumer/partition_update_generator_stub.cc b/payload_consumer/partition_update_generator_stub.cc
new file mode 100644
index 0000000..e2b64ec
--- /dev/null
+++ b/payload_consumer/partition_update_generator_stub.cc
@@ -0,0 +1,38 @@
+//
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#include "update_engine/payload_consumer/partition_update_generator_stub.h"
+
+#include <memory>
+
+namespace chromeos_update_engine {
+
+bool PartitionUpdateGeneratorStub::GenerateOperationsForPartitionsNotInPayload(
+    chromeos_update_engine::BootControlInterface::Slot source_slot,
+    chromeos_update_engine::BootControlInterface::Slot target_slot,
+    const std::set<std::string>& partitions_in_payload,
+    std::vector<PartitionUpdate>* update_list) {
+  return true;
+}
+
+namespace partition_update_generator {
+std::unique_ptr<PartitionUpdateGeneratorInterface> Create(
+    BootControlInterface* boot_control) {
+  return std::make_unique<PartitionUpdateGeneratorStub>();
+}
+}  // namespace partition_update_generator
+
+}  // namespace chromeos_update_engine
diff --git a/payload_consumer/partition_update_generator_stub.h b/payload_consumer/partition_update_generator_stub.h
new file mode 100644
index 0000000..282875e
--- /dev/null
+++ b/payload_consumer/partition_update_generator_stub.h
@@ -0,0 +1,40 @@
+//
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#ifndef UPDATE_ENGINE_PAYLOAD_CONSUMER_PARTITION_UPDATE_GENERATOR_STUB_H_
+#define UPDATE_ENGINE_PAYLOAD_CONSUMER_PARTITION_UPDATE_GENERATOR_STUB_H_
+
+#include <set>
+#include <string>
+#include <vector>
+
+#include "update_engine/common/boot_control_interface.h"
+#include "update_engine/payload_consumer/partition_update_generator_interface.h"
+
+namespace chromeos_update_engine {
+class PartitionUpdateGeneratorStub : public PartitionUpdateGeneratorInterface {
+ public:
+  PartitionUpdateGeneratorStub() = default;
+  bool GenerateOperationsForPartitionsNotInPayload(
+      BootControlInterface::Slot source_slot,
+      BootControlInterface::Slot target_slot,
+      const std::set<std::string>& partitions_in_payload,
+      std::vector<PartitionUpdate>* update_list) override;
+};
+
+}  // namespace chromeos_update_engine
+
+#endif
diff --git a/test_config.xml b/test_config.xml
index 2639e7f..fe3cbfd 100644
--- a/test_config.xml
+++ b/test_config.xml
@@ -16,13 +16,14 @@
 <configuration description="Config to run update_engine_unittests on device">
     <option name="test-suite-tag" value="apct" />
     <option name="test-suite-tag" value="apct-native" />
+    <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer" />
     <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
         <option name="cleanup" value="true" />
-        <option name="push" value="update_engine_unittests->/data/local/tmp/update_engine_unittests" />
+        <option name="push" value="update_engine_unittests->/data/nativetest/update_engine_unittests" />
     </target_preparer>
 
     <test class="com.android.tradefed.testtype.GTest" >
-        <option name="native-test-device-path" value="/data/local/tmp" />
+        <option name="native-test-device-path" value="/data/nativetest" />
         <!-- The following rules avoid test runner from calling the following helper executables
              directly as gtests. -->
         <option name="file-exclusion-filter-regex" value=".*/delta_generator$" />
diff --git a/update_metadata.proto b/update_metadata.proto
index 9bc0d8a..4b4c327 100644
--- a/update_metadata.proto
+++ b/update_metadata.proto
@@ -367,4 +367,7 @@
 
   // Metadata related to all dynamic partitions.
   optional DynamicPartitionMetadata dynamic_partition_metadata = 15;
+
+  // If the payload only updates a subset of partitions on the device.
+  optional bool partial_update = 16;
 }