Allow platform to recognize compressed APEX
Because compressed APEX files have a different file extension (.capex)
than normal APEX (.apex), they are ignored by apexd.
This CL allows apexd to find compressed APEX files using .capex
extension and treat it like an "inactive factory APEX".
Bug: 172911820
Test: atest apex_compression_platform_tests
Test: atest ApexFileTest
Test: atest ApexPreinstalledDataTest
Change-Id: I188ecbf1a0c6726941d8b26895039040586b3f9a
diff --git a/apexd/Android.bp b/apexd/Android.bp
index fd17a70..7c89476 100644
--- a/apexd/Android.bp
+++ b/apexd/Android.bp
@@ -383,7 +383,9 @@
":com.android.apex.cts.shim.v2_additional_folder_prebuilt",
":com.android.apex.cts.shim.v2_with_pre_install_hook_prebuilt",
":com.android.apex.cts.shim.v2_with_post_install_hook_prebuilt",
+ ":com.android.apex.compressed.v1",
"apexd_testdata/com.android.apex.test_package.avbpubkey",
+ "apexd_testdata/com.android.apex.compressed.avbpubkey",
],
srcs: [
"apex_database_test.cpp",
diff --git a/apexd/apex_constants.h b/apexd/apex_constants.h
index bf4b083..21515c9 100644
--- a/apexd/apex_constants.h
+++ b/apexd/apex_constants.h
@@ -49,6 +49,7 @@
static constexpr const char* kCeDataDir = "/data/misc_ce";
static constexpr const char* kApexPackageSuffix = ".apex";
+static constexpr const char* kCompressedApexPackageSuffix = ".capex";
static constexpr const char* kManifestFilenameJson = "apex_manifest.json";
static constexpr const char* kManifestFilenamePb = "apex_manifest.pb";
diff --git a/apexd/apex_file.cpp b/apexd/apex_file.cpp
index 2049ff9..269fd3d 100644
--- a/apexd/apex_file.cpp
+++ b/apexd/apex_file.cpp
@@ -47,6 +47,7 @@
namespace {
constexpr const char* kImageFilename = "apex_payload.img";
+constexpr const char* kCompressedApexFilename = "original_apex";
constexpr const char* kBundledPublicKeyFilename = "apex_pubkey";
struct FsMagic {
@@ -78,6 +79,8 @@
size_t image_size;
std::string manifest_content;
std::string pubkey;
+ Result<std::string> fs_type;
+ ZipEntry entry;
unique_fd fd(open(path.c_str(), O_RDONLY | O_BINARY | O_CLOEXEC));
if (fd < 0) {
@@ -94,20 +97,28 @@
<< ErrorCodeString(ret);
}
- // Locate the mountable image within the zipfile and store offset and size.
- ZipEntry entry;
- ret = FindEntry(handle, kImageFilename, &entry);
+ bool is_compressed = true;
+ ret = FindEntry(handle, kCompressedApexFilename, &entry);
if (ret < 0) {
- return Error() << "Could not find entry \"" << kImageFilename
- << "\" in package " << path << ": " << ErrorCodeString(ret);
+ is_compressed = false;
}
- image_offset = entry.offset;
- image_size = entry.uncompressed_length;
- auto fs_type = RetrieveFsType(fd, image_offset);
- if (!fs_type.ok()) {
- return Error() << "Failed to retrieve filesystem type for " << path << ": "
- << fs_type.error();
+ if (!is_compressed) {
+ // Locate the mountable image within the zipfile and store offset and size.
+ ret = FindEntry(handle, kImageFilename, &entry);
+ if (ret < 0) {
+ return Error() << "Could not find entry \"" << kImageFilename
+ << "\" in package " << path << ": "
+ << ErrorCodeString(ret);
+ }
+ image_offset = entry.offset;
+ image_size = entry.uncompressed_length;
+
+ fs_type = RetrieveFsType(fd, image_offset);
+ if (!fs_type.ok()) {
+ return Error() << "Failed to retrieve filesystem type for " << path
+ << ": " << fs_type.error();
+ }
}
ret = FindEntry(handle, kManifestFilenamePb, &entry);
@@ -144,7 +155,7 @@
}
return ApexFile(path, image_offset, image_size, std::move(*manifest), pubkey,
- *fs_type);
+ *fs_type, is_compressed);
}
// AVB-related code.
diff --git a/apexd/apex_file.h b/apexd/apex_file.h
index a7bc123..0d0b881 100644
--- a/apexd/apex_file.h
+++ b/apexd/apex_file.h
@@ -53,17 +53,20 @@
const std::string& GetFsType() const { return fs_type_; }
android::base::Result<ApexVerityData> VerifyApexVerity(
const std::string& public_key) const;
+ bool IsCompressed() const { return is_compressed_; }
private:
ApexFile(const std::string& apex_path, int32_t image_offset,
size_t image_size, ApexManifest manifest,
- const std::string& apex_pubkey, const std::string& fs_type)
+ const std::string& apex_pubkey, const std::string& fs_type,
+ bool is_compressed)
: apex_path_(apex_path),
image_offset_(image_offset),
image_size_(image_size),
manifest_(std::move(manifest)),
apex_pubkey_(apex_pubkey),
- fs_type_(fs_type) {}
+ fs_type_(fs_type),
+ is_compressed_(is_compressed) {}
std::string apex_path_;
int32_t image_offset_;
@@ -71,6 +74,7 @@
ApexManifest manifest_;
std::string apex_pubkey_;
std::string fs_type_;
+ bool is_compressed_;
};
} // namespace apex
diff --git a/apexd/apex_file_test.cpp b/apexd/apex_file_test.cpp
index f575434..36f6b2d 100644
--- a/apexd/apex_file_test.cpp
+++ b/apexd/apex_file_test.cpp
@@ -164,6 +164,51 @@
testing::HasSubstr("Failed to retrieve filesystem type"));
}
+TEST(ApexFileTest, OpenCompressedApexFile) {
+ const std::string file_path =
+ kTestDataDir + "com.android.apex.compressed.v1.capex";
+ Result<ApexFile> apex_file = ApexFile::Open(file_path);
+ ASSERT_TRUE(apex_file.ok());
+
+ ZipArchiveHandle handle;
+ int32_t rc = OpenArchive(file_path.c_str(), &handle);
+ ASSERT_EQ(0, rc);
+ auto close_guard =
+ android::base::make_scope_guard([&handle]() { CloseArchive(handle); });
+
+ ZipEntry entry;
+ rc = FindEntry(handle, "original_apex", &entry);
+ ASSERT_EQ(0, rc);
+
+ ASSERT_EQ(0, apex_file->GetImageOffset());
+ ASSERT_EQ(0, apex_file->GetImageOffset());
+ ASSERT_EQ("", apex_file->GetFsType());
+}
+
+TEST(ApexFileTest, GetCompressedApexManifest) {
+ const std::string file_path =
+ kTestDataDir + "com.android.apex.compressed.v1.capex";
+ Result<ApexFile> apex_file = ApexFile::Open(file_path);
+ ASSERT_RESULT_OK(apex_file);
+ EXPECT_EQ("com.android.apex.compressed", apex_file->GetManifest().name());
+ EXPECT_EQ(1u, apex_file->GetManifest().version());
+}
+
+TEST(ApexFileTest, GetBundledPublicKeyOfCompressedApex) {
+ const std::string file_path =
+ kTestDataDir + "com.android.apex.compressed.v1.capex";
+ Result<ApexFile> apex_file = ApexFile::Open(file_path);
+ ASSERT_RESULT_OK(apex_file);
+
+ const std::string key_path =
+ kTestDataDir + "apexd_testdata/com.android.apex.compressed.avbpubkey";
+ std::string key_content;
+ ASSERT_TRUE(android::base::ReadFileToString(key_path, &key_content))
+ << "Failed to read " << key_path;
+
+ EXPECT_EQ(key_content, apex_file->GetBundledPublicKey());
+}
+
} // namespace
} // namespace apex
} // namespace android
diff --git a/apexd/apex_preinstalled_data.cpp b/apexd/apex_preinstalled_data.cpp
index 4ca8373..ef42f35 100644
--- a/apexd/apex_preinstalled_data.cpp
+++ b/apexd/apex_preinstalled_data.cpp
@@ -43,12 +43,13 @@
return {};
}
- Result<std::vector<std::string>> apex_files = FindApexFilesByName(dir);
- if (!apex_files.ok()) {
- return apex_files.error();
+ Result<std::vector<std::string>> all_apex_files = FindFilesBySuffix(
+ dir, {kApexPackageSuffix, kCompressedApexPackageSuffix});
+ if (!all_apex_files.ok()) {
+ return all_apex_files.error();
}
- for (const auto& file : *apex_files) {
+ for (const auto& file : *all_apex_files) {
Result<ApexFile> apex_file = ApexFile::Open(file);
if (!apex_file.ok()) {
return Error() << "Failed to open " << file << " : " << apex_file.error();
diff --git a/apexd/apex_preinstalled_data_test.cpp b/apexd/apex_preinstalled_data_test.cpp
index a961ed2..ead85b3 100644
--- a/apexd/apex_preinstalled_data_test.cpp
+++ b/apexd/apex_preinstalled_data_test.cpp
@@ -52,6 +52,7 @@
TemporaryDir td;
fs::copy(GetTestFile("apex.apexd_test.apex"), td.path);
fs::copy(GetTestFile("apex.apexd_test_different_app.apex"), td.path);
+ fs::copy(GetTestFile("com.android.apex.compressed.v1.capex"), td.path);
ApexPreinstalledData instance;
ASSERT_TRUE(IsOk(instance.Initialize({td.path})));
@@ -78,12 +79,14 @@
test_fn("apex.apexd_test.apex");
test_fn("apex.apexd_test_different_app.apex");
+ test_fn("com.android.apex.compressed.v1.capex");
// Check that second call will succeed as well.
ASSERT_TRUE(IsOk(instance.Initialize({td.path})));
test_fn("apex.apexd_test.apex");
test_fn("apex.apexd_test_different_app.apex");
+ test_fn("com.android.apex.compressed.v1.capex");
}
TEST(ApexPreinstalledDataTest, InitializeFailureCorruptApex) {
@@ -149,10 +152,16 @@
// Prepare test data.
TemporaryDir td;
fs::copy(GetTestFile("apex.apexd_test.apex"), td.path);
+ fs::copy(GetTestFile("com.android.apex.compressed.v1.capex"), td.path);
ApexPreinstalledData instance;
ASSERT_TRUE(IsOk(instance.Initialize({td.path})));
+ auto compressed_apex = ApexFile::Open(
+ StringPrintf("%s/com.android.apex.compressed.v1.capex", td.path));
+ ASSERT_TRUE(IsOk(compressed_apex));
+ ASSERT_TRUE(instance.IsPreInstalledApex(*compressed_apex));
+
auto apex1 = ApexFile::Open(StringPrintf("%s/apex.apexd_test.apex", td.path));
ASSERT_TRUE(IsOk(apex1));
ASSERT_TRUE(instance.IsPreInstalledApex(*apex1));
diff --git a/apexd/apexd.cpp b/apexd/apexd.cpp
index 2af85f2..c3b9f5a 100644
--- a/apexd/apexd.cpp
+++ b/apexd/apexd.cpp
@@ -285,7 +285,8 @@
Result<void> RemovePreviouslyActiveApexFiles(
const std::unordered_set<std::string>& affected_packages,
const std::unordered_set<std::string>& files_to_keep) {
- auto all_active_apex_files = FindApexFilesByName(kActiveApexPackagesDataDir);
+ auto all_active_apex_files =
+ FindFilesBySuffix(kActiveApexPackagesDataDir, {kApexPackageSuffix});
if (!all_active_apex_files.ok()) {
return all_active_apex_files.error();
@@ -368,6 +369,11 @@
const std::string& hashtree_file,
bool verify_image,
bool temp_mount = false) {
+ if (apex.IsCompressed()) {
+ return Error() << "Cannot directly mount compressed APEX "
+ << apex.GetPath();
+ }
+
LOG(VERBOSE) << "Creating mount point: " << mount_point;
// Note: the mount point could exist in case when the APEX was activated
// during the bootstrap phase (e.g., the runtime or tzdata APEX).
@@ -767,7 +773,8 @@
std::to_string(session_id);
LOG(INFO) << "Scanning " << session_dir_path
<< " looking for packages to be validated";
- Result<std::vector<std::string>> scan = FindApexFilesByName(session_dir_path);
+ Result<std::vector<std::string>> scan =
+ FindFilesBySuffix(session_dir_path, {kApexPackageSuffix});
if (!scan.ok()) {
LOG(WARNING) << scan.error();
return scan.error();
@@ -817,7 +824,8 @@
return {};
}
- auto active_packages = FindApexFilesByName(kActiveApexPackagesDataDir);
+ auto active_packages =
+ FindFilesBySuffix(kActiveApexPackagesDataDir, {kApexPackageSuffix});
if (!active_packages.ok()) {
return Error() << "Backup failed : " << active_packages.error();
}
@@ -1361,12 +1369,14 @@
std::vector<ApexFile> GetFactoryPackages() {
std::vector<ApexFile> ret;
for (const auto& dir : kApexPackageBuiltinDirs) {
- auto apex_files = FindApexFilesByName(dir);
- if (!apex_files.ok()) {
- LOG(ERROR) << apex_files.error();
+ auto all_apex_files = FindFilesBySuffix(
+ dir, {kApexPackageSuffix, kCompressedApexPackageSuffix});
+ if (!all_apex_files.ok()) {
+ LOG(ERROR) << all_apex_files.error();
continue;
}
- for (const std::string& path : *apex_files) {
+
+ for (const std::string& path : *all_apex_files) {
Result<ApexFile> apex_file = ApexFile::Open(path);
if (!apex_file.ok()) {
LOG(ERROR) << apex_file.error();
@@ -1418,7 +1428,8 @@
LOG(INFO) << "... does not exist. Skipping";
return {};
}
- Result<std::vector<std::string>> scan = FindApexFilesByName(apex_package_dir);
+ Result<std::vector<std::string>> scan =
+ FindFilesBySuffix(apex_package_dir, {kApexPackageSuffix});
if (!scan.ok()) {
return Error() << "Failed to scan " << apex_package_dir << " : "
<< scan.error();
@@ -1841,7 +1852,8 @@
std::vector<std::string> apexes;
bool scan_successful = true;
for (const auto& dir_to_scan : dirs_to_scan) {
- Result<std::vector<std::string>> scan = FindApexFilesByName(dir_to_scan);
+ Result<std::vector<std::string>> scan =
+ FindFilesBySuffix(dir_to_scan, {kApexPackageSuffix});
if (!scan.ok()) {
LOG(WARNING) << scan.error();
scan_successful = false;
@@ -2525,7 +2537,8 @@
// Removes APEXes on /data that don't have corresponding pre-installed version
// or that are corrupt
void RemoveOrphanedApexes() {
- auto data_apexes = FindApexFilesByName(kActiveApexPackagesDataDir);
+ auto data_apexes =
+ FindFilesBySuffix(kActiveApexPackagesDataDir, {kApexPackageSuffix});
if (!data_apexes.ok()) {
LOG(ERROR) << "Failed to scan " << kActiveApexPackagesDataDir << " : "
<< data_apexes.error();
diff --git a/apexd/apexd_utils.h b/apexd/apexd_utils.h
index e268f59..835b8a2 100644
--- a/apexd/apexd_utils.h
+++ b/apexd/apexd_utils.h
@@ -233,16 +233,19 @@
return GetSubdirs(kDeNDataDir);
}
-inline Result<std::vector<std::string>> FindApexFilesByName(
- const std::string& path) {
- auto filter_fn = [](const std::filesystem::directory_entry& entry) {
- std::error_code ec;
- if (entry.is_regular_file(ec) &&
- EndsWith(entry.path().filename().string(), kApexPackageSuffix)) {
- return true; // APEX file, take.
- }
- return false;
- };
+inline Result<std::vector<std::string>> FindFilesBySuffix(
+ const std::string& path, const std::vector<std::string>& suffix_list) {
+ auto filter_fn =
+ [&suffix_list](const std::filesystem::directory_entry& entry) {
+ for (const std::string& suffix : suffix_list) {
+ std::error_code ec;
+ if (entry.is_regular_file(ec) &&
+ EndsWith(entry.path().filename().string(), suffix)) {
+ return true; // suffix matches, take.
+ }
+ }
+ return false;
+ };
return ReadDir(path, filter_fn);
}
@@ -256,7 +259,7 @@
}
if (!*exist) continue;
- const auto& apexes = FindApexFilesByName(path);
+ const auto& apexes = FindFilesBySuffix(path, {kApexPackageSuffix});
if (!apexes.ok()) {
return apexes;
}
diff --git a/apexd/apexd_utils_test.cpp b/apexd/apexd_utils_test.cpp
index 06b9c46..e5576d2 100644
--- a/apexd/apexd_utils_test.cpp
+++ b/apexd/apexd_utils_test.cpp
@@ -15,6 +15,7 @@
*/
#include <filesystem>
+#include <fstream>
#include <string>
#include <errno.h>
@@ -31,6 +32,8 @@
namespace apex {
namespace {
+namespace fs = std::filesystem;
+
using android::apex::testing::IsOk;
using android::base::Basename;
using android::base::StringPrintf;
@@ -108,8 +111,6 @@
}
TEST(ApexdUtilTest, MoveDir) {
- namespace fs = std::filesystem;
-
TemporaryDir from;
TemporaryDir to;
@@ -178,6 +179,26 @@
UnorderedElementsAre(from_1.path, from_subdir, from_2.path));
}
+TEST(ApexdUtilTest, FindFilesBySuffix) {
+ TemporaryDir td;
+
+ // create files with different suffix
+ const std::string first_filename = StringPrintf("%s/first.a", td.path);
+ const std::string second_filename = StringPrintf("%s/second.b", td.path);
+ const std::string third_filename = StringPrintf("%s/third.c", td.path);
+ const std::string fourth_filename = StringPrintf("%s/fourth.c", td.path);
+
+ std::ofstream first_file(first_filename);
+ std::ofstream second_file(second_filename);
+ std::ofstream third_file(third_filename);
+ std::ofstream fourth_file(fourth_filename);
+
+ auto result = FindFilesBySuffix(td.path, {".b", ".c"});
+ ASSERT_TRUE(IsOk(result));
+ ASSERT_THAT(*result, UnorderedElementsAre(second_filename, third_filename,
+ fourth_filename));
+}
+
} // namespace
} // namespace apex
} // namespace android
diff --git a/tests/Android.bp b/tests/Android.bp
index 6c81f9f..a6c4b41 100644
--- a/tests/Android.bp
+++ b/tests/Android.bp
@@ -123,7 +123,7 @@
java_test_host {
name: "apex_rollback_tests",
- srcs: ["src/**/ApexRollbackTests.java", "src/**/ApexTestUtils.java"],
+ srcs: ["src/**/ApexRollbackTests.java"],
libs: ["tradefed", "truth-prebuilt"],
static_libs: ["frameworks-base-hostutils", "cts-install-lib-host"],
test_config: "apex-rollback-tests.xml",
@@ -193,3 +193,24 @@
test_config: "shared-libs-apex-tests.xml",
test_suites: ["general-tests"],
}
+
+java_test_host {
+ name: "apex_compression_platform_tests",
+ srcs: ["src/**/ApexCompressionTests.java"],
+ libs: ["tradefed", "truth-prebuilt"],
+ static_libs: ["cts-install-lib-host"],
+ test_config: "apex-compression-tests.xml",
+ test_suites: ["general-tests"],
+ data: [
+ ":com.android.apex.compressed.v1",
+ ":apex_compression_tests_app"
+ ],
+}
+
+android_test_helper_app {
+ name: "apex_compression_tests_app",
+ manifest: "app/AndroidManifest.xml",
+ srcs: ["app/src/**/*.java"],
+ static_libs: ["androidx.test.rules", "cts-install-lib", "testng"],
+ test_suites: ["general-tests"],
+}
diff --git a/tests/apex-compression-tests.xml b/tests/apex-compression-tests.xml
new file mode 100644
index 0000000..63bb6ff
--- /dev/null
+++ b/tests/apex-compression-tests.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<configuration description="Runs the apex compression test cases">
+ <option name="test-suite-tag" value="apex_compression_tests" />
+ <option name="test-suite-tag" value="apct" />
+ <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer"/>
+ <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+ <option name="cleanup-apks" value="true" />
+ <option name="test-file-name" value="apex_compression_tests_app.apk" />
+ </target_preparer>
+ <test class="com.android.tradefed.testtype.HostTest" >
+ <option name="class"
+ value="com.android.tests.apex.host.ApexCompressionTests" />
+ </test>
+</configuration>
diff --git a/tests/app/AndroidManifest.xml b/tests/app/AndroidManifest.xml
new file mode 100644
index 0000000..0644ba7
--- /dev/null
+++ b/tests/app/AndroidManifest.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2020 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.tests.apex.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.apex.app"
+ android:label="ApexCompression Test"/>
+
+</manifest>
diff --git a/tests/app/src/com/android/tests/apex/app/ApexCompressionTests.java b/tests/app/src/com/android/tests/apex/app/ApexCompressionTests.java
new file mode 100644
index 0000000..4f094c5
--- /dev/null
+++ b/tests/app/src/com/android/tests/apex/app/ApexCompressionTests.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tests.apex.app;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+
+import androidx.test.InstrumentationRegistry;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class ApexCompressionTests {
+ private static final String COMPRESSED_APEX_PACKAGE_NAME = "com.android.apex.compressed";
+
+ @Test
+ public void testCompressedApexCanBeQueried() throws Exception {
+ Context context = InstrumentationRegistry.getContext();
+ PackageManager pm = context.getPackageManager();
+ PackageInfo pi = pm.getPackageInfo(COMPRESSED_APEX_PACKAGE_NAME,
+ PackageManager.MATCH_APEX | PackageManager.MATCH_FACTORY_ONLY);
+ assertThat(pi).isNotNull();
+ assertThat(pi.isApex).isTrue();
+ assertThat(pi.packageName).isEqualTo(COMPRESSED_APEX_PACKAGE_NAME);
+ assertThat(pi.getLongVersionCode()).isEqualTo(1);
+ }
+}
diff --git a/tests/src/com/android/tests/apex/host/ApexCompressionTests.java b/tests/src/com/android/tests/apex/host/ApexCompressionTests.java
new file mode 100644
index 0000000..c402681
--- /dev/null
+++ b/tests/src/com/android/tests/apex/host/ApexCompressionTests.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tests.apex.host;
+
+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.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;
+
+/**
+ * Test for platform support for Apex Compression feature
+ */
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class ApexCompressionTests extends BaseHostJUnit4Test {
+ private static final String COMPRESSED_APEX_PACKAGE_NAME = "com.android.apex.compressed";
+
+ private final InstallUtilsHost mHostUtils = new InstallUtilsHost(this);
+
+ private boolean mWasAdbRoot = false;
+
+ @Before
+ public void setUp() throws Exception {
+ mWasAdbRoot = getDevice().isAdbRoot();
+ if (!mWasAdbRoot) {
+ assumeTrue("Requires root", getDevice().enableAdbRoot());
+ }
+ deleteFiles("/system/apex/" + COMPRESSED_APEX_PACKAGE_NAME + "*apex",
+ "/data/apex/active/" + COMPRESSED_APEX_PACKAGE_NAME + "*apex");
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ if (!mWasAdbRoot) {
+ getDevice().disableAdbRoot();
+ }
+ deleteFiles("/system/apex/" + COMPRESSED_APEX_PACKAGE_NAME + "*apex",
+ "/data/apex/active/" + COMPRESSED_APEX_PACKAGE_NAME + "*apex");
+ }
+
+ /**
+ * Runs the given phase of a test by calling into the device.
+ * Throws an exception if the test phase fails.
+ * <p>
+ * For example, <code>runPhase("testApkOnlyEnableRollback");</code>
+ */
+ private void runPhase(String phase) throws Exception {
+ assertTrue(runDeviceTests("com.android.tests.apex.app",
+ "com.android.tests.apex.app.ApexCompressionTests",
+ phase));
+ }
+
+ /**
+ * 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().remountSystemWritable();
+ for (String file : files) {
+ getDevice().executeShellCommand("rm -rf " + file);
+ }
+ getDevice().reboot();
+ }
+ }
+
+ private void pushTestApex() throws Exception {
+ final String fileName = COMPRESSED_APEX_PACKAGE_NAME + ".v1.capex";
+ final File apex = mHostUtils.getTestFile(fileName);
+ getDevice().remountSystemWritable();
+ assertTrue(getDevice().pushFile(apex, "/system/apex/" + fileName));
+ getDevice().reboot();
+ }
+
+
+ @Test
+ @LargeTest
+ public void testCompressedApexCanBeQueried() throws Exception {
+ pushTestApex();
+ runPhase("testCompressedApexCanBeQueried");
+ }
+
+}