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-----
+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
+-----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>