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