Merge "Grant adb auth under recovery for unlocked & userdebug devices"
diff --git a/CleanSpec.mk b/CleanSpec.mk
index f6ef906..73379cd 100644
--- a/CleanSpec.mk
+++ b/CleanSpec.mk
@@ -88,3 +88,4 @@
 $(call add-clean-step, rm -rf $(PRODUCT_OUT)/root/product_services)
 $(call add-clean-step, rm -rf $(PRODUCT_OUT)/recovery/root/product_services)
 $(call add-clean-step, rm -rf $(PRODUCT_OUT)/debug_ramdisk/product_services)
+$(call add-clean-step, find $(PRODUCT_OUT) -type l -name "charger" -print0 | xargs -0 rm -f)
diff --git a/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h b/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h
index 431fea1..c820395 100644
--- a/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h
+++ b/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h
@@ -154,6 +154,7 @@
     // rebooting or after rolling back), or merge the OTA.
     bool FinishedSnapshotWrites();
 
+  private:
     // Initiate a merge on all snapshot devices. This should only be used after an
     // update has been marked successful after booting.
     bool InitiateMerge();
@@ -181,6 +182,15 @@
     //   GetUpdateState will return None, and a new update can begin.
     UpdateState ProcessUpdateState();
 
+  public:
+    // Initiate the merge if necessary, then wait for the merge to finish.
+    // See InitiateMerge() and ProcessUpdateState() for details.
+    // Returns:
+    //   - None if no merge to initiate
+    //   - MergeCompleted if merge is completed
+    //   - other states indicating an error has occurred
+    UpdateState InitiateMergeAndWait();
+
     // Find the status of the current update, if any.
     //
     // |progress| depends on the returned status:
@@ -238,12 +248,13 @@
     FRIEND_TEST(SnapshotTest, MapPartialSnapshot);
     FRIEND_TEST(SnapshotTest, MapSnapshot);
     FRIEND_TEST(SnapshotTest, Merge);
-    FRIEND_TEST(SnapshotTest, MergeCannotRemoveCow);
     FRIEND_TEST(SnapshotTest, NoMergeBeforeReboot);
     FRIEND_TEST(SnapshotTest, UpdateBootControlHal);
+    FRIEND_TEST(SnapshotUpdateTest, MergeCannotRemoveCow);
     FRIEND_TEST(SnapshotUpdateTest, SnapshotStatusFileWithoutCow);
     friend class SnapshotTest;
     friend class SnapshotUpdateTest;
+    friend class FlashAfterUpdateTest;
     friend struct AutoDeleteCowImage;
     friend struct AutoDeleteSnapshot;
     friend struct PartitionCowCreator;
@@ -341,6 +352,9 @@
     // condition was detected and handled.
     bool HandleCancelledUpdate(LockedFile* lock);
 
+    // Helper for HandleCancelledUpdate. Assumes booting from new slot.
+    bool HandleCancelledUpdateOnNewSlot(LockedFile* lock);
+
     // Remove artifacts created by the update process, such as snapshots, and
     // set the update state to None.
     bool RemoveAllUpdateState(LockedFile* lock);
@@ -359,7 +373,19 @@
     bool MarkSnapshotMergeCompleted(LockedFile* snapshot_lock, const std::string& snapshot_name);
     void AcknowledgeMergeSuccess(LockedFile* lock);
     void AcknowledgeMergeFailure();
-    bool IsCancelledSnapshot(const std::string& snapshot_name);
+    std::unique_ptr<LpMetadata> ReadCurrentMetadata();
+
+    enum class MetadataPartitionState {
+        // Partition does not exist.
+        None,
+        // Partition is flashed.
+        Flashed,
+        // Partition is created by OTA client.
+        Updated,
+    };
+    // Helper function to check the state of a partition as described in metadata.
+    MetadataPartitionState GetMetadataPartitionState(const LpMetadata& metadata,
+                                                     const std::string& name);
 
     // Note that these require the name of the device containing the snapshot,
     // which may be the "inner" device. Use GetsnapshotDeviecName().
diff --git a/fs_mgr/libsnapshot/snapshot.cpp b/fs_mgr/libsnapshot/snapshot.cpp
index 63d97d0..2c516a2 100644
--- a/fs_mgr/libsnapshot/snapshot.cpp
+++ b/fs_mgr/libsnapshot/snapshot.cpp
@@ -568,6 +568,27 @@
         }
     }
 
+    auto metadata = ReadCurrentMetadata();
+    for (auto it = snapshots.begin(); it != snapshots.end();) {
+        switch (GetMetadataPartitionState(*metadata, *it)) {
+            case MetadataPartitionState::Flashed:
+                LOG(WARNING) << "Detected re-flashing for partition " << *it
+                             << ". Skip merging it.";
+                [[fallthrough]];
+            case MetadataPartitionState::None: {
+                LOG(WARNING) << "Deleting snapshot for partition " << *it;
+                if (!DeleteSnapshot(lock.get(), *it)) {
+                    LOG(WARNING) << "Cannot delete snapshot for partition " << *it
+                                 << ". Skip merging it anyways.";
+                }
+                it = snapshots.erase(it);
+            } break;
+            case MetadataPartitionState::Updated: {
+                ++it;
+            } break;
+        }
+    }
+
     // Point of no return - mark that we're starting a merge. From now on every
     // snapshot must be a merge target.
     if (!WriteUpdateState(lock.get(), UpdateState::Merging)) {
@@ -855,8 +876,15 @@
 
     std::string dm_name = GetSnapshotDeviceName(name, snapshot_status);
 
+    std::unique_ptr<LpMetadata> current_metadata;
+
     if (!IsSnapshotDevice(dm_name)) {
-        if (IsCancelledSnapshot(name)) {
+        if (!current_metadata) {
+            current_metadata = ReadCurrentMetadata();
+        }
+
+        if (!current_metadata ||
+            GetMetadataPartitionState(*current_metadata, name) != MetadataPartitionState::Updated) {
             DeleteSnapshot(lock, name);
             return UpdateState::Cancelled;
         }
@@ -877,7 +905,8 @@
     }
 
     // This check is expensive so it is only enabled for debugging.
-    DCHECK(!IsCancelledSnapshot(name));
+    DCHECK((current_metadata = ReadCurrentMetadata()) &&
+           GetMetadataPartitionState(*current_metadata, name) == MetadataPartitionState::Updated);
 
     std::string target_type;
     DmTargetSnapshot::Status status;
@@ -1106,13 +1135,17 @@
     if (device_->GetSlotSuffix() != old_slot) {
         // We're booted into the target slot, which means we just rebooted
         // after applying the update.
-        return false;
+        if (!HandleCancelledUpdateOnNewSlot(lock)) {
+            return false;
+        }
     }
 
     // The only way we can get here is if:
     //  (1) The device rolled back to the previous slot.
     //  (2) This function was called prematurely before rebooting the device.
     //  (3) fastboot set_active was used.
+    //  (4) The device updates to the new slot but re-flashed *all* partitions
+    //      in the new slot.
     //
     // In any case, delete the snapshots. It may be worth using the boot_control
     // HAL to differentiate case (2).
@@ -1120,18 +1153,66 @@
     return true;
 }
 
-bool SnapshotManager::IsCancelledSnapshot(const std::string& snapshot_name) {
+std::unique_ptr<LpMetadata> SnapshotManager::ReadCurrentMetadata() {
     const auto& opener = device_->GetPartitionOpener();
     uint32_t slot = SlotNumberForSlotSuffix(device_->GetSlotSuffix());
     auto super_device = device_->GetSuperDevice(slot);
     auto metadata = android::fs_mgr::ReadMetadata(opener, super_device, slot);
     if (!metadata) {
         LOG(ERROR) << "Could not read dynamic partition metadata for device: " << super_device;
-        return false;
+        return nullptr;
     }
-    auto partition = android::fs_mgr::FindPartition(*metadata.get(), snapshot_name);
-    if (!partition) return false;
-    return (partition->attributes & LP_PARTITION_ATTR_UPDATED) == 0;
+    return metadata;
+}
+
+SnapshotManager::MetadataPartitionState SnapshotManager::GetMetadataPartitionState(
+        const LpMetadata& metadata, const std::string& name) {
+    auto partition = android::fs_mgr::FindPartition(metadata, name);
+    if (!partition) return MetadataPartitionState::None;
+    if (partition->attributes & LP_PARTITION_ATTR_UPDATED) {
+        return MetadataPartitionState::Updated;
+    }
+    return MetadataPartitionState::Flashed;
+}
+
+bool SnapshotManager::HandleCancelledUpdateOnNewSlot(LockedFile* lock) {
+    std::vector<std::string> snapshots;
+    if (!ListSnapshots(lock, &snapshots)) {
+        LOG(WARNING) << "Failed to list snapshots to determine whether device has been flashed "
+                     << "after applying an update. Assuming no snapshots.";
+        // Let HandleCancelledUpdate resets UpdateState.
+        return true;
+    }
+
+    // Attempt to detect re-flashing on each partition.
+    // - If all partitions are re-flashed, we can proceed to cancel the whole update.
+    // - If only some of the partitions are re-flashed, snapshots for re-flashed partitions are
+    //   deleted. Caller is responsible for merging the rest of the snapshots.
+    // - If none of the partitions are re-flashed, caller is responsible for merging the snapshots.
+    auto metadata = ReadCurrentMetadata();
+    if (!metadata) return false;
+    bool all_snapshot_cancelled = true;
+    for (const auto& snapshot_name : snapshots) {
+        if (GetMetadataPartitionState(*metadata, snapshot_name) ==
+            MetadataPartitionState::Updated) {
+            LOG(WARNING) << "Cannot cancel update because snapshot" << snapshot_name
+                         << " is in use.";
+            all_snapshot_cancelled = false;
+            continue;
+        }
+        // Delete snapshots for partitions that are re-flashed after the update.
+        LOG(INFO) << "Detected re-flashing of partition " << snapshot_name << ".";
+        if (!DeleteSnapshot(lock, snapshot_name)) {
+            // This is an error, but it is okay to leave the snapshot in the short term.
+            // However, if all_snapshot_cancelled == false after exiting the loop, caller may
+            // initiate merge for this unused snapshot, which is likely to fail.
+            LOG(WARNING) << "Failed to delete snapshot for re-flashed partition " << snapshot_name;
+        }
+    }
+    if (!all_snapshot_cancelled) return false;
+
+    LOG(INFO) << "All partitions are re-flashed after update, removing all update states.";
+    return true;
 }
 
 bool SnapshotManager::RemoveAllSnapshots(LockedFile* lock) {
@@ -2089,5 +2170,27 @@
     return AutoUnmountDevice::New(device_->GetMetadataDir());
 }
 
+UpdateState SnapshotManager::InitiateMergeAndWait() {
+    LOG(INFO) << "Waiting for any previous merge request to complete. "
+              << "This can take up to several minutes.";
+    auto state = ProcessUpdateState();
+    if (state == UpdateState::None) {
+        LOG(INFO) << "Can't find any snapshot to merge.";
+        return state;
+    }
+    if (state == UpdateState::Unverified) {
+        if (!InitiateMerge()) {
+            LOG(ERROR) << "Failed to initiate merge.";
+            return state;
+        }
+        // All other states can be handled by ProcessUpdateState.
+        LOG(INFO) << "Waiting for merge to complete. This can take up to several minutes.";
+        state = ProcessUpdateState();
+    }
+
+    LOG(INFO) << "Merge finished with state \"" << state << "\".";
+    return state;
+}
+
 }  // namespace snapshot
 }  // namespace android
diff --git a/fs_mgr/libsnapshot/snapshot_test.cpp b/fs_mgr/libsnapshot/snapshot_test.cpp
index 5728582..3c3d9a6 100644
--- a/fs_mgr/libsnapshot/snapshot_test.cpp
+++ b/fs_mgr/libsnapshot/snapshot_test.cpp
@@ -23,6 +23,7 @@
 #include <iostream>
 
 #include <android-base/file.h>
+#include <android-base/logging.h>
 #include <android-base/properties.h>
 #include <android-base/strings.h>
 #include <android-base/unique_fd.h>
@@ -56,6 +57,7 @@
 using android::fs_mgr::GetPartitionName;
 using android::fs_mgr::Interval;
 using android::fs_mgr::MetadataBuilder;
+using android::fs_mgr::SlotSuffixForSlotNumber;
 using chromeos_update_engine::DeltaArchiveManifest;
 using chromeos_update_engine::DynamicPartitionGroup;
 using chromeos_update_engine::PartitionUpdate;
@@ -445,61 +447,6 @@
     ASSERT_EQ(test_string, buffer);
 }
 
-TEST_F(SnapshotTest, MergeCannotRemoveCow) {
-    ASSERT_TRUE(AcquireLock());
-
-    static const uint64_t kDeviceSize = 1024 * 1024;
-    SnapshotStatus status;
-    status.set_name("test-snapshot");
-    status.set_device_size(kDeviceSize);
-    status.set_snapshot_size(kDeviceSize);
-    status.set_cow_file_size(kDeviceSize);
-    ASSERT_TRUE(sm->CreateSnapshot(lock_.get(), &status));
-    ASSERT_TRUE(CreateCowImage("test-snapshot"));
-
-    std::string base_device, cow_device, snap_device;
-    ASSERT_TRUE(CreatePartition("base-device", kDeviceSize, &base_device));
-    ASSERT_TRUE(MapCowImage("test-snapshot", 10s, &cow_device));
-    ASSERT_TRUE(sm->MapSnapshot(lock_.get(), "test-snapshot", base_device, cow_device, 10s,
-                                &snap_device));
-
-    // Keep an open handle to the cow device. This should cause the merge to
-    // be incomplete.
-    auto cow_path = android::base::GetProperty("gsid.mapped_image.test-snapshot-cow-img", "");
-    unique_fd fd(open(cow_path.c_str(), O_RDONLY | O_CLOEXEC));
-    ASSERT_GE(fd, 0);
-
-    // Release the lock.
-    lock_ = nullptr;
-
-    ASSERT_TRUE(sm->FinishedSnapshotWrites());
-
-    test_device->set_slot_suffix("_b");
-    ASSERT_TRUE(sm->InitiateMerge());
-
-    // COW cannot be removed due to open fd, so expect a soft failure.
-    ASSERT_EQ(sm->ProcessUpdateState(), UpdateState::MergeNeedsReboot);
-
-    // Release the handle to the COW device to fake a reboot.
-    fd.reset();
-    // Wait 1s, otherwise DeleteSnapshotDevice may fail with EBUSY.
-    sleep(1);
-    // Forcefully delete the snapshot device, so it looks like we just rebooted.
-    ASSERT_TRUE(DeleteSnapshotDevice("test-snapshot"));
-
-    // Map snapshot should fail now, because we're in a merge-complete state.
-    ASSERT_TRUE(AcquireLock());
-    ASSERT_TRUE(MapCowImage("test-snapshot", 10s, &cow_device));
-    ASSERT_FALSE(sm->MapSnapshot(lock_.get(), "test-snapshot", base_device, cow_device, 10s,
-                                 &snap_device));
-
-    // Release everything and now the merge should complete.
-    fd = {};
-    lock_ = nullptr;
-
-    ASSERT_EQ(sm->ProcessUpdateState(), UpdateState::MergeCompleted);
-}
-
 TEST_F(SnapshotTest, FirstStageMountAndMerge) {
     ASSERT_TRUE(AcquireLock());
 
@@ -680,9 +627,9 @@
         // Initialize source partition metadata using |manifest_|.
         src_ = MetadataBuilder::New(*opener_, "super", 0);
         ASSERT_TRUE(FillFakeMetadata(src_.get(), manifest_, "_a"));
-        ASSERT_NE(nullptr, src_);
         // Add sys_b which is like system_other.
-        auto partition = src_->AddPartition("sys_b", 0);
+        ASSERT_TRUE(src_->AddGroup("group_b", kGroupSize));
+        auto partition = src_->AddPartition("sys_b", "group_b", 0);
         ASSERT_NE(nullptr, partition);
         ASSERT_TRUE(src_->ResizePartition(partition, 1_MiB));
         auto metadata = src_->Export();
@@ -731,8 +678,12 @@
         if (!hash.has_value()) {
             return AssertionFailure() << "Cannot read partition " << name << ": " << path;
         }
-        if (hashes_[name] != *hash) {
-            return AssertionFailure() << "Content of " << name << " has changed after the merge";
+        auto it = hashes_.find(name);
+        if (it == hashes_.end()) {
+            return AssertionFailure() << "No existing hash for " << name << ". Bad test code?";
+        }
+        if (it->second != *hash) {
+            return AssertionFailure() << "Content of " << name << " has changed";
         }
         return AssertionSuccess();
     }
@@ -847,8 +798,7 @@
     }
 
     // Initiate the merge and wait for it to be completed.
-    ASSERT_TRUE(init->InitiateMerge());
-    ASSERT_EQ(UpdateState::MergeCompleted, init->ProcessUpdateState());
+    ASSERT_EQ(UpdateState::MergeCompleted, init->InitiateMergeAndWait());
 
     // Check that the target partitions have the same content after the merge.
     for (const auto& name : {"sys_b", "vnd_b", "prd_b"}) {
@@ -1052,8 +1002,7 @@
 
     // Initiate the merge and wait for it to be completed.
     auto new_sm = SnapshotManager::New(new TestDeviceInfo(fake_super, "_b"));
-    ASSERT_TRUE(new_sm->InitiateMerge());
-    ASSERT_EQ(UpdateState::MergeCompleted, new_sm->ProcessUpdateState());
+    ASSERT_EQ(UpdateState::MergeCompleted, new_sm->InitiateMergeAndWait());
 
     // Execute the second update.
     ASSERT_TRUE(new_sm->BeginUpdate());
@@ -1162,6 +1111,67 @@
     ASSERT_TRUE(sm->FinishedSnapshotWrites());
 }
 
+TEST_F(SnapshotUpdateTest, MergeCannotRemoveCow) {
+    // Make source partitions as big as possible to force COW image to be created.
+    SetSize(sys_, 5_MiB);
+    SetSize(vnd_, 5_MiB);
+    SetSize(prd_, 5_MiB);
+    src_ = MetadataBuilder::New(*opener_, "super", 0);
+    src_->RemoveGroupAndPartitions(group_->name() + "_a");
+    src_->RemoveGroupAndPartitions(group_->name() + "_b");
+    ASSERT_TRUE(FillFakeMetadata(src_.get(), manifest_, "_a"));
+    auto metadata = src_->Export();
+    ASSERT_NE(nullptr, metadata);
+    ASSERT_TRUE(UpdatePartitionTable(*opener_, "super", *metadata.get(), 0));
+
+    // OTA client blindly unmaps all partitions that are possibly mapped.
+    for (const auto& name : {"sys_b", "vnd_b", "prd_b"}) {
+        ASSERT_TRUE(sm->UnmapUpdateSnapshot(name));
+    }
+
+    // Add operations for sys. The whole device is written.
+    auto e = sys_->add_operations()->add_dst_extents();
+    e->set_start_block(0);
+    e->set_num_blocks(GetSize(sys_) / manifest_.block_size());
+
+    // Execute the update.
+    ASSERT_TRUE(sm->BeginUpdate());
+    ASSERT_TRUE(sm->CreateUpdateSnapshots(manifest_));
+    ASSERT_TRUE(sm->FinishedSnapshotWrites());
+
+    // Simulate shutting down the device.
+    ASSERT_TRUE(UnmapAll());
+
+    // After reboot, init does first stage mount.
+    // Normally we should use NewForFirstStageMount, but if so, "gsid.mapped_image.sys_b-cow-img"
+    // won't be set.
+    auto init = SnapshotManager::New(new TestDeviceInfo(fake_super, "_b"));
+    ASSERT_NE(init, nullptr);
+    ASSERT_TRUE(init->CreateLogicalAndSnapshotPartitions("super"));
+
+    // Keep an open handle to the cow device. This should cause the merge to
+    // be incomplete.
+    auto cow_path = android::base::GetProperty("gsid.mapped_image.sys_b-cow-img", "");
+    unique_fd fd(open(cow_path.c_str(), O_RDONLY | O_CLOEXEC));
+    ASSERT_GE(fd, 0);
+
+    // COW cannot be removed due to open fd, so expect a soft failure.
+    ASSERT_EQ(UpdateState::MergeNeedsReboot, init->InitiateMergeAndWait());
+
+    // Simulate shutting down the device.
+    fd.reset();
+    ASSERT_TRUE(UnmapAll());
+
+    // init does first stage mount again.
+    ASSERT_TRUE(init->CreateLogicalAndSnapshotPartitions("super"));
+
+    // sys_b should be mapped as a dm-linear device directly.
+    ASSERT_FALSE(sm->IsSnapshotDevice("sys_b", nullptr));
+
+    // Merge should be able to complete now.
+    ASSERT_EQ(UpdateState::MergeCompleted, init->InitiateMergeAndWait());
+}
+
 class MetadataMountedTest : public SnapshotUpdateTest {
   public:
     void SetUp() override {
@@ -1220,6 +1230,121 @@
     EXPECT_FALSE(IsMetadataMounted());
 }
 
+class FlashAfterUpdateTest : public SnapshotUpdateTest,
+                             public WithParamInterface<std::tuple<uint32_t, bool>> {
+  public:
+    AssertionResult InitiateMerge(const std::string& slot_suffix) {
+        auto sm = SnapshotManager::New(new TestDeviceInfo(fake_super, slot_suffix));
+        if (!sm->CreateLogicalAndSnapshotPartitions("super")) {
+            return AssertionFailure() << "Cannot CreateLogicalAndSnapshotPartitions";
+        }
+        if (!sm->InitiateMerge()) {
+            return AssertionFailure() << "Cannot initiate merge";
+        }
+        return AssertionSuccess();
+    }
+};
+
+TEST_P(FlashAfterUpdateTest, FlashSlotAfterUpdate) {
+    // OTA client blindly unmaps all partitions that are possibly mapped.
+    for (const auto& name : {"sys_b", "vnd_b", "prd_b"}) {
+        ASSERT_TRUE(sm->UnmapUpdateSnapshot(name));
+    }
+
+    // Execute the update.
+    ASSERT_TRUE(sm->BeginUpdate());
+    ASSERT_TRUE(sm->CreateUpdateSnapshots(manifest_));
+
+    ASSERT_TRUE(sm->FinishedSnapshotWrites());
+
+    // Simulate shutting down the device.
+    ASSERT_TRUE(UnmapAll());
+
+    if (std::get<1>(GetParam()) /* merge */) {
+        ASSERT_TRUE(InitiateMerge("_b"));
+        // Simulate shutting down the device after merge has initiated.
+        ASSERT_TRUE(UnmapAll());
+    }
+
+    auto flashed_slot = std::get<0>(GetParam());
+    auto flashed_slot_suffix = SlotSuffixForSlotNumber(flashed_slot);
+
+    // Simulate flashing |flashed_slot|. This clears the UPDATED flag.
+    auto flashed_builder = MetadataBuilder::New(*opener_, "super", flashed_slot);
+    flashed_builder->RemoveGroupAndPartitions(group_->name() + flashed_slot_suffix);
+    flashed_builder->RemoveGroupAndPartitions(kCowGroupName);
+    ASSERT_TRUE(FillFakeMetadata(flashed_builder.get(), manifest_, flashed_slot_suffix));
+
+    // Deliberately remove a partition from this build so that
+    // InitiateMerge do not switch state to "merging". This is possible in
+    // practice because the list of dynamic partitions may change.
+    ASSERT_NE(nullptr, flashed_builder->FindPartition("prd" + flashed_slot_suffix));
+    flashed_builder->RemovePartition("prd" + flashed_slot_suffix);
+
+    auto flashed_metadata = flashed_builder->Export();
+    ASSERT_NE(nullptr, flashed_metadata);
+    ASSERT_TRUE(UpdatePartitionTable(*opener_, "super", *flashed_metadata, flashed_slot));
+
+    std::string path;
+    for (const auto& name : {"sys", "vnd"}) {
+        ASSERT_TRUE(CreateLogicalPartition(
+                CreateLogicalPartitionParams{
+                        .block_device = fake_super,
+                        .metadata_slot = flashed_slot,
+                        .partition_name = name + flashed_slot_suffix,
+                        .timeout_ms = 1s,
+                        .partition_opener = opener_.get(),
+                },
+                &path));
+        ASSERT_TRUE(WriteRandomData(path));
+        auto hash = GetHash(path);
+        ASSERT_TRUE(hash.has_value());
+        hashes_[name + flashed_slot_suffix] = *hash;
+    }
+
+    // Simulate shutting down the device after flash.
+    ASSERT_TRUE(UnmapAll());
+
+    // Simulate reboot. After reboot, init does first stage mount.
+    auto init = SnapshotManager::NewForFirstStageMount(
+            new TestDeviceInfo(fake_super, flashed_slot_suffix));
+    ASSERT_NE(init, nullptr);
+    if (init->NeedSnapshotsInFirstStageMount()) {
+        ASSERT_TRUE(init->CreateLogicalAndSnapshotPartitions("super"));
+    } else {
+        for (const auto& name : {"sys", "vnd"}) {
+            ASSERT_TRUE(CreateLogicalPartition(
+                    CreateLogicalPartitionParams{
+                            .block_device = fake_super,
+                            .metadata_slot = flashed_slot,
+                            .partition_name = name + flashed_slot_suffix,
+                            .timeout_ms = 1s,
+                            .partition_opener = opener_.get(),
+                    },
+                    &path));
+        }
+    }
+
+    // Check that the target partitions have the same content.
+    for (const auto& name : {"sys", "vnd"}) {
+        ASSERT_TRUE(IsPartitionUnchanged(name + flashed_slot_suffix));
+    }
+
+    // There should be no snapshot to merge.
+    auto new_sm = SnapshotManager::New(new TestDeviceInfo(fake_super, flashed_slot_suffix));
+    ASSERT_EQ(UpdateState::Cancelled, new_sm->InitiateMergeAndWait());
+
+    // Next OTA calls CancelUpdate no matter what.
+    ASSERT_TRUE(new_sm->CancelUpdate());
+}
+
+INSTANTIATE_TEST_SUITE_P(, FlashAfterUpdateTest, Combine(Values(0, 1), Bool()),
+                         [](const TestParamInfo<FlashAfterUpdateTest::ParamType>& info) {
+                             return "Flash"s + (std::get<0>(info.param) ? "New"s : "Old"s) +
+                                    "Slot"s + (std::get<1>(info.param) ? "After"s : "Before"s) +
+                                    "Merge"s;
+                         });
+
 }  // namespace snapshot
 }  // namespace android
 
diff --git a/fs_mgr/libsnapshot/snapshotctl.cpp b/fs_mgr/libsnapshot/snapshotctl.cpp
index d65320c..1bc0357 100644
--- a/fs_mgr/libsnapshot/snapshotctl.cpp
+++ b/fs_mgr/libsnapshot/snapshotctl.cpp
@@ -60,24 +60,11 @@
         android::base::InitLogging(argv, &android::base::StdioLogger);
     }
 
-    auto sm = SnapshotManager::New();
+    auto state = SnapshotManager::New()->InitiateMergeAndWait();
 
-    auto state = sm->GetUpdateState();
     if (state == UpdateState::None) {
-        LOG(INFO) << "Can't find any snapshot to merge.";
         return true;
     }
-    if (state == UpdateState::Unverified) {
-        if (!sm->InitiateMerge()) {
-            LOG(ERROR) << "Failed to initiate merge.";
-            return false;
-        }
-    }
-
-    // All other states can be handled by ProcessUpdateState.
-    LOG(INFO) << "Waiting for any merge to complete. This can take up to 1 minute.";
-    state = SnapshotManager::New()->ProcessUpdateState();
-
     if (state == UpdateState::MergeCompleted) {
         auto end = std::chrono::steady_clock::now();
         auto passed = std::chrono::duration_cast<std::chrono::milliseconds>(end - begin).count();
diff --git a/healthd/Android.bp b/healthd/Android.bp
index e04f70f..14d46b3 100644
--- a/healthd/Android.bp
+++ b/healthd/Android.bp
@@ -89,6 +89,7 @@
 
 cc_library_static {
     name: "libhealthd_charger_nops",
+    recovery_available: true,
 
     srcs: [
         "healthd_mode_charger_nops.cpp",
@@ -104,17 +105,19 @@
     ],
 
     static_libs: [
-        "android.hardware.health@2.0-impl",
+        "libhealthloop",
+        "libhealth2impl",
     ],
 
     shared_libs: [
-        "android.hardware.health@2.0",
+        "android.hardware.health@2.1",
         "libutils",
     ],
 }
 
 sysprop_library {
     name: "charger_sysprop",
+    recovery_available: true,
     srcs: ["charger.sysprop"],
     property_owner: "Platform",
     api_packages: ["android.sysprop"],
@@ -141,16 +144,16 @@
     export_include_dirs: [".", "include"],
 
     static_libs: [
-        "android.hardware.health@2.0-impl",
         "android.hardware.health@1.0-convert",
         "libcharger_sysprop",
-        "libhealthstoragedefault",
         "libhealthd_draw",
+        "libhealthloop",
+        "libhealth2impl",
         "libminui",
     ],
 
     shared_libs: [
-        "android.hardware.health@2.0",
+        "android.hardware.health@2.1",
         "libbase",
         "libcutils",
         "liblog",
@@ -164,3 +167,76 @@
         "AnimationParser.cpp",
     ],
 }
+
+cc_defaults {
+    name: "charger_defaults",
+
+    cflags: [
+        "-Wall",
+        "-Werror",
+    ],
+
+    shared_libs: [
+        // common
+        "android.hardware.health@2.0",
+        "android.hardware.health@2.1",
+        "libbase",
+        "libcutils",
+        "libhidlbase",
+        "liblog",
+        "libutils",
+
+        // system charger only
+        "libpng",
+    ],
+
+    static_libs: [
+        // common
+        "android.hardware.health@1.0-convert",
+        "libbatterymonitor",
+        "libcharger_sysprop",
+        "libhealthd_charger_nops",
+        "libhealthloop",
+        "libhealth2impl",
+
+        // system charger only
+        "libhealthd_draw",
+        "libhealthd_charger",
+        "libminui",
+        "libsuspend",
+    ],
+}
+
+cc_binary {
+    name: "charger",
+    defaults: ["charger_defaults"],
+    recovery_available: true,
+    srcs: [
+        "charger.cpp",
+        "charger_utils.cpp",
+    ],
+
+    target: {
+        recovery: {
+            // No UI and libsuspend for recovery charger.
+            cflags: [
+                "-DCHARGER_FORCE_NO_UI=1",
+            ],
+            exclude_shared_libs: [
+                "libpng",
+            ],
+            exclude_static_libs: [
+                "libhealthd_draw",
+                "libhealthd_charger",
+                "libminui",
+                "libsuspend",
+            ],
+        }
+    }
+}
+
+cc_test {
+    name: "charger_test",
+    defaults: ["charger_defaults"],
+    srcs: ["charger_test.cpp"],
+}
diff --git a/healthd/Android.mk b/healthd/Android.mk
index 66ff399..4b09cf8 100644
--- a/healthd/Android.mk
+++ b/healthd/Android.mk
@@ -2,112 +2,10 @@
 
 LOCAL_PATH := $(call my-dir)
 
-### charger ###
-include $(CLEAR_VARS)
 ifeq ($(strip $(BOARD_CHARGER_NO_UI)),true)
 LOCAL_CHARGER_NO_UI := true
 endif
 
-LOCAL_SRC_FILES := \
-    charger.cpp \
-
-LOCAL_MODULE := charger
-LOCAL_C_INCLUDES := $(LOCAL_PATH)/include
-
-LOCAL_CFLAGS := -Werror
-
-CHARGER_STATIC_LIBRARIES := \
-    android.hardware.health@2.0-impl \
-    android.hardware.health@1.0-convert \
-    libbinderthreadstate \
-    libcharger_sysprop \
-    libhidlbase \
-    libhealthstoragedefault \
-    libminui \
-    libvndksupport \
-    libhealthd_charger \
-    libhealthd_charger_nops \
-    libhealthd_draw \
-    libbatterymonitor \
-
-CHARGER_SHARED_LIBRARIES := \
-    android.hardware.health@2.0 \
-    libbase \
-    libcutils \
-    libjsoncpp \
-    libpng \
-    libprocessgroup \
-    liblog \
-    libutils \
-
-CHARGER_SHARED_LIBRARIES += libsuspend
-
-LOCAL_STATIC_LIBRARIES := $(CHARGER_STATIC_LIBRARIES)
-LOCAL_SHARED_LIBRARIES := $(CHARGER_SHARED_LIBRARIES)
-
-LOCAL_HAL_STATIC_LIBRARIES := libhealthd
-
-# Symlink /charger to /system/bin/charger
-LOCAL_POST_INSTALL_CMD := $(hide) mkdir -p $(TARGET_ROOT_OUT) \
-    && ln -sf /system/bin/charger $(TARGET_ROOT_OUT)/charger
-
-include $(BUILD_EXECUTABLE)
-
-### charger.recovery ###
-include $(CLEAR_VARS)
-
-LOCAL_SRC_FILES := \
-    charger.cpp \
-
-LOCAL_MODULE := charger.recovery
-LOCAL_MODULE_PATH := $(TARGET_RECOVERY_ROOT_OUT)/system/bin
-LOCAL_MODULE_STEM := charger
-
-LOCAL_C_INCLUDES := $(LOCAL_PATH)/include
-LOCAL_CFLAGS := -Wall -Werror -DCHARGER_FORCE_NO_UI=1
-
-# charger.recovery doesn't link against libhealthd_{charger,draw} or libminui, since it doesn't need
-# any UI support.
-LOCAL_STATIC_LIBRARIES := \
-    android.hardware.health@2.0-impl \
-    android.hardware.health@1.0-convert \
-    libbinderthreadstate \
-    libcharger_sysprop \
-    libhidlbase \
-    libhealthstoragedefault \
-    libvndksupport \
-    libhealthd_charger_nops \
-    libbatterymonitor \
-
-# These shared libs will be installed to recovery image because of the dependency in `recovery`
-# module.
-LOCAL_SHARED_LIBRARIES := \
-    android.hardware.health@2.0 \
-    libbase \
-    libcutils \
-    liblog \
-    libutils \
-
-# The use of LOCAL_HAL_STATIC_LIBRARIES prevents from building this module with Android.bp.
-LOCAL_HAL_STATIC_LIBRARIES := libhealthd
-
-include $(BUILD_EXECUTABLE)
-
-### charger_test ###
-include $(CLEAR_VARS)
-LOCAL_MODULE := charger_test
-LOCAL_C_INCLUDES := $(LOCAL_PATH)/include
-LOCAL_CFLAGS := -Wall -Werror
-LOCAL_STATIC_LIBRARIES := $(CHARGER_STATIC_LIBRARIES)
-LOCAL_SHARED_LIBRARIES := $(CHARGER_SHARED_LIBRARIES)
-LOCAL_SRC_FILES := \
-    charger_test.cpp \
-
-include $(BUILD_EXECUTABLE)
-
-CHARGER_STATIC_LIBRARIES :=
-CHARGER_SHARED_LIBRARIES :=
-
 ### charger_res_images ###
 ifneq ($(strip $(LOCAL_CHARGER_NO_UI)),true)
 define _add-charger-image
diff --git a/healthd/animation.h b/healthd/animation.h
index 9476c91..d02d7a7 100644
--- a/healthd/animation.h
+++ b/healthd/animation.h
@@ -75,7 +75,7 @@
 
     bool run;
 
-    frame* frames;
+    frame* frames = nullptr;
     int cur_frame;
     int num_frames;
     int first_frame_repeats;  // Number of times to repeat the first frame in the current cycle
@@ -85,6 +85,8 @@
 
     int cur_level;  // current battery level being animated (0-100)
     int cur_status;  // current battery status - see BatteryService.h for BATTERY_STATUS_*
+
+    ~animation() { delete frames; }
 };
 
 }
diff --git a/healthd/charger_test.cpp b/healthd/charger_test.cpp
index a7e2161..e0bde68 100644
--- a/healthd/charger_test.cpp
+++ b/healthd/charger_test.cpp
@@ -21,13 +21,22 @@
 #include <condition_variable>
 #include <fstream>
 #include <iostream>
+#include <memory>
 #include <mutex>
 #include <streambuf>
 #include <string>
 #include <thread>
 #include <vector>
 
-#include <health2/Health.h>
+#include <health/utils.h>
+#include <health2impl/Health.h>
+
+#include "healthd_mode_charger.h"
+
+using android::hardware::health::InitHealthdConfig;
+using android::hardware::health::V2_1::HealthInfo;
+using android::hardware::health::V2_1::IHealth;
+using android::hardware::health::V2_1::implementation::Health;
 
 #define LOG_THIS(fmt, ...)     \
     ALOGE(fmt, ##__VA_ARGS__); \
@@ -129,22 +138,23 @@
     config->screen_on = NULL;
 }
 
-int healthd_board_battery_update(struct android::BatteryProperties*) {
-    getUpdateNotifier().set(true /* updated */);
+class TestHealth : public Health {
+  protected:
+    using Health::Health;
+    void UpdateHealthInfo(HealthInfo*) override { getUpdateNotifier().set(true /* updated */); }
+};
 
-    // return 0 to log periodic polled battery status to kernel log
-    return 0;
-}
-
-extern int healthd_charger_main(int argc, char** argv);
-
-int main(int argc, char** argv) {
-    using android::hardware::health::V2_0::implementation::Health;
-
+int main(int /*argc*/, char** /*argv*/) {
     const char* dumpFile = "/data/local/tmp/dump.txt";
 
+    auto config = std::make_unique<healthd_config>();
+    InitHealthdConfig(config.get());
+    healthd_board_init(config.get());
+    sp<IHealth> passthrough = new TestHealth(std::move(config));
+
     std::thread bgThread([=] {
-        healthd_charger_main(argc, argv);
+        android::Charger charger(passthrough);
+        charger.StartLoop();
     });
 
     // wait for healthd_init to finish
@@ -153,7 +163,7 @@
         exit(1);
     }
 
-    Health::getImplementation()->debug(createHidlHandle(dumpFile), {} /* options */);
+    passthrough->debug(createHidlHandle(dumpFile), {} /* options */);
 
     std::string content = openToString(dumpFile);
     int status = expectContains(content, {
diff --git a/healthd/charger_utils.cpp b/healthd/charger_utils.cpp
new file mode 100644
index 0000000..0cf9df5
--- /dev/null
+++ b/healthd/charger_utils.cpp
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2019 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 "charger_utils.h"
+
+#include <android-base/logging.h>
+#include <android/hidl/manager/1.0/IServiceManager.h>
+#include <health/utils.h>
+#include <health2impl/Health.h>
+#include <hidl/ServiceManagement.h>
+
+using android::hardware::getPassthroughServiceManager;
+using android::hidl::base::V1_0::IBase;
+using android::hidl::manager::V1_0::IServiceManager;
+
+namespace android {
+namespace hardware {
+namespace health {
+sp<V2_1::IHealth> GetPassthroughHealthImpl() {
+    // Not using getService() because there is no hwservicemanager in charger mode.
+    sp<IServiceManager> pm = getPassthroughServiceManager();
+    if (pm == nullptr) {
+        LOG(WARNING) << "Cannot get passthrough service manager.";
+        return nullptr;
+    }
+    sp<IBase> base = pm->get(V2_0::IHealth::descriptor, "default");
+    if (base == nullptr) {
+        LOG(WARNING) << "Cannot find passthrough implementation of health 2.0 HAL for instance "
+                        "'default' on the device.";
+        return nullptr;
+    }
+    sp<V2_1::IHealth> service = V2_1::IHealth::castFrom(base);
+    if (service == nullptr) {
+        LOG(WARNING)
+                << "Cannot cast passthrough implementation of health 2.0 HAL to 2.1 for instance "
+                   "'default' on the device.";
+        return nullptr;
+    }
+    return service;
+}
+
+sp<V2_1::IHealth> GetPassthroughHealth() {
+    auto impl = GetPassthroughHealthImpl();
+    if (impl == nullptr) {
+        LOG(WARNING) << "Charger uses system defaults.";
+        auto config = std::make_unique<healthd_config>();
+        InitHealthdConfig(config.get());
+        impl = new V2_1::implementation::Health(std::move(config));
+    }
+    return impl;
+}
+
+}  // namespace health
+}  // namespace hardware
+}  // namespace android
diff --git a/healthd/charger_utils.h b/healthd/charger_utils.h
new file mode 100644
index 0000000..f96e827
--- /dev/null
+++ b/healthd/charger_utils.h
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2019 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.
+ */
+
+#pragma once
+
+#include <android/hardware/health/2.1/IHealth.h>
+
+namespace android {
+namespace hardware {
+namespace health {
+sp<V2_1::IHealth> GetPassthroughHealth();
+}  // namespace health
+}  // namespace hardware
+}  // namespace android
diff --git a/healthd/healthd_mode_charger.cpp b/healthd/healthd_mode_charger.cpp
index d676083..7d844c9 100644
--- a/healthd/healthd_mode_charger.cpp
+++ b/healthd/healthd_mode_charger.cpp
@@ -14,6 +14,8 @@
  * limitations under the License.
  */
 
+#include "healthd_mode_charger.h"
+
 #include <dirent.h>
 #include <errno.h>
 #include <fcntl.h>
@@ -28,7 +30,7 @@
 #include <time.h>
 #include <unistd.h>
 
-#include <functional>
+#include <optional>
 
 #include <android-base/file.h>
 #include <android-base/macros.h>
@@ -47,16 +49,30 @@
 
 #include "AnimationParser.h"
 #include "charger.sysprop.h"
+#include "charger_utils.h"
 #include "healthd_draw.h"
 
-#include <health2/Health.h>
+#include <android/hardware/health/2.0/IHealthInfoCallback.h>
+#include <health/utils.h>
+#include <health2impl/HalHealthLoop.h>
+#include <health2impl/Health.h>
 #include <healthd/healthd.h>
 
 using namespace android;
+using android::hardware::Return;
+using android::hardware::health::GetPassthroughHealth;
+using android::hardware::health::HealthLoop;
+using android::hardware::health::V1_0::BatteryStatus;
+using android::hardware::health::V2_0::Result;
+using android::hardware::health::V2_1::IHealth;
+using IHealth_2_0 = android::hardware::health::V2_0::IHealth;
+using HealthInfo_1_0 = android::hardware::health::V1_0::HealthInfo;
+using HealthInfo_2_1 = android::hardware::health::V2_1::HealthInfo;
 
 // main healthd loop
 extern int healthd_main(void);
 
+// minui globals
 char* locale;
 
 #ifndef max
@@ -85,6 +101,8 @@
 #define LOGW(x...) KLOG_WARNING("charger", x);
 #define LOGV(x...) KLOG_DEBUG("charger", x);
 
+namespace android {
+
 // Resources in /product/etc/res overrides resources in /res.
 // If the device is using the Generic System Image (GSI), resources may exist in
 // both paths.
@@ -93,28 +111,6 @@
 static constexpr const char* product_animation_root = "/product/etc/res/images/";
 static constexpr const char* animation_desc_path = "/res/values/charger/animation.txt";
 
-struct key_state {
-    bool pending;
-    bool down;
-    int64_t timestamp;
-};
-
-struct charger {
-    bool have_battery_state;
-    bool charger_connected;
-    bool screen_blanked;
-    int64_t next_screen_transition;
-    int64_t next_key_check;
-    int64_t next_pwr_check;
-    int64_t wait_batt_level_timestamp;
-
-    key_state keys[KEY_MAX + 1];
-
-    animation* batt_anim;
-    GRSurface* surf_unknown;
-    int boot_min_cap;
-};
-
 static const animation BASE_ANIMATION = {
     .text_clock =
         {
@@ -153,51 +149,51 @@
     .cur_status = BATTERY_STATUS_UNKNOWN,
 };
 
-static animation::frame default_animation_frames[] = {
-    {
-        .disp_time = 750,
-        .min_level = 0,
-        .max_level = 19,
-        .surface = NULL,
-    },
-    {
-        .disp_time = 750,
-        .min_level = 0,
-        .max_level = 39,
-        .surface = NULL,
-    },
-    {
-        .disp_time = 750,
-        .min_level = 0,
-        .max_level = 59,
-        .surface = NULL,
-    },
-    {
-        .disp_time = 750,
-        .min_level = 0,
-        .max_level = 79,
-        .surface = NULL,
-    },
-    {
-        .disp_time = 750,
-        .min_level = 80,
-        .max_level = 95,
-        .surface = NULL,
-    },
-    {
-        .disp_time = 750,
-        .min_level = 0,
-        .max_level = 100,
-        .surface = NULL,
-    },
-};
+void Charger::InitDefaultAnimationFrames() {
+    owned_frames_ = {
+            {
+                    .disp_time = 750,
+                    .min_level = 0,
+                    .max_level = 19,
+                    .surface = NULL,
+            },
+            {
+                    .disp_time = 750,
+                    .min_level = 0,
+                    .max_level = 39,
+                    .surface = NULL,
+            },
+            {
+                    .disp_time = 750,
+                    .min_level = 0,
+                    .max_level = 59,
+                    .surface = NULL,
+            },
+            {
+                    .disp_time = 750,
+                    .min_level = 0,
+                    .max_level = 79,
+                    .surface = NULL,
+            },
+            {
+                    .disp_time = 750,
+                    .min_level = 80,
+                    .max_level = 95,
+                    .surface = NULL,
+            },
+            {
+                    .disp_time = 750,
+                    .min_level = 0,
+                    .max_level = 100,
+                    .surface = NULL,
+            },
+    };
+}
 
-static animation battery_animation = BASE_ANIMATION;
+Charger::Charger(const sp<IHealth>& service)
+    : HalHealthLoop("charger", service), batt_anim_(BASE_ANIMATION) {}
 
-static charger charger_state;
-static healthd_config* healthd_config;
-static android::BatteryProperties* batt_prop;
-static std::unique_ptr<HealthdDraw> healthd_draw;
+Charger::~Charger() {}
 
 /* current time in milliseconds */
 static int64_t curr_time_ms() {
@@ -284,123 +280,125 @@
     anim->run = false;
 }
 
-static void update_screen_state(charger* charger, int64_t now) {
-    animation* batt_anim = charger->batt_anim;
+void Charger::UpdateScreenState(int64_t now) {
     int disp_time;
 
-    if (!batt_anim->run || now < charger->next_screen_transition) return;
+    if (!batt_anim_.run || now < next_screen_transition_) return;
 
     // If battery level is not ready, keep checking in the defined time
-    if (batt_prop == nullptr ||
-        (batt_prop->batteryLevel == 0 && batt_prop->batteryStatus == BATTERY_STATUS_UNKNOWN)) {
-        if (charger->wait_batt_level_timestamp == 0) {
+    if (health_info_.batteryLevel == 0 && health_info_.batteryStatus == BatteryStatus::UNKNOWN) {
+        if (wait_batt_level_timestamp_ == 0) {
             // Set max delay time and skip drawing screen
-            charger->wait_batt_level_timestamp = now + MAX_BATT_LEVEL_WAIT_TIME;
+            wait_batt_level_timestamp_ = now + MAX_BATT_LEVEL_WAIT_TIME;
             LOGV("[%" PRId64 "] wait for battery capacity ready\n", now);
             return;
-        } else if (now <= charger->wait_batt_level_timestamp) {
+        } else if (now <= wait_batt_level_timestamp_) {
             // Do nothing, keep waiting
             return;
         }
         // If timeout and battery level is still not ready, draw unknown battery
     }
 
-    if (healthd_draw == nullptr) {
-        if (healthd_config && healthd_config->screen_on) {
-            if (!healthd_config->screen_on(batt_prop)) {
+    if (healthd_draw_ == nullptr) {
+        std::optional<bool> out_screen_on;
+        service()->shouldKeepScreenOn([&](Result res, bool screen_on) {
+            if (res == Result::SUCCESS) {
+                *out_screen_on = screen_on;
+            }
+        });
+        if (out_screen_on.has_value()) {
+            if (!*out_screen_on) {
                 LOGV("[%" PRId64 "] leave screen off\n", now);
-                batt_anim->run = false;
-                charger->next_screen_transition = -1;
-                if (charger->charger_connected) request_suspend(true);
+                batt_anim_.run = false;
+                next_screen_transition_ = -1;
+                if (charger_online()) request_suspend(true);
                 return;
             }
         }
 
-        healthd_draw.reset(new HealthdDraw(batt_anim));
+        healthd_draw_.reset(new HealthdDraw(&batt_anim_));
 
         if (android::sysprop::ChargerProperties::disable_init_blank().value_or(false)) {
-            healthd_draw->blank_screen(true);
-            charger->screen_blanked = true;
+            healthd_draw_->blank_screen(true);
+            screen_blanked_ = true;
         }
     }
 
     /* animation is over, blank screen and leave */
-    if (batt_anim->num_cycles > 0 && batt_anim->cur_cycle == batt_anim->num_cycles) {
-        reset_animation(batt_anim);
-        charger->next_screen_transition = -1;
-        healthd_draw->blank_screen(true);
-        charger->screen_blanked = true;
+    if (batt_anim_.num_cycles > 0 && batt_anim_.cur_cycle == batt_anim_.num_cycles) {
+        reset_animation(&batt_anim_);
+        next_screen_transition_ = -1;
+        healthd_draw_->blank_screen(true);
+        screen_blanked_ = true;
         LOGV("[%" PRId64 "] animation done\n", now);
-        if (charger->charger_connected) request_suspend(true);
+        if (charger_online()) request_suspend(true);
         return;
     }
 
-    disp_time = batt_anim->frames[batt_anim->cur_frame].disp_time;
+    disp_time = batt_anim_.frames[batt_anim_.cur_frame].disp_time;
 
-    if (charger->screen_blanked) {
-        healthd_draw->blank_screen(false);
-        charger->screen_blanked = false;
+    if (screen_blanked_) {
+        healthd_draw_->blank_screen(false);
+        screen_blanked_ = false;
     }
 
     /* animation starting, set up the animation */
-    if (batt_anim->cur_frame == 0) {
+    if (batt_anim_.cur_frame == 0) {
         LOGV("[%" PRId64 "] animation starting\n", now);
-        if (batt_prop) {
-            batt_anim->cur_level = batt_prop->batteryLevel;
-            batt_anim->cur_status = batt_prop->batteryStatus;
-            if (batt_prop->batteryLevel >= 0 && batt_anim->num_frames != 0) {
-                /* find first frame given current battery level */
-                for (int i = 0; i < batt_anim->num_frames; i++) {
-                    if (batt_anim->cur_level >= batt_anim->frames[i].min_level &&
-                        batt_anim->cur_level <= batt_anim->frames[i].max_level) {
-                        batt_anim->cur_frame = i;
-                        break;
-                    }
+        batt_anim_.cur_level = health_info_.batteryLevel;
+        batt_anim_.cur_status = (int)health_info_.batteryStatus;
+        if (health_info_.batteryLevel >= 0 && batt_anim_.num_frames != 0) {
+            /* find first frame given current battery level */
+            for (int i = 0; i < batt_anim_.num_frames; i++) {
+                if (batt_anim_.cur_level >= batt_anim_.frames[i].min_level &&
+                    batt_anim_.cur_level <= batt_anim_.frames[i].max_level) {
+                    batt_anim_.cur_frame = i;
+                    break;
                 }
-
-                if (charger->charger_connected) {
-                    // repeat the first frame first_frame_repeats times
-                    disp_time = batt_anim->frames[batt_anim->cur_frame].disp_time *
-                                batt_anim->first_frame_repeats;
-                } else {
-                    disp_time = UNPLUGGED_DISPLAY_TIME / batt_anim->num_cycles;
-                }
-
-                LOGV("cur_frame=%d disp_time=%d\n", batt_anim->cur_frame, disp_time);
             }
+
+            if (charger_online()) {
+                // repeat the first frame first_frame_repeats times
+                disp_time = batt_anim_.frames[batt_anim_.cur_frame].disp_time *
+                            batt_anim_.first_frame_repeats;
+            } else {
+                disp_time = UNPLUGGED_DISPLAY_TIME / batt_anim_.num_cycles;
+            }
+
+            LOGV("cur_frame=%d disp_time=%d\n", batt_anim_.cur_frame, disp_time);
         }
     }
 
     /* draw the new frame (@ cur_frame) */
-    healthd_draw->redraw_screen(charger->batt_anim, charger->surf_unknown);
+    healthd_draw_->redraw_screen(&batt_anim_, surf_unknown_);
 
     /* if we don't have anim frames, we only have one image, so just bump
      * the cycle counter and exit
      */
-    if (batt_anim->num_frames == 0 || batt_anim->cur_level < 0) {
+    if (batt_anim_.num_frames == 0 || batt_anim_.cur_level < 0) {
         LOGW("[%" PRId64 "] animation missing or unknown battery status\n", now);
-        charger->next_screen_transition = now + BATTERY_UNKNOWN_TIME;
-        batt_anim->cur_cycle++;
+        next_screen_transition_ = now + BATTERY_UNKNOWN_TIME;
+        batt_anim_.cur_cycle++;
         return;
     }
 
     /* schedule next screen transition */
-    charger->next_screen_transition = curr_time_ms() + disp_time;
+    next_screen_transition_ = curr_time_ms() + disp_time;
 
     /* advance frame cntr to the next valid frame only if we are charging
      * if necessary, advance cycle cntr, and reset frame cntr
      */
-    if (charger->charger_connected) {
-        batt_anim->cur_frame++;
+    if (charger_online()) {
+        batt_anim_.cur_frame++;
 
-        while (batt_anim->cur_frame < batt_anim->num_frames &&
-               (batt_anim->cur_level < batt_anim->frames[batt_anim->cur_frame].min_level ||
-                batt_anim->cur_level > batt_anim->frames[batt_anim->cur_frame].max_level)) {
-            batt_anim->cur_frame++;
+        while (batt_anim_.cur_frame < batt_anim_.num_frames &&
+               (batt_anim_.cur_level < batt_anim_.frames[batt_anim_.cur_frame].min_level ||
+                batt_anim_.cur_level > batt_anim_.frames[batt_anim_.cur_frame].max_level)) {
+            batt_anim_.cur_frame++;
         }
-        if (batt_anim->cur_frame >= batt_anim->num_frames) {
-            batt_anim->cur_cycle++;
-            batt_anim->cur_frame = 0;
+        if (batt_anim_.cur_frame >= batt_anim_.num_frames) {
+            batt_anim_.cur_cycle++;
+            batt_anim_.cur_frame = 0;
 
             /* don't reset the cycle counter, since we use that as a signal
              * in a test above to check if animation is over
@@ -411,29 +409,29 @@
          * If we stop it immediately instead of going through this loop, then
          * the animation would stop somewhere in the middle.
          */
-        batt_anim->cur_frame = 0;
-        batt_anim->cur_cycle++;
+        batt_anim_.cur_frame = 0;
+        batt_anim_.cur_cycle++;
     }
 }
 
-static int set_key_callback(charger* charger, int code, int value) {
+int Charger::SetKeyCallback(int code, int value) {
     int64_t now = curr_time_ms();
     int down = !!value;
 
     if (code > KEY_MAX) return -1;
 
     /* ignore events that don't modify our state */
-    if (charger->keys[code].down == down) return 0;
+    if (keys_[code].down == down) return 0;
 
     /* only record the down even timestamp, as the amount
      * of time the key spent not being pressed is not useful */
-    if (down) charger->keys[code].timestamp = now;
-    charger->keys[code].down = down;
-    charger->keys[code].pending = true;
+    if (down) keys_[code].timestamp = now;
+    keys_[code].down = down;
+    keys_[code].pending = true;
     if (down) {
         LOGV("[%" PRId64 "] key[%d] down\n", now, code);
     } else {
-        int64_t duration = now - charger->keys[code].timestamp;
+        int64_t duration = now - keys_[code].timestamp;
         int64_t secs = duration / 1000;
         int64_t msecs = duration - secs * 1000;
         LOGV("[%" PRId64 "] key[%d] up (was down for %" PRId64 ".%" PRId64 "sec)\n", now, code,
@@ -443,20 +441,19 @@
     return 0;
 }
 
-static void update_input_state(charger* charger, input_event* ev) {
+void Charger::UpdateInputState(input_event* ev) {
     if (ev->type != EV_KEY) return;
-    set_key_callback(charger, ev->code, ev->value);
+    SetKeyCallback(ev->code, ev->value);
 }
 
-static void set_next_key_check(charger* charger, key_state* key, int64_t timeout) {
+void Charger::SetNextKeyCheck(key_state* key, int64_t timeout) {
     int64_t then = key->timestamp + timeout;
 
-    if (charger->next_key_check == -1 || then < charger->next_key_check)
-        charger->next_key_check = then;
+    if (next_key_check_ == -1 || then < next_key_check_) next_key_check_ = then;
 }
 
-static void process_key(charger* charger, int code, int64_t now) {
-    key_state* key = &charger->keys[code];
+void Charger::ProcessKey(int code, int64_t now) {
+    key_state* key = &keys_[code];
 
     if (code == KEY_POWER) {
         if (key->down) {
@@ -469,7 +466,7 @@
                     LOGW("[%" PRId64 "] booting from charger mode\n", now);
                     property_set("sys.boot_from_charger_mode", "1");
                 } else {
-                    if (charger->batt_anim->cur_level >= charger->boot_min_cap) {
+                    if (batt_anim_.cur_level >= boot_min_cap_) {
                         LOGW("[%" PRId64 "] rebooting\n", now);
                         reboot(RB_AUTOBOOT);
                     } else {
@@ -483,18 +480,18 @@
                 /* if the key is pressed but timeout hasn't expired,
                  * make sure we wake up at the right-ish time to check
                  */
-                set_next_key_check(charger, key, POWER_ON_KEY_TIME);
+                SetNextKeyCheck(key, POWER_ON_KEY_TIME);
 
                 /* Turn on the display and kick animation on power-key press
                  * rather than on key release
                  */
-                kick_animation(charger->batt_anim);
+                kick_animation(&batt_anim_);
                 request_suspend(false);
             }
         } else {
             /* if the power key got released, force screen state cycle */
             if (key->pending) {
-                kick_animation(charger->batt_anim);
+                kick_animation(&batt_anim_);
                 request_suspend(false);
             }
         }
@@ -503,36 +500,35 @@
     key->pending = false;
 }
 
-static void handle_input_state(charger* charger, int64_t now) {
-    process_key(charger, KEY_POWER, now);
+void Charger::HandleInputState(int64_t now) {
+    ProcessKey(KEY_POWER, now);
 
-    if (charger->next_key_check != -1 && now > charger->next_key_check)
-        charger->next_key_check = -1;
+    if (next_key_check_ != -1 && now > next_key_check_) next_key_check_ = -1;
 }
 
-static void handle_power_supply_state(charger* charger, int64_t now) {
+void Charger::HandlePowerSupplyState(int64_t now) {
     int timer_shutdown = UNPLUGGED_SHUTDOWN_TIME;
-    if (!charger->have_battery_state) return;
+    if (!have_battery_state_) return;
 
-    if (!charger->charger_connected) {
+    if (!charger_online()) {
         request_suspend(false);
-        if (charger->next_pwr_check == -1) {
+        if (next_pwr_check_ == -1) {
             /* Last cycle would have stopped at the extreme top of battery-icon
              * Need to show the correct level corresponding to capacity.
              *
-             * Reset next_screen_transition to update screen immediately.
+             * Reset next_screen_transition_ to update screen immediately.
              * Reset & kick animation to show complete animation cycles
              * when charger disconnected.
              */
             timer_shutdown =
                     property_get_int32(UNPLUGGED_SHUTDOWN_TIME_PROP, UNPLUGGED_SHUTDOWN_TIME);
-            charger->next_screen_transition = now - 1;
-            reset_animation(charger->batt_anim);
-            kick_animation(charger->batt_anim);
-            charger->next_pwr_check = now + timer_shutdown;
+            next_screen_transition_ = now - 1;
+            reset_animation(&batt_anim_);
+            kick_animation(&batt_anim_);
+            next_pwr_check_ = now + timer_shutdown;
             LOGW("[%" PRId64 "] device unplugged: shutting down in %" PRId64 " (@ %" PRId64 ")\n",
-                 now, (int64_t)timer_shutdown, charger->next_pwr_check);
-        } else if (now >= charger->next_pwr_check) {
+                 now, (int64_t)timer_shutdown, next_pwr_check_);
+        } else if (now >= next_pwr_check_) {
             LOGW("[%" PRId64 "] shutting down\n", now);
             reboot(RB_POWER_OFF);
         } else {
@@ -540,64 +536,60 @@
         }
     } else {
         /* online supply present, reset shutdown timer if set */
-        if (charger->next_pwr_check != -1) {
-            /* Reset next_screen_transition to update screen immediately.
+        if (next_pwr_check_ != -1) {
+            /* Reset next_screen_transition_ to update screen immediately.
              * Reset & kick animation to show complete animation cycles
              * when charger connected again.
              */
             request_suspend(false);
-            charger->next_screen_transition = now - 1;
-            reset_animation(charger->batt_anim);
-            kick_animation(charger->batt_anim);
+            next_screen_transition_ = now - 1;
+            reset_animation(&batt_anim_);
+            kick_animation(&batt_anim_);
             LOGW("[%" PRId64 "] device plugged in: shutdown cancelled\n", now);
         }
-        charger->next_pwr_check = -1;
+        next_pwr_check_ = -1;
     }
 }
 
-void healthd_mode_charger_heartbeat() {
-    charger* charger = &charger_state;
+void Charger::Heartbeat() {
+    // charger* charger = &charger_state;
     int64_t now = curr_time_ms();
 
-    handle_input_state(charger, now);
-    handle_power_supply_state(charger, now);
+    HandleInputState(now);
+    HandlePowerSupplyState(now);
 
     /* do screen update last in case any of the above want to start
      * screen transitions (animations, etc)
      */
-    update_screen_state(charger, now);
+    UpdateScreenState(now);
 }
 
-void healthd_mode_charger_battery_update(android::BatteryProperties* props) {
-    charger* charger = &charger_state;
+void Charger::OnHealthInfoChanged(const HealthInfo_2_1& health_info) {
+    set_charger_online(health_info);
 
-    charger->charger_connected =
-        props->chargerAcOnline || props->chargerUsbOnline || props->chargerWirelessOnline;
-
-    if (!charger->have_battery_state) {
-        charger->have_battery_state = true;
-        charger->next_screen_transition = curr_time_ms() - 1;
+    if (!have_battery_state_) {
+        have_battery_state_ = true;
+        next_screen_transition_ = curr_time_ms() - 1;
         request_suspend(false);
-        reset_animation(charger->batt_anim);
-        kick_animation(charger->batt_anim);
+        reset_animation(&batt_anim_);
+        kick_animation(&batt_anim_);
     }
-    batt_prop = props;
+    health_info_ = health_info.legacy.legacy;
+
+    AdjustWakealarmPeriods(charger_online());
 }
 
-int healthd_mode_charger_preparetowait(void) {
-    charger* charger = &charger_state;
+int Charger::PrepareToWait(void) {
     int64_t now = curr_time_ms();
     int64_t next_event = INT64_MAX;
     int64_t timeout;
 
     LOGV("[%" PRId64 "] next screen: %" PRId64 " next key: %" PRId64 " next pwr: %" PRId64 "\n",
-         now, charger->next_screen_transition, charger->next_key_check, charger->next_pwr_check);
+         now, next_screen_transition_, next_key_check_, next_pwr_check_);
 
-    if (charger->next_screen_transition != -1) next_event = charger->next_screen_transition;
-    if (charger->next_key_check != -1 && charger->next_key_check < next_event)
-        next_event = charger->next_key_check;
-    if (charger->next_pwr_check != -1 && charger->next_pwr_check < next_event)
-        next_event = charger->next_pwr_check;
+    if (next_screen_transition_ != -1) next_event = next_screen_transition_;
+    if (next_key_check_ != -1 && next_key_check_ < next_event) next_event = next_key_check_;
+    if (next_pwr_check_ != -1 && next_pwr_check_ < next_event) next_event = next_pwr_check_;
 
     if (next_event != -1 && next_event != INT64_MAX)
         timeout = max(0, next_event - now);
@@ -607,32 +599,32 @@
     return (int)timeout;
 }
 
-static int input_callback(charger* charger, int fd, unsigned int epevents) {
+int Charger::InputCallback(int fd, unsigned int epevents) {
     input_event ev;
     int ret;
 
     ret = ev_get_input(fd, epevents, &ev);
     if (ret) return -1;
-    update_input_state(charger, &ev);
+    UpdateInputState(&ev);
     return 0;
 }
 
-static void charger_event_handler(uint32_t /*epevents*/) {
+static void charger_event_handler(HealthLoop* /*charger_loop*/, uint32_t /*epevents*/) {
     int ret;
 
     ret = ev_wait(-1);
     if (!ret) ev_dispatch();
 }
 
-animation* init_animation() {
+void Charger::InitAnimation() {
     bool parse_success;
 
     std::string content;
     if (base::ReadFileToString(product_animation_desc_path, &content)) {
-        parse_success = parse_animation_desc(content, &battery_animation);
-        battery_animation.set_resource_root(product_animation_root);
+        parse_success = parse_animation_desc(content, &batt_anim_);
+        batt_anim_.set_resource_root(product_animation_root);
     } else if (base::ReadFileToString(animation_desc_path, &content)) {
-        parse_success = parse_animation_desc(content, &battery_animation);
+        parse_success = parse_animation_desc(content, &batt_anim_);
     } else {
         LOGW("Could not open animation description at %s\n", animation_desc_path);
         parse_success = false;
@@ -640,41 +632,36 @@
 
     if (!parse_success) {
         LOGW("Could not parse animation description. Using default animation.\n");
-        battery_animation = BASE_ANIMATION;
-        battery_animation.animation_file.assign("charger/battery_scale");
-        battery_animation.frames = default_animation_frames;
-        battery_animation.num_frames = ARRAY_SIZE(default_animation_frames);
+        batt_anim_ = BASE_ANIMATION;
+        batt_anim_.animation_file.assign("charger/battery_scale");
+        InitDefaultAnimationFrames();
+        batt_anim_.frames = owned_frames_.data();
+        batt_anim_.num_frames = owned_frames_.size();
     }
-    if (battery_animation.fail_file.empty()) {
-        battery_animation.fail_file.assign("charger/battery_fail");
+    if (batt_anim_.fail_file.empty()) {
+        batt_anim_.fail_file.assign("charger/battery_fail");
     }
 
     LOGV("Animation Description:\n");
-    LOGV("  animation: %d %d '%s' (%d)\n", battery_animation.num_cycles,
-         battery_animation.first_frame_repeats, battery_animation.animation_file.c_str(),
-         battery_animation.num_frames);
-    LOGV("  fail_file: '%s'\n", battery_animation.fail_file.c_str());
-    LOGV("  clock: %d %d %d %d %d %d '%s'\n", battery_animation.text_clock.pos_x,
-         battery_animation.text_clock.pos_y, battery_animation.text_clock.color_r,
-         battery_animation.text_clock.color_g, battery_animation.text_clock.color_b,
-         battery_animation.text_clock.color_a, battery_animation.text_clock.font_file.c_str());
-    LOGV("  percent: %d %d %d %d %d %d '%s'\n", battery_animation.text_percent.pos_x,
-         battery_animation.text_percent.pos_y, battery_animation.text_percent.color_r,
-         battery_animation.text_percent.color_g, battery_animation.text_percent.color_b,
-         battery_animation.text_percent.color_a, battery_animation.text_percent.font_file.c_str());
-    for (int i = 0; i < battery_animation.num_frames; i++) {
-        LOGV("  frame %.2d: %d %d %d\n", i, battery_animation.frames[i].disp_time,
-             battery_animation.frames[i].min_level, battery_animation.frames[i].max_level);
+    LOGV("  animation: %d %d '%s' (%d)\n", batt_anim_.num_cycles, batt_anim_.first_frame_repeats,
+         batt_anim_.animation_file.c_str(), batt_anim_.num_frames);
+    LOGV("  fail_file: '%s'\n", batt_anim_.fail_file.c_str());
+    LOGV("  clock: %d %d %d %d %d %d '%s'\n", batt_anim_.text_clock.pos_x,
+         batt_anim_.text_clock.pos_y, batt_anim_.text_clock.color_r, batt_anim_.text_clock.color_g,
+         batt_anim_.text_clock.color_b, batt_anim_.text_clock.color_a,
+         batt_anim_.text_clock.font_file.c_str());
+    LOGV("  percent: %d %d %d %d %d %d '%s'\n", batt_anim_.text_percent.pos_x,
+         batt_anim_.text_percent.pos_y, batt_anim_.text_percent.color_r,
+         batt_anim_.text_percent.color_g, batt_anim_.text_percent.color_b,
+         batt_anim_.text_percent.color_a, batt_anim_.text_percent.font_file.c_str());
+    for (int i = 0; i < batt_anim_.num_frames; i++) {
+        LOGV("  frame %.2d: %d %d %d\n", i, batt_anim_.frames[i].disp_time,
+             batt_anim_.frames[i].min_level, batt_anim_.frames[i].max_level);
     }
-
-    return &battery_animation;
 }
 
-void healthd_mode_charger_init(struct healthd_config* config) {
-    using android::hardware::health::V2_0::implementation::Health;
-
+void Charger::Init(struct healthd_config* config) {
     int ret;
-    charger* charger = &charger_state;
     int i;
     int epollfd;
 
@@ -682,22 +669,22 @@
 
     LOGW("--------------- STARTING CHARGER MODE ---------------\n");
 
-    ret = ev_init(std::bind(&input_callback, charger, std::placeholders::_1, std::placeholders::_2));
+    ret = ev_init(
+            std::bind(&Charger::InputCallback, this, std::placeholders::_1, std::placeholders::_2));
     if (!ret) {
         epollfd = ev_get_epollfd();
-        healthd_register_event(epollfd, charger_event_handler, EVENT_WAKEUP_FD);
+        RegisterEvent(epollfd, &charger_event_handler, EVENT_WAKEUP_FD);
     }
 
-    animation* anim = init_animation();
-    charger->batt_anim = anim;
+    InitAnimation();
 
-    ret = res_create_display_surface(anim->fail_file.c_str(), &charger->surf_unknown);
+    ret = res_create_display_surface(batt_anim_.fail_file.c_str(), &surf_unknown_);
     if (ret < 0) {
         LOGE("Cannot load custom battery_fail image. Reverting to built in: %d\n", ret);
-        ret = res_create_display_surface("charger/battery_fail", &charger->surf_unknown);
+        ret = res_create_display_surface("charger/battery_fail", &surf_unknown_);
         if (ret < 0) {
             LOGE("Cannot load built in battery_fail image\n");
-            charger->surf_unknown = NULL;
+            surf_unknown_ = NULL;
         }
     }
 
@@ -705,49 +692,41 @@
     int scale_count;
     int scale_fps;  // Not in use (charger/battery_scale doesn't have FPS text
                     // chunk). We are using hard-coded frame.disp_time instead.
-    ret = res_create_multi_display_surface(anim->animation_file.c_str(), &scale_count, &scale_fps,
-                                           &scale_frames);
+    ret = res_create_multi_display_surface(batt_anim_.animation_file.c_str(), &scale_count,
+                                           &scale_fps, &scale_frames);
     if (ret < 0) {
         LOGE("Cannot load battery_scale image\n");
-        anim->num_frames = 0;
-        anim->num_cycles = 1;
-    } else if (scale_count != anim->num_frames) {
+        batt_anim_.num_frames = 0;
+        batt_anim_.num_cycles = 1;
+    } else if (scale_count != batt_anim_.num_frames) {
         LOGE("battery_scale image has unexpected frame count (%d, expected %d)\n", scale_count,
-             anim->num_frames);
-        anim->num_frames = 0;
-        anim->num_cycles = 1;
+             batt_anim_.num_frames);
+        batt_anim_.num_frames = 0;
+        batt_anim_.num_cycles = 1;
     } else {
-        for (i = 0; i < anim->num_frames; i++) {
-            anim->frames[i].surface = scale_frames[i];
+        for (i = 0; i < batt_anim_.num_frames; i++) {
+            batt_anim_.frames[i].surface = scale_frames[i];
         }
     }
-    ev_sync_key_state(
-        std::bind(&set_key_callback, charger, std::placeholders::_1, std::placeholders::_2));
+    ev_sync_key_state(std::bind(&Charger::SetKeyCallback, this, std::placeholders::_1,
+                                std::placeholders::_2));
 
-    charger->next_screen_transition = -1;
-    charger->next_key_check = -1;
-    charger->next_pwr_check = -1;
-    charger->wait_batt_level_timestamp = 0;
+    next_screen_transition_ = -1;
+    next_key_check_ = -1;
+    next_pwr_check_ = -1;
+    wait_batt_level_timestamp_ = 0;
 
-    // Initialize Health implementation (which initializes the internal BatteryMonitor).
-    Health::initInstance(config);
+    // Retrieve healthd_config from the existing health HAL.
+    HalHealthLoop::Init(config);
 
-    healthd_config = config;
-    charger->boot_min_cap = config->boot_min_cap;
+    boot_min_cap_ = config->boot_min_cap;
 }
 
-static struct healthd_mode_ops charger_ops = {
-        .init = healthd_mode_charger_init,
-        .preparetowait = healthd_mode_charger_preparetowait,
-        .heartbeat = healthd_mode_charger_heartbeat,
-        .battery_update = healthd_mode_charger_battery_update,
-};
+}  // namespace android
 
 int healthd_charger_main(int argc, char** argv) {
     int ch;
 
-    healthd_mode_ops = &charger_ops;
-
     while ((ch = getopt(argc, argv, "cr")) != -1) {
         switch (ch) {
             case 'c':
@@ -763,5 +742,6 @@
         }
     }
 
-    return healthd_main();
+    Charger charger(GetPassthroughHealth());
+    return charger.StartLoop();
 }
diff --git a/healthd/healthd_mode_charger.h b/healthd/healthd_mode_charger.h
index 2f0c9f2..370ca86 100644
--- a/healthd/healthd_mode_charger.h
+++ b/healthd/healthd_mode_charger.h
@@ -16,4 +16,72 @@
 
 #pragma once
 
+#include <linux/input.h>
+
+#include <memory>
+#include <vector>
+
+#include <android/hardware/health/2.0/IHealthInfoCallback.h>
+#include <android/hardware/health/2.1/IHealth.h>
+#include <health2impl/HalHealthLoop.h>
+
+#include "animation.h"
+
+class GRSurface;
+class HealthdDraw;
+
+namespace android {
+struct key_state {
+    bool pending;
+    bool down;
+    int64_t timestamp;
+};
+
+class Charger : public ::android::hardware::health::V2_1::implementation::HalHealthLoop {
+  public:
+    using HealthInfo_1_0 = android::hardware::health::V1_0::HealthInfo;
+    using HealthInfo_2_1 = android::hardware::health::V2_1::HealthInfo;
+
+    Charger(const sp<android::hardware::health::V2_1::IHealth>& service);
+    ~Charger();
+
+  protected:
+    // HealthLoop overrides.
+    void Heartbeat() override;
+    int PrepareToWait() override;
+    void Init(struct healthd_config* config) override;
+    // HalHealthLoop overrides
+    void OnHealthInfoChanged(const HealthInfo_2_1& health_info) override;
+
+  private:
+    void InitDefaultAnimationFrames();
+    void UpdateScreenState(int64_t now);
+    int SetKeyCallback(int code, int value);
+    void UpdateInputState(input_event* ev);
+    void SetNextKeyCheck(key_state* key, int64_t timeout);
+    void ProcessKey(int code, int64_t now);
+    void HandleInputState(int64_t now);
+    void HandlePowerSupplyState(int64_t now);
+    int InputCallback(int fd, unsigned int epevents);
+    void InitAnimation();
+
+    bool have_battery_state_ = false;
+    bool screen_blanked_ = false;
+    int64_t next_screen_transition_ = 0;
+    int64_t next_key_check_ = 0;
+    int64_t next_pwr_check_ = 0;
+    int64_t wait_batt_level_timestamp_ = 0;
+
+    key_state keys_[KEY_MAX + 1];
+
+    animation batt_anim_;
+    GRSurface* surf_unknown_ = nullptr;
+    int boot_min_cap_ = 0;
+
+    HealthInfo_1_0 health_info_ = {};
+    std::unique_ptr<HealthdDraw> healthd_draw_;
+    std::vector<animation::frame> owned_frames_;
+};
+}  // namespace android
+
 int healthd_charger_main(int argc, char** argv);
diff --git a/healthd/healthd_mode_charger_nops.cpp b/healthd/healthd_mode_charger_nops.cpp
index bcc04d5..13e7348 100644
--- a/healthd/healthd_mode_charger_nops.cpp
+++ b/healthd/healthd_mode_charger_nops.cpp
@@ -16,45 +16,14 @@
 
 #include "healthd_mode_charger_nops.h"
 
-#include <health2/Health.h>
-#include <healthd/healthd.h>
+#include <health2impl/HalHealthLoop.h>
 
-#include <stdlib.h>
-#include <string.h>
+#include "charger_utils.h"
 
-using namespace android;
-
-// main healthd loop
-extern int healthd_main(void);
-
-// NOPs for modes that need no special action
-
-static void healthd_mode_nop_init(struct healthd_config* config);
-static int healthd_mode_nop_preparetowait(void);
-static void healthd_mode_nop_heartbeat(void);
-static void healthd_mode_nop_battery_update(struct android::BatteryProperties* props);
-
-static struct healthd_mode_ops healthd_nops = {
-        .init = healthd_mode_nop_init,
-        .preparetowait = healthd_mode_nop_preparetowait,
-        .heartbeat = healthd_mode_nop_heartbeat,
-        .battery_update = healthd_mode_nop_battery_update,
-};
-
-static void healthd_mode_nop_init(struct healthd_config* config) {
-    using android::hardware::health::V2_0::implementation::Health;
-    Health::initInstance(config);
-}
-
-static int healthd_mode_nop_preparetowait(void) {
-    return -1;
-}
-
-static void healthd_mode_nop_heartbeat(void) {}
-
-static void healthd_mode_nop_battery_update(struct android::BatteryProperties* /*props*/) {}
+using android::hardware::health::GetPassthroughHealth;
+using android::hardware::health::V2_1::implementation::HalHealthLoop;
 
 int healthd_charger_nops(int /* argc */, char** /* argv */) {
-    healthd_mode_ops = &healthd_nops;
-    return healthd_main();
+    HalHealthLoop charger("charger", GetPassthroughHealth());
+    return charger.StartLoop();
 }
diff --git a/init/first_stage_init.cpp b/init/first_stage_init.cpp
index fd2d766..ac44796 100644
--- a/init/first_stage_init.cpp
+++ b/init/first_stage_init.cpp
@@ -239,11 +239,16 @@
     }
 
     Modprobe m({"/lib/modules"});
-    if (!m.LoadListedModules()) {
-        LOG(FATAL) << "Failed to load kernel modules";
+    auto want_console = ALLOW_FIRST_STAGE_CONSOLE && FirstStageConsole(cmdline);
+    if (!m.LoadListedModules(!want_console)) {
+        if (want_console) {
+            LOG(ERROR) << "Failed to load kernel modules, starting console";
+        } else {
+            LOG(FATAL) << "Failed to load kernel modules";
+        }
     }
 
-    if (ALLOW_FIRST_STAGE_CONSOLE && FirstStageConsole(cmdline)) {
+    if (want_console) {
         StartConsole();
     }
 
diff --git a/init/host_init_stubs.h b/init/host_init_stubs.h
index 5dd5cf1..9b33a1c 100644
--- a/init/host_init_stubs.h
+++ b/init/host_init_stubs.h
@@ -26,6 +26,7 @@
 
 // android/api-level.h
 #define __ANDROID_API_P__ 28
+#define __ANDROID_API_Q__ 29
 #define __ANDROID_API_R__ 30
 
 // sys/system_properties.h
diff --git a/init/service_parser.cpp b/init/service_parser.cpp
index e7808a9..e6a341d 100644
--- a/init/service_parser.cpp
+++ b/init/service_parser.cpp
@@ -560,6 +560,11 @@
             str_args[0] = "/system/bin/watchdogd";
         }
     }
+    if (SelinuxGetVendorAndroidVersion() <= __ANDROID_API_Q__) {
+        if (str_args[0] == "/charger") {
+            str_args[0] = "/system/bin/charger";
+        }
+    }
 
     service_ = std::make_unique<Service>(name, restart_action_subcontext, str_args);
     return {};
diff --git a/init/util_test.cpp b/init/util_test.cpp
index 8947256..a8fcc87 100644
--- a/init/util_test.cpp
+++ b/init/util_test.cpp
@@ -61,8 +61,8 @@
 
 TEST(util, ReadFileSymbolicLink) {
     errno = 0;
-    // lrwxrwxrwx 1 root root 13 1970-01-01 00:00 charger -> /sbin/healthd
-    auto file_contents = ReadFile("/charger");
+    // lrw------- 1 root root 23 2008-12-31 19:00 default.prop -> system/etc/prop.default
+    auto file_contents = ReadFile("/default.prop");
     EXPECT_EQ(ELOOP, errno);
     ASSERT_FALSE(file_contents);
     EXPECT_EQ("open() failed: Too many symbolic links encountered",
diff --git a/libmodprobe/include/modprobe/modprobe.h b/libmodprobe/include/modprobe/modprobe.h
index 421d826..333fc55 100644
--- a/libmodprobe/include/modprobe/modprobe.h
+++ b/libmodprobe/include/modprobe/modprobe.h
@@ -26,7 +26,7 @@
   public:
     Modprobe(const std::vector<std::string>&);
 
-    bool LoadListedModules();
+    bool LoadListedModules(bool strict = true);
     bool LoadWithAliases(const std::string& module_name, bool strict,
                          const std::string& parameters = "");
     bool Remove(const std::string& module_name);
diff --git a/libmodprobe/libmodprobe.cpp b/libmodprobe/libmodprobe.cpp
index 3c78ec9..6b9107f 100644
--- a/libmodprobe/libmodprobe.cpp
+++ b/libmodprobe/libmodprobe.cpp
@@ -360,13 +360,15 @@
     return true;
 }
 
-bool Modprobe::LoadListedModules() {
+bool Modprobe::LoadListedModules(bool strict) {
+    auto ret = true;
     for (const auto& module : module_load_) {
         if (!LoadWithAliases(module, true)) {
-            return false;
+            ret = false;
+            if (strict) break;
         }
     }
-    return true;
+    return ret;
 }
 
 bool Modprobe::Remove(const std::string& module_name) {
diff --git a/libstats/OWNERS b/libstats/OWNERS
index ed06fbc..7855774 100644
--- a/libstats/OWNERS
+++ b/libstats/OWNERS
@@ -1,4 +1,7 @@
-bookatz@google.com
 joeo@google.com
+muhammadq@google.com
+ruchirr@google.com
+singhtejinder@google.com
+tsaichristine@google.com
 yaochen@google.com
-yanglu@google.com
+yro@google.com
diff --git a/libziparchive/unzip.cpp b/libziparchive/unzip.cpp
index af70f1d..e4839b4 100644
--- a/libziparchive/unzip.cpp
+++ b/libziparchive/unzip.cpp
@@ -15,11 +15,12 @@
  */
 
 #include <errno.h>
-#include <error.h>
 #include <fcntl.h>
 #include <fnmatch.h>
 #include <getopt.h>
 #include <inttypes.h>
+#include <libgen.h>
+#include <stdarg.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <sys/stat.h>
@@ -64,6 +65,20 @@
 static uint64_t total_compressed_length = 0;
 static size_t file_count = 0;
 
+static const char* g_progname;
+
+static void die(int error, const char* fmt, ...) {
+  va_list ap;
+
+  va_start(ap, fmt);
+  fprintf(stderr, "%s: ", g_progname);
+  vfprintf(stderr, fmt, ap);
+  if (error != 0) fprintf(stderr, ": %s", strerror(error));
+  fprintf(stderr, "\n");
+  va_end(ap);
+  exit(1);
+}
+
 static bool ShouldInclude(const std::string& name) {
   // Explicitly excluded?
   if (!excludes.empty()) {
@@ -155,7 +170,7 @@
     char* line = nullptr;
     size_t n;
     if (getline(&line, &n, stdin) == -1) {
-      error(1, 0, "(EOF/read error; assuming [N]one...)");
+      die(0, "(EOF/read error; assuming [N]one...)");
       overwrite_mode = kNever;
       return false;
     }
@@ -183,10 +198,10 @@
   uint8_t* buffer = new uint8_t[entry.uncompressed_length];
   int err = ExtractToMemory(zah, &entry, buffer, entry.uncompressed_length);
   if (err < 0) {
-    error(1, 0, "failed to extract %s: %s", name.c_str(), ErrorCodeString(err));
+    die(0, "failed to extract %s: %s", name.c_str(), ErrorCodeString(err));
   }
   if (!android::base::WriteFully(1, buffer, entry.uncompressed_length)) {
-    error(1, errno, "failed to write %s to stdout", name.c_str());
+    die(errno, "failed to write %s to stdout", name.c_str());
   }
   delete[] buffer;
 }
@@ -194,7 +209,7 @@
 static void ExtractOne(ZipArchiveHandle zah, ZipEntry& entry, const std::string& name) {
   // Bad filename?
   if (StartsWith(name, "/") || StartsWith(name, "../") || name.find("/../") != std::string::npos) {
-    error(1, 0, "bad filename %s", name.c_str());
+    die(0, "bad filename %s", name.c_str());
   }
 
   // Where are we actually extracting to (for human-readable output)?
@@ -207,7 +222,7 @@
 
   // Ensure the directory hierarchy exists.
   if (!MakeDirectoryHierarchy(android::base::Dirname(name))) {
-    error(1, errno, "couldn't create directory hierarchy for %s", dst.c_str());
+    die(errno, "couldn't create directory hierarchy for %s", dst.c_str());
   }
 
   // An entry in a zip file can just be a directory itself.
@@ -218,7 +233,7 @@
         struct stat sb;
         if (stat(name.c_str(), &sb) != -1 && S_ISDIR(sb.st_mode)) return;
       }
-      error(1, errno, "couldn't extract directory %s", dst.c_str());
+      die(errno, "couldn't extract directory %s", dst.c_str());
     }
     return;
   }
@@ -231,12 +246,12 @@
     // Either overwrite_mode is kAlways or the user consented to this specific case.
     fd = open(name.c_str(), O_WRONLY | O_CREAT | O_CLOEXEC | O_TRUNC, entry.unix_mode);
   }
-  if (fd == -1) error(1, errno, "couldn't create file %s", dst.c_str());
+  if (fd == -1) die(errno, "couldn't create file %s", dst.c_str());
 
   // Actually extract into the file.
   if (!flag_q) printf("  inflating: %s\n", dst.c_str());
   int err = ExtractEntryToFile(zah, &entry, fd);
-  if (err < 0) error(1, 0, "failed to extract %s: %s", dst.c_str(), ErrorCodeString(err));
+  if (err < 0) die(0, "failed to extract %s: %s", dst.c_str(), ErrorCodeString(err));
   close(fd);
 }
 
@@ -345,7 +360,7 @@
   void* cookie;
   int err = StartIteration(zah, &cookie);
   if (err != 0) {
-    error(1, 0, "couldn't iterate %s: %s", archive_name, ErrorCodeString(err));
+    die(0, "couldn't iterate %s: %s", archive_name, ErrorCodeString(err));
   }
 
   ZipEntry entry;
@@ -354,7 +369,7 @@
     if (ShouldInclude(name)) ProcessOne(zah, entry, name);
   }
 
-  if (err < -1) error(1, 0, "failed iterating %s: %s", archive_name, ErrorCodeString(err));
+  if (err < -1) die(0, "failed iterating %s: %s", archive_name, ErrorCodeString(err));
   EndIteration(cookie);
 
   MaybeShowFooter();
@@ -420,14 +435,14 @@
 
 int main(int argc, char* argv[]) {
   // Who am I, and what am I doing?
-  const char* base = basename(argv[0]);
-  if (!strcmp(base, "ziptool") && argc > 1) return main(argc - 1, argv + 1);
-  if (!strcmp(base, "unzip")) {
+  g_progname = basename(argv[0]);
+  if (!strcmp(g_progname, "ziptool") && argc > 1) return main(argc - 1, argv + 1);
+  if (!strcmp(g_progname, "unzip")) {
     role = kUnzip;
-  } else if (!strcmp(base, "zipinfo")) {
+  } else if (!strcmp(g_progname, "zipinfo")) {
     role = kZipinfo;
   } else {
-    error(1, 0, "run as ziptool with unzip or zipinfo as the first argument, or symlink");
+    die(0, "run as ziptool with unzip or zipinfo as the first argument, or symlink");
   }
 
   static const struct option opts[] = {
@@ -484,19 +499,19 @@
     }
   }
 
-  if (!archive_name) error(1, 0, "missing archive filename");
+  if (!archive_name) die(0, "missing archive filename");
 
   // We can't support "-" to unzip from stdin because libziparchive relies on mmap.
   ZipArchiveHandle zah;
   int32_t err;
   if ((err = OpenArchive(archive_name, &zah)) != 0) {
-    error(1, 0, "couldn't open %s: %s", archive_name, ErrorCodeString(err));
+    die(0, "couldn't open %s: %s", archive_name, ErrorCodeString(err));
   }
 
   // Implement -d by changing into that directory.
   // We'll create implicit directories based on paths in the zip file, but we
   // require that the -d directory already exists.
-  if (flag_d && chdir(flag_d) == -1) error(1, errno, "couldn't chdir to %s", flag_d);
+  if (flag_d && chdir(flag_d) == -1) die(errno, "couldn't chdir to %s", flag_d);
 
   ProcessAll(zah);
 
diff --git a/rootdir/Android.mk b/rootdir/Android.mk
index 5241730..eac3f06 100644
--- a/rootdir/Android.mk
+++ b/rootdir/Android.mk
@@ -293,78 +293,26 @@
 LOCAL_MODULE_STEM := ld.config.txt
 include $(BUILD_PREBUILT)
 
-# Returns the unique installed basenames of a module, or module.so if there are
-# none.  The guess is to handle cases like libc, where the module itself is
-# marked uninstallable but a symlink is installed with the name libc.so.
-# $(1): list of libraries
-# $(2): suffix to to add to each library (not used for guess)
-define module-installed-files-or-guess
-$(foreach lib,$(1),$(or $(strip $(sort $(notdir $(call module-installed-files,$(lib)$(2))))),$(lib).so))
-endef
-
 #######################################
-# llndk.libraries.txt
-include $(CLEAR_VARS)
-LOCAL_MODULE := llndk.libraries.txt
-LOCAL_MODULE_CLASS := ETC
-LOCAL_MODULE_PATH := $(TARGET_OUT_ETC)
-LOCAL_MODULE_STEM := $(call append_vndk_version,$(LOCAL_MODULE))
-include $(BUILD_SYSTEM)/base_rules.mk
-$(LOCAL_BUILT_MODULE): PRIVATE_LLNDK_LIBRARIES := $(call module-installed-files-or-guess,$(LLNDK_LIBRARIES),)
-$(LOCAL_BUILT_MODULE):
-	@echo "Generate: $@"
-	@mkdir -p $(dir $@)
-	$(hide) echo -n > $@
-	$(hide) $(foreach lib,$(PRIVATE_LLNDK_LIBRARIES), \
-		echo $(lib) >> $@;)
+# {llndk,vndkcore,vndksp,vndkprivate,vndkcorevariant}.libraries.txt
+vndk_libraries_files := \
+  llndk.libraries.txt:$(SOONG_LLNDK_LIBRARIES_FILE)\
+  vndkcore.libraries.txt:$(SOONG_VNDKCORE_LIBRARIES_FILE)\
+  vndksp.libraries.txt:$(SOONG_VNDKSP_LIBRARIES_FILE)\
+  vndkprivate.libraries.txt:$(SOONG_VNDKPRIVATE_LIBRARIES_FILE)\
+  vndkcorevariant.libraries.txt:$(SOONG_VNDKCOREVARIANT_LIBRARIES_FILE)
 
-#######################################
-# vndksp.libraries.txt
-include $(CLEAR_VARS)
-LOCAL_MODULE := vndksp.libraries.txt
-LOCAL_MODULE_CLASS := ETC
-LOCAL_MODULE_PATH := $(TARGET_OUT_ETC)
-LOCAL_MODULE_STEM := $(call append_vndk_version,$(LOCAL_MODULE))
-include $(BUILD_SYSTEM)/base_rules.mk
-$(LOCAL_BUILT_MODULE): PRIVATE_VNDK_SAMEPROCESS_LIBRARIES := $(call module-installed-files-or-guess,$(VNDK_SAMEPROCESS_LIBRARIES),.com.android.vndk.current)
-$(LOCAL_BUILT_MODULE):
-	@echo "Generate: $@"
-	@mkdir -p $(dir $@)
-	$(hide) echo -n > $@
-	$(hide) $(foreach lib,$(PRIVATE_VNDK_SAMEPROCESS_LIBRARIES), \
-		echo $(lib) >> $@;)
-
-#######################################
-# vndkcore.libraries.txt
-include $(CLEAR_VARS)
-LOCAL_MODULE := vndkcore.libraries.txt
-LOCAL_MODULE_CLASS := ETC
-LOCAL_MODULE_PATH := $(TARGET_OUT_ETC)
-LOCAL_MODULE_STEM := $(call append_vndk_version,$(LOCAL_MODULE))
-include $(BUILD_SYSTEM)/base_rules.mk
-$(LOCAL_BUILT_MODULE): PRIVATE_VNDK_CORE_LIBRARIES := $(call module-installed-files-or-guess,$(VNDK_CORE_LIBRARIES),.com.android.vndk.current)
-$(LOCAL_BUILT_MODULE):
-	@echo "Generate: $@"
-	@mkdir -p $(dir $@)
-	$(hide) echo -n > $@
-	$(hide) $(foreach lib,$(PRIVATE_VNDK_CORE_LIBRARIES), \
-		echo $(lib) >> $@;)
-
-#######################################
-# vndkprivate.libraries.txt
-include $(CLEAR_VARS)
-LOCAL_MODULE := vndkprivate.libraries.txt
-LOCAL_MODULE_CLASS := ETC
-LOCAL_MODULE_PATH := $(TARGET_OUT_ETC)
-LOCAL_MODULE_STEM := $(call append_vndk_version,$(LOCAL_MODULE))
-include $(BUILD_SYSTEM)/base_rules.mk
-$(LOCAL_BUILT_MODULE): PRIVATE_VNDK_PRIVATE_LIBRARIES := $(call module-installed-files-or-guess,$(VNDK_PRIVATE_LIBRARIES),.com.android.vndk.current)
-$(LOCAL_BUILT_MODULE):
-	@echo "Generate: $@"
-	@mkdir -p $(dir $@)
-	$(hide) echo -n > $@
-	$(hide) $(foreach lib,$(PRIVATE_VNDK_PRIVATE_LIBRARIES), \
-		echo $(lib) >> $@;)
+$(foreach pair,$(vndk_libraries_files),\
+  $(eval _filename := $(call word-colon,1,$(pair)))\
+  $(eval _prebuilt := $(call word-colon,2,$(pair)))\
+  $(eval include $(CLEAR_VARS))\
+  $(eval LOCAL_MODULE := $(_filename))\
+  $(eval LOCAL_MODULE_CLASS := ETC)\
+  $(eval LOCAL_PREBUILT_MODULE_FILE := $(_prebuilt))\
+  $(eval LOCAL_MODULE_PATH := $(TARGET_OUT_ETC))\
+  $(eval LOCAL_MODULE_STEM := $(call append_vndk_version,$(LOCAL_MODULE)))\
+  $(eval include $(BUILD_PREBUILT)))
+vndk_libraries_files :=
 
 #######################################
 # sanitizer.libraries.txt
@@ -391,22 +339,6 @@
 		echo $(lib) >> $@;)
 
 #######################################
-# vndkcorevariant.libraries.txt
-include $(CLEAR_VARS)
-LOCAL_MODULE := vndkcorevariant.libraries.txt
-LOCAL_MODULE_CLASS := ETC
-LOCAL_MODULE_PATH := $(TARGET_OUT_ETC)
-LOCAL_MODULE_STEM := $(call append_vndk_version,$(LOCAL_MODULE))
-include $(BUILD_SYSTEM)/base_rules.mk
-$(LOCAL_BUILT_MODULE): PRIVATE_VNDK_CORE_VARIANT_LIBRARIES := $(call module-installed-files-or-guess,$(VNDK_USING_CORE_VARIANT_LIBRARIES),.vendor)
-$(LOCAL_BUILT_MODULE):
-	@echo "Generate: $@"
-	@mkdir -p $(dir $@)
-	$(hide) echo -n > $@
-	$(hide) $(foreach lib,$(PRIVATE_VNDK_CORE_VARIANT_LIBRARIES), \
-		echo $(lib) >> $@;)
-
-#######################################
 # adb_debug.prop in debug ramdisk
 include $(CLEAR_VARS)
 LOCAL_MODULE := adb_debug.prop
diff --git a/rootdir/init.rc b/rootdir/init.rc
index 9b77ce2..66d60fa 100644
--- a/rootdir/init.rc
+++ b/rootdir/init.rc
@@ -919,7 +919,7 @@
 on init && property:ro.debuggable=1
     start console
 
-on userspace-reboot
+on userspace-reboot-requested
   # TODO(b/135984674): reset all necessary properties here.
   setprop sys.init.userspace_reboot_in_progress 1
   setprop sys.boot_completed 0