[PM] Add the test for forcing multiarch app match natively ABIs
Bug: 282783453
Test: atest -c CtsPackageManagerTestCases --test-filter 'PackageManagerMultiArchAppTest*'
Test: atest CtsPackageManagerHostTestCases:SplitTests
Change-Id: Ic9a71ac432a0802d34f9672fa0d7bf8c3e6fc4ad
diff --git a/hostsidetests/appsecurity/Android.bp b/hostsidetests/appsecurity/Android.bp
index a5e94c0..04b6980 100644
--- a/hostsidetests/appsecurity/Android.bp
+++ b/hostsidetests/appsecurity/Android.bp
@@ -299,6 +299,10 @@
"src/**/SplitTests.java",
"src/**/UseProcessTest.java",
],
+ static_libs: [
+ "flag-junit-host",
+ "android.content.pm.flags-aconfig-java-host",
+ ],
data: [
// ApplicationVisibilityTest
":CtsPkgInstallTinyApp",
diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/BaseInstallMultiple.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/BaseInstallMultiple.java
index 01c9625..8d9101d 100644
--- a/hostsidetests/appsecurity/src/android/appsecurity/cts/BaseInstallMultiple.java
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/BaseInstallMultiple.java
@@ -219,7 +219,7 @@
run(false, failure);
}
- private void run(boolean expectingSuccess, String failure) throws DeviceNotAvailableException {
+ void run(boolean expectingSuccess, String failure) throws DeviceNotAvailableException {
if (mUseIncremental) {
runIncremental(expectingSuccess, failure);
} else {
diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/SplitTests.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/SplitTests.java
index 9eddbef..162dea9 100644
--- a/hostsidetests/appsecurity/src/android/appsecurity/cts/SplitTests.java
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/SplitTests.java
@@ -27,10 +27,15 @@
import static org.junit.Assert.assertNotNull;
+import android.content.pm.Flags;
import android.platform.test.annotations.AppModeFull;
import android.platform.test.annotations.AppModeInstant;
import android.platform.test.annotations.PlatinumTest;
import android.platform.test.annotations.Presubmit;
+import android.platform.test.annotations.RequiresFlagsDisabled;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.host.HostFlagsValueProvider;
import com.android.compatibility.common.util.CpuFeatures;
import com.android.tradefed.device.DeviceNotAvailableException;
@@ -42,6 +47,7 @@
import org.junit.After;
import org.junit.Assume;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -104,6 +110,8 @@
private static final String APK_REVISION_A = "CtsSplitAppRevisionA.apk";
private static final String APK_FEATURE_WARM_REVISION_A = "CtsSplitAppFeatureWarmRevisionA.apk";
+ private static final String BITNESS_32 = "32";
+ private static final String BITNESS_64 = "64";
// Apk includes a provider and service declared in other split apk. And only could be tested in
// instant app mode.
@@ -121,6 +129,88 @@
ABI_TO_REVISION_APK.put("mips", "CtsSplitApp_revision12_mips.apk");
}
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule =
+ HostFlagsValueProvider.createCheckFlagsRule(this::getDevice);
+
+ private String mDeviceDefaultAbi = null;
+ private String mDeviceDefaultBitness = null;
+ private String mDeviceDefaultBaseArch = null;
+ private String[] mDeviceSupported32BitSet = null;
+ private String[] mDeviceSupported64BitSet = null;
+
+ private String[] getDeviceSupported32AbiSet() throws Exception {
+ if (mDeviceSupported32BitSet != null) {
+ return mDeviceSupported32BitSet;
+ }
+ mDeviceSupported32BitSet = getDeviceSupportedAbiSet(BITNESS_32);
+ return mDeviceSupported32BitSet;
+ }
+
+ private String[] getDeviceSupported64AbiSet() throws Exception {
+ if (mDeviceSupported64BitSet != null) {
+ return mDeviceSupported64BitSet;
+ }
+ mDeviceSupported64BitSet = getDeviceSupportedAbiSet(BITNESS_64);
+ return mDeviceSupported64BitSet;
+ }
+
+ private String[] getDeviceSupportedAbiSet(String bitness) throws Exception {
+ String abis = getDevice().getProperty("ro.product.cpu.abilist" + bitness);
+ if (abis == null) {
+ return new String[0];
+ }
+ return abis.split(",");
+ }
+
+ private String getDeviceDefaultAbi() throws Exception {
+ if (mDeviceDefaultAbi != null) {
+ return mDeviceDefaultAbi;
+ }
+ mDeviceDefaultAbi = getDevice().getProperty("ro.product.cpu.abi");
+ return mDeviceDefaultAbi;
+ }
+
+ private String getDeviceDefaultBitness() throws Exception {
+ if (mDeviceDefaultBitness != null) {
+ return mDeviceDefaultBitness;
+ }
+ mDeviceDefaultBitness = AbiUtils.getBitness(getDeviceDefaultAbi());
+ return mDeviceDefaultBitness;
+ }
+
+ private String getDeviceDefaultBaseArch() throws Exception {
+ if (mDeviceDefaultBaseArch != null) {
+ return mDeviceDefaultBaseArch;
+ }
+ mDeviceDefaultBaseArch = AbiUtils.getBaseArchForAbi(getDeviceDefaultAbi());
+ return mDeviceDefaultBaseArch;
+ }
+
+ /*
+ * Return true if the device supports both 32 bit and 64 bit ABIs. Otherwise, false.
+ */
+ private boolean isDeviceSupportedBothBitness() throws Exception {
+ final String deviceDefaultBaseArch = getDeviceDefaultBaseArch();
+ if (getDeviceDefaultBitness().equals(BITNESS_32)) {
+ String[] abis64 = getDeviceSupported64AbiSet();
+ for (int i = 0; i < abis64.length; i++) {
+ if (AbiUtils.getBaseArchForAbi(abis64[i]).equals(deviceDefaultBaseArch)) {
+ return true;
+ }
+ }
+ } else {
+ String[] abis32 = getDeviceSupported32AbiSet();
+ for (int i = 0; i < abis32.length; i++) {
+ if (AbiUtils.getBaseArchForAbi(abis32[i]).equals(deviceDefaultBaseArch)) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
@Before
public void setUp() throws Exception {
Utils.prepareSingleUser(getDevice());
@@ -260,6 +350,19 @@
return installMultiple;
}
+ private void testNativeSingle_assertFail(boolean instant, boolean useNaturalAbi,
+ String failure) throws Exception {
+ Assume.assumeTrue(isDeviceSupportedBothBitness());
+ final String abi = getAbi().getName();
+ final String apk = ABI_TO_APK.get(abi);
+ assertNotNull("Failed to find APK for ABI " + abi, apk);
+
+ getInstallMultiple(instant, useNaturalAbi)
+ .addFile(APK)
+ .addFile(apk)
+ .run(/* expectingSuccess= */ false, failure);
+ }
+
private void testNativeSingle(boolean instant, boolean useNaturalAbi) throws Exception {
final String abi = getAbi().getName();
final String apk = ABI_TO_APK.get(abi);
@@ -329,15 +432,32 @@
*/
@Test
@AppModeFull(reason = "'full' portion of the hostside test")
+ @RequiresFlagsDisabled(Flags.FLAG_FORCE_MULTI_ARCH_NATIVE_LIBS_MATCH)
public void testNativeSingleNatural_full() throws Exception {
testNativeSingle(false, true);
}
@Test
@AppModeInstant(reason = "'instant' portion of the hostside test")
+ @RequiresFlagsDisabled(Flags.FLAG_FORCE_MULTI_ARCH_NATIVE_LIBS_MATCH)
public void testNativeSingleNatural_instant() throws Exception {
testNativeSingle(true, true);
}
+ @Test
+ @AppModeFull(reason = "'full' portion of the hostside test")
+ @RequiresFlagsEnabled(Flags.FLAG_FORCE_MULTI_ARCH_NATIVE_LIBS_MATCH)
+ public void testNativeSingleNatural_full_fail() throws Exception {
+ testNativeSingle_assertFail(/* instant= */ false, /* useNaturalAbi= */ true,
+ "don't support all the natively supported ABIs of the device");
+ }
+ @Test
+ @AppModeInstant(reason = "'instant' portion of the hostside test")
+ @RequiresFlagsEnabled(Flags.FLAG_FORCE_MULTI_ARCH_NATIVE_LIBS_MATCH)
+ public void testNativeSingleNatural_instant_fail() throws Exception {
+ testNativeSingle_assertFail(/* instant= */ true, /* useNaturalAbi= */ true,
+ "don't support all the natively supported ABIs of the device");
+ }
+
private void assumeNativeAbi() throws Exception {
// Skip this test if not running on the device's native abi.
Assume.assumeTrue(CpuFeatures.isNativeAbi(getDevice(), getAbi().getName()));
diff --git a/tests/tests/content/Android.bp b/tests/tests/content/Android.bp
index c3c2635..dc76393 100644
--- a/tests/tests/content/Android.bp
+++ b/tests/tests/content/Android.bp
@@ -240,6 +240,18 @@
":HelloVerifierDelayedReject",
":HelloVerifierDisabled",
":HelloWorldSystemUserOnly",
+ ":CtsMultiArchApp32_arm",
+ ":CtsMultiArchApp32_x86",
+ ":CtsMultiArchApp64_arm",
+ ":CtsMultiArchApp64_x86",
+ ":CtsMultiArchAppBoth_arm",
+ ":CtsMultiArchAppBoth_x86",
+ ":CtsMultiArchApp32_targetSdk33_arm",
+ ":CtsMultiArchApp32_targetSdk33_x86",
+ ":CtsMultiArchApp64_targetSdk33_arm",
+ ":CtsMultiArchApp64_targetSdk33_x86",
+ ":CtsMultiArchAppBoth_targetSdk33_arm",
+ ":CtsMultiArchAppBoth_targetSdk33_x86",
],
java_resources: [
":PackagePropertyTestApp1",
diff --git a/tests/tests/content/CtsPackageManagerTest.xml b/tests/tests/content/CtsPackageManagerTest.xml
index 62e7333..cad5b92 100644
--- a/tests/tests/content/CtsPackageManagerTest.xml
+++ b/tests/tests/content/CtsPackageManagerTest.xml
@@ -183,6 +183,18 @@
<option name="push-file" key="HelloVerifierDisabled.apk.idsig" value="/data/local/tmp/cts/content/HelloVerifierDisabled.apk.idsig" />
<option name="push-file" key="HelloWorldSystemUserOnly.apk" value="/data/local/tmp/cts/content/HelloWorldSystemUserOnly.apk" />
<option name="push-file" key="HelloWorldSystemUserOnly.apk.idsig" value="/data/local/tmp/cts/content/HelloWorldSystemUserOnly.apk.idsig" />
+ <option name="push-file" key="CtsMultiArchApp32_arm.apk" value="/data/local/tmp/cts/content/CtsMultiArchApp32_arm.apk" />
+ <option name="push-file" key="CtsMultiArchApp32_x86.apk" value="/data/local/tmp/cts/content/CtsMultiArchApp32_x86.apk" />
+ <option name="push-file" key="CtsMultiArchApp64_arm.apk" value="/data/local/tmp/cts/content/CtsMultiArchApp64_arm.apk" />
+ <option name="push-file" key="CtsMultiArchApp64_x86.apk" value="/data/local/tmp/cts/content/CtsMultiArchApp64_x86.apk" />
+ <option name="push-file" key="CtsMultiArchAppBoth_arm.apk" value="/data/local/tmp/cts/content/CtsMultiArchAppBoth_arm.apk" />
+ <option name="push-file" key="CtsMultiArchAppBoth_x86.apk" value="/data/local/tmp/cts/content/CtsMultiArchAppBoth_x86.apk" />
+ <option name="push-file" key="CtsMultiArchApp32_targetSdk33_arm.apk" value="/data/local/tmp/cts/content/CtsMultiArchApp32_targetSdk33_arm.apk" />
+ <option name="push-file" key="CtsMultiArchApp32_targetSdk33_x86.apk" value="/data/local/tmp/cts/content/CtsMultiArchApp32_targetSdk33_x86.apk" />
+ <option name="push-file" key="CtsMultiArchApp64_targetSdk33_arm.apk" value="/data/local/tmp/cts/content/CtsMultiArchApp64_targetSdk33_arm.apk" />
+ <option name="push-file" key="CtsMultiArchApp64_targetSdk33_x86.apk" value="/data/local/tmp/cts/content/CtsMultiArchApp64_targetSdk33_x86.apk" />
+ <option name="push-file" key="CtsMultiArchAppBoth_targetSdk33_arm.apk" value="/data/local/tmp/cts/content/CtsMultiArchAppBoth_targetSdk33_arm.apk" />
+ <option name="push-file" key="CtsMultiArchAppBoth_targetSdk33_x86.apk" value="/data/local/tmp/cts/content/CtsMultiArchAppBoth_targetSdk33_x86.apk" />
</target_preparer>
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
diff --git a/tests/tests/content/MultiArchApp/Android.bp b/tests/tests/content/MultiArchApp/Android.bp
new file mode 100644
index 0000000..d708550
--- /dev/null
+++ b/tests/tests/content/MultiArchApp/Android.bp
@@ -0,0 +1,221 @@
+// 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_team: "trendy_team_framework_android_packages",
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+cc_test_library {
+ name: "libtest_multi_arch_native_libs",
+ gtest: false,
+ srcs: ["jni/*.cpp"],
+ cflags: [
+ "-Wall",
+ "-Werror",
+ "-Wno-unused-parameter",
+ ],
+ header_libs: ["jni_headers"],
+ shared_libs: ["liblog"],
+ sdk_version: "current",
+ target: {
+ android_arm: {
+ cflags: [
+ "-D__ANDROID_ARCH__=\"armeabi-v7a\"",
+ ],
+ },
+ android_arm64: {
+ cflags: [
+ "-D__ANDROID_ARCH__=\"arm64-v8a\"",
+ ],
+ },
+ android_x86: {
+ cflags: [
+ "-D__ANDROID_ARCH__=\"x86\"",
+ ],
+ },
+ android_x86_64: {
+ cflags: [
+ "-D__ANDROID_ARCH__=\"x86_64\"",
+ ],
+ },
+ android_riscv64: {
+ cflags: [
+ "-D__ANDROID_ARCH__=\"riscv64\"",
+ ],
+ },
+ },
+}
+
+android_test_helper_app {
+ name: "CtsMultiArchApp32_arm",
+ defaults: ["cts_support_defaults"],
+ sdk_version: "current",
+ srcs: ["src/**/*.java"],
+ test_suites: [
+ "cts",
+ "general-tests",
+ ],
+ use_embedded_native_libs: false,
+ java_resource_dirs: ["raw/arm"],
+}
+
+android_test_helper_app {
+ name: "CtsMultiArchApp64_arm",
+ defaults: ["cts_support_defaults"],
+ sdk_version: "current",
+ srcs: ["src/**/*.java"],
+ test_suites: [
+ "cts",
+ "general-tests",
+ ],
+ use_embedded_native_libs: false,
+ java_resource_dirs: ["raw/arm64"],
+}
+
+android_test_helper_app {
+ name: "CtsMultiArchAppBoth_arm",
+ defaults: ["cts_support_defaults"],
+ sdk_version: "current",
+ srcs: ["src/**/*.java"],
+ test_suites: [
+ "cts",
+ "general-tests",
+ ],
+ use_embedded_native_libs: false,
+ java_resource_dirs: ["raw/arm_both"],
+}
+
+android_test_helper_app {
+ name: "CtsMultiArchApp32_targetSdk33_arm",
+ defaults: ["cts_support_defaults"],
+ sdk_version: "current",
+ srcs: ["src/**/*.java"],
+ manifest: "AndroidManifest33.xml",
+ test_suites: [
+ "cts",
+ "general-tests",
+ ],
+ use_embedded_native_libs: false,
+ java_resource_dirs: ["raw/arm"],
+}
+
+android_test_helper_app {
+ name: "CtsMultiArchApp64_targetSdk33_arm",
+ defaults: ["cts_support_defaults"],
+ sdk_version: "current",
+ srcs: ["src/**/*.java"],
+ manifest: "AndroidManifest33.xml",
+ test_suites: [
+ "cts",
+ "general-tests",
+ ],
+ use_embedded_native_libs: false,
+ java_resource_dirs: ["raw/arm64"],
+}
+
+android_test_helper_app {
+ name: "CtsMultiArchAppBoth_targetSdk33_arm",
+ defaults: ["cts_support_defaults"],
+ sdk_version: "current",
+ srcs: ["src/**/*.java"],
+ manifest: "AndroidManifest33.xml",
+ test_suites: [
+ "cts",
+ "general-tests",
+ ],
+ use_embedded_native_libs: false,
+ java_resource_dirs: ["raw/arm_both"],
+}
+
+android_test_helper_app {
+ name: "CtsMultiArchApp32_x86",
+ defaults: ["cts_support_defaults"],
+ sdk_version: "current",
+ srcs: ["src/**/*.java"],
+ test_suites: [
+ "cts",
+ "general-tests",
+ ],
+ use_embedded_native_libs: false,
+ java_resource_dirs: ["raw/x86"],
+}
+
+android_test_helper_app {
+ name: "CtsMultiArchApp64_x86",
+ defaults: ["cts_support_defaults"],
+ sdk_version: "current",
+ srcs: ["src/**/*.java"],
+ test_suites: [
+ "cts",
+ "general-tests",
+ ],
+ use_embedded_native_libs: false,
+ java_resource_dirs: ["raw/x86_64"],
+}
+
+android_test_helper_app {
+ name: "CtsMultiArchAppBoth_x86",
+ defaults: ["cts_support_defaults"],
+ sdk_version: "current",
+ srcs: ["src/**/*.java"],
+ test_suites: [
+ "cts",
+ "general-tests",
+ ],
+ use_embedded_native_libs: false,
+ java_resource_dirs: ["raw/x86_both"],
+}
+
+android_test_helper_app {
+ name: "CtsMultiArchApp32_targetSdk33_x86",
+ defaults: ["cts_support_defaults"],
+ sdk_version: "current",
+ srcs: ["src/**/*.java"],
+ manifest: "AndroidManifest33.xml",
+ test_suites: [
+ "cts",
+ "general-tests",
+ ],
+ use_embedded_native_libs: false,
+ java_resource_dirs: ["raw/x86"],
+}
+
+android_test_helper_app {
+ name: "CtsMultiArchApp64_targetSdk33_x86",
+ defaults: ["cts_support_defaults"],
+ sdk_version: "current",
+ srcs: ["src/**/*.java"],
+ manifest: "AndroidManifest33.xml",
+ test_suites: [
+ "cts",
+ "general-tests",
+ ],
+ use_embedded_native_libs: false,
+ java_resource_dirs: ["raw/x86_64"],
+}
+
+android_test_helper_app {
+ name: "CtsMultiArchAppBoth_targetSdk33_x86",
+ defaults: ["cts_support_defaults"],
+ sdk_version: "current",
+ srcs: ["src/**/*.java"],
+ manifest: "AndroidManifest33.xml",
+ test_suites: [
+ "cts",
+ "general-tests",
+ ],
+ use_embedded_native_libs: false,
+ java_resource_dirs: ["raw/x86_both"],
+}
diff --git a/tests/tests/content/MultiArchApp/AndroidManifest.xml b/tests/tests/content/MultiArchApp/AndroidManifest.xml
new file mode 100644
index 0000000..e842bfa
--- /dev/null
+++ b/tests/tests/content/MultiArchApp/AndroidManifest.xml
@@ -0,0 +1,36 @@
+<?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="com.android.cts.multiarch.app">
+ <application
+ android:multiArch="true"
+ android:extractNativeLibs="true">
+ <uses-library android:name="android.test.runner"/>
+ <activity android:name=".MainActivity"
+ android:exported="true">
+ <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="com.android.cts.multiarch.app"/>
+</manifest>
diff --git a/tests/tests/content/MultiArchApp/AndroidManifest33.xml b/tests/tests/content/MultiArchApp/AndroidManifest33.xml
new file mode 100644
index 0000000..5dd3aa4
--- /dev/null
+++ b/tests/tests/content/MultiArchApp/AndroidManifest33.xml
@@ -0,0 +1,38 @@
+<?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="com.android.cts.multiarch.app">
+ <uses-sdk android:minSdkVersion="33" android:targetSdkVersion="33" />
+
+ <application
+ android:multiArch="true"
+ android:extractNativeLibs="true">
+ <uses-library android:name="android.test.runner"/>
+ <activity android:name=".MainActivity"
+ android:exported="true">
+ <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="com.android.cts.multiarch.app"/>
+</manifest>
diff --git a/tests/tests/content/MultiArchApp/README.md b/tests/tests/content/MultiArchApp/README.md
new file mode 100644
index 0000000..a872522
--- /dev/null
+++ b/tests/tests/content/MultiArchApp/README.md
@@ -0,0 +1,8 @@
+How to build the prebuilt libs
+=================================
+1. m libtest_multi_arch_native_libs in the different arch (E.g. arm, x86)
+
+2. Find the libs in out/target/product/{$target}/data/
+E.g. out/target/product/redfin/data/nativetest/ and out/target/product/redfin/data/nativetest64/
+
+3. Put them into the specific folders by the arch (E.g. arm, arm64, x86, x86_64)
diff --git a/tests/tests/content/MultiArchApp/jni/native.cpp b/tests/tests/content/MultiArchApp/jni/native.cpp
new file mode 100644
index 0000000..7108a4d
--- /dev/null
+++ b/tests/tests/content/MultiArchApp/jni/native.cpp
@@ -0,0 +1,28 @@
+/*
+ * 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 <android/log.h>
+#include <jni.h>
+#define LOG(...) __android_log_write(ANDROID_LOG_INFO, "NativeLibOutput", __VA_ARGS__)
+
+jint JNI_OnLoad(JavaVM* vm, void* reserved) {
+ JNIEnv* env = nullptr;
+ if (vm->GetEnv((void**)&env, JNI_VERSION_1_6) != JNI_OK) {
+ return JNI_ERR;
+ }
+ LOG("libtest_multi_arch_native_libs is loaded");
+ return JNI_VERSION_1_6;
+}
diff --git a/tests/tests/content/MultiArchApp/raw/arm/lib/armeabi-v7a/libtest_multi_arch_native_libs.so b/tests/tests/content/MultiArchApp/raw/arm/lib/armeabi-v7a/libtest_multi_arch_native_libs.so
new file mode 100755
index 0000000..c0c95e0
--- /dev/null
+++ b/tests/tests/content/MultiArchApp/raw/arm/lib/armeabi-v7a/libtest_multi_arch_native_libs.so
Binary files differ
diff --git a/tests/tests/content/MultiArchApp/raw/arm64/lib/arm64-v8a/libtest_multi_arch_native_libs.so b/tests/tests/content/MultiArchApp/raw/arm64/lib/arm64-v8a/libtest_multi_arch_native_libs.so
new file mode 100755
index 0000000..b69db7f
--- /dev/null
+++ b/tests/tests/content/MultiArchApp/raw/arm64/lib/arm64-v8a/libtest_multi_arch_native_libs.so
Binary files differ
diff --git a/tests/tests/content/MultiArchApp/raw/arm_both/lib/arm64-v8a/libtest_multi_arch_native_libs.so b/tests/tests/content/MultiArchApp/raw/arm_both/lib/arm64-v8a/libtest_multi_arch_native_libs.so
new file mode 100755
index 0000000..b69db7f
--- /dev/null
+++ b/tests/tests/content/MultiArchApp/raw/arm_both/lib/arm64-v8a/libtest_multi_arch_native_libs.so
Binary files differ
diff --git a/tests/tests/content/MultiArchApp/raw/arm_both/lib/armeabi-v7a/libtest_multi_arch_native_libs.so b/tests/tests/content/MultiArchApp/raw/arm_both/lib/armeabi-v7a/libtest_multi_arch_native_libs.so
new file mode 100755
index 0000000..c0c95e0
--- /dev/null
+++ b/tests/tests/content/MultiArchApp/raw/arm_both/lib/armeabi-v7a/libtest_multi_arch_native_libs.so
Binary files differ
diff --git a/tests/tests/content/MultiArchApp/raw/x86/lib/x86/libtest_multi_arch_native_libs.so b/tests/tests/content/MultiArchApp/raw/x86/lib/x86/libtest_multi_arch_native_libs.so
new file mode 100755
index 0000000..d095622
--- /dev/null
+++ b/tests/tests/content/MultiArchApp/raw/x86/lib/x86/libtest_multi_arch_native_libs.so
Binary files differ
diff --git a/tests/tests/content/MultiArchApp/raw/x86_64/lib/x86_64/libtest_multi_arch_native_libs.so b/tests/tests/content/MultiArchApp/raw/x86_64/lib/x86_64/libtest_multi_arch_native_libs.so
new file mode 100755
index 0000000..94b8dd9
--- /dev/null
+++ b/tests/tests/content/MultiArchApp/raw/x86_64/lib/x86_64/libtest_multi_arch_native_libs.so
Binary files differ
diff --git a/tests/tests/content/MultiArchApp/raw/x86_both/lib/x86/libtest_multi_arch_native_libs.so b/tests/tests/content/MultiArchApp/raw/x86_both/lib/x86/libtest_multi_arch_native_libs.so
new file mode 100755
index 0000000..d095622
--- /dev/null
+++ b/tests/tests/content/MultiArchApp/raw/x86_both/lib/x86/libtest_multi_arch_native_libs.so
Binary files differ
diff --git a/tests/tests/content/MultiArchApp/raw/x86_both/lib/x86_64/libtest_multi_arch_native_libs.so b/tests/tests/content/MultiArchApp/raw/x86_both/lib/x86_64/libtest_multi_arch_native_libs.so
new file mode 100755
index 0000000..94b8dd9
--- /dev/null
+++ b/tests/tests/content/MultiArchApp/raw/x86_both/lib/x86_64/libtest_multi_arch_native_libs.so
Binary files differ
diff --git a/tests/tests/content/MultiArchApp/src/com/android/cts/multiarch/app/MainActivity.java b/tests/tests/content/MultiArchApp/src/com/android/cts/multiarch/app/MainActivity.java
new file mode 100644
index 0000000..64b2fd53
--- /dev/null
+++ b/tests/tests/content/MultiArchApp/src/com/android/cts/multiarch/app/MainActivity.java
@@ -0,0 +1,25 @@
+/*
+ * 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.cts.multiarch.app;
+
+import android.app.Activity;
+
+/**
+ * Empty activity
+ */
+public class MainActivity extends Activity {
+}
diff --git a/tests/tests/content/src/android/content/pm/cts/PackageManagerMultiArchAppTest.java b/tests/tests/content/src/android/content/pm/cts/PackageManagerMultiArchAppTest.java
new file mode 100644
index 0000000..d301c5d
--- /dev/null
+++ b/tests/tests/content/src/android/content/pm/cts/PackageManagerMultiArchAppTest.java
@@ -0,0 +1,424 @@
+/*
+ * 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.content.pm.cts;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assume.assumeTrue;
+
+import android.content.Context;
+import android.content.pm.Flags;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.os.Build;
+import android.os.SystemProperties;
+import android.platform.test.annotations.RequiresFlagsDisabled;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
+import android.text.TextUtils;
+import android.util.ArraySet;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.compatibility.common.util.SystemUtil;
+
+import dalvik.system.VMRuntime;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+
+@RunWith(AndroidJUnit4.class)
+public class PackageManagerMultiArchAppTest {
+
+ private static final String TEST_APP_PATH = "/data/local/tmp/cts/content/";
+ private static final String TEST_APP_APK_BASE = "CtsMultiArchApp";
+ private static final String BITNESS_32 = "32";
+ private static final String BITNESS_64 = "64";
+ private static final String BITNESS_BOTH = "Both";
+ private static final String BASE_ARCH_ARM = "arm";
+ private static final String BASE_ARCH_X86 = "x86";
+ private static final String BASE_ARCH_MIPS = "mips";
+
+ // List of supported abi
+ private static final String ABI_ARM_32 = "armeabi";
+ private static final String ABI_ARM_V7A = "armeabi-v7a";
+ private static final String ABI_ARM_64_V8A = "arm64-v8a";
+ private static final String ABI_X86 = "x86";
+ private static final String ABI_X86_64 = "x86_64";
+ private static final String ABI_MIPS = "mips";
+ private static final String ABI_MIPS64 = "mips64";
+ private static final String ABI_RISCV64 = "riscv64";
+
+ private static final String TEST_APP_PKG = "com.android.cts.multiarch.app";
+ private static final String EXPECTED_FAILED_ERROR_MESSAGE =
+ "don't support all the natively supported ABIs of the device";
+
+ private static final Set<String> BITS_32_SET = new HashSet<>(Arrays.asList(
+ "armeabi", "armeabi-v7a", "x86"));
+ private static final Map<String, String> ABI_TO_BASE_ARCH = new LinkedHashMap<String, String>();
+
+ private static String[] sDeviceSupported32Bits = null;
+ private static String[] sDeviceSupported64Bits = null;
+ private static String[] sSupportedEmulatedAbis = null;
+ private static String sDeviceDefaultAbi = null;
+ private static String sDeviceDefaultBitness = null;
+ private static String sDeviceDefaultBaseArch = null;
+ private static String sTestBaseArch = null;
+
+ static {
+ ABI_TO_BASE_ARCH.put(ABI_ARM_32, BASE_ARCH_ARM);
+ ABI_TO_BASE_ARCH.put(ABI_ARM_V7A, BASE_ARCH_ARM);
+ ABI_TO_BASE_ARCH.put(ABI_ARM_64_V8A, BASE_ARCH_ARM);
+ ABI_TO_BASE_ARCH.put(ABI_X86, BASE_ARCH_X86);
+ ABI_TO_BASE_ARCH.put(ABI_X86_64, BASE_ARCH_X86);
+ ABI_TO_BASE_ARCH.put(ABI_MIPS, BASE_ARCH_MIPS);
+ ABI_TO_BASE_ARCH.put(ABI_MIPS64, BASE_ARCH_MIPS);
+ ABI_TO_BASE_ARCH.put(ABI_RISCV64, ABI_RISCV64);
+ }
+
+ private PackageManager mPackageManager;
+
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+
+ private static String[] getSupportedEmulatedAbis() throws Exception {
+ if (sSupportedEmulatedAbis != null) {
+ return sSupportedEmulatedAbis;
+ }
+
+ Set<String> abiSet = new ArraySet<>();
+ getSupportedEmulatedAbis(getDeviceSupported64Abis(), abiSet);
+ getSupportedEmulatedAbis(getDeviceSupported32Abis(), abiSet);
+ sSupportedEmulatedAbis = abiSet.toArray(new String[0]);
+ return sSupportedEmulatedAbis;
+ }
+
+ private static void getSupportedEmulatedAbis(String[] supportedAbis, Set<String> abiSet)
+ throws Exception {
+ for (int i = 0; i < supportedAbis.length; i++) {
+ final String currentAbi = supportedAbis[i];
+ // In presence of a native bridge this means the Abi is emulated.
+ final String currentIsa = VMRuntime.getInstructionSet(currentAbi);
+ if (!TextUtils.isEmpty(SystemProperties.get("ro.dalvik.vm.isa." + currentIsa))) {
+ abiSet.add(currentAbi);
+ }
+ }
+ }
+
+ /** Returns the base architecture matching the abi. Null if the abi is not supported. */
+ @Nullable
+ private static String getBaseArchForAbi(@NonNull String abi) {
+ Objects.requireNonNull(abi);
+ if (abi.isEmpty()) {
+ throw new IllegalArgumentException("Abi cannot be empty");
+ }
+ return ABI_TO_BASE_ARCH.get(abi);
+ }
+
+ @NonNull
+ private static String getTestAppPath(@NonNull String abi, @NonNull String baseArch) {
+ Objects.requireNonNull(abi);
+
+ String bitness = BITNESS_64;
+ if (getBitness(abi).equals(BITNESS_32)) {
+ bitness = BITNESS_32;
+ }
+
+ return getTestApkPath(bitness, /* isTargetSDK33= */ false, baseArch);
+ }
+
+ @NonNull
+ private static String getTestApkPath(@NonNull String abiBit) {
+ return getTestApkPath(abiBit, /* isTargetSDK33= */ false, sTestBaseArch);
+ }
+
+ @NonNull
+ private static String getTestApkPath(@NonNull String abiBit, boolean isTargetSDK33) {
+ return getTestApkPath(abiBit, isTargetSDK33, sTestBaseArch);
+ }
+
+ @NonNull
+ private static String getTestApkPath(@NonNull String abiBit, boolean isTargetSDK33,
+ @NonNull String baseArch) {
+ Objects.requireNonNull(abiBit);
+ Objects.requireNonNull(baseArch);
+ return TEST_APP_PATH + TEST_APP_APK_BASE + abiBit + (isTargetSDK33 ? "_targetSdk33_" : "_")
+ + baseArch + ".apk";
+ }
+
+ @NonNull
+ private static String getBitness(String abi) {
+ return BITS_32_SET.contains(abi) ? BITNESS_32 : BITNESS_64;
+ }
+
+ @NonNull
+ private static String[] getDeviceSupported32Abis() throws Exception {
+ if (sDeviceSupported32Bits != null) {
+ return sDeviceSupported32Bits;
+ }
+
+ sDeviceSupported32Bits = Build.SUPPORTED_32_BIT_ABIS;
+ return sDeviceSupported32Bits;
+ }
+
+ @NonNull
+ private static String[] getDeviceSupported64Abis() throws Exception {
+ if (sDeviceSupported64Bits != null) {
+ return sDeviceSupported64Bits;
+ }
+
+ sDeviceSupported64Bits = Build.SUPPORTED_64_BIT_ABIS;
+ return sDeviceSupported64Bits;
+ }
+
+ @NonNull
+ private static String getDeviceDefaultAbi() throws Exception {
+ if (sDeviceDefaultAbi != null) {
+ return sDeviceDefaultAbi;
+ }
+ sDeviceDefaultAbi = SystemProperties.get("ro.product.cpu.abi");
+ return sDeviceDefaultAbi;
+ }
+
+ @NonNull
+ private static String getDeviceDefaultBitness() throws Exception {
+ if (sDeviceDefaultBitness != null) {
+ return sDeviceDefaultBitness;
+ }
+ sDeviceDefaultBitness = getBitness(getDeviceDefaultAbi());
+ return sDeviceDefaultBitness;
+ }
+
+ @NonNull
+ private static String getDeviceDefaultBaseArch() throws Exception {
+ if (sDeviceDefaultBaseArch != null) {
+ return sDeviceDefaultBaseArch;
+ }
+ sDeviceDefaultBaseArch = getBaseArchForAbi(getDeviceDefaultAbi());
+ assumeTrue("The default abi on the device is not supported.",
+ sDeviceDefaultBaseArch != null);
+ return sDeviceDefaultBaseArch;
+ }
+
+ private static boolean isBaseArchSupportedInAbis(@NonNull String baseArch,
+ @NonNull String[] abis) {
+ Objects.requireNonNull(baseArch);
+ Objects.requireNonNull(abis);
+
+ for (int i = 0; i < abis.length; i++) {
+ if (baseArch.equals(getBaseArchForAbi(abis[i]))) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private static boolean isDeviceSupportsEmulatedAbi() throws Exception {
+ return getSupportedEmulatedAbis().length > 0;
+ }
+
+ /*
+ * Return true if the device supports both 32 bit and 64 bit ABIs. Otherwise, false.
+ */
+ private static boolean isDeviceSupportedBothBitness() throws Exception {
+ if (getDeviceDefaultBitness().equals(BITNESS_32)) {
+ return isBaseArchSupportedInAbis(getDeviceDefaultBaseArch(),
+ getDeviceSupported64Abis());
+ } else {
+ return isBaseArchSupportedInAbis(getDeviceDefaultBaseArch(),
+ getDeviceSupported32Abis());
+ }
+ }
+
+ private static boolean isSupportedBaseArch(@Nullable String baseArch) {
+ return BASE_ARCH_ARM.equals(baseArch) || BASE_ARCH_X86.equals(baseArch);
+ }
+
+ private boolean isInstalled() {
+ try {
+ PackageInfo pi = mPackageManager.getPackageInfo(TEST_APP_PKG, 0);
+ return pi != null;
+ } catch (PackageManager.NameNotFoundException e) {
+ return false;
+ }
+ }
+
+ @NonNull
+ private String installPackage(@NonNull String apkPath) {
+ Objects.requireNonNull(apkPath);
+ return SystemUtil.runShellCommand("pm install " + apkPath);
+ }
+
+ @NonNull
+ private void uninstallPackage(@NonNull String packageName) {
+ Objects.requireNonNull(packageName);
+ SystemUtil.runShellCommand("pm uninstall " + packageName);
+ }
+
+ /** Uninstall app before tests. */
+ @Before
+ public void setUp() throws Exception {
+ final Context context = InstrumentationRegistry.getInstrumentation().getContext();
+ mPackageManager = context.getPackageManager();
+ sTestBaseArch = getDeviceDefaultBaseArch();
+ uninstallPackage(TEST_APP_PKG);
+ }
+
+ /** Uninstall app after tests. */
+ @After
+ public void cleanUp() throws Exception {
+ uninstallPackage(TEST_APP_PKG);
+ }
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_FORCE_MULTI_ARCH_NATIVE_LIBS_MATCH)
+ public void testInstallMultiArchApp_wrongBaseArch_fail() throws Exception {
+ String baseArch = null;
+ if (getDeviceDefaultBaseArch().equals(BASE_ARCH_ARM)) {
+ baseArch = BASE_ARCH_X86;
+ } else {
+ baseArch = BASE_ARCH_ARM;
+ }
+
+ String result = installPackage(getTestApkPath(getDeviceDefaultBitness(),
+ /* isTargetSDK33= */ false, baseArch));
+
+ assertThat(result).contains(EXPECTED_FAILED_ERROR_MESSAGE);
+ assertThat(isInstalled()).isFalse();
+ }
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_FORCE_MULTI_ARCH_NATIVE_LIBS_MATCH)
+ public void testInstallMultiArchApp32_notMatchAllNativelyAbis_fail() throws Exception {
+ assumeTrue(isDeviceSupportedBothBitness());
+
+ String result = installPackage(getTestApkPath(BITNESS_32));
+
+ assertThat(result).contains(EXPECTED_FAILED_ERROR_MESSAGE);
+ assertThat(isInstalled()).isFalse();
+ }
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_FORCE_MULTI_ARCH_NATIVE_LIBS_MATCH)
+ public void testInstallMultiArchApp64_notMatchAllNativelyAbis_fail() throws Exception {
+ assumeTrue(isDeviceSupportedBothBitness());
+
+ String result = installPackage(getTestApkPath(BITNESS_64));
+
+ assertThat(result).contains(EXPECTED_FAILED_ERROR_MESSAGE);
+ assertThat(isInstalled()).isFalse();
+ }
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_FORCE_MULTI_ARCH_NATIVE_LIBS_MATCH)
+ public void testInstallMultiArchApp32_targetSdk33_success() throws Exception {
+ assumeTrue(isDeviceSupportedBothBitness());
+
+ installPackage(getTestApkPath(BITNESS_32, /* isTargetSDK33= */ true));
+
+ assertThat(isInstalled()).isTrue();
+ }
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_FORCE_MULTI_ARCH_NATIVE_LIBS_MATCH)
+ public void testInstallMultiArchApp64_targetSdk33_success() throws Exception {
+ assumeTrue(isDeviceSupportedBothBitness());
+
+ installPackage(getTestApkPath(BITNESS_64, /* isTargetSDK33= */ true));
+
+ assertThat(isInstalled()).isTrue();
+ }
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_FORCE_MULTI_ARCH_NATIVE_LIBS_MATCH)
+ public void testInstallMultiArchApp_emulatedAbiNoNativelyAbi_fail() throws Exception {
+ assumeTrue(isDeviceSupportsEmulatedAbi());
+ final String firstEmulatedAbi = getSupportedEmulatedAbis()[0];
+ final String baseArch = getBaseArchForAbi(firstEmulatedAbi);
+ assumeTrue(isSupportedBaseArch(baseArch));
+
+ String result = installPackage(getTestAppPath(firstEmulatedAbi, baseArch));
+
+ assertThat(result).contains(EXPECTED_FAILED_ERROR_MESSAGE);
+ assertThat(isInstalled()).isFalse();
+ }
+
+ @Test
+ @RequiresFlagsDisabled(Flags.FLAG_FORCE_MULTI_ARCH_NATIVE_LIBS_MATCH)
+ public void testInstallMultiArchApp_emulatedAbiNoNativelyAbi_success() throws Exception {
+ assumeTrue(isDeviceSupportsEmulatedAbi());
+ final String firstEmulatedAbi = getSupportedEmulatedAbis()[0];
+ final String baseArch = getBaseArchForAbi(firstEmulatedAbi);
+ assumeTrue(isSupportedBaseArch(baseArch));
+
+ installPackage(getTestAppPath(firstEmulatedAbi, baseArch));
+
+ assertThat(isInstalled()).isTrue();
+ }
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_FORCE_MULTI_ARCH_NATIVE_LIBS_MATCH)
+ public void testInstallMultiArchAppBoth_targetSdk33_success() throws Exception {
+ assumeTrue(isDeviceSupportedBothBitness());
+
+ installPackage(getTestApkPath(BITNESS_BOTH, /* isTargetSDK33= */ true));
+
+ assertThat(isInstalled()).isTrue();
+ }
+
+ @Test
+ @RequiresFlagsDisabled(Flags.FLAG_FORCE_MULTI_ARCH_NATIVE_LIBS_MATCH)
+ public void testInstallMultiArchApp32_success() throws Exception {
+ assumeTrue(isDeviceSupportedBothBitness());
+
+ installPackage(getTestApkPath(BITNESS_32));
+
+ assertThat(isInstalled()).isTrue();
+ }
+
+ @Test
+ @RequiresFlagsDisabled(Flags.FLAG_FORCE_MULTI_ARCH_NATIVE_LIBS_MATCH)
+ public void testInstallMultiArchApp64_success() throws Exception {
+ assumeTrue(isDeviceSupportedBothBitness());
+
+ installPackage(getTestApkPath(BITNESS_64));
+
+ assertThat(isInstalled()).isTrue();
+ }
+
+ @Test
+ public void testInstallMultiArchAppBoth_success() throws Exception {
+ assumeTrue(isDeviceSupportedBothBitness());
+
+ installPackage(getTestApkPath(BITNESS_BOTH));
+
+ assertThat(isInstalled()).isTrue();
+ }
+}