diff options
16 files changed, 803 insertions, 0 deletions
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 <android-base/file.h> +#include <android-base/hex.h> +#include <android-base/unique_fd.h> +#include <elf.h> +#include <errno.h> +#include <fcntl.h> +#include <inttypes.h> +#include <linux/fs.h> +#include <sys/ioctl.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <utils/Log.h> + +#include <array> +#include <fstream> +#include <vector> + +using android::base::HexString; +using android::base::ReadFullyAtOffset; + +namespace android { +bool punchHoles(const char *filePath, const uint64_t offset, + const std::vector<Elf64_Phdr> &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<uint64_t>(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<uint8_t, decltype(&free)> buffer(static_cast<uint8_t *>(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<uint64_t>(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<Elf64_Phdr> 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 <sys/types.h> + +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 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 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ 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. + --> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + android:installLocation="internalOnly" + package="com.android.internal.content.fstests"> + + <instrumentation + android:name="androidx.test.runner.AndroidJUnitRunner" + android:targetPackage="com.android.internal.content.fstests" + android:label="Frameworks FileSystemUtils Tests" /> + +</manifest> 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 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ 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. + --> + +<configuration description="Runs FileSystemUtilsTest."> + <option name="test-suite-tag" value="apct"/> + + <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller"> + <option name="cleanup-apks" value="true" /> + <option name="test-file-name" value="embedded_native_libs_test_app.apk" /> + <option name="test-file-name" value="extract_native_libs_test_app.apk" /> + </target_preparer> + + <test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" > + <option name="jar" value="FileSystemUtilsTests.jar" /> + </test> +</configuration> 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 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ 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. + --> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="android.test.embedded"> + <application android:extractNativeLibs="false"> + <uses-library android:name="android.test.runner"/> + <activity android:name=".MainActivity" + android:exported="true" + android:process=":NewProcess"> + <intent-filter> + <action android:name="android.intent.action.MAIN"/> + <category android:name="android.intent.category.LAUNCHER"/> + <category android:name="android.intent.category.DEFAULT"/> + </intent-filter> + </activity> + </application> + <instrumentation + android:name="androidx.test.runner.AndroidJUnitRunner" + android:targetPackage="android.test.embedded"/> +</manifest>
\ 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 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ 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. + --> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="android.test.extract"> + <application android:extractNativeLibs="true"> + <uses-library android:name="android.test.runner"/> + <activity android:name=".MainActivity" + android:exported="true" + android:process=":NewProcess"> + <intent-filter> + <action android:name="android.intent.action.MAIN"/> + <category android:name="android.intent.category.LAUNCHER"/> + <category android:name="android.intent.category.DEFAULT"/> + </intent-filter> + </activity> + </application> + <instrumentation + android:name="androidx.test.runner.AndroidJUnitRunner" + android:targetPackage="android.test.extract"/> +</manifest>
\ 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 <jni.h> + +// 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); + } +} 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: |