From 38d8f72e8dfca3ddc40edb057246f0645cc6f360 Mon Sep 17 00:00:00 2001 From: Pawan Wagh Date: Thu, 28 Mar 2024 16:06:21 +0000 Subject: Add method to return Zip file name Test: m libandroidfw Bug: 301631861 Change-Id: If6d0ddf6e3465f984209325406cfeaffc44f9d6a --- libs/androidfw/ZipFileRO.cpp | 4 ++++ libs/androidfw/include/androidfw/ZipFileRO.h | 2 ++ 2 files changed, 6 insertions(+) diff --git a/libs/androidfw/ZipFileRO.cpp b/libs/androidfw/ZipFileRO.cpp index 9d4b426a6759..34a6bc27b93f 100644 --- a/libs/androidfw/ZipFileRO.cpp +++ b/libs/androidfw/ZipFileRO.cpp @@ -310,3 +310,7 @@ bool ZipFileRO::uncompressEntry(ZipEntryRO entry, int fd) const return true; } + +const char* ZipFileRO::getZipFileName() { + return mFileName; +} diff --git a/libs/androidfw/include/androidfw/ZipFileRO.h b/libs/androidfw/include/androidfw/ZipFileRO.h index be1f98f4843d..031d2e8fd48f 100644 --- a/libs/androidfw/include/androidfw/ZipFileRO.h +++ b/libs/androidfw/include/androidfw/ZipFileRO.h @@ -187,6 +187,8 @@ public: */ bool uncompressEntry(ZipEntryRO entry, int fd) const; + const char* getZipFileName(); + ~ZipFileRO(); private: -- cgit v1.2.3-59-g8ed1b From 5f47f06075d971897ad21e17ee88ede1e5cc305d Mon Sep 17 00:00:00 2001 From: Pawan Wagh Date: Tue, 19 Mar 2024 18:10:04 +0000 Subject: Punch holes in 64 bit native libs Extra padding at the end of LOAD segments is being introduced when libraries are being aligned to 64KB. This increases space used by shared libraries. This change deallocates space used by zero padding at the end of LOAD segments in given uncompressed ELF file. Zero padding can be detected by reading ELF headers. Executable header gives out the position of program headers. LOAD segments can be identified from program headers. FileSiz specifies the size of the data in corresponding LOAD segment. Ex. ELF header format for libpunchtest.so Program Headers: Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align PHDR 0x000040 0x0000000000000040 0x0000000000000040 0x0001f8 0x0001f8 R 0x8 LOAD 0x000000 0x0000000000000000 0x0000000000000000 0x0003e8 0x0003e8 R 0x10000 LOAD 0x010000 0x0000000000010000 0x0000000000010000 0x000050 0x000050 R E 0x10000 LOAD 0x020000 0x0000000000020000 0x0000000000020000 0x0001b8 0x0001b8 RW 0x10000 DYNAMIC 0x020018 0x0000000000020018 0x0000000000020018 0x000180 0x000180 RW 0x8 GNU_RELRO 0x020000 0x0000000000020000 0x0000000000020000 0x0001b8 0x001000 R 0x1 GNU_EH_FRAME 0x000368 0x0000000000000368 0x0000000000000368 0x000024 0x000024 R 0x4 GNU_STACK 0x000000 0x0000000000000000 0x0000000000000000 0x000000 0x000000 RW 0 NOTE 0x000238 0x0000000000000238 0x0000000000000238 0x000050 0x000050 R 0x4 Zero padding can be found as : Padding Length = Offset of LOAD SEGMENT 2 - (Offset of LOAD SEGMENT 1 + FileSiz of LOAD SEGMENT 1) This padding is present at position = (Offset of LOAD SEGMENT 1 + FileSiz of LOAD SEGMENT 1) [fallocate(2)](http://man7.org/linux/man-pages/man2/fallocate.2.html) is used to deallocate the zero ranges at the end of LOAD segments. It is called with above padding length and position. If ELF file is present inside of ApK/Zip file, offset to the start of the ELF file should be added to the position. From test logs, stats for installation embedded_native_libs_test_app.apk. Note: ELF was 64bit aligned during tests. Size before punching holes st_blocks: 2072, st_blksize: 4096, st_size: 1058429 Size after punching holes st_blocks: 1832, st_blksize: 4096, st_size: 1058429 Punching will be skipped for content which is less than 4096 bytes in size. Test: acloud delete --all && m && acloud create --local-instance --local-image && adb logcat -c && m FileSystemUtilsTests && atest -c FileSystemUtilsTests Bug: 301631861 Change-Id: I86060f877f90e98c103e884cf6d303f0bdfa4d12 --- core/jni/Android.bp | 1 + ...om_android_internal_content_FileSystemUtils.cpp | 218 +++++++++++++++++++++ .../com_android_internal_content_FileSystemUtils.h | 31 +++ 3 files changed, 250 insertions(+) create mode 100644 core/jni/com_android_internal_content_FileSystemUtils.cpp create mode 100644 core/jni/com_android_internal_content_FileSystemUtils.h diff --git a/core/jni/Android.bp b/core/jni/Android.bp index 8c6bf79d9731..305809b973bf 100644 --- a/core/jni/Android.bp +++ b/core/jni/Android.bp @@ -120,6 +120,7 @@ cc_library_shared_for_libandroid_runtime { srcs: [ "AndroidRuntime.cpp", "com_android_internal_content_F2fsUtils.cpp", + "com_android_internal_content_FileSystemUtils.cpp", "com_android_internal_content_NativeLibraryHelper.cpp", "com_google_android_gles_jni_EGLImpl.cpp", "com_google_android_gles_jni_GLImpl.cpp", // TODO: .arm diff --git a/core/jni/com_android_internal_content_FileSystemUtils.cpp b/core/jni/com_android_internal_content_FileSystemUtils.cpp new file mode 100644 index 000000000000..4bd2d72a1eb4 --- /dev/null +++ b/core/jni/com_android_internal_content_FileSystemUtils.cpp @@ -0,0 +1,218 @@ +/* + * Copyright (C) 2024 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. + */ + +#define LOG_TAG "FileSystemUtils" + +#include "com_android_internal_content_FileSystemUtils.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +using android::base::HexString; +using android::base::ReadFullyAtOffset; + +namespace android { +bool punchHoles(const char *filePath, const uint64_t offset, + const std::vector &programHeaders) { + struct stat64 beforePunch; + lstat64(filePath, &beforePunch); + uint64_t blockSize = beforePunch.st_blksize; + IF_ALOGD() { + ALOGD("Total number of LOAD segments %zu", programHeaders.size()); + + ALOGD("Size before punching holes st_blocks: %" PRIu64 + ", st_blksize: %ld, st_size: %" PRIu64 "", + beforePunch.st_blocks, beforePunch.st_blksize, + static_cast(beforePunch.st_size)); + } + + android::base::unique_fd fd(open(filePath, O_RDWR | O_CLOEXEC)); + if (!fd.ok()) { + ALOGE("Can't open file to punch %s", filePath); + return false; + } + + // read in chunks of 64KB + constexpr uint64_t kChunkSize = 64 * 1024; + + // malloc is used to gracefully handle oom which might occur during the allocation of buffer. + // allocating using new or vector here results in oom/exception on failure where as malloc will + // return nullptr. + std::unique_ptr buffer(static_cast(malloc(kChunkSize)), + &free); + if (buffer == nullptr) { + ALOGE("Failed to allocate read buffer"); + return false; + } + + for (size_t index = 0; programHeaders.size() >= 2 && index < programHeaders.size() - 1; + index++) { + // find LOAD segments from program headers, calculate padding and punch holes + uint64_t punchOffset; + if (__builtin_add_overflow(programHeaders[index].p_offset, programHeaders[index].p_filesz, + &punchOffset)) { + ALOGE("Overflow occurred when adding offset and filesize"); + return false; + } + + uint64_t punchLen; + if (__builtin_sub_overflow(programHeaders[index + 1].p_offset, punchOffset, &punchLen)) { + ALOGE("Overflow occurred when calculating length"); + return false; + } + + if (punchLen < blockSize) { + continue; + } + + uint64_t punchStartOffset; + if (__builtin_add_overflow(offset, punchOffset, &punchStartOffset)) { + ALOGE("Overflow occurred when calculating length"); + return false; + } + + uint64_t position = punchStartOffset; + uint64_t endPosition; + if (__builtin_add_overflow(position, punchLen, &endPosition)) { + ALOGE("Overflow occurred when calculating length"); + return false; + } + + // Read content in kChunkSize and verify it is zero + while (position <= endPosition) { + uint64_t uncheckedChunkEnd; + if (__builtin_add_overflow(position, kChunkSize, &uncheckedChunkEnd)) { + ALOGE("Overflow occurred when calculating uncheckedChunkEnd"); + return false; + } + + uint64_t readLength; + if (__builtin_sub_overflow(std::min(uncheckedChunkEnd, endPosition), position, + &readLength)) { + ALOGE("Overflow occurred when calculating readLength"); + return false; + } + + if (!ReadFullyAtOffset(fd, buffer.get(), readLength, position)) { + ALOGE("Failed to read content to punch holes"); + return false; + } + + IF_ALOGD() { + ALOGD("Punching holes for length:%" PRIu64 " content which should be zero: %s", + readLength, HexString(buffer.get(), readLength).c_str()); + } + + bool isZero = std::all_of(buffer.get(), buffer.get() + readLength, + [](uint8_t i) constexpr { return i == 0; }); + if (!isZero) { + ALOGE("Found non zero content while trying to punch hole. Skipping operation"); + return false; + } + + position = uncheckedChunkEnd; + } + + // if we have a uncompressed file which is being opened from APK, use the offset to + // punch native lib inside Apk. + int result = fallocate(fd, FALLOC_FL_PUNCH_HOLE | FALLOC_FL_KEEP_SIZE, punchStartOffset, + punchLen); + if (result < 0) { + ALOGE("fallocate failed to punch hole, error:%d", errno); + return false; + } + } + + IF_ALOGD() { + struct stat64 afterPunch; + lstat64(filePath, &afterPunch); + ALOGD("Size after punching holes st_blocks: %" PRIu64 ", st_blksize: %ld, st_size: %" PRIu64 + "", + afterPunch.st_blocks, afterPunch.st_blksize, + static_cast(afterPunch.st_size)); + } + + return true; +} + +bool punchHolesInElf64(const char *filePath, const uint64_t offset) { + // Open Elf file + Elf64_Ehdr ehdr; + std::ifstream inputStream(filePath, std::ifstream::in); + + // If this is a zip file, set the offset so that we can read elf file directly + inputStream.seekg(offset); + // read executable headers + inputStream.read((char *)&ehdr, sizeof(ehdr)); + if (!inputStream.good()) { + return false; + } + + // only consider elf64 for punching holes + if (ehdr.e_ident[EI_CLASS] != ELFCLASS64) { + ALOGE("Provided file is not ELF64"); + return false; + } + + // read the program headers from elf file + uint64_t programHeaderOffset = ehdr.e_phoff; + uint16_t programHeaderNum = ehdr.e_phnum; + + IF_ALOGD() { + ALOGD("Punching holes in file: %s programHeaderOffset: %" PRIu64 " programHeaderNum: %hu", + filePath, programHeaderOffset, programHeaderNum); + } + + // if this is a zip file, also consider elf offset inside a file + uint64_t phOffset; + if (__builtin_add_overflow(offset, programHeaderOffset, &phOffset)) { + ALOGE("Overflow occurred when calculating phOffset"); + return false; + } + inputStream.seekg(phOffset); + + std::vector programHeaders; + for (int headerIndex = 0; headerIndex < programHeaderNum; headerIndex++) { + Elf64_Phdr header; + inputStream.read((char *)&header, sizeof(header)); + if (!inputStream.good()) { + return false; + } + + if (header.p_type != PT_LOAD) { + continue; + } + programHeaders.push_back(header); + } + + return punchHoles(filePath, offset, programHeaders); +} + +}; // namespace android diff --git a/core/jni/com_android_internal_content_FileSystemUtils.h b/core/jni/com_android_internal_content_FileSystemUtils.h new file mode 100644 index 000000000000..a6b145c690d1 --- /dev/null +++ b/core/jni/com_android_internal_content_FileSystemUtils.h @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#pragma once + +#include + +namespace android { + +/* + * This function deallocates space used by zero padding at the end of LOAD segments in given + * uncompressed ELF file. Read ELF headers and find out the offset and sizes of LOAD segments. + * [fallocate(2)](http://man7.org/linux/man-pages/man2/fallocate.2.html) is used to deallocate the + * zero ranges at the end of LOAD segments. If ELF file is present inside of ApK/Zip file, offset to + * the start of the ELF file should be provided. + */ +bool punchHolesInElf64(const char* filePath, uint64_t offset); + +} // namespace android \ No newline at end of file -- cgit v1.2.3-59-g8ed1b From 1ad068f53cc875485f027cb74d67901a871514b7 Mon Sep 17 00:00:00 2001 From: Pawan Wagh Date: Fri, 29 Mar 2024 21:35:35 +0000 Subject: Adding FileSystemUtils test Adding tests to check whether apps can be launched after loading the punched libs. One app has uncompressed shared lib which will be directly loaded from apk and other will extract it. Test: acloud delete --all && m && acloud create --local-instance --local-image && adb logcat -c && m FileSystemUtilsTests && atest -c FileSystemUtilsTests Bug: 301631861 Change-Id: I4f7b38902f2e02e83a350cd1cc907f8edaa20c81 --- core/tests/FileSystemUtilsTest/Android.bp | 78 ++++++++++++++++++++++ core/tests/FileSystemUtilsTest/AndroidManifest.xml | 27 ++++++++ core/tests/FileSystemUtilsTest/AndroidTest.xml | 30 +++++++++ .../embedded_native_libs_test_app.xml | 35 ++++++++++ .../src/android/test/embedded/MainActivity.java | 60 +++++++++++++++++ .../test/embedded/PunchEmbeddedLibTest.java | 66 ++++++++++++++++++ .../extract_native_libs_test_app.xml | 35 ++++++++++ .../src/android/test/extract/MainActivity.java | 60 +++++++++++++++++ .../test/extract/PunchExtractedLibTest.java | 65 ++++++++++++++++++ .../jni/android_test_jni_source.cpp | 41 ++++++++++++ .../internal/content/FileSystemUtilsTest.java | 50 ++++++++++++++ 11 files changed, 547 insertions(+) create mode 100644 core/tests/FileSystemUtilsTest/Android.bp create mode 100644 core/tests/FileSystemUtilsTest/AndroidManifest.xml create mode 100644 core/tests/FileSystemUtilsTest/AndroidTest.xml create mode 100644 core/tests/FileSystemUtilsTest/apk_embedded_native_libs/embedded_native_libs_test_app.xml create mode 100644 core/tests/FileSystemUtilsTest/apk_embedded_native_libs/src/android/test/embedded/MainActivity.java create mode 100644 core/tests/FileSystemUtilsTest/apk_embedded_native_libs/src/android/test/embedded/PunchEmbeddedLibTest.java create mode 100644 core/tests/FileSystemUtilsTest/apk_extract_native_libs/extract_native_libs_test_app.xml create mode 100644 core/tests/FileSystemUtilsTest/apk_extract_native_libs/src/android/test/extract/MainActivity.java create mode 100644 core/tests/FileSystemUtilsTest/apk_extract_native_libs/src/android/test/extract/PunchExtractedLibTest.java create mode 100644 core/tests/FileSystemUtilsTest/jni/android_test_jni_source.cpp create mode 100644 core/tests/FileSystemUtilsTest/src/com/android/internal/content/FileSystemUtilsTest.java diff --git a/core/tests/FileSystemUtilsTest/Android.bp b/core/tests/FileSystemUtilsTest/Android.bp new file mode 100644 index 000000000000..53c22df67b85 --- /dev/null +++ b/core/tests/FileSystemUtilsTest/Android.bp @@ -0,0 +1,78 @@ +// Copyright (C) 2024 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: ["frameworks_base_license"], + default_team: "trendy_team_android_kernel", +} + +cc_library { + name: "libpunchtest", + stl: "none", + host_supported: true, + srcs: ["jni/android_test_jni_source.cpp"], + header_libs: ["jni_headers"], +} + +android_test_helper_app { + name: "embedded_native_libs_test_app", + srcs: ["apk_embedded_native_libs/src/**/*.java"], + manifest: "apk_embedded_native_libs/embedded_native_libs_test_app.xml", + compile_multilib: "64", + jni_libs: [ + "libpunchtest", + ], + static_libs: [ + "androidx.test.rules", + "platform-test-annotations", + ], + use_embedded_native_libs: true, +} + +android_test_helper_app { + name: "extract_native_libs_test_app", + srcs: ["apk_extract_native_libs/src/**/*.java"], + manifest: "apk_extract_native_libs/extract_native_libs_test_app.xml", + compile_multilib: "64", + jni_libs: [ + "libpunchtest", + ], + static_libs: [ + "androidx.test.rules", + "platform-test-annotations", + ], + use_embedded_native_libs: false, +} + +java_test_host { + name: "FileSystemUtilsTests", + // Include all test java files + srcs: ["src/**/*.java"], + static_libs: [ + "junit", + "platform-test-annotations", + "truth", + ], + libs: [ + "tradefed", + "compatibility-host-util", + "compatibility-tradefed", + ], + data: [ + ":embedded_native_libs_test_app", + ":extract_native_libs_test_app", + ], + test_suites: ["general-tests"], + test_config: "AndroidTest.xml", +} diff --git a/core/tests/FileSystemUtilsTest/AndroidManifest.xml b/core/tests/FileSystemUtilsTest/AndroidManifest.xml new file mode 100644 index 000000000000..acd5ef3c90c2 --- /dev/null +++ b/core/tests/FileSystemUtilsTest/AndroidManifest.xml @@ -0,0 +1,27 @@ + + + + + + + + diff --git a/core/tests/FileSystemUtilsTest/AndroidTest.xml b/core/tests/FileSystemUtilsTest/AndroidTest.xml new file mode 100644 index 000000000000..27f49b2289ba --- /dev/null +++ b/core/tests/FileSystemUtilsTest/AndroidTest.xml @@ -0,0 +1,30 @@ + + + + + diff --git a/core/tests/FileSystemUtilsTest/apk_embedded_native_libs/embedded_native_libs_test_app.xml b/core/tests/FileSystemUtilsTest/apk_embedded_native_libs/embedded_native_libs_test_app.xml new file mode 100644 index 000000000000..868f7f3b7799 --- /dev/null +++ b/core/tests/FileSystemUtilsTest/apk_embedded_native_libs/embedded_native_libs_test_app.xml @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/core/tests/FileSystemUtilsTest/apk_embedded_native_libs/src/android/test/embedded/MainActivity.java b/core/tests/FileSystemUtilsTest/apk_embedded_native_libs/src/android/test/embedded/MainActivity.java new file mode 100644 index 000000000000..efa2a39881dc --- /dev/null +++ b/core/tests/FileSystemUtilsTest/apk_embedded_native_libs/src/android/test/embedded/MainActivity.java @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2024 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.test.embedded; + +import android.app.Activity; +import android.content.Intent; +import android.os.Bundle; + +import androidx.annotation.VisibleForTesting; + +public class MainActivity extends Activity { + + static { + System.loadLibrary("punchtest"); + } + + @VisibleForTesting + static final String INTENT_TYPE = "android.test.embedded.EMBEDDED_LIB_LOADED"; + + @VisibleForTesting + static final String KEY_OPERAND_1 = "OP1"; + + @VisibleForTesting + static final String KEY_OPERAND_2 = "OP2"; + + @VisibleForTesting + static final String KEY_RESULT = "RESULT"; + + @Override + public void onCreate(Bundle savedOnstanceState) { + super.onCreate(savedOnstanceState); + + Intent received = getIntent(); + int op1 = received.getIntExtra(KEY_OPERAND_1, -1); + int op2 = received.getIntExtra(KEY_OPERAND_2, -1); + int result = add(op1, op2); + + // Send broadcast so that test can know app has launched and lib is loaded + // attach result which has been fetched from JNI lib + Intent intent = new Intent(INTENT_TYPE); + intent.putExtra(KEY_RESULT, result); + sendBroadcast(intent); + } + + private native int add(int op1, int op2); +} diff --git a/core/tests/FileSystemUtilsTest/apk_embedded_native_libs/src/android/test/embedded/PunchEmbeddedLibTest.java b/core/tests/FileSystemUtilsTest/apk_embedded_native_libs/src/android/test/embedded/PunchEmbeddedLibTest.java new file mode 100644 index 000000000000..d7d67b888490 --- /dev/null +++ b/core/tests/FileSystemUtilsTest/apk_embedded_native_libs/src/android/test/embedded/PunchEmbeddedLibTest.java @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2024 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.test.embedded; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; + +import androidx.test.InstrumentationRegistry; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +@RunWith(AndroidJUnit4.class) +public class PunchEmbeddedLibTest { + + @Test + public void testPunchedNativeLibs_embeddedLib() throws Exception { + Context context = InstrumentationRegistry.getContext(); + CountDownLatch receivedSignal = new CountDownLatch(1); + + // Test app is expected to receive this and perform addition of operands using punched lib + int op1 = 48; + int op2 = 75; + IntentFilter intentFilter = new IntentFilter(MainActivity.INTENT_TYPE); + BroadcastReceiver broadcastReceiver = + new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + receivedSignal.countDown(); + int result = intent.getIntExtra(MainActivity.KEY_RESULT, 1000); + Assert.assertEquals(result, op1 + op2); + + } + }; + context.registerReceiver(broadcastReceiver, intentFilter, Context.RECEIVER_EXPORTED); + + Intent launchIntent = + context.getPackageManager().getLaunchIntentForPackage(context.getPackageName()); + launchIntent.putExtra(MainActivity.KEY_OPERAND_1, op1); + launchIntent.putExtra(MainActivity.KEY_OPERAND_2, op2); + context.startActivity(launchIntent); + + Assert.assertTrue("Failed to launch app", receivedSignal.await(10, TimeUnit.SECONDS)); + } +} diff --git a/core/tests/FileSystemUtilsTest/apk_extract_native_libs/extract_native_libs_test_app.xml b/core/tests/FileSystemUtilsTest/apk_extract_native_libs/extract_native_libs_test_app.xml new file mode 100644 index 000000000000..6db96f79b3f1 --- /dev/null +++ b/core/tests/FileSystemUtilsTest/apk_extract_native_libs/extract_native_libs_test_app.xml @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/core/tests/FileSystemUtilsTest/apk_extract_native_libs/src/android/test/extract/MainActivity.java b/core/tests/FileSystemUtilsTest/apk_extract_native_libs/src/android/test/extract/MainActivity.java new file mode 100644 index 000000000000..b1c157e17985 --- /dev/null +++ b/core/tests/FileSystemUtilsTest/apk_extract_native_libs/src/android/test/extract/MainActivity.java @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2024 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.test.extract; + +import android.app.Activity; +import android.content.Intent; +import android.os.Bundle; + +import androidx.annotation.VisibleForTesting; + +public class MainActivity extends Activity { + + static { + System.loadLibrary("punchtest"); + } + + @VisibleForTesting + static final String INTENT_TYPE = "android.test.extract.EXTRACTED_LIB_LOADED"; + + @VisibleForTesting + static final String KEY_OPERAND_1 = "OP1"; + + @VisibleForTesting + static final String KEY_OPERAND_2 = "OP2"; + + @VisibleForTesting + static final String KEY_RESULT = "RESULT"; + + @Override + public void onCreate(Bundle savedOnstanceState) { + super.onCreate(savedOnstanceState); + + Intent received = getIntent(); + int op1 = received.getIntExtra(KEY_OPERAND_1, -1); + int op2 = received.getIntExtra(KEY_OPERAND_2, -1); + int result = subtract(op1, op2); + + // Send broadcast so that test can know app has launched and lib is loaded + // attach result which has been fetched from JNI lib + Intent intent = new Intent(INTENT_TYPE); + intent.putExtra(KEY_RESULT, result); + sendBroadcast(intent); + } + + private native int subtract(int op1, int op2); +} diff --git a/core/tests/FileSystemUtilsTest/apk_extract_native_libs/src/android/test/extract/PunchExtractedLibTest.java b/core/tests/FileSystemUtilsTest/apk_extract_native_libs/src/android/test/extract/PunchExtractedLibTest.java new file mode 100644 index 000000000000..7cc101751b3d --- /dev/null +++ b/core/tests/FileSystemUtilsTest/apk_extract_native_libs/src/android/test/extract/PunchExtractedLibTest.java @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2024 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.test.extract; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; + +import androidx.test.InstrumentationRegistry; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +@RunWith(AndroidJUnit4.class) +public class PunchExtractedLibTest { + + @Test + public void testPunchedNativeLibs_extractedLib() throws Exception { + Context context = InstrumentationRegistry.getContext(); + CountDownLatch receivedSignal = new CountDownLatch(1); + + // Test app is expected to receive this and perform subtraction using extracted lib + int op1 = 100; + int op2 = 71; + IntentFilter intentFilter = new IntentFilter(MainActivity.INTENT_TYPE); + BroadcastReceiver broadcastReceiver = + new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + receivedSignal.countDown(); + int result = intent.getIntExtra(MainActivity.KEY_RESULT, 1000); + Assert.assertEquals(result, op1 - op2); + } + }; + context.registerReceiver(broadcastReceiver, intentFilter, Context.RECEIVER_EXPORTED); + + Intent launchIntent = + context.getPackageManager().getLaunchIntentForPackage(context.getPackageName()); + launchIntent.putExtra(MainActivity.KEY_OPERAND_1, op1); + launchIntent.putExtra(MainActivity.KEY_OPERAND_2, op2); + context.startActivity(launchIntent); + + Assert.assertTrue("Failed to launch app", receivedSignal.await(10, TimeUnit.SECONDS)); + } +} diff --git a/core/tests/FileSystemUtilsTest/jni/android_test_jni_source.cpp b/core/tests/FileSystemUtilsTest/jni/android_test_jni_source.cpp new file mode 100644 index 000000000000..2a5ba817d9db --- /dev/null +++ b/core/tests/FileSystemUtilsTest/jni/android_test_jni_source.cpp @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2024 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 + +// This will be called from embedded_native_libs_test_app +extern "C" JNIEXPORT +jint JNICALL Java_android_test_embedded_MainActivity_add(JNIEnv*, jclass, jint op1, jint op2) { + return op1 + op2; +} + +// This will be called from extract_native_libs_test_app +extern "C" JNIEXPORT +jint JNICALL Java_android_test_extract_MainActivity_subtract(JNIEnv*, jclass, jint op1, jint op2) { + return op1 - op2; +} + +// Initialize JNI +jint JNI_OnLoad(JavaVM *jvm, void */* reserved */) { + JNIEnv *e; + + // Check JNI version + if (jvm->GetEnv((void **) &e, JNI_VERSION_1_6)) { + return JNI_ERR; + } + + return JNI_VERSION_1_6; +} diff --git a/core/tests/FileSystemUtilsTest/src/com/android/internal/content/FileSystemUtilsTest.java b/core/tests/FileSystemUtilsTest/src/com/android/internal/content/FileSystemUtilsTest.java new file mode 100644 index 000000000000..77802e5e811a --- /dev/null +++ b/core/tests/FileSystemUtilsTest/src/com/android/internal/content/FileSystemUtilsTest.java @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2024 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.internal.content; + +import static org.junit.Assert.assertTrue; + +import android.platform.test.annotations.AppModeFull; + +import com.android.tradefed.device.DeviceNotAvailableException; +import com.android.tradefed.testtype.DeviceJUnit4ClassRunner; +import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test; + +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(DeviceJUnit4ClassRunner.class) +public class FileSystemUtilsTest extends BaseHostJUnit4Test { + + @Test + @AppModeFull + public void runPunchedApp_embeddedNativeLibs() throws DeviceNotAvailableException { + String appPackage = "android.test.embedded"; + String testName = "PunchEmbeddedLibTest"; + assertTrue(isPackageInstalled(appPackage)); + runDeviceTests(appPackage, appPackage + "." + testName); + } + + @Test + @AppModeFull + public void runPunchedApp_extractedNativeLibs() throws DeviceNotAvailableException { + String appPackage = "android.test.extract"; + String testName = "PunchExtractedLibTest"; + assertTrue(isPackageInstalled(appPackage)); + runDeviceTests(appPackage, appPackage + "." + testName); + } +} -- cgit v1.2.3-59-g8ed1b