Merge "Provide APEX list information to clients."
diff --git a/apexd/Android.bp b/apexd/Android.bp
index ce5d170..952a405 100644
--- a/apexd/Android.bp
+++ b/apexd/Android.bp
@@ -254,6 +254,24 @@
   export_include_dirs: ["."],
 }
 
+cc_binary_host {
+  name: "dump_apex_info",
+  defaults: [
+    "apex_flags_defaults",
+    "libapex-deps",
+  ],
+  static_libs: [
+    "libapex",
+  ],
+  srcs: [
+    "dump_apex_info.cpp",
+  ],
+  shared_libs: [
+    "libtinyxml2",
+  ],
+  generated_sources: ["apex-info-list-tinyxml"],
+}
+
 genrule {
   // Generates an apex which has a different manifest outside the filesystem
   // image.
diff --git a/apexd/apex_file_repository.cpp b/apexd/apex_file_repository.cpp
index 084f9a7..dc9061e 100644
--- a/apexd/apex_file_repository.cpp
+++ b/apexd/apex_file_repository.cpp
@@ -138,6 +138,9 @@
       pre_installed_store_.emplace(name, std::move(*apex_file));
     } else if (it->second.GetPath() != apex_file->GetPath()) {
       auto level = base::FATAL;
+      if (ignore_duplicate_apex_definitions_) {
+        level = base::INFO;
+      }
       // On some development (non-REL) builds the VNDK apex could be in /vendor.
       // When testing CTS-on-GSI on these builds, there would be two VNDK apexes
       // in the system, one in /system and one in /vendor.
diff --git a/apexd/apex_file_repository.h b/apexd/apex_file_repository.h
index 1a1cf94..1b59a80 100644
--- a/apexd/apex_file_repository.h
+++ b/apexd/apex_file_repository.h
@@ -52,6 +52,11 @@
       : multi_install_select_prop_prefixes_(multi_install_select_prop_prefixes),
         enforce_multi_install_partition_(enforce_multi_install_partition){};
 
+  explicit ApexFileRepository(const std::string& decompression_dir,
+                              bool ignore_duplicate_apex_definitions)
+      : ignore_duplicate_apex_definitions_(ignore_duplicate_apex_definitions),
+        decompression_dir_(decompression_dir){};
+
   ~ApexFileRepository() {
     pre_installed_store_.clear();
     data_store_.clear();
@@ -186,6 +191,10 @@
   // Only set false in tests.
   bool enforce_multi_install_partition_ = true;
 
+  // Ignore duplicate vendor APEX definitions, normally a duplicate definition
+  // is considered an error.
+  bool ignore_duplicate_apex_definitions_ = false;
+
   // Decompression directory which will be used to determine if apex is
   // decompressed or not
   std::string decompression_dir_;
diff --git a/apexd/apexd.cpp b/apexd/apexd.cpp
index e8a71ef..8be1779 100644
--- a/apexd/apexd.cpp
+++ b/apexd/apexd.cpp
@@ -3776,9 +3776,7 @@
   return RunVerifyFnInsideTempMount(apex_file, check_fn, true);
 }
 
-Result<void> CheckSupportsNonStagedInstall(const ApexFile& cur_apex,
-                                           const ApexFile& new_apex) {
-  const auto& cur_manifest = cur_apex.GetManifest();
+Result<void> CheckSupportsNonStagedInstall(const ApexFile& new_apex) {
   const auto& new_manifest = new_apex.GetManifest();
 
   if (!new_manifest.supportsrebootlessupdate()) {
@@ -3810,25 +3808,6 @@
     return Error() << new_apex.GetPath() << " requires JNI libs";
   }
 
-  // For requireNativeLibs bit, we only allow updates that don't change list of
-  // required libs.
-
-  std::vector<std::string> cur_required_libs(
-      cur_manifest.requirenativelibs().begin(),
-      cur_manifest.requirenativelibs().end());
-  sort(cur_required_libs.begin(), cur_required_libs.end());
-
-  std::vector<std::string> new_required_libs(
-      new_manifest.requirenativelibs().begin(),
-      new_manifest.requirenativelibs().end());
-  sort(new_required_libs.begin(), new_required_libs.end());
-
-  if (cur_required_libs != new_required_libs) {
-    return Error() << "Set of native libs required by " << new_apex.GetPath()
-                   << " differs from the one required by the currently active "
-                   << cur_apex.GetPath();
-  }
-
   auto expected_public_key =
       ApexFileRepository::GetInstance().GetPublicKey(new_manifest.name());
   if (!expected_public_key.ok()) {
@@ -3970,7 +3949,7 @@
   // Do a quick check if this APEX can be installed without a reboot.
   // Note that passing this check doesn't guarantee that APEX will be
   // successfully installed.
-  if (auto r = CheckSupportsNonStagedInstall(*cur_apex, *temp_apex); !r.ok()) {
+  if (auto r = CheckSupportsNonStagedInstall(*temp_apex); !r.ok()) {
     return r.error();
   }
 
diff --git a/apexd/apexd_test.cpp b/apexd/apexd_test.cpp
index 31744fa..a7b24f2 100644
--- a/apexd/apexd_test.cpp
+++ b/apexd/apexd_test.cpp
@@ -1056,7 +1056,7 @@
   ASSERT_THAT(ret, HasError(WithMessage(HasSubstr(" requires JNI libs"))));
 }
 
-TEST_F(ApexdMountTest, InstallPackageRejectsAddRequiredNativeLib) {
+TEST_F(ApexdMountTest, InstallPackageAcceptsAddRequiredNativeLib) {
   std::string file_path = AddPreInstalledApex("test.rebootless_apex_v1.apex");
   ApexFileRepository::GetInstance().AddPreInstalledApex({GetBuiltInDir()});
 
@@ -1065,14 +1065,11 @@
 
   auto ret =
       InstallPackage(GetTestFile("test.rebootless_apex_add_native_lib.apex"));
-  ASSERT_THAT(
-      ret, HasError(WithMessage(HasSubstr("Set of native libs required by"))));
-  ASSERT_THAT(ret,
-              HasError(WithMessage(HasSubstr(
-                  "differs from the one required by the currently active"))));
+  ASSERT_THAT(ret, Ok());
+  UnmountOnTearDown(ret->GetPath());
 }
 
-TEST_F(ApexdMountTest, InstallPackageRejectsRemovesRequiredNativeLib) {
+TEST_F(ApexdMountTest, InstallPackageAcceptsRemoveRequiredNativeLib) {
   std::string file_path = AddPreInstalledApex("test.rebootless_apex_v1.apex");
   ApexFileRepository::GetInstance().AddPreInstalledApex({GetBuiltInDir()});
 
@@ -1081,11 +1078,8 @@
 
   auto ret = InstallPackage(
       GetTestFile("test.rebootless_apex_remove_native_lib.apex"));
-  ASSERT_THAT(
-      ret, HasError(WithMessage(HasSubstr("Set of native libs required by"))));
-  ASSERT_THAT(ret,
-              HasError(WithMessage(HasSubstr(
-                  "differs from the one required by the currently active"))));
+  ASSERT_THAT(ret, Ok());
+  UnmountOnTearDown(ret->GetPath());
 }
 
 TEST_F(ApexdMountTest, InstallPackageRejectsAppInApex) {
diff --git a/apexd/dump_apex_info.cpp b/apexd/dump_apex_info.cpp
new file mode 100644
index 0000000..bd14831
--- /dev/null
+++ b/apexd/dump_apex_info.cpp
@@ -0,0 +1,143 @@
+/*
+ * Copyright (C) 2022 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 <android-base/file.h>
+#include <getopt.h>
+
+#include <chrono>
+#include <string>
+
+#include "apex_constants.h"
+#include "apex_file_repository.h"
+#include "com_android_apex.h"
+
+void usage(const char* cmd) {
+  std::cout << "Usage: " << cmd << " --root_dir=<dir> --out_file=<file.xsd>"
+            << std::endl;
+}
+
+// Create apex-info-list based on pre installed apexes
+int main(int argc, char** argv) {
+  static constexpr const char* kRootDir = "root_dir";
+  static constexpr const char* kOutFile = "out_file";
+
+  static struct option long_options[] = {{kRootDir, required_argument, 0, 0},
+                                         {kOutFile, required_argument, 0, 0},
+                                         {0, 0, 0, 0}};
+
+  std::map<std::string, std::string> opts;
+  int index = 0;
+  for (;;) {
+    int c = getopt_long(argc, argv, "h", long_options, &index);
+
+    if (c == -1) {
+      break;
+    }
+    switch (c) {
+      case 0:
+        opts[long_options[index].name] = optarg;
+        break;
+      case 'h':
+        usage(argv[0]);
+        return 0;
+
+      case '?':
+      default:
+        usage(argv[0]);
+        return -1;
+    }
+  }
+
+  if (opts.size() != 2) {
+    usage(argv[0]);
+    return -1;
+  }
+
+  std::string root_dir(opts[kRootDir]);
+  const std::string apex_root =
+      root_dir + std::string(::android::apex::kApexRoot);
+
+  // Ignore duplicate definitions to support multi-installed APEXes, the first
+  // found APEX package for a name is chosen.
+  const bool ignore_duplicate_definitions = true;
+  ::android::apex::ApexFileRepository repo(apex_root,
+                                           ignore_duplicate_definitions);
+
+  std::vector<std::string> prebuilt_dirs{
+      ::android::apex::kApexPackageBuiltinDirs};
+
+  // Add pre-installed apex directories
+  for (auto& dir : prebuilt_dirs) {
+    dir.insert(0, root_dir);
+  }
+  auto ret = repo.AddPreInstalledApex(prebuilt_dirs);
+  if (!ret.ok()) {
+    std::cerr << "Failed to add pre-installed apex directories" << std::endl;
+    return -1;
+  }
+
+  std::vector<com::android::apex::ApexInfo> apex_infos;
+  for (const auto& [name, files] : repo.AllApexFilesByName()) {
+    if (files.size() != 1) {
+      std::cerr << "Multiple APEXs found for " << name << std::endl;
+      return -1;
+    }
+
+    const android::apex::ApexFile& apex = files[0];
+
+    // Remove leading path from module names
+    std::optional<std::string> preinstalled_module_path;
+    {
+      auto preinstalled_path = repo.GetPreinstalledPath(name);
+      if (preinstalled_path.ok()) {
+        preinstalled_module_path =
+            (*preinstalled_path).substr(root_dir.length());
+      }
+    }
+    auto path = apex.GetPath().substr(root_dir.length());
+
+    const bool is_active = true;
+    const std::optional<int64_t> mtime;
+    com::android::apex::ApexInfo apex_info(
+        apex.GetManifest().name(), path, preinstalled_module_path,
+        apex.GetManifest().version(), apex.GetManifest().versionname(),
+        repo.IsPreInstalledApex(apex), is_active, mtime,
+        apex.GetManifest().providesharedapexlibs());
+    apex_infos.emplace_back(std::move(apex_info));
+  }
+
+  std::stringstream xml;
+  com::android::apex::ApexInfoList apex_info_list(apex_infos);
+  com::android::apex::write(xml, apex_info_list);
+
+  const std::string file_name = opts[kOutFile];
+  android::base::unique_fd fd(TEMP_FAILURE_RETRY(
+      open(file_name.c_str(), O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, 0644)));
+
+  if (fd.get() == -1) {
+    std::cerr << "Can't create " << file_name << " " << strerror(errno)
+              << std::endl;
+    return -1;
+  }
+
+  if (!android::base::WriteStringToFd(xml.str(), fd)) {
+    std::cerr << "Can't write to " << file_name << " " << strerror(errno)
+              << std::endl;
+    return -1;
+  }
+
+  return 0;
+}
diff --git a/tests/Android.bp b/tests/Android.bp
index 438a799..e036159 100644
--- a/tests/Android.bp
+++ b/tests/Android.bp
@@ -170,6 +170,55 @@
 }
 
 java_test_host {
+    name: "VendorApexHostTestCases",
+    srcs: [
+        "src/**/VendorApexTests.java",
+        ":apex-info-list",
+    ],
+    libs: [
+        "compatibility-tradefed",
+        "tradefed",
+        "truth-prebuilt",
+        "hamcrest",
+        "hamcrest-library",
+    ],
+    static_libs: [
+        "cts-install-lib-host",
+        "frameworks-base-hostutils",
+        "testng",
+    ],
+    data: [
+        ":VendorApexTestsApp",
+        ":com.android.apex.vendor.foo",
+    ],
+    test_config: "vendor-apex-tests.xml",
+    test_suites: [
+        "general-tests",
+    ],
+}
+
+android_test_helper_app {
+    name: "VendorApexTestsApp",
+    srcs:  ["app/src/**/VendorApexTests.java"],
+    manifest : "app/VendorApexTests_AndroidManifest.xml",
+    static_libs: [
+        "androidx.test.runner",
+        "androidx.test.core",
+        "truth-prebuilt",
+        "cts-install-lib",
+        "testng",
+    ],
+    sdk_version: "test_current",
+    java_resources: [
+        ":com.android.apex.vendor.foo.v2",
+        ":com.android.apex.vendor.foo.v2_with_requireNativeLibs",
+    ],
+    test_suites: [
+        "general-tests",
+    ],
+}
+
+java_test_host {
     name: "sharedlibs_host_tests",
     srcs: [
         "src/**/SharedLibsApexTest.java"
diff --git a/tests/TEST_MAPPING b/tests/TEST_MAPPING
index dd4daa2..ff4ce09 100644
--- a/tests/TEST_MAPPING
+++ b/tests/TEST_MAPPING
@@ -11,6 +11,9 @@
     },
     {
       "name": "CtsApexSharedLibrariesTestCases"
+    },
+    {
+      "name": "VendorApexHostTestCases"
     }
   ],
   "presubmit-large": [
diff --git a/tests/app/VendorApexTests_AndroidManifest.xml b/tests/app/VendorApexTests_AndroidManifest.xml
new file mode 100644
index 0000000..de4149a
--- /dev/null
+++ b/tests/app/VendorApexTests_AndroidManifest.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2022 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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="com.android.tests.vendorapex.app" >
+
+    <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
+    <application>
+        <receiver android:name="com.android.cts.install.lib.LocalIntentSender"
+                  android:exported="true" />
+        <uses-library android:name="android.test.runner" />
+    </application>
+
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+                     android:targetPackage="com.android.tests.vendorapex.app"
+                     android:label="Vendor APEX Tests"/>
+</manifest>
diff --git a/tests/app/src/com/android/tests/apex/app/VendorApexTests.java b/tests/app/src/com/android/tests/apex/app/VendorApexTests.java
new file mode 100644
index 0000000..1e8fec5
--- /dev/null
+++ b/tests/app/src/com/android/tests/apex/app/VendorApexTests.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2022 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 com.android.tests.apex.app;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertTrue;
+
+import android.Manifest;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.cts.install.lib.Install;
+import com.android.cts.install.lib.InstallUtils;
+import com.android.cts.install.lib.TestApp;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+
+@RunWith(JUnit4.class)
+public class VendorApexTests {
+
+    private static final String TAG = "VendorApexTests";
+
+    private static final String APEX_PACKAGE_NAME = "com.android.apex.vendor.foo";
+    private static final TestApp Apex2Rebootless = new TestApp(
+            "com.android.apex.vendor.foo.v2", APEX_PACKAGE_NAME, 2,
+            /*isApex*/true, "com.android.apex.vendor.foo.v2.apex");
+    private static final TestApp Apex2RequireNativeLibs = new TestApp(
+            "com.android.apex.vendor.foo.v2_with_requireNativeLibs", APEX_PACKAGE_NAME, 2,
+            /*isApex*/true, "com.android.apex.vendor.foo.v2_with_requireNativeLibs.apex");
+
+    @Test
+    public void testRebootlessUpdate() throws Exception {
+        InstallUtils.dropShellPermissionIdentity();
+        InstallUtils.adoptShellPermissionIdentity(Manifest.permission.INSTALL_PACKAGE_UPDATES);
+
+        final PackageManager pm =
+                InstrumentationRegistry.getInstrumentation().getContext().getPackageManager();
+        {
+            PackageInfo apex = pm.getPackageInfo(APEX_PACKAGE_NAME, PackageManager.MATCH_APEX);
+            assertThat(apex.getLongVersionCode()).isEqualTo(1);
+            assertThat(apex.applicationInfo.sourceDir).startsWith("/vendor/apex");
+        }
+
+        Install.single(Apex2Rebootless).commit();
+
+        {
+            PackageInfo apex = pm.getPackageInfo(APEX_PACKAGE_NAME, PackageManager.MATCH_APEX);
+            assertThat(apex.getLongVersionCode()).isEqualTo(2);
+            assertThat(apex.applicationInfo.sourceDir).startsWith("/data/apex/active");
+        }
+    }
+
+    @Test
+    public void testGenerateLinkerConfigurationOnUpdate() throws Exception {
+        InstallUtils.dropShellPermissionIdentity();
+        InstallUtils.adoptShellPermissionIdentity(Manifest.permission.INSTALL_PACKAGE_UPDATES);
+
+        // There's no ld.config.txt for v1 (preinstalled, empty)
+        final Path ldConfigTxt = Paths.get("/linkerconfig", APEX_PACKAGE_NAME, "ld.config.txt");
+        assertTrue(Files.notExists(ldConfigTxt));
+
+        Install.single(Apex2RequireNativeLibs).commit();
+
+        // v2 uses "libbinder_ndk.so" (requireNativeLibs)
+        assertTrue(Files.exists(ldConfigTxt));
+        assertThat(Files.readAllLines(ldConfigTxt))
+            .contains("namespace.default.link.system.shared_libs += libbinder_ndk.so");
+    }
+}
diff --git a/tests/src/com/android/tests/apex/host/VendorApexTests.java b/tests/src/com/android/tests/apex/host/VendorApexTests.java
new file mode 100644
index 0000000..e042b08
--- /dev/null
+++ b/tests/src/com/android/tests/apex/host/VendorApexTests.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2022 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 com.android.tests.apex.host;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
+
+import android.cts.install.lib.host.InstallUtilsHost;
+import android.platform.test.annotations.LargeTest;
+
+import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
+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.io.File;
+import java.nio.file.Paths;
+
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class VendorApexTests extends BaseHostJUnit4Test {
+
+    private static final String TAG = "VendorApexTests";
+    private static final String APEX_PACKAGE_NAME = "com.android.apex.vendor.foo";
+
+    private final InstallUtilsHost mHostUtils = new InstallUtilsHost(this);
+
+    private void runPhase(String phase) throws Exception {
+        assertThat(runDeviceTests("com.android.tests.vendorapex.app",
+                "com.android.tests.apex.app.VendorApexTests",
+                phase)).isTrue();
+    }
+
+    @Before
+    public void setUp() throws Exception {
+        assumeTrue("Device does not support updating APEX", mHostUtils.isApexUpdateSupported());
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        deleteFiles("/vendor/apex/" + APEX_PACKAGE_NAME + "*apex",
+                "/data/apex/active/" + APEX_PACKAGE_NAME + "*apex");
+    }
+
+    @Test
+    @LargeTest
+    public void testRebootlessUpdate() throws Exception {
+        pushPreinstalledApex("com.android.apex.vendor.foo.apex");
+
+        runPhase("testRebootlessUpdate");
+    }
+
+    @Test
+    @LargeTest
+    public void testGenerateLinkerConfigurationOnUpdate() throws Exception {
+        pushPreinstalledApex("com.android.apex.vendor.foo.apex");
+
+        runPhase("testGenerateLinkerConfigurationOnUpdate");
+    }
+
+    private void pushPreinstalledApex(String fileName) throws Exception {
+        CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(getBuild());
+        final File apex = buildHelper.getTestFile(fileName);
+        getDevice().remountVendorWritable();
+        assertTrue(getDevice().pushFile(apex, Paths.get("/vendor/apex", fileName).toString()));
+        getDevice().reboot();
+    }
+
+    /**
+     * Deletes files and reboots the device if necessary.
+     * @param files the paths of files which might contain wildcards
+     */
+    private void deleteFiles(String... files) throws Exception {
+        boolean found = false;
+        for (String file : files) {
+            CommandResult result = getDevice().executeShellV2Command("ls " + file);
+            if (result.getStatus() == CommandStatus.SUCCESS) {
+                found = true;
+                break;
+            }
+        }
+
+        if (found) {
+            getDevice().remountVendorWritable();
+            for (String file : files) {
+                getDevice().executeShellCommand("rm -rf " + file);
+            }
+            getDevice().reboot();
+        }
+    }
+}
diff --git a/tests/testdata/vendorapex/Android.bp b/tests/testdata/vendorapex/Android.bp
new file mode 100644
index 0000000..f6c626d
--- /dev/null
+++ b/tests/testdata/vendorapex/Android.bp
@@ -0,0 +1,77 @@
+// Copyright (C) 2022 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 {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+apex_key {
+    name: "com.android.apex.vendor.foo.key",
+    public_key: "com.android.apex.vendor.foo.avbpubkey",
+    private_key: "com.android.apex.vendor.foo.pem",
+}
+
+android_app_certificate {
+    name: "com.android.apex.vendor.foo.certificate",
+    certificate: "com.android.apex.vendor.foo",
+}
+
+apex_defaults {
+    name: "com.android.apex.vendor.foo.defaults",
+    manifest: "manifest_v1.json",
+    file_contexts: "file_contexts",
+    key: "com.android.apex.vendor.foo.key",
+    certificate: ":com.android.apex.vendor.foo.certificate",
+    vendor: true,
+    updatable: false,
+    installable: false,
+}
+
+apex {
+    name: "com.android.apex.vendor.foo",
+    defaults: [
+        "com.android.apex.vendor.foo.defaults",
+    ],
+}
+
+apex {
+    name: "com.android.apex.vendor.foo.v2",
+    defaults: [
+        "com.android.apex.vendor.foo.defaults",
+    ],
+    manifest: "manifest_v2.json",
+}
+
+apex {
+    name: "com.android.apex.vendor.foo.v2_with_requireNativeLibs",
+    defaults: [
+        "com.android.apex.vendor.foo.defaults",
+    ],
+    manifest: "manifest_v2.json",
+    binaries: [
+        "apex_vendor_foo_test_binary",
+    ],
+}
+
+cc_binary {
+    name: "apex_vendor_foo_test_binary",
+    shared_libs: [
+        "libbinder_ndk",  // will add "requireNativeLibs"
+    ],
+    srcs: [
+        "apex_vendor_foo_test_binary.cpp",
+    ],
+    vendor: true,
+    installable: false,
+}
\ No newline at end of file
diff --git a/tests/testdata/vendorapex/apex_vendor_foo_test_binary.cpp b/tests/testdata/vendorapex/apex_vendor_foo_test_binary.cpp
new file mode 100644
index 0000000..5c87629
--- /dev/null
+++ b/tests/testdata/vendorapex/apex_vendor_foo_test_binary.cpp
@@ -0,0 +1,17 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+int main() { return 0; }
diff --git a/tests/testdata/vendorapex/com.android.apex.vendor.foo.avbpubkey b/tests/testdata/vendorapex/com.android.apex.vendor.foo.avbpubkey
new file mode 100644
index 0000000..53fd6bb
--- /dev/null
+++ b/tests/testdata/vendorapex/com.android.apex.vendor.foo.avbpubkey
Binary files differ
diff --git a/tests/testdata/vendorapex/com.android.apex.vendor.foo.pem b/tests/testdata/vendorapex/com.android.apex.vendor.foo.pem
new file mode 100644
index 0000000..e8f15f8
--- /dev/null
+++ b/tests/testdata/vendorapex/com.android.apex.vendor.foo.pem
@@ -0,0 +1,51 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIJKgIBAAKCAgEAwQanMOUpX0zO1Uq4XOD9a+K2gbk/0LtApj04vmefwRVNXHVr
+kLHeKdJsJ5BfVRKS1S+pimo7wxtphALsXDN2HFc4yVnsR2bYgItvZMRJtCvaJMzI
+usA3n3WGkLi6E5MMJX9spenA014Ifku2pC2plfu40zPHrGaY34yMh7XX+aqg1pji
+vAH/IJuDDypz4L+o6950thPholAiYv+PT4g7q60V/1W1bobTHwWjBhd9Ovi34PS5
+5YP/WRjFNQhEwZ3d3UYYcfjmAD88GciMqJM7Ii+OynSMPFlW1afdIWhUCFh5uZ/Z
+v1uChvEygvfajsXVaDLsnHgssjyI4tM3guL1nGFszpj1abUX4M0mTiYKAXEYMotv
+pl9ZffPHqv17Gtey3o6n2JOuGN9My2yYH7IzNXyN4GHlQHkHmcr19N5Wr5PFAsQE
+IjoxsmGqt8nKYLcYfKYkWEmqejVXewLxc2i0fEBF7yjy8KPVO9yT6kNt2fZPZlEs
+JcZlWpOEgTyELphVEA2K+58XMZtqdwwwpUMGRc1vo53OL8QmLxfN2FezRyD+538C
+CRMyDH7734/uIbkM9nTGIYTWmvKIpiYWTBeHYwF4o8mDS8e42R3SX6ulDk/DuuIa
+UNMlGNBqTjFr2NCOJ6RmN99N/pIxEXaDoIbQDa+gmdl/uCsxU0WmILSN+8UCAwEA
+AQKCAgEAnnyJ9jmSeK8l/Db3nTsWmOhzFZw263l0IYqO9rc6klydQlce1JVWZlxh
+dTKzM7SmXuhdekqzewUc48lKrIGMbsSm2Zw9xnqJNTJHaiNIqOiAmkqSXdPJV+I1
+dMpX7g6EoJ05ZhjBvEqvCpO8CJ19aqpeHPuc7M7oolRSZnNGO7Z/jPPG5rt08R7+
+wwsGTfjQB6qFhaJZVt4Y/dP7pT+kTtc1AosrBu8olYYZTr0mk673u3r0z6BLnqoZ
+8esyGQ83xaDyHVJR9s302O6znw4UNYN66Hw9UKfCBndntzBkHt4WQ/Ud4mKOj6Gm
+6aX8C9If4Qg/AlIh0M6nTiZCo/MZ2bmEJXK904oJQh2ii5KEZSBKuUQPHa6QPQ9T
+kavgpPIiAv61GqrvWFPGyIt069sAeidk0BfDC8zrmqbJkeAZEqUX8Fwf7kpR/SiL
+tjhYUCt5+owCwFlNxXMo5XNhOAENszcK5/oeD/0gEfk2HRS87fVZ9gFjqBPvBprH
+JPzAGh8PeUpD+HfHWusguW961zSdCpmEQVutDShnslkJ5HAdn0v/CWOsI+LK7iPG
+bUhk20DV3t1ZhZtpbByzqAe46sWN9WGwxTjm+ggSUFOH0dVW10vtZjmL42w8qH/M
+EkbI5XYeXIHrAh59ZCe7Xxb1zWq0iRb0izrHRIF1pPYrWDRdWAECggEBAOIvaW8k
+5YU2EYllCaU+6HYzbjCI/c7/XjDG4yVWvYn3KttTRU3clIB9OFMxm15DiriTeKEe
+FyAYz+OX+5h3SZ2vLTRD9XZhRwRMULeiYAUGd8sDNuOc3LFvoXbE4iSdaek74HuY
+rPZ7poSCnJGhkhsNbvknwqsg+FiQQ26pTzbZZChYu/8TrxL+J7CCJN3d37GKjfLv
+5n6dWbeeMr8YPI0+u4dBOI646oGGMkC2gXp8RBX8Y7RGVhcnmaB70f1jeK/7ZPKq
+emFREbVwpVQr52z3ogoHYP6cnb+TVUhMkZwWarehA6ucb926p8B2LQ6BAWwkCClE
+xoXompCRWV/58wECggEBANp4ScZqSUpDZPMkHN5EuqBedygPjhNkd/esxLAe4UNP
+TVqrZItpKRaHqLTgoCtEhoYPSJa8R0qHqub09qX8/uxIN/InLB3fNUd2EAv874DI
+PWdHUmGjPnTVEul0oI4jDyiRUTD969HjGQ2ywH97mZuQnrIuuMMbE6M9Pcas7iIK
+DTmd1Ki4gnk4Sf2f/OuG4tthHhQ5uwQbBvu0/fO2Cb3cv4NNRFaxXJu/c3HvzJhX
+0VhdFejGM7hMbS/0a2Y1fCfhLT9leSJfLO7gAQNnxklRULYdUpNr/7sPRaAsx1j8
+qyPdP3+igt9btcWr/z4Hpgv80gND0yWQZUzTBNsB/MUCggEAQkx3cTa1eEiS910A
+aMl5xjvpDpz5GJXN/CowJp+4SxqCG2vbIqmHdeo+elROIGFX5iaD82YojSX4udOw
+0c4Va/0PGQTajGqTMHVWK52S26Y7suwsSKeQIQqBn5iyWN0zUERW1qO3/z/bXXgT
+gLSFaRyU7L901kiBwyP2QBesun0aWKE56djRNpX8+EYNnGMO0LG2TgF35KEmzSW3
+5j+qcBR1T34Un/Ef+/tj+4gDh+2o33DtoMgFMCBRbbMdqFJh6+OagW7rFF94+2Ab
+dgKwgUZM3veuvLMXojIDi3+2JrSDb3Po6YKfX7T4uvdo2ZmC2znsknwwXMwDkmCo
+e/N+AQKCAQEAxqwI+yUAzUYIcYvvrLl3tgrx9T5gB4agCl3U6AzM8XcWc1PVtWnG
+cbSgWQzE21QPua4AZwOFGWPSqQEvo7c05A6wwceZuPiY7QmSgjRcYRK3tEoJwry/
+OWPjNOZYc6mySUQNP65KW65XxDtADy6JfAzCJGuUnejrrNEucpQkYlQdvr2m/F/+
+Vto3fyuUx2L8vl/NCLuPNKaXbSMkphJvPXeXuYH0mZnlC8XI6F4YApopyF+uYuaL
+dhgaWze8y0/sPh/qE/Lle7ptlDWk9kHS8i2+Zj64L1RRVP0IZicSifwMbirvadSR
+iylNXhuRnAk8mT4qhcmSJGDxnjFwAvIFuQKCAQEA1MNiYqNdHE3BbattBFCIDKnF
+4wF8hgMcSGQcyTnPnIl/j1+29/N8KDwkF/c26xGbdNn5d4k9KmU0FSbtsS/WMV3M
+5Tp2Y45xGR7O7JYDN7TL7px7JgwVDZaxu0ZsX+9YWk1KTLV2kbjs1gBzj2e4kDhn
+51DGWRnTLpe30KvbFlBx8wLv+zGKF1DfXzMYE9MIR1JApy3pnAkqoZHICihiKOt/
++kP8V5UR7sVJk6/wUzKnUVrcQu3Rlg0y1ESDCwZF93hwFov8LnhetmNrnXLBoPj8
+IJ3MQ8N5WkZ3wd2VUQEFFdnZIICznSeWJN1DEZJc4w38ClljrmT5dSJq0Y/wKg==
+-----END RSA PRIVATE KEY-----
diff --git a/tests/testdata/vendorapex/com.android.apex.vendor.foo.pk8 b/tests/testdata/vendorapex/com.android.apex.vendor.foo.pk8
new file mode 100644
index 0000000..00f68c1
--- /dev/null
+++ b/tests/testdata/vendorapex/com.android.apex.vendor.foo.pk8
Binary files differ
diff --git a/tests/testdata/vendorapex/com.android.apex.vendor.foo.x509.pem b/tests/testdata/vendorapex/com.android.apex.vendor.foo.x509.pem
new file mode 100644
index 0000000..e7be8f5
--- /dev/null
+++ b/tests/testdata/vendorapex/com.android.apex.vendor.foo.x509.pem
@@ -0,0 +1,33 @@
+-----BEGIN CERTIFICATE-----
+MIIFwTCCA6kCFBWVJoK97rX24tFzqD3O1rWA7IGBMA0GCSqGSIb3DQEBCwUAMIGb
+MQswCQYDVQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNTW91
+bnRhaW4gVmlldzEQMA4GA1UECgwHQW5kcm9pZDEQMA4GA1UECwwHQW5kcm9pZDEi
+MCAGCSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbTEXMBUGA1UEAwwOY29t
+LnZlbmRvci5mb28wIBcNMjIwNzE0MDcxMTMzWhgPNDc2MDA2MDkwNzExMzNaMIGb
+MQswCQYDVQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNTW91
+bnRhaW4gVmlldzEQMA4GA1UECgwHQW5kcm9pZDEQMA4GA1UECwwHQW5kcm9pZDEi
+MCAGCSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbTEXMBUGA1UEAwwOY29t
+LnZlbmRvci5mb28wggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC6jFYt
+46EzamkcMDOR9gPkGSFq6gVUqaNt1BMEsAouZRLea/cK1W17snyrz5IB9k7x/ufx
+LEVL3o5CIMQdV8D4v4lP84Prxfwm9DNqm+uUovRImNjxiA8Iz+ylwLpgJw9hOVdI
+y/dNig+GZOB8ZsgmNXGN6w+5CdU5gvjeTzr4J7ocZY6Qr3sDnO5I+WzZEAFwS8X7
+C3N++xN/CEXTwOmd4rKQ75YhVAUyBN6ewHBvIPvAx4ut3cgRAUwFi0XZLnJbujVZ
+hc8BBf0heKcf7/13tivQPnczkyqwgj5P+H5TLo0s98sB1PEpv+DtsqdYzj0F28q5
+IgWWRUy5nNVBKrS5CE/oopcwy46c+IrlwB1vjf3oRVtP5G2Um+AyGvLQeOtivL5F
+sGdnqltbZeQ5C2EkrRJYXLHVyaTYY96OZ6s54gCY+7Y7w9F2nCGmcrfnN0sC3ofG
+aLE8yFDWK1UdVVwwAK4XZvE1SIFRAAyLwuuN8WWr+BKRSIYn2KGerdc89D+TTq5D
+eVV1SPqpgKrX9vOc32ruiTXQr+pRsJCuDIAWEuf22N5qiCKqUBfPxhFM1HZ5+SKp
+KMyr02J2YxStBymcsUKTg/7A3qUZJJP727bamPUvqHETic2C/4BSo6fXPzO6dBNg
+ru4h+lb53ncziyuuCVd/6qmQ2oJ3/xKi77o+nwIDAQABMA0GCSqGSIb3DQEBCwUA
+A4ICAQCfApnPSCXDBrVYW6srAxDH8o3sOQxX0SOZZPhDBIDVinKIH+5IdVrrcoow
+wJOgm0v8wZ4InZ9uUSsqQWfFzBxpdeyKOD0JsxpnRo+0kiIsSJXmFOoT81O5Bx1V
+oYbxawDRw7Z0hsipdNP4H98+ubRbqTTP9gljweQZz/g6tsQvsppe8Y7qXZjEMHNB
+uGMmUMasMZqm+fK93wgY5Ic1zXcncWHOoMsXyNyI9+D8ZxHRAE0S8QI2Y5njLjhH
+SXlZF8fzgU1GbD9D1jXHWkojr8ntdyTWsvxWSrW7H3WgHZpvQGZ4rCZiZ8+w/VXF
+xtgwZ26qKGw26PNXc+PbXZ3JlUyiV3LyQg+XhAOef7jSNmgDsURWvHmT8rO+q9Cy
+gNPu+qYorPUkLixPYUw63P0kGFxNobfW7FG/kNUnaa+EBzUY3v10h0EOhhdP6h7Q
+mGcALb8hjQT5PgXsrTy1flKvLBd5FGqzlFfeMGJlkfdirrAgWle7eDVHSC1WQ0No
++24wKVFBXHofeh3AJHg0h7egDaXLx37M4v209usB9cwUKCJibiiJ5INIWixIVv3A
+781M2CW+S1pM9X8P+kHWhYOWDJLX1w+IWm/zA8eNRPSECvnM8oqhjYoRgeCW6cow
+I+kzOloHJ3znEhOlCFaOL20K4bauc/jIhVwxH9XcDk0XT1jdFA==
+-----END CERTIFICATE-----
diff --git a/tests/testdata/vendorapex/file_contexts b/tests/testdata/vendorapex/file_contexts
new file mode 100644
index 0000000..6524a5e
--- /dev/null
+++ b/tests/testdata/vendorapex/file_contexts
@@ -0,0 +1,2 @@
+(/.*)?		u:object_r:vendor_file:s0
+/etc(/.*)?	u:object_r:vendor_configs_file:s0
diff --git a/tests/testdata/vendorapex/manifest_v1.json b/tests/testdata/vendorapex/manifest_v1.json
new file mode 100644
index 0000000..bada5e0
--- /dev/null
+++ b/tests/testdata/vendorapex/manifest_v1.json
@@ -0,0 +1,4 @@
+{
+  "name": "com.android.apex.vendor.foo",
+  "version": 1
+}
diff --git a/tests/testdata/vendorapex/manifest_v2.json b/tests/testdata/vendorapex/manifest_v2.json
new file mode 100644
index 0000000..e64c3a8
--- /dev/null
+++ b/tests/testdata/vendorapex/manifest_v2.json
@@ -0,0 +1,5 @@
+{
+  "name": "com.android.apex.vendor.foo",
+  "version": 2,
+  "supportsRebootlessUpdate": true
+}
diff --git a/tests/vendor-apex-tests.xml b/tests/vendor-apex-tests.xml
new file mode 100644
index 0000000..74e9e51
--- /dev/null
+++ b/tests/vendor-apex-tests.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2022 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.
+-->
+<configuration description="Runs the vendor apex tests">
+    <option name="test-suite-tag" value="apct" />
+    <option name="config-descriptor:metadata" key="component" value="framework" />
+    <!-- Instant apps can't have INSTALL_PACKAGES permission. -->
+    <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
+    <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+    <option name="config-descriptor:metadata" key="parameter" value="no_foldable_states" />
+
+    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+        <option name="cleanup-apks" value="true" />
+        <option name="test-file-name" value="VendorApexTestsApp.apk" />
+    </target_preparer>
+    <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer"/>
+    <test class="com.android.tradefed.testtype.HostTest" >
+        <option name="class" value="com.android.tests.apex.host.VendorApexTests" />
+    </test>
+</configuration>