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 17 package android.packageinstaller.criticaluserjourney.cts; 18 19 import static android.Manifest.permission.CHANGE_COMPONENT_ENABLED_STATE; 20 import static android.view.WindowInsets.Type.displayCutout; 21 import static android.view.WindowInsets.Type.systemBars; 22 23 import static com.google.common.truth.Truth.assertThat; 24 import static com.google.common.truth.Truth.assertWithMessage; 25 26 import static org.junit.Assert.fail; 27 import static org.junit.Assume.assumeFalse; 28 29 import android.app.Instrumentation; 30 import android.content.ComponentName; 31 import android.content.Context; 32 import android.content.Intent; 33 import android.content.pm.PackageInfo; 34 import android.content.pm.PackageManager; 35 import android.content.pm.ResolveInfo; 36 import android.graphics.Insets; 37 import android.graphics.Rect; 38 import android.net.Uri; 39 import android.provider.DeviceConfig; 40 import android.util.DisplayMetrics; 41 import android.util.Log; 42 import android.view.WindowManager; 43 44 import androidx.annotation.NonNull; 45 import androidx.annotation.Nullable; 46 import androidx.test.platform.app.InstrumentationRegistry; 47 import androidx.test.uiautomator.By; 48 import androidx.test.uiautomator.BySelector; 49 import androidx.test.uiautomator.UiDevice; 50 import androidx.test.uiautomator.UiObject2; 51 import androidx.test.uiautomator.UiScrollable; 52 import androidx.test.uiautomator.UiSelector; 53 import androidx.test.uiautomator.Until; 54 55 import com.android.compatibility.common.util.DisableAnimationRule; 56 import com.android.compatibility.common.util.FeatureUtil; 57 import com.android.compatibility.common.util.SystemUtil; 58 59 import org.junit.After; 60 import org.junit.AssumptionViolatedException; 61 import org.junit.Before; 62 import org.junit.ClassRule; 63 64 import java.io.ByteArrayOutputStream; 65 import java.io.File; 66 import java.io.IOException; 67 import java.nio.charset.StandardCharsets; 68 import java.util.regex.Pattern; 69 70 /** 71 * The test base to test PackageInstaller CUJs. 72 */ 73 public class PackageInstallerCujTestBase { 74 public static final String TAG = "PackageInstallerCujTestBase"; 75 76 public static final String AUTHORITY_NAME = ".fileprovider"; 77 public static final String DEVICE_ADMIN_APK_NAME = "CtsPackageInstallerDeviceAdminApp.apk"; 78 public static final String DEVICE_ADMIN_APP_PACKAGE_LABEL = 79 "Cts Package Installer Device Admin App"; 80 public static final String DEVICE_ADMIN_APP_PACKAGE_NAME = 81 "android.packageinstaller.cts.deviceadminapp"; 82 public static final String DEVICE_ADMIN_APP_RECEIVER_NAME = 83 "android.packageinstaller.cts.deviceadminapp.TestDeviceAdminReceiver"; 84 public static final String INSTALLER_APK_NAME = "CtsInstallerCujTestInstaller.apk"; 85 public static final String INSTALLER_APK_V2_NAME = "CtsInstallerCujTestInstallerV2.apk"; 86 public static final String INSTALLER_LABEL = "CTS CUJ Installer"; 87 public static final String INSTALLER_PACKAGE_NAME = 88 "android.packageinstaller.cts.cuj.installer"; 89 public static final String TEST_APK_LOCATION = "/data/local/tmp/cts/packageinstaller/cuj"; 90 public static final String TEST_APK_NAME = "CtsInstallerCujTestApp.apk"; 91 public static final String TEST_APK_V2_NAME = "CtsInstallerCujTestAppV2.apk"; 92 public static final String TEST_APP_LABEL = "Installer CUJ Test App"; 93 public static final String TEST_APP_PACKAGE_NAME = 94 "android.packageinstaller.cts.cuj.app"; 95 public static final String TEST_NO_LAUNCHER_ACTIVITY_APK_NAME = 96 "CtsInstallerCujTestNoLauncherActivityApp.apk"; 97 public static final String TEST_NO_LAUNCHER_ACTIVITY_APK_V2_NAME = 98 "CtsInstallerCujTestNoLauncherActivityAppV2.apk"; 99 100 public static final String APP_INSTALLED_LABEL = "App installed"; 101 public static final String BUTTON_CANCEL_LABEL = "Cancel"; 102 public static final String BUTTON_DONE_LABEL = "Done"; 103 public static final String BUTTON_GPP_MORE_DETAILS_LABEL = "More details"; 104 public static final String BUTTON_GPP_INSTALL_WITHOUT_SCANNING_LABEL = 105 "Install without scanning"; 106 public static final String BUTTON_INSTALL_LABEL = "Install"; 107 public static final String BUTTON_OK_LABEL = "OK"; 108 public static final String BUTTON_OPEN_LABEL = "Open"; 109 public static final String BUTTON_SETTINGS_LABEL = "Settings"; 110 public static final String BUTTON_UPDATE_LABEL = "Update"; 111 public static final String BUTTON_UPDATE_ANYWAY_LABEL = "Update anyway"; 112 public static final String CLONE_LABEL = "Clone"; 113 public static final String DELETE_LABEL = "delete"; 114 public static final String INSTALLING_LABEL = "Installing"; 115 public static final String TOGGLE_ALLOW_LABEL = "allow"; 116 public static final String TOGGLE_ALLOW_FROM_LABEL = "Allow from"; 117 public static final String TOGGLE_ALLOW_PERMISSION_LABEL = "allow permission"; 118 public static final String TOGGLE_INSTALL_UNKNOWN_APPS_LABEL = "install unknown apps"; 119 public static final String UNINSTALL_LABEL = "uninstall"; 120 public static final String WORK_PROFILE_LABEL = "work profile"; 121 public static final String TEXTVIEW_WIDGET_CLASSNAME = "android.widget.TextView"; 122 123 public static final long FIND_OBJECT_TIMEOUT_MS = 20 * 1000L; 124 private static final long WAIT_OBJECT_GONE_TIMEOUT_MS = 3 * 1000L; 125 126 private static final long TEST_APK_VERSION = 1; 127 private static final long TEST_APK_V2_VERSION = 2; 128 129 private static final ComponentName TEST_APP_ACTIVITY_COMPONENT = new ComponentName( 130 TEST_APP_PACKAGE_NAME, "android.packageinstaller.cts.cuj.app.MainActivity"); 131 132 private static boolean sUsePiaV2 = false; 133 134 @ClassRule 135 public static final DisableAnimationRule sDisableAnimationRule = new DisableAnimationRule(); 136 137 private static String sPackageInstallerPackageName = null; 138 139 private static double sSwipeDeadZonePercentage = 0f; 140 getInstrumentation()141 public static Instrumentation getInstrumentation() { 142 return InstrumentationRegistry.getInstrumentation(); 143 } 144 getContext()145 public static Context getContext() { 146 return getInstrumentation().getContext(); 147 } 148 getPackageManager()149 public static PackageManager getPackageManager() { 150 return getContext().getPackageManager(); 151 } 152 getUiDevice()153 public static UiDevice getUiDevice() { 154 return UiDevice.getInstance(getInstrumentation()); 155 } 156 157 @Before setup()158 public void setup() throws Exception { 159 setupTestEnvironment(); 160 } 161 162 /** 163 * 1. Assume the device is running on the supported device and the system has the installed 164 * system package Installer. 165 * 2. Uninstall the {@link #TEST_APP_PACKAGE_NAME} and assert it is not installed. 166 */ setupTestEnvironment()167 public static void setupTestEnvironment() { 168 assumeFalse("The device is not supported", isNotSupportedDevice()); 169 170 assumeFalse("The device doesn't have package installer", 171 getPackageInstallerPackageName() == null); 172 173 uninstallTestPackage(); 174 assertTestPackageNotInstalled(); 175 } 176 177 @After tearDown()178 public void tearDown() throws Exception { 179 uninstallTestPackage(); 180 // to avoid any UI is still on the screen 181 pressBack(); 182 } 183 184 /** 185 * Assert the test package that is the version 1 is installed. 186 */ assertTestPackageInstalled()187 public static void assertTestPackageInstalled() { 188 assertThat(isInstalledAndVerifyVersionCode( 189 TEST_APP_PACKAGE_NAME, TEST_APK_VERSION)).isTrue(); 190 } 191 192 /** 193 * Assert the test package that is the version 2 is installed. 194 */ assertTestPackageVersion2Installed()195 public static void assertTestPackageVersion2Installed() { 196 assertThat(isTestPackageVersion2Installed()).isTrue(); 197 } 198 199 /** 200 * Assert the test package is NOT installed. 201 */ assertTestPackageNotInstalled()202 public static void assertTestPackageNotInstalled() { 203 assertThat(isTestPackageInstalled()).isFalse(); 204 } 205 206 /** 207 * Wait for the device idle. 208 */ waitForUiIdle()209 public static void waitForUiIdle() { 210 // Make sure the application is idle and input windows is up-to-date. 211 getInstrumentation().getUiAutomation().syncInputTransactions(); 212 getUiDevice().waitForIdle(); 213 } 214 215 /** 216 * Press the back key. 217 */ pressBack()218 public static void pressBack() { 219 getUiDevice().pressBack(); 220 waitForUiIdle(); 221 } 222 223 /** Use UiScrollable to scroll forward. */ scrollForward()224 public static void scrollForward() throws Exception { 225 new UiScrollable(new UiSelector().scrollable(true)) 226 .setSwipeDeadZonePercentage(getSwipeDeadZonePercentage()) 227 .scrollForward(); 228 } 229 230 /** 231 * Get the swipe dead zone percentage on the current device. E.g. If display height * 0.1 is 232 * larger than the inset of the system bar + gap buffer, return 0.1f. Otherwise, if the display 233 * height * 0.2 is larger than the inset of the system bar + gap buffer, then return 0.2f. The 234 * maximum value is 0.3f. If the percentage is larger than 0.3f, the range is too small to 235 * scroll. 236 */ getSwipeDeadZonePercentage()237 private static double getSwipeDeadZonePercentage() { 238 if (sSwipeDeadZonePercentage != 0f) { 239 return sSwipeDeadZonePercentage; 240 } 241 242 DisplayMetrics displayMetrics = getContext().getResources().getDisplayMetrics(); 243 // the gap buffer is 24 * dp 244 int gapBuffer = (int) (24 * displayMetrics.density); 245 246 // Get the insets of system bars and displayCutOut 247 WindowManager wm = getContext().getSystemService(WindowManager.class); 248 Insets insets = wm.getCurrentWindowMetrics().getWindowInsets() 249 .getInsets(displayCutout() | systemBars()); 250 251 double percentage = 0.1; 252 while (displayMetrics.heightPixels * percentage < insets.bottom + gapBuffer 253 && percentage < 0.3) { 254 percentage += 0.05; 255 } 256 257 sSwipeDeadZonePercentage = percentage; 258 Log.d(TAG, "sSwipeDeadZonePercentage = " + sSwipeDeadZonePercentage); 259 return sSwipeDeadZonePercentage; 260 } 261 262 /** 263 * Click the object and wait for the new window content is changed 264 */ clickAndWaitForNewWindow(UiObject2 uiObject2)265 public static void clickAndWaitForNewWindow(UiObject2 uiObject2) { 266 uiObject2.clickAndWait(Until.newWindow(), WAIT_OBJECT_GONE_TIMEOUT_MS); 267 } 268 269 /** 270 * Assert the title of the install dialog is {@link #TEST_APP_LABEL}. 271 */ assertTitleIsTestAppLabel()272 public static void assertTitleIsTestAppLabel() throws Exception { 273 findPackageInstallerObject(TEST_APP_LABEL); 274 } 275 276 /** 277 * Assert the content includes the installer label {@link #INSTALLER_LABEL}. 278 */ assertContentIncludesInstallerLabel()279 public static void assertContentIncludesInstallerLabel() throws Exception { 280 findPackageInstallerObject(By.textContains(INSTALLER_LABEL), /* checkNull= */ true); 281 } 282 283 /** 284 * Assert the title of the install dialog is {@link #INSTALLER_LABEL}. 285 */ assertTitleIsInstallerLabel()286 public static void assertTitleIsInstallerLabel() throws Exception { 287 findPackageInstallerObject(INSTALLER_LABEL); 288 } 289 290 /** 291 * Click the Cancel button and wait for the dialog to disappear. 292 */ clickCancelButton()293 public static void clickCancelButton() throws Exception { 294 clickAndWaitForNewWindow(findPackageInstallerObject(BUTTON_CANCEL_LABEL)); 295 } 296 297 /** 298 * Touch outside of the PackageInstaller dialog. 299 */ touchOutside()300 public static void touchOutside() throws Exception { 301 Rect bound = getPackageInstallerDialogBound(); 302 DisplayMetrics displayMetrics = getContext().getResources().getDisplayMetrics(); 303 304 // Get the insets of system bars and displayCutOut 305 WindowManager wm = getContext().getSystemService(WindowManager.class); 306 Insets insets = wm.getCurrentWindowMetrics().getWindowInsets().getInsets( 307 displayCutout() | systemBars()); 308 309 // the minimum of top is the maximum of (display height / 10) and 310 // the top of the insets + 24 * dp 311 int gapBuffer = (int) (24 * displayMetrics.density); 312 int minTop = Math.max(insets.top + gapBuffer, displayMetrics.heightPixels / 10); 313 int maxTop = bound.top - gapBuffer; 314 315 Log.d(TAG, "touchOutside heightPixels = " + displayMetrics.heightPixels 316 + ", displayMetrics.density = " + displayMetrics.density + ", insets = " + insets 317 + ", minTop = " + minTop + ", maxTop = " + maxTop); 318 319 // x is the center of the dialog 320 int x = (bound.left + bound.right) / 2; 321 // The default value of y is the (minTop + maxTop) / 2 322 int y = (minTop + maxTop) / 2; 323 if (minTop > maxTop) { 324 // the maximum of bottom is the minimum of (display height * 9 / 10) and 325 // the display height - the bottom of the insets - 24 * dp 326 int maxBottom = Math.min(displayMetrics.heightPixels - insets.bottom - gapBuffer, 327 displayMetrics.heightPixels * 9 / 10); 328 int minBottom = bound.bottom + gapBuffer; 329 Log.d(TAG, "minBottom = " + minBottom + ", maxBottom = " + maxBottom); 330 if (minBottom > maxBottom) { 331 // close the dialog 332 pressBack(); 333 throw new AssumptionViolatedException("There is no space to touch outside!"); 334 } 335 y = (minBottom + maxBottom) / 2; 336 } 337 338 Log.d(TAG, "touchOutside x = " + x + ", y = " + y); 339 getUiDevice().click(x, y); 340 waitForUiIdle(); 341 } 342 getPackageInstallerDialogBound()343 private static Rect getPackageInstallerDialogBound() { 344 UiObject2 object = getUiDevice().findObject(By.pkg(getPackageInstallerPackageName())); 345 UiObject2 parent = object.getParent(); 346 while (parent != null) { 347 object = parent; 348 parent = object.getParent(); 349 } 350 logUiObject(object); 351 return object.getVisibleBounds(); 352 } 353 354 /** 355 * Log some values about the {@code uiObject} 356 */ logUiObject(@onNull UiObject2 uiObject)357 public static void logUiObject(@NonNull UiObject2 uiObject) { 358 Log.d(TAG, "Found bounds: " + uiObject.getVisibleBounds() 359 + " of object: " + uiObject + ", text: " + uiObject.getText() 360 + ", package: " + uiObject.getApplicationPackage() + ", className: " 361 + uiObject.getClassName()); 362 } 363 364 /** 365 * Get the new BySelector with the package name is {@link #getPackageInstallerPackageName()}. 366 */ getPackageInstallerBySelector(BySelector bySelector)367 public static BySelector getPackageInstallerBySelector(BySelector bySelector) { 368 return bySelector.pkg(getPackageInstallerPackageName()); 369 } 370 371 /** 372 * Find the UiObject2 with the {@code name} and the object's package name is 373 * {@link #getPackageInstallerPackageName()}. 374 */ findPackageInstallerObject(String name)375 public static UiObject2 findPackageInstallerObject(String name) throws Exception { 376 final Pattern namePattern = Pattern.compile(name, Pattern.CASE_INSENSITIVE); 377 return findPackageInstallerObject(By.text(namePattern), /* checkNull= */ true); 378 } 379 380 /** 381 * Find the UiObject2 with the {@code name} and the object's package name is 382 * {@link #getPackageInstallerPackageName()}. If {@code checkNull} is true, also check the 383 * object is not null. 384 */ findPackageInstallerObject(BySelector bySelector, boolean checkNull)385 public static UiObject2 findPackageInstallerObject(BySelector bySelector, boolean checkNull) 386 throws Exception { 387 return findObject(getPackageInstallerBySelector(bySelector), checkNull); 388 } 389 390 /** 391 * Find the UiObject2 with the {@code name}. 392 */ findObject(String name)393 public static UiObject2 findObject(String name) throws Exception { 394 final Pattern namePattern = Pattern.compile(name, Pattern.CASE_INSENSITIVE); 395 return findObject(By.text(namePattern), /* checkNull= */ true); 396 } 397 398 /** 399 * Find the UiObject2 with the {@code bySelector}. If {@code checkNull} is true, also 400 * check the object is not null. 401 */ 402 @Nullable findObject(BySelector bySelector, boolean checkNull)403 public static UiObject2 findObject(BySelector bySelector, boolean checkNull) throws Exception { 404 return findObject(bySelector, checkNull, FIND_OBJECT_TIMEOUT_MS); 405 } 406 407 /** 408 * Find the UiObject2 with the {@code bySelector}. If {@code checkNull} is true, also 409 * check the object is not null. The {@code timeoutMs} is the value for waiting time. 410 */ 411 @Nullable findObject(BySelector bySelector, boolean checkNull, long timeoutMs)412 public static UiObject2 findObject(BySelector bySelector, boolean checkNull, long timeoutMs) 413 throws Exception { 414 waitForUiIdle(); 415 416 UiObject2 object = null; 417 long startTime = System.currentTimeMillis(); 418 while (startTime + timeoutMs > System.currentTimeMillis()) { 419 try { 420 waitForUiIdle(); 421 object = getUiDevice().wait(Until.findObject(bySelector), /* timeout= */ 2 * 1000); 422 if (object != null) { 423 Log.d(TAG, "Found bounds: " + object.getVisibleBounds() 424 + " of object: " + bySelector + ", text: " + object.getText() 425 + " package: " + object.getApplicationPackage() + ", enabled: " 426 + object.isEnabled() + ", clickable: " + object.isClickable() 427 + ", contentDescription: " + object.getContentDescription() 428 + ", resourceName: " + object.getResourceName() + ", visibleCenter: " 429 + object.getVisibleCenter()); 430 return object; 431 } else { 432 // Maybe the screen is small. Scroll forward. 433 scrollForward(); 434 } 435 } catch (Exception ignored) { 436 // do nothing 437 } 438 } 439 440 // dump window hierarchy for debug 441 if (object == null) { 442 dumpWindowHierarchy(); 443 } 444 445 if (checkNull) { 446 assertWithMessage("Can't find object " + bySelector).that(object).isNotNull(); 447 } 448 return object; 449 } 450 451 /** 452 * Wait for the UiObject2 with the {@code bySelector} is gone. 453 */ waitUntilObjectGone(BySelector bySelector)454 public static void waitUntilObjectGone(BySelector bySelector) throws Exception { 455 if (!getUiDevice().wait(Until.gone(bySelector), WAIT_OBJECT_GONE_TIMEOUT_MS)) { 456 // dump window hierarchy for debug 457 dumpWindowHierarchy(); 458 fail("The Object: " + bySelector + "did not disappear within " 459 + WAIT_OBJECT_GONE_TIMEOUT_MS + " milliseconds"); 460 } 461 waitForUiIdle(); 462 } 463 464 /** 465 * Dump current window hierarchy to help debug UI 466 */ dumpWindowHierarchy()467 public static void dumpWindowHierarchy() throws InterruptedException, IOException { 468 ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); 469 getUiDevice().dumpWindowHierarchy(outputStream); 470 String windowHierarchy = outputStream.toString(StandardCharsets.UTF_8.name()); 471 472 Log.w(TAG, "Window hierarchy:"); 473 for (String line : windowHierarchy.split("\n")) { 474 Thread.sleep(10); 475 Log.w(TAG, line); 476 } 477 } 478 479 /** 480 * Uninstall the test package {@link #TEST_APP_PACKAGE_NAME}. 481 */ uninstallTestPackage()482 public static void uninstallTestPackage() { 483 uninstallPackage(TEST_APP_PACKAGE_NAME); 484 } 485 486 /** 487 * Uninstall the package with {@code packageName}. 488 */ uninstallPackage(String packageName)489 public static void uninstallPackage(String packageName) { 490 SystemUtil.runShellCommand(String.format("pm uninstall %s", packageName)); 491 } 492 493 /** 494 * Install the test apk with update-ownership. 495 */ installTestPackageWithUpdateOwnership()496 public static void installTestPackageWithUpdateOwnership() throws Exception { 497 SystemUtil.runShellCommand(String.format("pm install -t --update-ownership -i %s %s", 498 getContext().getPackageName(), 499 new File(TEST_APK_LOCATION, TEST_APK_NAME).getCanonicalPath())); 500 assertTestPackageInstalled(); 501 502 // assert the updateOwner package name is getContext().getPackageName() 503 final String updateOwnerPackageName = getPackageManager().getInstallSourceInfo( 504 TEST_APP_PACKAGE_NAME).getUpdateOwnerPackageName(); 505 assertThat(updateOwnerPackageName).isEqualTo(getContext().getPackageName()); 506 } 507 508 /** 509 * Install the test apk {@link #TEST_APK_NAME} and set the installer to be 510 * the package name of the test case. 511 */ installTestPackageWithInstallerPackageName()512 public static void installTestPackageWithInstallerPackageName() throws IOException { 513 installPackage(TEST_APK_NAME, getContext().getPackageName()); 514 assertTestPackageInstalled(); 515 } 516 517 /** 518 * Install the test apk {@link #TEST_APK_NAME}. 519 */ installTestPackage()520 public static void installTestPackage() throws IOException { 521 installPackage(TEST_APK_NAME); 522 assertTestPackageInstalled(); 523 } 524 525 /** 526 * Install the test apk that has no launcher activity 527 * {@link #TEST_NO_LAUNCHER_ACTIVITY_APK_NAME}. 528 */ installNoLauncherActivityTestPackage()529 public static void installNoLauncherActivityTestPackage() throws IOException { 530 installPackage(TEST_NO_LAUNCHER_ACTIVITY_APK_NAME); 531 assertTestPackageInstalled(); 532 } 533 534 /** 535 * Return the value of the device config of the {@code name} in PackageManagerService 536 * namespace. 537 */ 538 @Nullable getPackageManagerDeviceProperty(@onNull String name)539 public static String getPackageManagerDeviceProperty(@NonNull String name) 540 throws Exception { 541 return SystemUtil.callWithShellPermissionIdentity(() -> 542 DeviceConfig.getProperty(DeviceConfig.NAMESPACE_PACKAGE_MANAGER_SERVICE, name)); 543 } 544 545 /** 546 * Set the {@code value} to the device config with the {@code name} in PackageManagerService 547 * namespace. 548 */ setPackageManagerDeviceProperty(@onNull String name, @Nullable String value)549 public static void setPackageManagerDeviceProperty(@NonNull String name, 550 @Nullable String value) throws Exception { 551 SystemUtil.callWithShellPermissionIdentity(() -> DeviceConfig.setProperty( 552 DeviceConfig.NAMESPACE_PACKAGE_MANAGER_SERVICE, name, value, 553 /* makeDefault= */ false)); 554 } 555 556 /** 557 * Install the test apk {@code apkName} and set the installer is {@code installerPackageName}. 558 */ installPackage(@onNull String apkName, @NonNull String installerPackageName)559 public static void installPackage(@NonNull String apkName, @NonNull String installerPackageName) 560 throws IOException { 561 Log.d(TAG, "installPackage(): apkName= " + apkName + " installerPackageName= " 562 + installerPackageName); 563 SystemUtil.runShellCommand(String.format("pm install -i %s -t %s", installerPackageName, 564 new File(TEST_APK_LOCATION, apkName).getCanonicalPath())); 565 } 566 567 /** 568 * Install the test apk {@code apkName} for all users. 569 */ installPackage(@onNull String apkName)570 public static void installPackage(@NonNull String apkName) throws IOException { 571 Log.d(TAG, "installPackage(): apkName= " + apkName); 572 SystemUtil.runShellCommand("pm install -t " 573 + new File(TEST_APK_LOCATION, apkName).getCanonicalPath()); 574 } 575 576 /** 577 * Install the installed {@code packageName} on the user {@code user}. 578 */ installExistingPackageOnUser(String packageName, int userId)579 public static void installExistingPackageOnUser(String packageName, int userId) { 580 Log.d(TAG, "installExistingPackageAsUser(): packageName= " + packageName 581 + ", userId= " + userId); 582 assertThat(SystemUtil.runShellCommand( 583 String.format("pm install-existing --user %s %s", userId, packageName))) 584 .isEqualTo( 585 String.format("Package %s installed for user: %s\n", packageName, userId)); 586 } 587 588 /** 589 * If the test package {@link #TEST_APP_PACKAGE_NAME} is installed, return true. Otherwise, 590 * return false. 591 */ isTestPackageInstalled()592 public static boolean isTestPackageInstalled() { 593 return isInstalled(TEST_APP_PACKAGE_NAME); 594 } 595 596 /** 597 * If the test package {@link #TEST_APP_PACKAGE_NAME} is installed on the {@code userContext}, 598 * return true. Otherwise, return false. 599 */ isTestPackageInstalledOnUser(@onNull Context userContext)600 public static boolean isTestPackageInstalledOnUser(@NonNull Context userContext) { 601 return isInstalled(userContext, TEST_APP_PACKAGE_NAME); 602 } 603 604 /** 605 * If the test package {@code packageName} is installed, return true. Otherwise, 606 * return false. 607 */ isInstalled(@onNull String packageName)608 public static boolean isInstalled(@NonNull String packageName) { 609 return isInstalled(getContext(), packageName); 610 } 611 612 /** 613 * If the test package {@code packageName} is installed on the {@code context}, 614 * return true. Otherwise, return false. 615 */ isInstalled(@onNull Context context, @NonNull String packageName)616 public static boolean isInstalled(@NonNull Context context, @NonNull String packageName) { 617 Log.d(TAG, "Testing if package " + packageName + " is installed for user " 618 + context.getUser()); 619 try { 620 context.getPackageManager().getPackageInfo(packageName, /* flags= */ 0); 621 return true; 622 } catch (PackageManager.NameNotFoundException e) { 623 Log.v(TAG, "Package " + packageName + " not installed for user " 624 + context.getUser() + ": " + e); 625 return false; 626 } 627 } 628 629 /** 630 * If the test package {@link #TEST_APP_PACKAGE_NAME} with version {@link #TEST_APK_V2_VERSION} 631 * is installed, return true. Otherwise, return false. 632 */ isTestPackageVersion2Installed()633 public static boolean isTestPackageVersion2Installed() { 634 return isInstalledAndVerifyVersionCode(TEST_APP_PACKAGE_NAME, TEST_APK_V2_VERSION); 635 } 636 637 /** 638 * If the test package {@code packageName} with version {@code versionCode} 639 * is installed, return true. Otherwise, return false. 640 */ isInstalledAndVerifyVersionCode(@onNull String packageName, long versionCode)641 public static boolean isInstalledAndVerifyVersionCode(@NonNull String packageName, 642 long versionCode) { 643 Log.d(TAG, "Testing if package " + packageName + " is installed for user " 644 + getContext().getUser() + ", with version code " + versionCode); 645 try { 646 PackageInfo packageInfo = getPackageManager().getPackageInfo(packageName, 647 /* flags= */ 0); 648 return packageInfo.getLongVersionCode() == versionCode; 649 } catch (PackageManager.NameNotFoundException e) { 650 Log.v(TAG, "Package " + packageName + " not installed for user " 651 + getContext().getUser() + ": " + e); 652 return false; 653 } 654 } 655 656 /** 657 * Disable the launcher activity of the test app. 658 */ disableTestPackageLauncherActivity()659 public static void disableTestPackageLauncherActivity() { 660 SystemUtil.runWithShellPermissionIdentity( 661 () -> getPackageManager().setComponentEnabledSetting(TEST_APP_ACTIVITY_COMPONENT, 662 PackageManager.COMPONENT_ENABLED_STATE_DISABLED, 663 PackageManager.DONT_KILL_APP), CHANGE_COMPONENT_ENABLED_STATE); 664 } 665 666 @Nullable getPackageInstallerPackageName()667 public static String getPackageInstallerPackageName() { 668 if (sPackageInstallerPackageName != null) { 669 return sPackageInstallerPackageName; 670 } 671 final Intent intent = new Intent( 672 Intent.ACTION_INSTALL_PACKAGE).setData(Uri.parse("content:")); 673 final ResolveInfo ri = getPackageManager().resolveActivity(intent, /* flags= */ 0); 674 sPackageInstallerPackageName = ri != null ? ri.activityInfo.packageName : null; 675 Log.d(TAG, "sPackageInstallerPackageName = " + sPackageInstallerPackageName); 676 return sPackageInstallerPackageName; 677 } 678 isNotSupportedDevice()679 private static boolean isNotSupportedDevice() { 680 return FeatureUtil.isArc() 681 || FeatureUtil.isAutomotive() 682 || FeatureUtil.isTV() 683 || FeatureUtil.isWatch() 684 || FeatureUtil.isVrHeadset(); 685 } 686 } 687