Merge changes I22498d8a,I3bf409d9

* changes:
  Use generated wrappers for system properties.
  Check system properties to determine whether protected or unprotected VMs are supported.
diff --git a/apex/sign_virt_apex.py b/apex/sign_virt_apex.py
index 9a0fe1a..8fe3403 100644
--- a/apex/sign_virt_apex.py
+++ b/apex/sign_virt_apex.py
@@ -294,6 +294,8 @@
         input_dir, 'etc', 'microdroid_bootconfig.app_debuggable')
     bootconfig_full_debuggable = os.path.join(
         input_dir, 'etc', 'microdroid_bootconfig.full_debuggable')
+    uboot_env_img = os.path.join(
+        input_dir, 'etc', 'uboot_env.img')
 
     # Key(pubkey) for bootloader should match with the one used to make VBmeta below
     # while it's okay to use different keys for other image files.
@@ -330,17 +332,21 @@
         MakeVbmetaImage(args, key, vbmeta_img, images=[
                         boot_img, vendor_boot_img, init_boot_img, system_a_img, vendor_a_img])
 
-    # Re-sign bootconfigs with the same key
+    # Re-sign bootconfigs and the uboot_env with the same key
     bootconfig_sign_key = key
     AddHashFooter(args, bootconfig_sign_key, bootconfig_normal)
     AddHashFooter(args, bootconfig_sign_key, bootconfig_app_debuggable)
     AddHashFooter(args, bootconfig_sign_key, bootconfig_full_debuggable)
+    AddHashFooter(args, bootconfig_sign_key, uboot_env_img)
 
-    # Re-sign vbmeta_bootconfig with a chained_partition to "bootconfig"
-    # Note that, for now, `key` and `bootconfig_sign_key` are the same, but technically they
-    # can be different. Vbmeta records pubkeys which signed chained partitions.
+    # Re-sign vbmeta_bootconfig with chained_partitions to "bootconfig" and
+    # "uboot_env". Note that, for now, `key` and `bootconfig_sign_key` are the
+    # same, but technically they can be different. Vbmeta records pubkeys which
+    # signed chained partitions.
     MakeVbmetaImage(args, key, vbmeta_bootconfig_img, chained_partitions={
-                    'bootconfig': bootconfig_sign_key})
+                    'bootconfig': bootconfig_sign_key,
+                    'uboot_env': bootconfig_sign_key,
+    })
 
 
 def VerifyVirtApex(args):
diff --git a/compos/apex/Android.bp b/compos/apex/Android.bp
index ea72018..aec3c88 100644
--- a/compos/apex/Android.bp
+++ b/compos/apex/Android.bp
@@ -33,7 +33,7 @@
     key: "com.android.compos.key",
     certificate: ":com.android.compos.certificate",
 
-    // TODO(victorhsieh): make it updatable
+    // TODO(b/206618706): make it updatable
     updatable: false,
     future_updatable: true,
     platform_apis: true,
@@ -42,7 +42,6 @@
 
     binaries: [
         // Used in Android
-        "compos_key_cmd",
         "compos_verify_key",
         "composd",
         "composd_cmd",
diff --git a/compos/apk/assets/vm_test_config.json b/compos/apk/assets/vm_test_config.json
deleted file mode 100644
index 16d1037..0000000
--- a/compos/apk/assets/vm_test_config.json
+++ /dev/null
@@ -1,24 +0,0 @@
-{
-  "version": 1,
-  "os": {
-    "name": "microdroid"
-  },
-  "task": {
-    "type": "executable",
-    "command": "/apex/com.android.compos/bin/compsvc"
-  },
-  "apexes": [
-    {
-      "name": "com.android.art"
-    },
-    {
-      "name": "com.android.compos"
-    },
-    {
-      "name": "com.android.sdkext"
-    },
-    {
-      "name": "{CLASSPATH}"
-    }
-  ]
-}
diff --git a/compos/compos_key_cmd/Android.bp b/compos/compos_key_cmd/Android.bp
deleted file mode 100644
index d412f66..0000000
--- a/compos/compos_key_cmd/Android.bp
+++ /dev/null
@@ -1,23 +0,0 @@
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
-cc_binary {
-    name: "compos_key_cmd",
-    srcs: ["compos_key_cmd.cpp"],
-    apex_available: ["com.android.compos"],
-
-    static_libs: [
-        "lib_odsign_proto",
-    ],
-
-    shared_libs: [
-        "android.system.virtualizationservice-ndk",
-        "compos_aidl_interface-ndk",
-        "libbase",
-        "libbinder_ndk",
-        "libbinder_rpc_unstable",
-        "libfsverity",
-        "libprotobuf-cpp-lite",
-    ],
-}
diff --git a/compos/compos_key_cmd/compos_key_cmd.cpp b/compos/compos_key_cmd/compos_key_cmd.cpp
deleted file mode 100644
index 07ff636..0000000
--- a/compos/compos_key_cmd/compos_key_cmd.cpp
+++ /dev/null
@@ -1,530 +0,0 @@
-/*
- * Copyright (C) 2021 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 <aidl/android/system/virtualizationservice/BnVirtualMachineCallback.h>
-#include <aidl/android/system/virtualizationservice/IVirtualizationService.h>
-#include <aidl/com/android/compos/ICompOsService.h>
-#include <android-base/file.h>
-#include <android-base/logging.h>
-#include <android-base/result.h>
-#include <android-base/unique_fd.h>
-#include <android/binder_auto_utils.h>
-#include <android/binder_manager.h>
-#include <android/binder_process.h>
-#include <asm/byteorder.h>
-#include <libfsverity.h>
-#include <linux/fsverity.h>
-#include <stdio.h>
-#include <unistd.h>
-
-#include <binder_rpc_unstable.hpp>
-#include <chrono>
-#include <condition_variable>
-#include <filesystem>
-#include <iostream>
-#include <map>
-#include <mutex>
-#include <string>
-#include <string_view>
-#include <thread>
-
-#include "odsign_info.pb.h"
-
-using namespace std::literals;
-
-using aidl::android::system::virtualizationservice::BnVirtualMachineCallback;
-using aidl::android::system::virtualizationservice::DeathReason;
-using aidl::android::system::virtualizationservice::IVirtualizationService;
-using aidl::android::system::virtualizationservice::IVirtualMachine;
-using aidl::android::system::virtualizationservice::IVirtualMachineCallback;
-using aidl::android::system::virtualizationservice::PartitionType;
-using aidl::android::system::virtualizationservice::VirtualMachineAppConfig;
-using aidl::android::system::virtualizationservice::VirtualMachineConfig;
-using aidl::com::android::compos::CompOsKeyData;
-using aidl::com::android::compos::ICompOsService;
-using android::base::Dirname;
-using android::base::ErrnoError;
-using android::base::Error;
-using android::base::Fdopen;
-using android::base::Result;
-using android::base::unique_fd;
-using android::base::WriteFully;
-using ndk::ScopedAStatus;
-using ndk::ScopedFileDescriptor;
-using ndk::SharedRefBase;
-using odsign::proto::OdsignInfo;
-
-constexpr unsigned int kRpcPort = 6432;
-
-constexpr int kVmMemoryMib = 1024;
-
-constexpr const char* kConfigApkPath =
-        "/apex/com.android.compos/app/CompOSPayloadApp/CompOSPayloadApp.apk";
-
-// These are paths inside the APK
-constexpr const char* kDefaultConfigFilePath = "assets/vm_config.json";
-constexpr const char* kPreferStagedConfigFilePath = "assets/vm_config_staged.json";
-
-static bool writeBytesToFile(const std::vector<uint8_t>& bytes, const std::string& path) {
-    std::string str(bytes.begin(), bytes.end());
-    return android::base::WriteStringToFile(str, path);
-}
-
-static Result<std::vector<uint8_t>> readBytesFromFile(const std::string& path) {
-    std::string str;
-    if (!android::base::ReadFileToString(path, &str)) {
-        return Error() << "Failed to read " << path;
-    }
-    return std::vector<uint8_t>(str.begin(), str.end());
-}
-
-static std::shared_ptr<ICompOsService> getService(int cid) {
-    LOG(INFO) << "Connecting to cid " << cid;
-    ndk::SpAIBinder binder(cid == 0 ? AServiceManager_getService("android.system.composkeyservice")
-                                    : RpcClient(cid, kRpcPort));
-    return ICompOsService::fromBinder(binder);
-}
-
-namespace {
-
-void copyToLog(unique_fd&& fd) {
-    FILE* source = Fdopen(std::move(fd), "r");
-    if (source == nullptr) {
-        LOG(INFO) << "Can't log VM output";
-        return;
-    }
-    size_t size = 0;
-    char* line = nullptr;
-
-    LOG(INFO) << "Started logging VM output";
-
-    for (;;) {
-        ssize_t len = getline(&line, &size, source);
-        if (len < 0) {
-            LOG(INFO) << "VM logging ended: " << ErrnoError().str();
-            break;
-        }
-        LOG(DEBUG) << "VM: " << std::string_view(line, len);
-    }
-    free(line);
-}
-
-class Callback : public BnVirtualMachineCallback {
-public:
-    ::ndk::ScopedAStatus onPayloadStarted(int32_t in_cid,
-                                          const ::ndk::ScopedFileDescriptor& stream) override {
-        LOG(INFO) << "Payload started! cid = " << in_cid;
-
-        unique_fd stream_fd(dup(stream.get()));
-        std::thread logger([fd = std::move(stream_fd)]() mutable { copyToLog(std::move(fd)); });
-        logger.detach();
-
-        return ScopedAStatus::ok();
-    }
-
-    ::ndk::ScopedAStatus onPayloadReady(int32_t in_cid) override {
-        LOG(INFO) << "Payload is ready! cid = " << in_cid;
-        {
-            std::unique_lock lock(mMutex);
-            mReady = true;
-        }
-        mCv.notify_all();
-        return ScopedAStatus::ok();
-    }
-
-    ::ndk::ScopedAStatus onPayloadFinished(int32_t in_cid, int32_t in_exit_code) override {
-        LOG(INFO) << "Payload finished! cid = " << in_cid << ", exit_code = " << in_exit_code;
-        return ScopedAStatus::ok();
-    }
-
-    ::ndk::ScopedAStatus onError(int32_t in_cid, int32_t in_error_code,
-                                 const std::string& in_message) override {
-        LOG(WARNING) << "VM error! cid = " << in_cid << ", error_code = " << in_error_code
-                     << ", message = " << in_message;
-        {
-            std::unique_lock lock(mMutex);
-            mDied = true;
-        }
-        mCv.notify_all();
-        return ScopedAStatus::ok();
-    }
-
-    ::ndk::ScopedAStatus onDied(int32_t in_cid, DeathReason reason) override {
-        LOG(WARNING) << "VM died! cid = " << in_cid << " reason = " << toString(reason);
-        {
-            std::unique_lock lock(mMutex);
-            mDied = true;
-        }
-        mCv.notify_all();
-        return ScopedAStatus::ok();
-    }
-
-    bool waitUntilReady() {
-        std::unique_lock lock(mMutex);
-        // 10s is long enough on real hardware, but it can take 90s when using nested
-        // virtualization.
-        // TODO(b/200924405): Reduce timeout/detect nested virtualization
-        return mCv.wait_for(lock, std::chrono::seconds(120), [this] { return mReady || mDied; }) &&
-                !mDied;
-    }
-
-private:
-    std::mutex mMutex;
-    std::condition_variable mCv;
-    bool mReady;
-    bool mDied;
-};
-
-class TargetVm {
-public:
-    TargetVm(int cid, const std::string& logFile, const std::string& instanceImageFile,
-             bool debuggable, bool preferStaged)
-          : mCid(cid),
-            mLogFile(logFile),
-            mInstanceImageFile(instanceImageFile),
-            mDebuggable(debuggable),
-            mPreferStaged(preferStaged) {}
-
-    // Returns 0 if we are to connect to a local service, otherwise the CID of
-    // either an existing VM or a VM we have started, depending on the command
-    // line arguments.
-    Result<int> resolveCid() {
-        if (mInstanceImageFile.empty()) {
-            return mCid;
-        }
-        if (mCid != 0) {
-            return Error() << "Can't specify both cid and image file.";
-        }
-
-        // Start a new VM with a given instance.img
-
-        // We need a thread pool to receive VM callbacks.
-        ABinderProcess_startThreadPool();
-
-        ndk::SpAIBinder binder(
-                AServiceManager_waitForService("android.system.virtualizationservice"));
-        auto service = IVirtualizationService::fromBinder(binder);
-        if (!service) {
-            return Error() << "Failed to connect to virtualization service.";
-        }
-
-        // Console output and the system log output from the VM are redirected to this file.
-        ScopedFileDescriptor logFd;
-        if (mLogFile.empty()) {
-            logFd.set(dup(STDOUT_FILENO));
-            if (logFd.get() == -1) {
-                return ErrnoError() << "dup() failed: ";
-            }
-        } else {
-            logFd.set(TEMP_FAILURE_RETRY(open(mLogFile.c_str(),
-                                              O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC,
-                                              S_IRUSR | S_IWUSR)));
-            if (logFd.get() == -1) {
-                return ErrnoError() << "Failed to open " << mLogFile;
-            }
-        }
-
-        ScopedFileDescriptor apkFd(TEMP_FAILURE_RETRY(open(kConfigApkPath, O_RDONLY | O_CLOEXEC)));
-        if (apkFd.get() == -1) {
-            return ErrnoError() << "Failed to open config APK";
-        }
-
-        // Prepare an idsig file
-        std::string idsigPath = Dirname(mInstanceImageFile) + "/idsig";
-        {
-            ScopedFileDescriptor idsigFd(TEMP_FAILURE_RETRY(
-                    open(idsigPath.c_str(), O_RDWR | O_CREAT | O_TRUNC | O_CLOEXEC,
-                         S_IRUSR | S_IWUSR | S_IRGRP)));
-            if (idsigFd.get() == -1) {
-                return ErrnoError() << "Failed to create an idsig file";
-            }
-            auto status = service->createOrUpdateIdsigFile(apkFd, idsigFd);
-            if (!status.isOk()) {
-                return Error() << status.getDescription();
-            }
-        }
-
-        ScopedFileDescriptor idsigFd(
-                TEMP_FAILURE_RETRY(open(idsigPath.c_str(), O_RDONLY | O_CLOEXEC)));
-        if (idsigFd.get() == -1) {
-            return ErrnoError() << "Failed to open an idsig file";
-        }
-
-        ScopedFileDescriptor instanceFd(
-                TEMP_FAILURE_RETRY(open(mInstanceImageFile.c_str(), O_RDWR | O_CLOEXEC)));
-        if (instanceFd.get() == -1) {
-            return ErrnoError() << "Failed to open instance image file";
-        }
-
-        auto config = VirtualMachineConfig::make<VirtualMachineConfig::Tag::appConfig>();
-        auto& appConfig = config.get<VirtualMachineConfig::Tag::appConfig>();
-        appConfig.apk = std::move(apkFd);
-        appConfig.idsig = std::move(idsigFd);
-        appConfig.instanceImage = std::move(instanceFd);
-        appConfig.configPath = mPreferStaged ? kPreferStagedConfigFilePath : kDefaultConfigFilePath;
-        appConfig.debugLevel = mDebuggable ? VirtualMachineAppConfig::DebugLevel::FULL
-                                           : VirtualMachineAppConfig::DebugLevel::NONE;
-        appConfig.memoryMib = kVmMemoryMib;
-
-        LOG(INFO) << "Starting VM";
-        auto status = service->createVm(config, logFd, logFd, &mVm);
-        if (!status.isOk()) {
-            return Error() << status.getDescription();
-        }
-
-        int32_t cid;
-        status = mVm->getCid(&cid);
-        if (!status.isOk()) {
-            return Error() << status.getDescription();
-        }
-
-        LOG(INFO) << "Created VM with CID = " << cid;
-
-        // We need to use this rather than std::make_shared to make sure the
-        // embedded weak_ptr is initialised.
-        mCallback = SharedRefBase::make<Callback>();
-
-        status = mVm->registerCallback(mCallback);
-        if (!status.isOk()) {
-            return Error() << status.getDescription();
-        }
-
-        status = mVm->start();
-        if (!status.isOk()) {
-            return Error() << status.getDescription();
-        }
-        LOG(INFO) << "Started VM";
-
-        if (!mCallback->waitUntilReady()) {
-            return Error() << "VM Payload failed to start";
-        }
-
-        return cid;
-    }
-
-private:
-    const int mCid;
-    const std::string mLogFile;
-    const std::string mInstanceImageFile;
-    const bool mDebuggable;
-    const bool mPreferStaged;
-    std::shared_ptr<Callback> mCallback;
-    std::shared_ptr<IVirtualMachine> mVm;
-};
-
-} // namespace
-
-static Result<void> generate(TargetVm& vm, const std::string& blob_file,
-                             const std::string& public_key_file) {
-    auto cid = vm.resolveCid();
-    if (!cid.ok()) {
-        return cid.error();
-    }
-    auto service = getService(*cid);
-    if (!service) {
-        return Error() << "No service";
-    }
-
-    CompOsKeyData key_data;
-    auto status = service->generateSigningKey(&key_data);
-    if (!status.isOk()) {
-        return Error() << "Failed to generate key: " << status.getDescription();
-    }
-
-    if (!writeBytesToFile(key_data.keyBlob, blob_file)) {
-        return Error() << "Failed to write keyBlob to " << blob_file;
-    }
-
-    if (!writeBytesToFile(key_data.publicKey, public_key_file)) {
-        return Error() << "Failed to write public key to " << public_key_file;
-    }
-
-    return {};
-}
-
-static Result<bool> verify(TargetVm& vm, const std::string& blob_file,
-                           const std::string& public_key_file) {
-    auto cid = vm.resolveCid();
-    if (!cid.ok()) {
-        return cid.error();
-    }
-    auto service = getService(*cid);
-    if (!service) {
-        return Error() << "No service";
-    }
-
-    auto blob = readBytesFromFile(blob_file);
-    if (!blob.ok()) {
-        return blob.error();
-    }
-
-    auto public_key = readBytesFromFile(public_key_file);
-    if (!public_key.ok()) {
-        return public_key.error();
-    }
-
-    bool result = false;
-    auto status = service->verifySigningKey(blob.value(), public_key.value(), &result);
-    if (!status.isOk()) {
-        return Error() << "Failed to verify key: " << status.getDescription();
-    }
-
-    return result;
-}
-
-static Result<void> initializeKey(TargetVm& vm, const std::string& blob_file) {
-    auto cid = vm.resolveCid();
-    if (!cid.ok()) {
-        return cid.error();
-    }
-    auto service = getService(*cid);
-    if (!service) {
-        return Error() << "No service";
-    }
-
-    auto blob = readBytesFromFile(blob_file);
-    if (!blob.ok()) {
-        return blob.error();
-    }
-
-    auto status = service->initializeSigningKey(blob.value());
-    if (!status.isOk()) {
-        return Error() << "Failed to initialize signing key: " << status.getDescription();
-    }
-    return {};
-}
-
-static Result<void> makeInstanceImage(const std::string& image_path) {
-    ndk::SpAIBinder binder(AServiceManager_waitForService("android.system.virtualizationservice"));
-    auto service = IVirtualizationService::fromBinder(binder);
-    if (!service) {
-        return Error() << "Failed to connect to virtualization service.";
-    }
-
-    ScopedFileDescriptor fd(TEMP_FAILURE_RETRY(
-            open(image_path.c_str(), O_CREAT | O_RDWR | O_TRUNC | O_CLOEXEC, S_IRUSR | S_IWUSR)));
-    if (fd.get() == -1) {
-        return ErrnoError() << "Failed to create image file";
-    }
-
-    auto status = service->initializeWritablePartition(fd, 10 * 1024 * 1024,
-                                                       PartitionType::ANDROID_VM_INSTANCE);
-    if (!status.isOk()) {
-        return Error() << "Failed to initialize partition: " << status.getDescription();
-    }
-    return {};
-}
-
-int main(int argc, char** argv) {
-    // Restrict access to our outputs to the current user.
-    umask(077);
-
-    int cid = 0;
-    std::string imageFile;
-    std::string logFile;
-    bool debuggable = false;
-    bool preferStaged = false;
-
-    for (;;) {
-        // Options with no associated value
-        if (argc >= 2) {
-            if (argv[1] == "--debug"sv) {
-                debuggable = true;
-                argc -= 1;
-                argv += 1;
-                continue;
-            } else if (argv[1] == "--staged"sv) {
-                preferStaged = true;
-                argc -= 1;
-                argv += 1;
-                continue;
-            }
-        }
-        if (argc < 3) break;
-        // Options requiring a value
-        if (argv[1] == "--cid"sv) {
-            cid = atoi(argv[2]);
-            if (cid == 0) {
-                std::cerr << "Invalid cid\n";
-                return 1;
-            }
-        } else if (argv[1] == "--start"sv) {
-            imageFile = argv[2];
-        } else if (argv[1] == "--log"sv) {
-            logFile = argv[2];
-        } else {
-            break;
-        }
-        argc -= 2;
-        argv += 2;
-    }
-
-    TargetVm vm(cid, logFile, imageFile, debuggable, preferStaged);
-
-    if (argc == 4 && argv[1] == "generate"sv) {
-        auto result = generate(vm, argv[2], argv[3]);
-        if (result.ok()) {
-            return 0;
-        } else {
-            std::cerr << result.error() << '\n';
-        }
-    } else if (argc == 4 && argv[1] == "verify"sv) {
-        auto result = verify(vm, argv[2], argv[3]);
-        if (result.ok()) {
-            if (result.value()) {
-                std::cerr << "Key files are valid.\n";
-                return 0;
-            } else {
-                std::cerr << "Key files are not valid.\n";
-            }
-        } else {
-            std::cerr << result.error() << '\n';
-        }
-    } else if (argc == 3 && argv[1] == "init-key"sv) {
-        auto result = initializeKey(vm, argv[2]);
-        if (result.ok()) {
-            return 0;
-        } else {
-            std::cerr << result.error() << '\n';
-        }
-    } else if (argc == 3 && argv[1] == "make-instance"sv) {
-        auto result = makeInstanceImage(argv[2]);
-        if (result.ok()) {
-            return 0;
-        } else {
-            std::cerr << result.error() << '\n';
-        }
-    } else {
-        std::cerr << "Usage: compos_key_cmd [OPTIONS] COMMAND\n"
-                  << "Where COMMAND can be:\n"
-                  << "  make-instance <image file> Create an empty instance image file for a VM.\n"
-                  << "  generate <blob file> <public key file> Generate new key pair and write\n"
-                  << "    the private key blob and public key to the specified files.\n "
-                  << "  verify <blob file> <public key file> Verify that the content of the\n"
-                  << "    specified private key blob and public key files are valid.\n "
-                  << "  init-key <blob file> Initialize the service key.\n"
-                  << "\n"
-                  << "OPTIONS: --log <log file> --debug --staged\n"
-                  << "    (--cid <cid> | --start <image file>)\n"
-                  << "  Specify --log to write VM log to a file rather than stdout.\n"
-                  << "  Specify --debug with --start to make the VM fully debuggable.\n"
-                  << "  Specify --staged with --start to prefer staged APEXes in the VM.\n"
-                  << "  Specify --cid to connect to a VM rather than the host.\n"
-                  << "  Specify --start to start a VM from the given instance image file and\n "
-                  << "    connect to that.\n";
-    }
-    return 1;
-}
diff --git a/compos/composd/src/composd_main.rs b/compos/composd/src/composd_main.rs
index 235c107..d1b711d 100644
--- a/compos/composd/src/composd_main.rs
+++ b/compos/composd/src/composd_main.rs
@@ -29,6 +29,7 @@
 use anyhow::{Context, Result};
 use compos_common::compos_client::VmInstance;
 use log::{error, info};
+use std::panic;
 use std::sync::Arc;
 
 fn try_main() -> Result<()> {
@@ -38,6 +39,11 @@
         android_logger::Config::default().with_tag("composd").with_min_level(log_level),
     );
 
+    // Redirect panic messages to logcat.
+    panic::set_hook(Box::new(|panic_info| {
+        log::error!("{}", panic_info);
+    }));
+
     ProcessState::start_thread_pool();
 
     let virtualization_service = VmInstance::connect_to_virtualization_service()?;
diff --git a/compos/src/artifact_signer.rs b/compos/src/artifact_signer.rs
index e1b0efa..b5eb8cb 100644
--- a/compos/src/artifact_signer.rs
+++ b/compos/src/artifact_signer.rs
@@ -20,7 +20,7 @@
 #![allow(dead_code)] // Will be used soon
 
 use crate::fsverity;
-use crate::signing_key::Signer;
+use crate::signing_key::DiceSigner;
 use anyhow::{anyhow, Context, Result};
 use odsign_proto::odsign_info::OdsignInfo;
 use protobuf::Message;
@@ -63,7 +63,7 @@
 
     /// Consume this ArtifactSigner and write details of all its artifacts to the given path,
     /// with accompanying sigature file.
-    pub fn write_info_and_signature(self, signer: Signer, info_path: &Path) -> Result<()> {
+    pub fn write_info_and_signature(self, signer: DiceSigner, info_path: &Path) -> Result<()> {
         let mut info = OdsignInfo::new();
         info.mut_file_hashes().extend(self.file_digests.into_iter());
         let bytes = info.write_to_bytes()?;
diff --git a/compos/src/compilation.rs b/compos/src/compilation.rs
index 7e3834a..48ba4a6 100644
--- a/compos/src/compilation.rs
+++ b/compos/src/compilation.rs
@@ -27,7 +27,7 @@
 use std::process::Command;
 
 use crate::artifact_signer::ArtifactSigner;
-use crate::signing_key::Signer;
+use crate::signing_key::DiceSigner;
 use authfs_aidl_interface::aidl::com::android::virt::fs::{
     AuthFsConfig::{
         AuthFsConfig, InputDirFdAnnotation::InputDirFdAnnotation,
@@ -101,7 +101,7 @@
     odrefresh_path: &Path,
     context: OdrefreshContext,
     authfs_service: Strong<dyn IAuthFsService>,
-    signer: Signer,
+    signer: DiceSigner,
 ) -> Result<ExitCode> {
     // Mount authfs (via authfs_service). The authfs instance unmounts once the `authfs` variable
     // is out of scope.
diff --git a/compos/src/compsvc.rs b/compos/src/compsvc.rs
index 9d754a7..3ec15dd 100644
--- a/compos/src/compsvc.rs
+++ b/compos/src/compsvc.rs
@@ -27,7 +27,8 @@
 use std::sync::RwLock;
 
 use crate::compilation::{odrefresh, OdrefreshContext};
-use crate::signing_key::{Signer, SigningKey};
+use crate::dice::Dice;
+use crate::signing_key::{DiceSigner, DiceSigningKey};
 use authfs_aidl_interface::aidl::com::android::virt::fs::IAuthFsService::IAuthFsService;
 use compos_aidl_interface::aidl::com::android::compos::{
     CompOsKeyData::CompOsKeyData,
@@ -44,7 +45,7 @@
 pub fn new_binder() -> Result<Strong<dyn ICompOsService>> {
     let service = CompOsService {
         odrefresh_path: PathBuf::from(ODREFRESH_PATH),
-        signing_key: SigningKey::new()?,
+        signing_key: DiceSigningKey::new(Dice::new()?),
         key_blob: RwLock::new(Vec::new()),
     };
     Ok(BnCompOsService::new_binder(service, BinderFeatures::default()))
@@ -52,12 +53,12 @@
 
 struct CompOsService {
     odrefresh_path: PathBuf,
-    signing_key: SigningKey,
+    signing_key: DiceSigningKey,
     key_blob: RwLock<Vec<u8>>,
 }
 
 impl CompOsService {
-    fn new_signer(&self) -> BinderResult<Signer> {
+    fn new_signer(&self) -> BinderResult<DiceSigner> {
         let key = &*self.key_blob.read().unwrap();
         if key.is_empty() {
             Err(new_binder_exception(ExceptionCode::ILLEGAL_STATE, "Key is not initialized"))
diff --git a/compos/src/compsvc_main.rs b/compos/src/compsvc_main.rs
index c2923f0..ebb5514 100644
--- a/compos/src/compsvc_main.rs
+++ b/compos/src/compsvc_main.rs
@@ -38,6 +38,7 @@
 use binder_common::rpc_server::run_rpc_server;
 use compos_common::COMPOS_VSOCK_PORT;
 use log::{debug, error};
+use std::panic;
 
 /// The CID representing the host VM
 const VMADDR_CID_HOST: u32 = 2;
@@ -59,6 +60,10 @@
         android_logger::init_once(
             android_logger::Config::default().with_tag("compsvc").with_min_level(log::Level::Debug),
         );
+        // Redirect panic messages to logcat.
+        panic::set_hook(Box::new(|panic_info| {
+            log::error!("{}", panic_info);
+        }));
     }
 
     let service = compsvc::new_binder()?.as_binder();
diff --git a/compos/src/dice.rs b/compos/src/dice.rs
index 9f66b5e..25148ab 100644
--- a/compos/src/dice.rs
+++ b/compos/src/dice.rs
@@ -20,6 +20,7 @@
 use android_security_dice::binder::{wait_for_interface, Strong};
 use anyhow::{Context, Result};
 
+#[derive(Clone)]
 pub struct Dice {
     node: Strong<dyn IDiceNode>,
 }
diff --git a/compos/src/signing_key.rs b/compos/src/signing_key.rs
index 175a11b..90beecf 100644
--- a/compos/src/signing_key.rs
+++ b/compos/src/signing_key.rs
@@ -28,13 +28,26 @@
     signature,
 };
 
-pub struct SigningKey {
-    _unused: (), // Prevent construction other than by new()
+pub type DiceSigningKey = SigningKey<Dice>;
+pub type DiceSigner = Signer<Dice>;
+
+pub struct SigningKey<T: SecretStore> {
+    secret_store: T,
 }
 
-impl SigningKey {
-    pub fn new() -> Result<Self> {
-        Ok(Self { _unused: () })
+pub trait SecretStore: Clone {
+    fn get_secret(&self) -> Result<Vec<u8>>;
+}
+
+impl SecretStore for Dice {
+    fn get_secret(&self) -> Result<Vec<u8>> {
+        self.get_sealing_cdi()
+    }
+}
+
+impl<T: SecretStore> SigningKey<T> {
+    pub fn new(secret_store: T) -> Self {
+        Self { secret_store }
     }
 
     pub fn generate(&self) -> Result<CompOsKeyData> {
@@ -43,7 +56,8 @@
             bail!("Failed to generate key pair: {}", key_result.error);
         }
 
-        let encrypted = encrypt_private_key(&Dice::new()?, &key_result.private_key)?;
+        let encrypted =
+            encrypt_private_key(&self.secret_store.get_secret()?, &key_result.private_key)?;
         Ok(CompOsKeyData { publicKey: key_result.public_key, keyBlob: encrypted })
     }
 
@@ -63,19 +77,19 @@
         Ok(())
     }
 
-    pub fn new_signer(&self, key_blob: &[u8]) -> Result<Signer> {
-        Ok(Signer { key_blob: key_blob.to_owned(), dice: Dice::new()? })
+    pub fn new_signer(&self, key_blob: &[u8]) -> Result<Signer<T>> {
+        Ok(Signer { key_blob: key_blob.to_owned(), secret_store: self.secret_store.clone() })
     }
 }
 
-pub struct Signer {
+pub struct Signer<T: SecretStore> {
     key_blob: Vec<u8>,
-    dice: Dice,
+    secret_store: T,
 }
 
-impl Signer {
+impl<T: SecretStore> Signer<T> {
     pub fn sign(self, data: &[u8]) -> Result<Vec<u8>> {
-        let private_key = decrypt_private_key(&self.dice, &self.key_blob)?;
+        let private_key = decrypt_private_key(&self.secret_store.get_secret()?, &self.key_blob)?;
         let sign_result = compos_native::sign(&private_key, data);
         if sign_result.signature.is_empty() {
             bail!("Failed to sign: {}", sign_result.error);
@@ -84,14 +98,69 @@
     }
 }
 
-fn encrypt_private_key(dice: &Dice, private_key: &[u8]) -> Result<Vec<u8>> {
-    let cdi = dice.get_sealing_cdi()?;
-    let aead_key = blob_encryption::derive_aead_key(&cdi)?;
+fn encrypt_private_key(vm_secret: &[u8], private_key: &[u8]) -> Result<Vec<u8>> {
+    let aead_key = blob_encryption::derive_aead_key(vm_secret)?;
     blob_encryption::encrypt_bytes(aead_key, private_key)
 }
 
-fn decrypt_private_key(dice: &Dice, blob: &[u8]) -> Result<Vec<u8>> {
-    let cdi = dice.get_sealing_cdi()?;
-    let aead_key = blob_encryption::derive_aead_key(&cdi)?;
+fn decrypt_private_key(vm_secret: &[u8], blob: &[u8]) -> Result<Vec<u8>> {
+    let aead_key = blob_encryption::derive_aead_key(vm_secret)?;
     blob_encryption::decrypt_bytes(aead_key, blob)
 }
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    const SECRET: &[u8] = b"This is not very secret";
+
+    #[derive(Clone)]
+    struct TestSecretStore;
+
+    impl SecretStore for TestSecretStore {
+        fn get_secret(&self) -> Result<Vec<u8>> {
+            Ok(SECRET.to_owned())
+        }
+    }
+
+    type TestSigningKey = SigningKey<TestSecretStore>;
+
+    fn signing_key_for_test() -> TestSigningKey {
+        TestSigningKey::new(TestSecretStore)
+    }
+
+    #[test]
+    fn test_generated_key_verifies() -> Result<()> {
+        let signing_key = signing_key_for_test();
+        let key_pair = signing_key.generate()?;
+
+        signing_key.verify(&key_pair.keyBlob, &key_pair.publicKey)
+    }
+
+    #[test]
+    fn test_bogus_key_pair_rejected() -> Result<()> {
+        let signing_key = signing_key_for_test();
+        let key_pair = signing_key.generate()?;
+
+        // Swap public key & key blob - clearly invalid
+        assert!(signing_key.verify(&key_pair.publicKey, &key_pair.keyBlob).is_err());
+
+        Ok(())
+    }
+
+    #[test]
+    fn test_mismatched_key_rejected() -> Result<()> {
+        let signing_key = signing_key_for_test();
+        let key_pair1 = signing_key.generate()?;
+        let key_pair2 = signing_key.generate()?;
+
+        // Both pairs should be valid
+        signing_key.verify(&key_pair1.keyBlob, &key_pair1.publicKey)?;
+        signing_key.verify(&key_pair2.keyBlob, &key_pair2.publicKey)?;
+
+        // But using the public key from one and the private key from the other should not,
+        // even though both are well-formed
+        assert!(signing_key.verify(&key_pair1.publicKey, &key_pair2.keyBlob).is_err());
+        Ok(())
+    }
+}
diff --git a/compos/tests/java/android/compos/test/ComposKeyTestCase.java b/compos/tests/java/android/compos/test/ComposKeyTestCase.java
deleted file mode 100644
index 49235fe..0000000
--- a/compos/tests/java/android/compos/test/ComposKeyTestCase.java
+++ /dev/null
@@ -1,171 +0,0 @@
-/*
- * Copyright (C) 2021 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.
- */
-
-package android.compos.test;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import android.platform.test.annotations.RootPermissionTest;
-import android.virt.test.CommandRunner;
-import android.virt.test.VirtualizationTestCaseBase;
-
-import com.android.compatibility.common.util.PollingCheck;
-import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
-import com.android.tradefed.util.CommandResult;
-import com.android.tradefed.util.CommandStatus;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.Optional;
-
-@RootPermissionTest
-@RunWith(DeviceJUnit4ClassRunner.class)
-public final class ComposKeyTestCase extends VirtualizationTestCaseBase {
-
-    /**
-     * Wait time for service to be ready on boot
-     */
-    private static final int READY_LATENCY_MS = 10 * 1000; // 10 seconds
-
-    /**
-     * Path to compos_key_cmd tool
-     */
-    private static final String COMPOS_KEY_CMD_BIN = "/apex/com.android.compos/bin/compos_key_cmd";
-
-    /**
-     * Path to the com.android.compos.payload APK
-     */
-    private static final String COMPOS_PAYLOAD_APK_PATH =
-            "/apex/com.android.compos/app/CompOSPayloadApp/CompOSPayloadApp.apk";
-
-    /**
-     * Config of the test VM. This is a path inside the APK.
-     */
-    private static final String VM_TEST_CONFIG_PATH = "assets/vm_test_config.json";
-
-    private String mCid;
-
-    @Before
-    public void setUp() throws Exception {
-        testIfDeviceIsCapable(getDevice());
-
-        prepareVirtualizationTestSetup(getDevice());
-    }
-
-    @After
-    public void tearDown() throws Exception {
-        if (mCid != null) {
-            shutdownMicrodroid(getDevice(), mCid);
-            mCid = null;
-        }
-
-        cleanUpVirtualizationTestSetup(getDevice());
-    }
-
-    @Test
-    public void testKeyService() throws Exception {
-        startVm();
-        waitForServiceRunning();
-
-        CommandRunner android = new CommandRunner(getDevice());
-        CommandResult result;
-
-        // Generate keys - should succeed
-        android.run(
-                COMPOS_KEY_CMD_BIN,
-                "--cid " + mCid,
-                "generate",
-                TEST_ROOT + "test_key.blob",
-                TEST_ROOT + "test_key.pubkey");
-
-        // Verify them - should also succeed, since we just generated them
-        android.run(
-                COMPOS_KEY_CMD_BIN,
-                "--cid " + mCid,
-                "verify",
-                TEST_ROOT + "test_key.blob",
-                TEST_ROOT + "test_key.pubkey");
-
-        // Swap public key & blob - should fail to verify
-        result =
-                android.runForResult(
-                        COMPOS_KEY_CMD_BIN,
-                        "--cid " + mCid,
-                        "verify",
-                        TEST_ROOT + "test_key.pubkey",
-                        TEST_ROOT + "test_key.blob");
-        assertThat(result.getStatus()).isEqualTo(CommandStatus.FAILED);
-
-        // Generate another set of keys - should succeed
-        android.run(
-                COMPOS_KEY_CMD_BIN,
-                "--cid " + mCid,
-                "generate",
-                TEST_ROOT + "test_key2.blob",
-                TEST_ROOT + "test_key2.pubkey");
-
-        // They should also verify ok
-        android.run(
-                COMPOS_KEY_CMD_BIN,
-                "--cid " + mCid,
-                "verify",
-                TEST_ROOT + "test_key2.blob",
-                TEST_ROOT + "test_key2.pubkey");
-
-        // Mismatched key blob & public key should fail to verify
-        result =
-                android.runForResult(
-                        COMPOS_KEY_CMD_BIN,
-                        "--cid " + mCid,
-                        "verify",
-                        TEST_ROOT + "test_key.pubkey",
-                        TEST_ROOT + "test_key2.blob");
-        assertThat(result.getStatus()).isEqualTo(CommandStatus.FAILED);
-    }
-
-    private void startVm() throws Exception {
-        final String packageName = "com.android.compos.payload";
-        mCid =
-                startMicrodroid(
-                        getDevice(),
-                        getBuild(),
-                        /* apkName, no need to install */ null,
-                        COMPOS_PAYLOAD_APK_PATH,
-                        /* packageName - not needed, we know the path */ null,
-                        /* extraIdSigPaths */ null,
-                        VM_TEST_CONFIG_PATH,
-                        /* debug */ true,
-                        /* use default memoryMib */ 0,
-                        Optional.empty(),
-                        Optional.empty());
-        adbConnectToMicrodroid(getDevice(), mCid);
-    }
-
-    private void waitForServiceRunning() {
-        try {
-            PollingCheck.waitFor(READY_LATENCY_MS, this::isServiceRunning);
-        } catch (Exception e) {
-            throw new RuntimeException("Service unavailable", e);
-        }
-    }
-
-    private boolean isServiceRunning() {
-        return tryRunOnMicrodroid("pidof compsvc") != null;
-    }
-}
diff --git a/compos/tests/java/android/compos/test/ComposTestCase.java b/compos/tests/java/android/compos/test/ComposTestCase.java
index ff3baa2..1213ada 100644
--- a/compos/tests/java/android/compos/test/ComposTestCase.java
+++ b/compos/tests/java/android/compos/test/ComposTestCase.java
@@ -38,6 +38,8 @@
     // Binaries used in test. (These paths are valid both in host and Microdroid.)
     private static final String ODREFRESH_BIN = "/apex/com.android.art/bin/odrefresh";
     private static final String COMPOSD_CMD_BIN = "/apex/com.android.compos/bin/composd_cmd";
+    private static final String COMPOS_VERIFY_KEY_BIN =
+            "/apex/com.android.compos/bin/compos_verify_key";
 
     /** Output directory of odrefresh */
     private static final String TEST_ARTIFACTS_DIR = "test-artifacts";
@@ -148,8 +150,11 @@
         assertThat(actualChecksumSnapshot).isEqualTo(expectedChecksumSnapshot);
 
         // Expect extra files generated by CompOS exist.
-        android.assumeSuccess("test -f " + ODREFRESH_OUTPUT_DIR + "/compos.info");
-        android.assumeSuccess("test -f " + ODREFRESH_OUTPUT_DIR + "/compos.info.signature");
+        android.run("test -f " + ODREFRESH_OUTPUT_DIR + "/compos.info");
+        android.run("test -f " + ODREFRESH_OUTPUT_DIR + "/compos.info.signature");
+
+        // Expect the CompOS public key & key blob to be valid
+        android.run(COMPOS_VERIFY_KEY_BIN + " --debug --instance test");
     }
 
     private CommandResult runOdrefresh(CommandRunner android, String command) throws Exception {
diff --git a/compos/verify_key/verify_key.rs b/compos/verify_key/verify_key.rs
index 13d3c8b..9454442 100644
--- a/compos/verify_key/verify_key.rs
+++ b/compos/verify_key/verify_key.rs
@@ -27,6 +27,7 @@
 };
 use std::fs::{self, File};
 use std::io::Read;
+use std::panic;
 use std::path::{Path, PathBuf};
 
 const MAX_FILE_SIZE_BYTES: u64 = 8 * 1024;
@@ -38,6 +39,11 @@
             .with_min_level(log::Level::Info),
     );
 
+    // Redirect panic messages to logcat.
+    panic::set_hook(Box::new(|panic_info| {
+        log::error!("{}", panic_info);
+    }));
+
     if let Err(e) = try_main() {
         log::error!("{:?}", e);
         std::process::exit(-1)
diff --git a/microdroid/Android.bp b/microdroid/Android.bp
index 6f27ce1..3566bd2 100644
--- a/microdroid/Android.bp
+++ b/microdroid/Android.bp
@@ -348,6 +348,10 @@
             name: "bootconfig",
             private_key: ":microdroid_sign_key",
         },
+        {
+            name: "uboot_env",
+            private_key: ":microdroid_sign_key",
+        },
     ],
 }
 
@@ -524,18 +528,42 @@
 
 genrule {
     name: "microdroid_uboot_env_gen",
-    tools: ["mkenvimage_host"],
-    srcs: ["uboot-env.txt"],
+    tools: [
+        "mkenvimage_host",
+        "avbtool",
+    ],
+    srcs: [
+        "uboot-env.txt",
+        ":microdroid_sign_key",
+    ],
     out: ["output.img"],
-    cmd: "$(location mkenvimage_host) -s 4096 -o $(out) $(in)",
+    cmd: "$(location mkenvimage_host) -s 4096 -o $(out) $(location uboot-env.txt) && " +
+        "$(location avbtool) add_hash_footer " +
+        "--algorithm SHA256_RSA4096 " +
+        "--partition_name uboot_env " +
+        "--key $(location :microdroid_sign_key) " +
+        "--partition_size $$(( " + avb_hash_footer_kb + " * 1024 + ( $$(stat --format=%s $(out)) + 4096 - 1 ) / 4096 * 4096 )) " +
+        "--image $(out)",
 }
 
 genrule {
     name: "microdroid_uboot_env_gen_x86_64",
-    tools: ["mkenvimage_host"],
-    srcs: ["uboot-env-x86_64.txt"],
+    tools: [
+        "mkenvimage_host",
+        "avbtool",
+    ],
+    srcs: [
+        "uboot-env-x86_64.txt",
+        ":microdroid_sign_key",
+    ],
     out: ["output.img"],
-    cmd: "$(location mkenvimage_host) -s 4096 -o $(out) $(in)",
+    cmd: "$(location mkenvimage_host) -s 4096 -o $(out) $(location uboot-env-x86_64.txt) && " +
+        "$(location avbtool) add_hash_footer " +
+        "--algorithm SHA256_RSA4096 " +
+        "--partition_name uboot_env " +
+        "--key $(location :microdroid_sign_key) " +
+        "--partition_size $$(( " + avb_hash_footer_kb + " * 1024 + ( $$(stat --format=%s $(out)) + 4096 - 1 ) / 4096 * 4096 )) " +
+        "--image $(out)",
 }
 
 // Note that keys can be different for filesystem images even though we're using the same key
diff --git a/virtualizationservice/src/payload.rs b/virtualizationservice/src/payload.rs
index c661d44..7b8cb7f 100644
--- a/virtualizationservice/src/payload.rs
+++ b/virtualizationservice/src/payload.rs
@@ -53,7 +53,7 @@
     list: Vec<ApexInfo>,
 }
 
-#[derive(Clone, Debug, Deserialize)]
+#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq)]
 struct ApexInfo {
     #[serde(rename = "moduleName")]
     name: String,
@@ -69,6 +69,12 @@
 
     #[serde(rename = "isFactory")]
     is_factory: bool,
+
+    #[serde(rename = "isActive")]
+    is_active: bool,
+
+    #[serde(rename = "provideSharedApexLibs")]
+    provide_shared_apex_libs: bool,
 }
 
 impl ApexInfoList {
@@ -94,21 +100,19 @@
             Ok(apex_info_list)
         })
     }
+}
 
-    /// Returns the list of apex names matching with the predicate
-    fn get_matching(&self, predicate: fn(&ApexInfo) -> bool) -> Vec<String> {
-        self.list.iter().filter(|info| predicate(info)).map(|info| info.name.clone()).collect()
-    }
-
-    fn get(&self, apex_name: &str) -> Result<&ApexInfo> {
-        self.list
-            .iter()
-            .find(|apex| apex.name == apex_name)
-            .ok_or_else(|| anyhow!("{} not found.", apex_name))
-    }
-
-    fn get_path_for(&self, apex_name: &str) -> Result<PathBuf> {
-        Ok(self.get(apex_name)?.path.clone())
+impl ApexInfo {
+    fn matches(&self, apex_config: &ApexConfig) -> bool {
+        // Match with pseudo name "{CLASSPATH}" which represents APEXes contributing
+        // to any derive_classpath environment variable
+        if apex_config.name == "{CLASSPATH}" && self.has_classpath_jar {
+            return true;
+        }
+        if apex_config.name == self.name {
+            return true;
+        }
+        false
     }
 }
 
@@ -151,20 +155,18 @@
 
 fn make_metadata_file(
     config_path: &str,
-    apex_names: &[String],
+    apex_infos: &[&ApexInfo],
     temporary_directory: &Path,
-    apex_list: &ApexInfoList,
 ) -> Result<ParcelFileDescriptor> {
     let metadata_path = temporary_directory.join("metadata");
     let metadata = Metadata {
         version: 1,
-        apexes: apex_names
+        apexes: apex_infos
             .iter()
             .enumerate()
-            .map(|(i, apex_name)| {
-                let apex_info = apex_list.get(apex_name)?;
+            .map(|(i, apex_info)| {
                 Ok(ApexPayload {
-                    name: apex_name.clone(),
+                    name: apex_info.name.clone(),
                     partition_name: format!("microdroid-apex-{}", i),
                     last_update_seconds: apex_info.last_update_seconds,
                     is_factory: apex_info.is_factory,
@@ -226,12 +228,13 @@
     let pm = PackageManager::new()?;
     let apex_list = pm.get_apex_list(vm_payload_config.prefer_staged)?;
 
-    // collect APEX names from config
-    let apexes = collect_apex_names(&apex_list, &vm_payload_config.apexes, app_config.debugLevel);
-    info!("Microdroid payload APEXes: {:?}", apexes);
+    // collect APEXes from config
+    let apex_infos =
+        collect_apex_infos(&apex_list, &vm_payload_config.apexes, app_config.debugLevel);
+    info!("Microdroid payload APEXes: {:?}", apex_infos.iter().map(|ai| &ai.name));
 
     let metadata_file =
-        make_metadata_file(&app_config.configPath, &apexes, temporary_directory, &apex_list)?;
+        make_metadata_file(&app_config.configPath, &apex_infos, temporary_directory)?;
     // put metadata at the first partition
     let mut partitions = vec![Partition {
         label: "payload-metadata".to_owned(),
@@ -239,9 +242,8 @@
         writable: false,
     }];
 
-    for (i, apex) in apexes.iter().enumerate() {
-        let apex_path = apex_list.get_path_for(apex)?;
-        let apex_file = open_parcel_file(&apex_path, false)?;
+    for (i, apex_info) in apex_infos.iter().enumerate() {
+        let apex_file = open_parcel_file(&apex_info.path, false)?;
         partitions.push(Partition {
             label: format!("microdroid-apex-{}", i),
             image: Some(apex_file),
@@ -316,30 +318,26 @@
     Ok(apexes)
 }
 
-// Collect APEX names from config
-fn collect_apex_names(
-    apex_list: &ApexInfoList,
-    apexes: &[ApexConfig],
+// Collect ApexInfos from VM config
+fn collect_apex_infos<'a>(
+    apex_list: &'a ApexInfoList,
+    apex_configs: &[ApexConfig],
     debug_level: DebugLevel,
-) -> Vec<String> {
-    // Process pseudo names like "{CLASSPATH}".
-    // For now we have following pseudo APEX names:
-    // - {CLASSPATH}: represents APEXes contributing to any derive_classpath environment variable
-    let mut apex_names: Vec<String> = apexes
-        .iter()
-        .flat_map(|apex| match apex.name.as_str() {
-            "{CLASSPATH}" => apex_list.get_matching(|apex| apex.has_classpath_jar),
-            _ => vec![apex.name.clone()],
-        })
-        .collect();
-    // Add required APEXes
-    apex_names.extend(MICRODROID_REQUIRED_APEXES.iter().map(|name| name.to_string()));
+) -> Vec<&'a ApexInfo> {
+    let mut additional_apexes: Vec<&str> = MICRODROID_REQUIRED_APEXES.to_vec();
     if debug_level != DebugLevel::NONE {
-        apex_names.extend(MICRODROID_REQUIRED_APEXES_DEBUG.iter().map(|name| name.to_string()));
+        additional_apexes.extend(MICRODROID_REQUIRED_APEXES_DEBUG.to_vec());
     }
-    apex_names.sort();
-    apex_names.dedup();
-    apex_names
+
+    apex_list
+        .list
+        .iter()
+        .filter(|ai| {
+            apex_configs.iter().any(|cfg| ai.matches(cfg) && ai.is_active)
+                || additional_apexes.iter().any(|name| name == &ai.name && ai.is_active)
+                || ai.provide_shared_apex_libs
+        })
+        .collect()
 }
 
 pub fn add_microdroid_images(
@@ -409,36 +407,125 @@
     }
 
     #[test]
-    fn test_collect_apex_names() {
-        let apex_list = ApexInfoList {
+    fn test_collect_apexes() {
+        let apex_info_list = ApexInfoList {
             list: vec![
                 ApexInfo {
-                    name: "hasnt_classpath".to_string(),
-                    path: PathBuf::from("path0"),
+                    // 0
+                    name: "com.android.adbd".to_string(),
+                    path: PathBuf::from("adbd"),
                     has_classpath_jar: false,
                     last_update_seconds: 12345678,
                     is_factory: true,
+                    is_active: true,
+                    ..Default::default()
                 },
                 ApexInfo {
+                    // 1
+                    name: "com.android.os.statsd".to_string(),
+                    path: PathBuf::from("statsd"),
+                    has_classpath_jar: false,
+                    last_update_seconds: 12345678,
+                    is_factory: true,
+                    is_active: false,
+                    ..Default::default()
+                },
+                ApexInfo {
+                    // 2
+                    name: "com.android.os.statsd".to_string(),
+                    path: PathBuf::from("statsd/updated"),
+                    has_classpath_jar: false,
+                    last_update_seconds: 12345678 + 1,
+                    is_factory: false,
+                    is_active: true,
+                    ..Default::default()
+                },
+                ApexInfo {
+                    // 3
+                    name: "no_classpath".to_string(),
+                    path: PathBuf::from("no_classpath"),
+                    has_classpath_jar: false,
+                    last_update_seconds: 12345678,
+                    is_factory: true,
+                    is_active: true,
+                    ..Default::default()
+                },
+                ApexInfo {
+                    // 4
                     name: "has_classpath".to_string(),
-                    path: PathBuf::from("path1"),
+                    path: PathBuf::from("has_classpath"),
                     has_classpath_jar: true,
                     last_update_seconds: 87654321,
+                    is_factory: true,
+                    is_active: false,
+                    ..Default::default()
+                },
+                ApexInfo {
+                    // 5
+                    name: "has_classpath".to_string(),
+                    path: PathBuf::from("has_classpath/updated"),
+                    has_classpath_jar: true,
+                    last_update_seconds: 87654321 + 1,
                     is_factory: false,
+                    is_active: true,
+                    ..Default::default()
+                },
+                ApexInfo {
+                    // 6
+                    name: "apex-foo".to_string(),
+                    path: PathBuf::from("apex-foo"),
+                    has_classpath_jar: false,
+                    last_update_seconds: 87654321,
+                    is_factory: true,
+                    is_active: false,
+                    ..Default::default()
+                },
+                ApexInfo {
+                    // 7
+                    name: "apex-foo".to_string(),
+                    path: PathBuf::from("apex-foo/updated"),
+                    has_classpath_jar: false,
+                    last_update_seconds: 87654321 + 1,
+                    is_factory: false,
+                    is_active: true,
+                    ..Default::default()
+                },
+                ApexInfo {
+                    // 8
+                    name: "sharedlibs".to_string(),
+                    path: PathBuf::from("apex-foo"),
+                    last_update_seconds: 87654321,
+                    is_factory: true,
+                    provide_shared_apex_libs: true,
+                    ..Default::default()
+                },
+                ApexInfo {
+                    // 9
+                    name: "sharedlibs".to_string(),
+                    path: PathBuf::from("apex-foo/updated"),
+                    last_update_seconds: 87654321 + 1,
+                    is_active: true,
+                    provide_shared_apex_libs: true,
+                    ..Default::default()
                 },
             ],
         };
-        let apexes = vec![
-            ApexConfig { name: "config_name".to_string() },
+        let apex_configs = vec![
+            ApexConfig { name: "apex-foo".to_string() },
             ApexConfig { name: "{CLASSPATH}".to_string() },
         ];
         assert_eq!(
-            collect_apex_names(&apex_list, &apexes, DebugLevel::FULL),
+            collect_apex_infos(&apex_info_list, &apex_configs, DebugLevel::FULL),
             vec![
-                "com.android.adbd".to_string(),
-                "com.android.os.statsd".to_string(),
-                "config_name".to_string(),
-                "has_classpath".to_string(),
+                // Pass active/required APEXes
+                &apex_info_list.list[0],
+                &apex_info_list.list[2],
+                // Pass active APEXes specified in the config
+                &apex_info_list.list[5],
+                &apex_info_list.list[7],
+                // Pass both preinstalled(inactive) and updated(active) for "sharedlibs" APEXes
+                &apex_info_list.list[8],
+                &apex_info_list.list[9],
             ]
         );
     }