1 /*
2  * Copyright (C) 2024 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package android.content.pm.cts;
17 
18 import static com.google.common.truth.Truth.assertThat;
19 
20 import static org.junit.Assume.assumeTrue;
21 
22 import android.content.Context;
23 import android.content.pm.Flags;
24 import android.content.pm.PackageInfo;
25 import android.content.pm.PackageManager;
26 import android.os.Build;
27 import android.os.SystemProperties;
28 import android.platform.test.annotations.AppModeFull;
29 import android.platform.test.annotations.RequiresFlagsDisabled;
30 import android.platform.test.annotations.RequiresFlagsEnabled;
31 import android.platform.test.flag.junit.CheckFlagsRule;
32 import android.platform.test.flag.junit.DeviceFlagsValueProvider;
33 import android.text.TextUtils;
34 import android.util.ArraySet;
35 
36 import androidx.annotation.NonNull;
37 import androidx.annotation.Nullable;
38 import androidx.test.ext.junit.runners.AndroidJUnit4;
39 import androidx.test.platform.app.InstrumentationRegistry;
40 
41 import com.android.compatibility.common.util.SystemUtil;
42 
43 import dalvik.system.VMRuntime;
44 
45 import org.junit.After;
46 import org.junit.Before;
47 import org.junit.Rule;
48 import org.junit.Test;
49 import org.junit.runner.RunWith;
50 
51 import java.util.Arrays;
52 import java.util.HashSet;
53 import java.util.LinkedHashMap;
54 import java.util.Map;
55 import java.util.Objects;
56 import java.util.Set;
57 
58 @AppModeFull(reason = "Instant applications cannot see any other application")
59 @RunWith(AndroidJUnit4.class)
60 public class PackageManagerMultiArchAppTest {
61 
62     private static final String TEST_APP_PATH = "/data/local/tmp/cts/content/";
63     private static final String TEST_APP_APK_BASE = "CtsMultiArchApp";
64     private static final String BITNESS_32 = "32";
65     private static final String BITNESS_64 = "64";
66     private static final String BITNESS_BOTH = "Both";
67     private static final String BASE_ARCH_ARM = "arm";
68     private static final String BASE_ARCH_X86 = "x86";
69     private static final String BASE_ARCH_MIPS = "mips";
70 
71     // List of supported abi
72     private static final String ABI_ARM_32 = "armeabi";
73     private static final String ABI_ARM_V7A = "armeabi-v7a";
74     private static final String ABI_ARM_64_V8A = "arm64-v8a";
75     private static final String ABI_X86 = "x86";
76     private static final String ABI_X86_64 = "x86_64";
77     private static final String ABI_MIPS = "mips";
78     private static final String ABI_MIPS64 = "mips64";
79     private static final String ABI_RISCV64 = "riscv64";
80 
81     private static final String TEST_APP_PKG = "com.android.cts.multiarch.app";
82     private static final String EXPECTED_FAILED_ERROR_MESSAGE =
83             "don't support all the natively supported ABIs of the device";
84 
85     private static final Set<String> BITS_32_SET = new HashSet<>(Arrays.asList(
86             "armeabi", "armeabi-v7a", "x86"));
87     private static final Map<String, String> ABI_TO_BASE_ARCH = new LinkedHashMap<String, String>();
88 
89     private static String[] sDeviceSupported32Bits = null;
90     private static String[] sDeviceSupported64Bits = null;
91     private static String[] sSupportedEmulatedAbis = null;
92     private static String sDeviceDefaultAbi = null;
93     private static String sDeviceDefaultBitness = null;
94     private static String sDeviceDefaultBaseArch = null;
95     private static String sTestBaseArch = null;
96 
97     static {
ABI_TO_BASE_ARCH.put(ABI_ARM_32, BASE_ARCH_ARM)98         ABI_TO_BASE_ARCH.put(ABI_ARM_32, BASE_ARCH_ARM);
ABI_TO_BASE_ARCH.put(ABI_ARM_V7A, BASE_ARCH_ARM)99         ABI_TO_BASE_ARCH.put(ABI_ARM_V7A, BASE_ARCH_ARM);
ABI_TO_BASE_ARCH.put(ABI_ARM_64_V8A, BASE_ARCH_ARM)100         ABI_TO_BASE_ARCH.put(ABI_ARM_64_V8A, BASE_ARCH_ARM);
ABI_TO_BASE_ARCH.put(ABI_X86, BASE_ARCH_X86)101         ABI_TO_BASE_ARCH.put(ABI_X86, BASE_ARCH_X86);
ABI_TO_BASE_ARCH.put(ABI_X86_64, BASE_ARCH_X86)102         ABI_TO_BASE_ARCH.put(ABI_X86_64, BASE_ARCH_X86);
ABI_TO_BASE_ARCH.put(ABI_MIPS, BASE_ARCH_MIPS)103         ABI_TO_BASE_ARCH.put(ABI_MIPS, BASE_ARCH_MIPS);
ABI_TO_BASE_ARCH.put(ABI_MIPS64, BASE_ARCH_MIPS)104         ABI_TO_BASE_ARCH.put(ABI_MIPS64, BASE_ARCH_MIPS);
ABI_TO_BASE_ARCH.put(ABI_RISCV64, ABI_RISCV64)105         ABI_TO_BASE_ARCH.put(ABI_RISCV64, ABI_RISCV64);
106     }
107 
108     private PackageManager mPackageManager;
109 
110     @Rule
111     public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
112 
getSupportedEmulatedAbis()113     private static String[] getSupportedEmulatedAbis() throws Exception {
114         if (sSupportedEmulatedAbis != null) {
115             return sSupportedEmulatedAbis;
116         }
117 
118         Set<String> abiSet = new ArraySet<>();
119         getSupportedEmulatedAbis(getDeviceSupported64Abis(), abiSet);
120         getSupportedEmulatedAbis(getDeviceSupported32Abis(), abiSet);
121         sSupportedEmulatedAbis = abiSet.toArray(new String[0]);
122         return sSupportedEmulatedAbis;
123     }
124 
getSupportedEmulatedAbis(String[] supportedAbis, Set<String> abiSet)125     private static void getSupportedEmulatedAbis(String[] supportedAbis, Set<String> abiSet)
126             throws Exception {
127         for (int i = 0; i < supportedAbis.length; i++) {
128             final String currentAbi = supportedAbis[i];
129             // In presence of a native bridge this means the Abi is emulated.
130             final String currentIsa = VMRuntime.getInstructionSet(currentAbi);
131             if (!TextUtils.isEmpty(SystemProperties.get("ro.dalvik.vm.isa." + currentIsa))) {
132                 abiSet.add(currentAbi);
133             }
134         }
135     }
136 
137     /** Returns the base architecture matching the abi. Null if the abi is not supported. */
138     @Nullable
getBaseArchForAbi(@onNull String abi)139     private static String getBaseArchForAbi(@NonNull String abi) {
140         Objects.requireNonNull(abi);
141         if (abi.isEmpty()) {
142             throw new IllegalArgumentException("Abi cannot be empty");
143         }
144         return ABI_TO_BASE_ARCH.get(abi);
145     }
146 
147     @NonNull
getTestAppPath(@onNull String abi, @NonNull String baseArch)148     private static String getTestAppPath(@NonNull String abi, @NonNull String baseArch) {
149         Objects.requireNonNull(abi);
150 
151         String bitness = BITNESS_64;
152         if (getBitness(abi).equals(BITNESS_32)) {
153             bitness = BITNESS_32;
154         }
155 
156         return getTestApkPath(bitness, /* isTargetSDK33= */ false, baseArch);
157     }
158 
159     @NonNull
getTestApkPath(@onNull String abiBit)160     private static String getTestApkPath(@NonNull String abiBit) {
161         return getTestApkPath(abiBit, /* isTargetSDK33= */ false, sTestBaseArch);
162     }
163 
164     @NonNull
getTestApkPath(@onNull String abiBit, boolean isTargetSDK33)165     private static String getTestApkPath(@NonNull String abiBit, boolean isTargetSDK33) {
166         return getTestApkPath(abiBit, isTargetSDK33, sTestBaseArch);
167     }
168 
169     @NonNull
getTestApkPath(@onNull String abiBit, boolean isTargetSDK33, @NonNull String baseArch)170     private static String getTestApkPath(@NonNull String abiBit, boolean isTargetSDK33,
171             @NonNull String baseArch) {
172         Objects.requireNonNull(abiBit);
173         Objects.requireNonNull(baseArch);
174         return TEST_APP_PATH + TEST_APP_APK_BASE + abiBit + (isTargetSDK33 ? "_targetSdk33_" : "_")
175                 + baseArch + ".apk";
176     }
177 
178     @NonNull
getBitness(String abi)179     private static String getBitness(String abi) {
180         return BITS_32_SET.contains(abi) ? BITNESS_32 : BITNESS_64;
181     }
182 
183     @NonNull
getDeviceSupported32Abis()184     private static String[] getDeviceSupported32Abis() throws Exception {
185         if (sDeviceSupported32Bits != null) {
186             return sDeviceSupported32Bits;
187         }
188 
189         sDeviceSupported32Bits = Build.SUPPORTED_32_BIT_ABIS;
190         return sDeviceSupported32Bits;
191     }
192 
193     @NonNull
getDeviceSupported64Abis()194     private static String[] getDeviceSupported64Abis() throws Exception {
195         if (sDeviceSupported64Bits != null) {
196             return sDeviceSupported64Bits;
197         }
198 
199         sDeviceSupported64Bits = Build.SUPPORTED_64_BIT_ABIS;
200         return sDeviceSupported64Bits;
201     }
202 
203     @NonNull
getDeviceDefaultAbi()204     private static String getDeviceDefaultAbi() throws Exception {
205         if (sDeviceDefaultAbi != null) {
206             return sDeviceDefaultAbi;
207         }
208         sDeviceDefaultAbi = SystemProperties.get("ro.product.cpu.abi");
209         return sDeviceDefaultAbi;
210     }
211 
212     @NonNull
getDeviceDefaultBitness()213     private static String getDeviceDefaultBitness() throws Exception {
214         if (sDeviceDefaultBitness != null) {
215             return sDeviceDefaultBitness;
216         }
217         sDeviceDefaultBitness = getBitness(getDeviceDefaultAbi());
218         return sDeviceDefaultBitness;
219     }
220 
221     @NonNull
getDeviceDefaultBaseArch()222     private static String getDeviceDefaultBaseArch() throws Exception {
223         if (sDeviceDefaultBaseArch != null) {
224             return sDeviceDefaultBaseArch;
225         }
226         sDeviceDefaultBaseArch = getBaseArchForAbi(getDeviceDefaultAbi());
227         assumeTrue("The default abi on the device is not supported.",
228                 sDeviceDefaultBaseArch != null);
229         return sDeviceDefaultBaseArch;
230     }
231 
isBaseArchSupportedInAbis(@onNull String baseArch, @NonNull String[] abis)232     private static boolean isBaseArchSupportedInAbis(@NonNull String baseArch,
233             @NonNull String[] abis) {
234         Objects.requireNonNull(baseArch);
235         Objects.requireNonNull(abis);
236 
237         for (int i = 0; i < abis.length; i++) {
238             if (baseArch.equals(getBaseArchForAbi(abis[i]))) {
239                 return true;
240             }
241         }
242         return false;
243     }
244 
isDeviceSupportsEmulatedAbi()245     private static boolean isDeviceSupportsEmulatedAbi() throws Exception {
246         return getSupportedEmulatedAbis().length > 0;
247     }
248 
249     /*
250      * Return true if the device supports both 32 bit and 64 bit ABIs. Otherwise, false.
251      */
isDeviceSupportedBothBitness()252     private static boolean isDeviceSupportedBothBitness() throws Exception {
253         if (getDeviceDefaultBitness().equals(BITNESS_32)) {
254             return isBaseArchSupportedInAbis(getDeviceDefaultBaseArch(),
255                     getDeviceSupported64Abis());
256         } else {
257             return isBaseArchSupportedInAbis(getDeviceDefaultBaseArch(),
258                     getDeviceSupported32Abis());
259         }
260     }
261 
isSupportedBaseArch(@ullable String baseArch)262     private static boolean isSupportedBaseArch(@Nullable String baseArch) {
263         return BASE_ARCH_ARM.equals(baseArch) || BASE_ARCH_X86.equals(baseArch);
264     }
265 
isInstalled()266     private boolean isInstalled() {
267         try {
268             PackageInfo pi = mPackageManager.getPackageInfo(TEST_APP_PKG, 0);
269             return pi != null;
270         } catch (PackageManager.NameNotFoundException e) {
271             return false;
272         }
273     }
274 
275     @NonNull
installPackage(@onNull String apkPath)276     private String installPackage(@NonNull String apkPath) {
277         Objects.requireNonNull(apkPath);
278         return SystemUtil.runShellCommand("pm install " + apkPath);
279     }
280 
281     @NonNull
uninstallPackage(@onNull String packageName)282     private void uninstallPackage(@NonNull String packageName) {
283         Objects.requireNonNull(packageName);
284         SystemUtil.runShellCommand("pm uninstall " + packageName);
285     }
286 
287     /** Uninstall app before tests. */
288     @Before
setUp()289     public void setUp() throws Exception {
290         final Context context = InstrumentationRegistry.getInstrumentation().getContext();
291         mPackageManager = context.getPackageManager();
292         sTestBaseArch = getDeviceDefaultBaseArch();
293         uninstallPackage(TEST_APP_PKG);
294     }
295 
296     /** Uninstall app after tests. */
297     @After
cleanUp()298     public void cleanUp() throws Exception {
299         uninstallPackage(TEST_APP_PKG);
300     }
301 
302     @Test
303     @RequiresFlagsEnabled(Flags.FLAG_FORCE_MULTI_ARCH_NATIVE_LIBS_MATCH)
testInstallMultiArchApp32_notMatchAllNativelyAbis_fail()304     public void testInstallMultiArchApp32_notMatchAllNativelyAbis_fail() throws Exception {
305         assumeTrue(isDeviceSupportedBothBitness());
306 
307         String result = installPackage(getTestApkPath(BITNESS_32));
308 
309         assertThat(result).contains(EXPECTED_FAILED_ERROR_MESSAGE);
310         assertThat(isInstalled()).isFalse();
311     }
312 
313     @Test
314     @RequiresFlagsEnabled(Flags.FLAG_FORCE_MULTI_ARCH_NATIVE_LIBS_MATCH)
testInstallMultiArchApp64_notMatchAllNativelyAbis_fail()315     public void testInstallMultiArchApp64_notMatchAllNativelyAbis_fail() throws Exception {
316         assumeTrue(isDeviceSupportedBothBitness());
317 
318         String result = installPackage(getTestApkPath(BITNESS_64));
319 
320         assertThat(result).contains(EXPECTED_FAILED_ERROR_MESSAGE);
321         assertThat(isInstalled()).isFalse();
322     }
323 
324     @Test
325     @RequiresFlagsEnabled(Flags.FLAG_FORCE_MULTI_ARCH_NATIVE_LIBS_MATCH)
testInstallMultiArchApp32_targetSdk33_success()326     public void testInstallMultiArchApp32_targetSdk33_success() throws Exception {
327         assumeTrue(isDeviceSupportedBothBitness());
328 
329         installPackage(getTestApkPath(BITNESS_32, /* isTargetSDK33= */ true));
330 
331         assertThat(isInstalled()).isTrue();
332     }
333 
334     @Test
335     @RequiresFlagsEnabled(Flags.FLAG_FORCE_MULTI_ARCH_NATIVE_LIBS_MATCH)
testInstallMultiArchApp64_targetSdk33_success()336     public void testInstallMultiArchApp64_targetSdk33_success() throws Exception {
337         assumeTrue(isDeviceSupportedBothBitness());
338 
339         installPackage(getTestApkPath(BITNESS_64, /* isTargetSDK33= */ true));
340 
341         assertThat(isInstalled()).isTrue();
342     }
343 
344     @Test
345     @RequiresFlagsEnabled(Flags.FLAG_FORCE_MULTI_ARCH_NATIVE_LIBS_MATCH)
testInstallMultiArchApp_emulatedAbiNoNativelyAbi_fail()346     public void testInstallMultiArchApp_emulatedAbiNoNativelyAbi_fail() throws Exception {
347         assumeTrue(isDeviceSupportsEmulatedAbi());
348         final String firstEmulatedAbi = getSupportedEmulatedAbis()[0];
349         final String baseArch = getBaseArchForAbi(firstEmulatedAbi);
350         assumeTrue(isSupportedBaseArch(baseArch));
351 
352         String result = installPackage(getTestAppPath(firstEmulatedAbi, baseArch));
353 
354         assertThat(result).contains(EXPECTED_FAILED_ERROR_MESSAGE);
355         assertThat(isInstalled()).isFalse();
356     }
357 
358     @Test
359     @RequiresFlagsDisabled(Flags.FLAG_FORCE_MULTI_ARCH_NATIVE_LIBS_MATCH)
testInstallMultiArchApp_emulatedAbiNoNativelyAbi_success()360     public void testInstallMultiArchApp_emulatedAbiNoNativelyAbi_success() throws Exception {
361         assumeTrue(isDeviceSupportsEmulatedAbi());
362         final String firstEmulatedAbi = getSupportedEmulatedAbis()[0];
363         final String baseArch = getBaseArchForAbi(firstEmulatedAbi);
364         assumeTrue(isSupportedBaseArch(baseArch));
365 
366         installPackage(getTestAppPath(firstEmulatedAbi, baseArch));
367 
368         assertThat(isInstalled()).isTrue();
369     }
370 
371     @Test
372     @RequiresFlagsEnabled(Flags.FLAG_FORCE_MULTI_ARCH_NATIVE_LIBS_MATCH)
testInstallMultiArchAppBoth_targetSdk33_success()373     public void testInstallMultiArchAppBoth_targetSdk33_success() throws Exception {
374         assumeTrue(isDeviceSupportedBothBitness());
375 
376         installPackage(getTestApkPath(BITNESS_BOTH, /* isTargetSDK33= */ true));
377 
378         assertThat(isInstalled()).isTrue();
379     }
380 
381     @Test
382     @RequiresFlagsDisabled(Flags.FLAG_FORCE_MULTI_ARCH_NATIVE_LIBS_MATCH)
testInstallMultiArchApp32_success()383     public void testInstallMultiArchApp32_success() throws Exception {
384         assumeTrue(isDeviceSupportedBothBitness());
385 
386         installPackage(getTestApkPath(BITNESS_32));
387 
388         assertThat(isInstalled()).isTrue();
389     }
390 
391     @Test
392     @RequiresFlagsDisabled(Flags.FLAG_FORCE_MULTI_ARCH_NATIVE_LIBS_MATCH)
testInstallMultiArchApp64_success()393     public void testInstallMultiArchApp64_success() throws Exception {
394         assumeTrue(isDeviceSupportedBothBitness());
395 
396         installPackage(getTestApkPath(BITNESS_64));
397 
398         assertThat(isInstalled()).isTrue();
399     }
400 
401     @Test
testInstallMultiArchAppBoth_success()402     public void testInstallMultiArchAppBoth_success() throws Exception {
403         assumeTrue(isDeviceSupportedBothBitness());
404 
405         installPackage(getTestApkPath(BITNESS_BOTH));
406 
407         assertThat(isInstalled()).isTrue();
408     }
409 }
410