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.app.AppOpsManager.MODE_ALLOWED; 20 import static android.app.AppOpsManager.OPSTR_REQUEST_INSTALL_PACKAGES; 21 import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; 22 import static android.content.pm.PackageInstaller.EXTRA_STATUS; 23 import static android.content.pm.PackageInstaller.STATUS_FAILURE_ABORTED; 24 import static android.content.pm.PackageInstaller.STATUS_PENDING_USER_ACTION; 25 import static android.content.pm.PackageInstaller.STATUS_SUCCESS; 26 27 import static com.google.common.truth.Truth.assertThat; 28 import static com.google.common.truth.Truth.assertWithMessage; 29 30 import static org.junit.Assert.fail; 31 import static org.junit.Assume.assumeFalse; 32 33 import android.app.Activity; 34 import android.app.Instrumentation; 35 import android.content.BroadcastReceiver; 36 import android.content.Context; 37 import android.content.Intent; 38 import android.content.IntentFilter; 39 import android.content.pm.PackageInfo; 40 import android.content.pm.PackageManager; 41 import android.content.pm.ResolveInfo; 42 import android.graphics.Rect; 43 import android.net.Uri; 44 import android.text.TextUtils; 45 import android.util.DisplayMetrics; 46 import android.util.Log; 47 48 import androidx.annotation.NonNull; 49 import androidx.annotation.Nullable; 50 import androidx.core.content.FileProvider; 51 import androidx.test.platform.app.InstrumentationRegistry; 52 import androidx.test.uiautomator.By; 53 import androidx.test.uiautomator.BySelector; 54 import androidx.test.uiautomator.UiDevice; 55 import androidx.test.uiautomator.UiObject2; 56 import androidx.test.uiautomator.UiScrollable; 57 import androidx.test.uiautomator.UiSelector; 58 import androidx.test.uiautomator.Until; 59 60 import com.android.compatibility.common.util.AppOpsUtils; 61 import com.android.compatibility.common.util.DisableAnimationRule; 62 import com.android.compatibility.common.util.FeatureUtil; 63 import com.android.compatibility.common.util.SystemUtil; 64 65 import org.junit.After; 66 import org.junit.AfterClass; 67 import org.junit.Before; 68 import org.junit.BeforeClass; 69 import org.junit.ClassRule; 70 71 import java.io.File; 72 import java.io.FileInputStream; 73 import java.io.FileOutputStream; 74 import java.io.IOException; 75 import java.io.InputStream; 76 import java.io.OutputStream; 77 import java.util.List; 78 import java.util.Locale; 79 import java.util.concurrent.CompletableFuture; 80 import java.util.concurrent.TimeUnit; 81 import java.util.regex.Pattern; 82 83 /** 84 * The test base to test PackageInstaller CUJs. 85 */ 86 public class PackageInstallerCujTestBase { 87 private static final String TAG = "PackageInstallerCujTestBase"; 88 89 private static final String CONTENT_AUTHORITY = 90 "android.packageinstaller.criticaluserjourney.cts.fileprovider"; 91 private static final String TEST_APK_LABEL = "Empty Test App"; 92 private static final String TEST_APK_NAME = "CtsEmptyTestApp.apk"; 93 private static final String TEST_APK_V2_LABEL = "Empty Test App V2"; 94 private static final String TEST_APK_V2_NAME = "CtsEmptyTestAppV2.apk"; 95 private static final String TEST_APK_PACKAGE_NAME = "android.packageinstaller.emptytestapp.cts"; 96 private static final String TEST_INSTALLER_PACKAGE_NAME = 97 "android.packageinstaller.cts.cujinstaller"; 98 private static final String TEST_INSTALLER_APK_NAME = "CtsCujInstallerTestApp.apk"; 99 private static final String TEST_APK_LOCATION = "/data/local/tmp/cts/packageinstaller/cuj"; 100 private static final String APP_INSTALLED_LABEL = "App installed"; 101 private static final String BUTTON_CANCEL_LABEL = "Cancel"; 102 private static final String BUTTON_DONE_LABEL = "Done"; 103 private static final String BUTTON_GPP_MORE_DETAILS_LABEL = "More details"; 104 private static final String BUTTON_GPP_INSTALL_ANYWAY_LABEL = "Install anyway"; 105 private static final String BUTTON_INSTALL_LABEL = "Install"; 106 private static final String BUTTON_SETTINGS_LABEL = "Settings"; 107 private static final String BUTTON_UPDATE_LABEL = "Update"; 108 private static final String TOGGLE_ALLOW_LABEL = "allow"; 109 private static final String TOGGLE_ALLOW_FROM_LABEL = "Allow from"; 110 private static final String TOGGLE_ALLOW_PERMISSION_LABEL = "allow permission"; 111 private static final String TOGGLE_INSTALL_UNKNOWN_APPS_LABEL = "install unknown apps"; 112 private static final String INSTALLING_LABEL = "Installing"; 113 private static final String TEXTVIEW_WIDGET_CLASSNAME = "android.widget.TextView"; 114 115 private static final String ACTION_LAUNCH_INSTALLER = 116 "android.packageinstaller.cts.cujinstaller.action.LAUNCH_INSTALLER"; 117 118 private static final String ACTION_REQUEST_INSTALLER = 119 "android.packageinstaller.cts.cujinstaller.action.REQUEST_INSTALLER"; 120 121 private static final String ACTION_RESPONSE_INSTALLER = 122 "android.packageinstaller.cts.cujinstaller.action.RESPONSE_INSTALLER"; 123 124 private static final String EXTRA_EVENT = "extra_event"; 125 private static final String EXTRA_TEST_APK_URI = "extra_test_apk_uri"; 126 private static final String EXTRA_TEST_APK_V2_URI = "extra_test_apk_v2_uri"; 127 private static final String EXTRA_USE_APK_V2 = "extra_use_apk_v2"; 128 129 private static final int EVENT_REQUEST_INSTALLER_CLEAN_UP = -1; 130 private static final int EVENT_REQUEST_INSTALLER_SESSION = 0; 131 private static final int EVENT_REQUEST_INSTALLER_INTENT = 1; 132 private static final int EVENT_REQUEST_INSTALLER_INTENT_FOR_RESULT = 2; 133 private static final int EVENT_REQUEST_INSTALLER_INTENT_WITH_PACKAGE_URI = 3; 134 private static final int EVENT_REQUEST_INSTALLER_INTENT_WITH_PACKAGE_URI_FOR_RESULT = 4; 135 136 private static final int STATUS_CUJ_INSTALLER_READY = 1000; 137 private static final int STATUS_CUJ_INSTALLER_START_ACTIVITY_READY = 1001; 138 139 private static final long FIND_OBJECT_TIMEOUT_MS = 30 * 1000L; 140 private static final long WAIT_OBJECT_GONE_TIMEOUT_MS = 3 * 1000L; 141 142 @ClassRule 143 public static final DisableAnimationRule sDisableAnimationRule = new DisableAnimationRule(); 144 145 private static Context sContext; 146 private static PackageManager sPackageManager; 147 private static InstallerResponseReceiver sInstallerResponseReceiver; 148 private static Instrumentation sInstrumentation; 149 private static UiDevice sUiDevice; 150 private static String sToggleLabel = null; 151 private static String sPackageInstallerPackageName = null; 152 153 @BeforeClass setUpClass()154 public static void setUpClass() throws Exception { 155 sInstrumentation = InstrumentationRegistry.getInstrumentation(); 156 sContext = sInstrumentation.getTargetContext(); 157 sPackageManager = sContext.getPackageManager(); 158 sInstallerResponseReceiver = new InstallerResponseReceiver(); 159 sPackageInstallerPackageName = getPackageInstallerPackageName(); 160 Log.d(TAG, "sPackageInstallerPackageName = " + sPackageInstallerPackageName); 161 162 copyTestFiles(); 163 164 // Unblock UI 165 sUiDevice = UiDevice.getInstance(sInstrumentation); 166 if (!sUiDevice.isScreenOn()) { 167 sUiDevice.wakeUp(); 168 } 169 sUiDevice.executeShellCommand("wm dismiss-keyguard"); 170 171 sContext.registerReceiver(sInstallerResponseReceiver, 172 new IntentFilter(ACTION_RESPONSE_INSTALLER), Context.RECEIVER_EXPORTED); 173 } 174 175 @Before setup()176 public void setup() throws Exception { 177 assumeFalse("The device is not supported", isNotSupportedDevice()); 178 179 assumeFalse("The device doesn't have package installer", 180 sPackageInstallerPackageName == null); 181 182 uninstallTestPackage(); 183 assertTestPackageNotInstalled(); 184 185 uninstallInstallerPackage(); 186 assertInstallerNotInstalled(); 187 188 sInstallerResponseReceiver.resetResult(); 189 190 // install the test installer before the test case is running everytime to make sure the 191 // AppOps permission mode is the default mode. 192 installPackage(TEST_INSTALLER_APK_NAME); 193 assertThat(isInstalled(TEST_INSTALLER_PACKAGE_NAME)).isTrue(); 194 startInstallerActivity(); 195 196 waitForUiIdle(); 197 assertCUJInstallerReady(); 198 } 199 200 @After tearDown()201 public void tearDown() throws Exception { 202 requestInstallerCleanUp(); 203 204 uninstallTestPackage(); 205 uninstallInstallerPackage(); 206 // to avoid any UI is still on the screen 207 pressBack(); 208 } 209 210 @AfterClass tearDownClass()211 public static void tearDownClass() throws Exception { 212 sInstallerResponseReceiver.unregisterReceiver(sContext); 213 sInstallerResponseReceiver = null; 214 sPackageManager = null; 215 sContext = null; 216 sUiDevice = null; 217 sInstrumentation = null; 218 } 219 220 /** 221 * Grant the REQUEST_INSTALL_PACKAGES AppOps permission to the CUJ Installer. 222 */ grantRequestInstallPackagesPermission()223 public static void grantRequestInstallPackagesPermission() throws Exception { 224 AppOpsUtils.setOpMode(TEST_INSTALLER_PACKAGE_NAME, OPSTR_REQUEST_INSTALL_PACKAGES, 225 MODE_ALLOWED); 226 } 227 copyTestFiles()228 private static void copyTestFiles() throws Exception { 229 final File apkFile = new File(TEST_APK_LOCATION, TEST_APK_NAME); 230 final File dstFile = new File(sContext.getFilesDir(), TEST_APK_NAME); 231 copyFile(apkFile, dstFile); 232 233 final File apkV2File = new File(TEST_APK_LOCATION, TEST_APK_V2_NAME); 234 final File dstV2File = new File(sContext.getFilesDir(), TEST_APK_V2_NAME); 235 copyFile(apkV2File, dstV2File); 236 } 237 copyFile(File src, File dst)238 private static void copyFile(File src, File dst) throws Exception { 239 try (InputStream source = new FileInputStream(src); 240 OutputStream target = new FileOutputStream(dst)) { 241 byte[] buffer = new byte[1024]; 242 for (int len = source.read(buffer); len > 0; len = source.read(buffer)) { 243 target.write(buffer, 0, len); 244 } 245 } 246 } 247 startInstallerActivity()248 private static void startInstallerActivity() { 249 final File apkFile = new File(sContext.getFilesDir(), TEST_APK_NAME); 250 final File apkV2File = new File(sContext.getFilesDir(), TEST_APK_V2_NAME); 251 final Intent intent = new Intent(); 252 intent.setPackage(TEST_INSTALLER_PACKAGE_NAME); 253 intent.setFlags(FLAG_ACTIVITY_NEW_TASK); 254 intent.setAction(ACTION_LAUNCH_INSTALLER); 255 Uri testApkUri = FileProvider.getUriForFile(sContext, CONTENT_AUTHORITY, apkFile); 256 Uri testApkV2Uri = FileProvider.getUriForFile(sContext, CONTENT_AUTHORITY, apkV2File); 257 intent.putExtra(EXTRA_TEST_APK_URI, testApkUri.toString()); 258 intent.putExtra(EXTRA_TEST_APK_V2_URI, testApkV2Uri.toString()); 259 260 // grant read uri permission to the installer 261 sContext.grantUriPermission(TEST_INSTALLER_PACKAGE_NAME, testApkUri, 262 Intent.FLAG_GRANT_READ_URI_PERMISSION); 263 sContext.grantUriPermission(TEST_INSTALLER_PACKAGE_NAME, testApkV2Uri, 264 Intent.FLAG_GRANT_READ_URI_PERMISSION); 265 sContext.startActivity(intent); 266 } 267 requestInstallerCleanUp()268 private static void requestInstallerCleanUp() throws Exception { 269 sendRequestInstallerBroadcast(EVENT_REQUEST_INSTALLER_CLEAN_UP); 270 } 271 272 /** 273 * Start the installation via PackageInstaller.Session APIs. 274 */ startInstallationViaPackageInstallerSession()275 public static void startInstallationViaPackageInstallerSession() throws Exception { 276 sendRequestInstallerBroadcast(EVENT_REQUEST_INSTALLER_SESSION); 277 assertInstallPendingUserAction(); 278 } 279 280 /** 281 * Start the installation to update the test apk to V2 version via 282 * PackageInstaller.Session APIs. 283 */ startInstallationUpdateViaPackageInstallerSession()284 public static void startInstallationUpdateViaPackageInstallerSession() throws Exception { 285 sendRequestInstallerBroadcast(EVENT_REQUEST_INSTALLER_SESSION, /* useV2= */ true); 286 assertInstallPendingUserAction(); 287 } 288 sendRequestInstallerBroadcast(int event)289 private static void sendRequestInstallerBroadcast(int event) throws Exception { 290 sendRequestInstallerBroadcast(event, /* useV2= */ false); 291 } 292 293 /** 294 * Start the installation via startActivity. 295 */ startInstallationViaIntent()296 public static void startInstallationViaIntent() throws Exception { 297 sendRequestInstallerBroadcast(EVENT_REQUEST_INSTALLER_INTENT); 298 assertCUJInstallerStartActivityReady(); 299 } 300 301 /** 302 * Start the installation to update the test apk to label V2 version via startActivity. 303 */ startInstallationUpdateViaIntent()304 public static void startInstallationUpdateViaIntent() throws Exception { 305 sendRequestInstallerBroadcast(EVENT_REQUEST_INSTALLER_INTENT, /* useV2= */ true); 306 assertCUJInstallerStartActivityReady(); 307 } 308 309 /** 310 * Start the installation via startActivity with Package uri. 311 */ startInstallationViaIntentWithPackageUri()312 public static void startInstallationViaIntentWithPackageUri() throws Exception { 313 sendRequestInstallerBroadcast(EVENT_REQUEST_INSTALLER_INTENT_WITH_PACKAGE_URI); 314 assertCUJInstallerStartActivityReady(); 315 } 316 317 /** 318 * Start the installation via startActivityForResult. 319 */ startInstallationViaIntentForResult()320 public static void startInstallationViaIntentForResult() throws Exception { 321 sendRequestInstallerBroadcast(EVENT_REQUEST_INSTALLER_INTENT_FOR_RESULT); 322 assertCUJInstallerStartActivityReady(); 323 } 324 325 /** 326 * Start the installation to update the test apk to label V2 version via startActivityForResult. 327 */ startInstallationUpdateViaIntentForResult()328 public static void startInstallationUpdateViaIntentForResult() throws Exception { 329 sendRequestInstallerBroadcast(EVENT_REQUEST_INSTALLER_INTENT_FOR_RESULT, 330 /* useV2= */ true); 331 assertCUJInstallerStartActivityReady(); 332 } 333 334 /** 335 * Start the installation via startActivityForResult with Package uri. 336 */ startInstallationViaIntentWithPackageUriForResult()337 public static void startInstallationViaIntentWithPackageUriForResult() throws Exception { 338 sendRequestInstallerBroadcast(EVENT_REQUEST_INSTALLER_INTENT_WITH_PACKAGE_URI_FOR_RESULT); 339 assertCUJInstallerStartActivityReady(); 340 } 341 sendRequestInstallerBroadcast(int event, boolean useV2)342 private static void sendRequestInstallerBroadcast(int event, boolean useV2) throws Exception { 343 final Intent intent = new Intent(ACTION_REQUEST_INSTALLER); 344 intent.setPackage(TEST_INSTALLER_PACKAGE_NAME); 345 intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND); 346 intent.putExtra(EXTRA_EVENT, event); 347 intent.putExtra(EXTRA_USE_APK_V2, useV2); 348 sContext.sendBroadcast(intent); 349 } 350 getInstallerResponseResult()351 private static int getInstallerResponseResult() throws Exception { 352 return sInstallerResponseReceiver.getInstallerResponseResult(); 353 } 354 355 /** 356 * Assert the install status is Activity#RESULT_OK). 357 */ assertInstallerResponseActivityResultOK()358 public static void assertInstallerResponseActivityResultOK() throws Exception { 359 assertThat(getInstallerResponseResult()).isEqualTo(Activity.RESULT_OK); 360 sInstallerResponseReceiver.resetResult(); 361 } 362 363 /** 364 * Assert the install status is Activity#RESULT_CANCELED. 365 */ assertInstallerResponseActivityResultCanceled()366 public static void assertInstallerResponseActivityResultCanceled() throws Exception { 367 assertThat(getInstallerResponseResult()).isEqualTo(Activity.RESULT_CANCELED); 368 sInstallerResponseReceiver.resetResult(); 369 } 370 371 /** 372 * Assert the install status is PackageInstaller#STATUS_SUCCESS. 373 */ assertInstallSuccess()374 public static void assertInstallSuccess() throws Exception { 375 assertThat(getInstallerResponseResult()).isEqualTo(STATUS_SUCCESS); 376 sInstallerResponseReceiver.resetResult(); 377 } 378 379 /** 380 * Assert the install status is PackageInstaller#STATUS_FAILURE_ABORTED. 381 */ assertInstallFailureAborted()382 public static void assertInstallFailureAborted() throws Exception { 383 assertThat(getInstallerResponseResult()).isEqualTo(STATUS_FAILURE_ABORTED); 384 sInstallerResponseReceiver.resetResult(); 385 } 386 assertInstallPendingUserAction()387 private static void assertInstallPendingUserAction() throws Exception { 388 assertThat(getInstallerResponseResult()).isEqualTo(STATUS_PENDING_USER_ACTION); 389 sInstallerResponseReceiver.resetResult(); 390 } 391 assertCUJInstallerReady()392 private static void assertCUJInstallerReady() throws Exception { 393 assertThat(getInstallerResponseResult()).isEqualTo(STATUS_CUJ_INSTALLER_READY); 394 sInstallerResponseReceiver.resetResult(); 395 } 396 assertCUJInstallerStartActivityReady()397 private static void assertCUJInstallerStartActivityReady() throws Exception { 398 assertThat(getInstallerResponseResult()).isEqualTo( 399 STATUS_CUJ_INSTALLER_START_ACTIVITY_READY); 400 sInstallerResponseReceiver.resetResult(); 401 } 402 403 /** 404 * Assert the test package is installed. 405 */ assertTestPackageInstalled()406 public static void assertTestPackageInstalled() { 407 assertThat(isInstalledAndVerifyAppName(TEST_APK_PACKAGE_NAME, TEST_APK_LABEL)).isTrue(); 408 } 409 410 /** 411 * Assert the test package has the V2 label is installed. 412 */ assertTestPackageLabelV2Installed()413 public static void assertTestPackageLabelV2Installed() { 414 assertThat(isTestPackageLabelV2Installed()).isTrue(); 415 } 416 417 /** 418 * Assert the test package is NOT installed. 419 */ assertTestPackageNotInstalled()420 public static void assertTestPackageNotInstalled() { 421 assertThat(isTestPackageInstalled()).isFalse(); 422 } 423 assertInstallerNotInstalled()424 private static void assertInstallerNotInstalled() { 425 assertThat(isInstallerInstalled()).isFalse(); 426 } 427 428 /** 429 * Wait for the device idle. 430 */ waitForUiIdle()431 public static void waitForUiIdle() { 432 sUiDevice.waitForIdle(); 433 } 434 435 /** 436 * Press the back key. 437 */ pressBack()438 public static void pressBack() { 439 sUiDevice.pressBack(); 440 waitForUiIdle(); 441 } 442 clickAndWaitForNewWindow(UiObject2 uiObject2)443 private static void clickAndWaitForNewWindow(UiObject2 uiObject2) { 444 uiObject2.clickAndWait(Until.newWindow(), WAIT_OBJECT_GONE_TIMEOUT_MS); 445 } 446 allowInstallIfGPPDialogExists()447 private static void allowInstallIfGPPDialogExists() { 448 final Pattern morePattern = Pattern.compile(BUTTON_GPP_MORE_DETAILS_LABEL, 449 Pattern.CASE_INSENSITIVE); 450 UiObject2 more = sUiDevice.findObject(By.text(morePattern)); 451 if (more != null) { 452 more.click(); 453 waitForUiIdle(); 454 455 BySelector installAnyWaySelector = By.textContains(BUTTON_GPP_INSTALL_ANYWAY_LABEL); 456 UiObject2 installAnyway = findObject(installAnyWaySelector, /* checkNull= */ false); 457 if (installAnyway != null) { 458 Rect rect = installAnyway.getVisibleBounds(); 459 sUiDevice.click(rect.left, rect.bottom - 10); 460 // wait for the dialog disappear 461 waitUntilObjectGone(installAnyWaySelector); 462 } 463 } 464 waitForUiIdle(); 465 } 466 467 /** 468 * Assert the install success dialog and click the Done button. 469 */ assertInstallSuccessDialogAndClickDoneButton()470 public static void assertInstallSuccessDialogAndClickDoneButton() throws Exception { 471 findPackageInstallerObject(By.textContains(APP_INSTALLED_LABEL), /* checkNull= */ true); 472 clickAndWaitForNewWindow(findPackageInstallerObject(BUTTON_DONE_LABEL)); 473 } 474 475 /** 476 * Click the Install button and wait for the dialog to disappear. Also allow install if the 477 * GPP dialog exists. 478 */ clickInstallButton()479 public static void clickInstallButton() { 480 clickInstallButton(/* checkInstallingDialog= */ false); 481 } 482 483 /** 484 * Click the Install button and wait for the dialog to disappear. Also allow install if the 485 * GPP dialog exists. If {@code checkInstallingDialog} is true, check the Installing dialog. 486 * Otherwise, don't check the Installing dialog. E.g. The installation via intent triggers 487 * the Installing dialog. 488 */ clickInstallButton(boolean checkInstallingDialog)489 public static void clickInstallButton(boolean checkInstallingDialog) { 490 clickAndWaitForNewWindow(findPackageInstallerObject(BUTTON_INSTALL_LABEL)); 491 492 if (checkInstallingDialog) { 493 waitForInstallingDialogGone(); 494 } 495 496 if (!isTestPackageInstalled()) { 497 allowInstallIfGPPDialogExists(); 498 } 499 } 500 501 /** 502 * Click the Update button and wait for the dialog to disappear. Also allow install if the 503 * GPP dialog exists. 504 */ clickUpdateButton()505 public static void clickUpdateButton() { 506 clickUpdateButton(/* checkInstallingDialog= */ false); 507 } 508 509 /** 510 * Click the Update button and wait for the dialog to disappear. Also allow install if the 511 * GPP dialog exists. If {@code checkInstallingDialog} is true, check the Installing dialog. 512 * Otherwise, don't check the Installing dialog. E.g. The installation via intent triggers 513 * the Installing dialog. 514 */ clickUpdateButton(boolean checkInstallingDialog)515 public static void clickUpdateButton(boolean checkInstallingDialog) { 516 clickUpdateButton(checkInstallingDialog, /* checkGPPDialog= */ true); 517 } 518 519 /** 520 * Click the Update button and wait for the dialog to disappear. If 521 * {@code checkInstallingDialog} is true, check the Installing dialog. Otherwise, don't 522 * check the Installing dialog. E.g. The installation via intent triggers Installing dialog. 523 * If {@code checkGPPDialog} is true, check the GPP dialog. Otherwise, don't check the GPP 524 * dialog. E.g. The installation via intent with package uri doesn't trigger the GPP dialog. 525 */ clickUpdateButton(boolean checkInstallingDialog, boolean checkGPPDialog)526 public static void clickUpdateButton(boolean checkInstallingDialog, boolean checkGPPDialog) { 527 clickAndWaitForNewWindow(findPackageInstallerObject(BUTTON_UPDATE_LABEL)); 528 529 if (checkInstallingDialog) { 530 waitForInstallingDialogGone(); 531 } 532 533 if (checkGPPDialog && !isTestPackageLabelV2Installed()) { 534 allowInstallIfGPPDialogExists(); 535 } 536 } 537 538 /** 539 * Click the Cancel button and wait for the dialog to disappear. 540 */ clickCancelButton()541 public static void clickCancelButton() { 542 clickAndWaitForNewWindow(findPackageInstallerObject(BUTTON_CANCEL_LABEL)); 543 } 544 545 /** 546 * Click the Settings button and wait for the dialog to disappear. 547 */ clickSettingsButton()548 public static void clickSettingsButton() { 549 clickAndWaitForNewWindow(findPackageInstallerObject(BUTTON_SETTINGS_LABEL)); 550 } 551 552 /** 553 * Toggle to grant the AppOps permission REQUEST_INSTALL_PACKAGES to the CUJ Installer. 554 */ toggleToGrantRequestInstallPackagesPermission()555 public static void toggleToGrantRequestInstallPackagesPermission() { 556 // Already know which toggle label on the device, find it and click it directly 557 if (sToggleLabel != null) { 558 clickAndWaitForNewWindow(findObject(sToggleLabel)); 559 return; 560 } 561 562 // Start to find the objects, find the checkable items first 563 final List<UiObject2> uiObjects = sUiDevice.wait( 564 Until.findObjects(By.checkable(true).checked(false)), FIND_OBJECT_TIMEOUT_MS); 565 566 if (uiObjects == null || uiObjects.isEmpty()) { 567 fail("No toggle to grant permission"); 568 } 569 570 Log.d(TAG, "The count of checkable objects is " + uiObjects.size()); 571 572 // Only one item, find the text object 573 if (uiObjects.size() == 1) { 574 UiObject2 toggle = uiObjects.get(0); 575 logUiObject(toggle); 576 UiObject2 text = findSiblingTextObject(toggle); 577 if (text != null) { 578 sToggleLabel = text.getText(); 579 clickAndWaitForNewWindow(text); 580 return; 581 } 582 clickAndWaitForNewWindow(toggle); 583 return; 584 } 585 586 UiObject2 text = null; 587 for (int i = 0; i < uiObjects.size(); i++) { 588 UiObject2 toggle = uiObjects.get(i); 589 text = findSiblingTextObject(toggle); 590 if (text != null) { 591 break; 592 } 593 } 594 if (text != null) { 595 sToggleLabel = text.getText(); 596 clickAndWaitForNewWindow(text); 597 } else { 598 fail("Do NOT find the suitable toggle to grant permission!"); 599 } 600 } 601 602 /** 603 * Exit the grant permission settings and wait for it to disappear. 604 */ exitGrantPermissionSettings()605 public static void exitGrantPermissionSettings() { 606 pressBack(); 607 waitForUiIdle(); 608 if (sToggleLabel != null) { 609 // wait for exiting the grant permission settings 610 waitUntilObjectGone(By.text(sToggleLabel)); 611 } 612 } 613 614 /** 615 * Touch outside of the PackageInstaller dialog. 616 */ touchOutside()617 public static void touchOutside() { 618 DisplayMetrics displayMetrics = sContext.getResources().getDisplayMetrics(); 619 sUiDevice.click(displayMetrics.widthPixels / 3, displayMetrics.heightPixels / 10); 620 waitForUiIdle(); 621 } 622 waitForInstallingDialogGone()623 private static void waitForInstallingDialogGone() { 624 BySelector installingSelector = 625 getPackageInstallerBySelector(By.textContains(INSTALLING_LABEL)); 626 UiObject2 installing = sUiDevice.findObject(installingSelector); 627 if (installing != null) { 628 waitUntilObjectGone(installingSelector); 629 } 630 } 631 632 @Nullable findSiblingTextObject(@onNull UiObject2 uiObject)633 private static UiObject2 findSiblingTextObject(@NonNull UiObject2 uiObject) { 634 UiObject2 parent = uiObject.getParent(); 635 if (parent == null) { 636 return null; 637 } 638 639 // If the child count is 1, it means the parent object only has the uiObject. 640 // Try to find the parent's parent that has more than two children. 641 while (parent.getChildCount() <= 1) { 642 parent = parent.getParent(); 643 if (parent == null) { 644 return null; 645 } 646 } 647 648 // Find all TextViews to match the label 649 final List<UiObject2> uiObjects = parent.findObjects(By.clazz(TEXTVIEW_WIDGET_CLASSNAME)); 650 Log.d(TAG, "The count of findSiblingTextObject objects is " + uiObjects.size()); 651 for (int i = 0; i < uiObjects.size(); i++) { 652 UiObject2 uiObject2 = uiObjects.get(i); 653 if (uiObject2 != null) { 654 logUiObject(uiObject2); 655 if (uiObject2.getText() != null) { 656 String label = uiObject2.getText().toLowerCase(Locale.ROOT); 657 if (label.contains(TOGGLE_ALLOW_FROM_LABEL) 658 || label.contains(TOGGLE_ALLOW_PERMISSION_LABEL) 659 || label.contains(TOGGLE_INSTALL_UNKNOWN_APPS_LABEL) 660 || label.contains(TOGGLE_ALLOW_LABEL)) { 661 return uiObject2; 662 } 663 } 664 } 665 } 666 return null; 667 } 668 logUiObject(@onNull UiObject2 uiObject)669 private static void logUiObject(@NonNull UiObject2 uiObject) { 670 Log.d(TAG, "Found bounds: " + uiObject.getVisibleBounds() 671 + " of object: " + uiObject + ", text: " + uiObject.getText() 672 + ", package: " + uiObject.getApplicationPackage() + ", className: " 673 + uiObject.getClassName()); 674 } 675 getPackageInstallerBySelector(BySelector bySelector)676 private static BySelector getPackageInstallerBySelector(BySelector bySelector) { 677 return bySelector.pkg(sPackageInstallerPackageName); 678 } 679 findPackageInstallerObject(String name)680 private static UiObject2 findPackageInstallerObject(String name) { 681 final Pattern namePattern = Pattern.compile(name, Pattern.CASE_INSENSITIVE); 682 return findPackageInstallerObject(By.text(namePattern), /* checkNull= */ true); 683 } 684 findPackageInstallerObject(BySelector bySelector, boolean checkNull)685 private static UiObject2 findPackageInstallerObject(BySelector bySelector, boolean checkNull) { 686 return findObject(getPackageInstallerBySelector(bySelector), checkNull); 687 } 688 findObject(String name)689 private static UiObject2 findObject(String name) { 690 final Pattern namePattern = Pattern.compile(name, Pattern.CASE_INSENSITIVE); 691 return findObject(By.text(namePattern), /* checkNull= */ true); 692 } 693 694 @Nullable findObject(BySelector bySelector, boolean checkNull)695 private static UiObject2 findObject(BySelector bySelector, boolean checkNull) { 696 return findObject(bySelector, checkNull, FIND_OBJECT_TIMEOUT_MS); 697 } 698 699 @Nullable findObject(BySelector bySelector, boolean checkNull, long timeoutMs)700 private static UiObject2 findObject(BySelector bySelector, boolean checkNull, long timeoutMs) { 701 waitForUiIdle(); 702 703 UiObject2 object = null; 704 long startTime = System.currentTimeMillis(); 705 while (startTime + timeoutMs > System.currentTimeMillis()) { 706 try { 707 object = sUiDevice.wait(Until.findObject(bySelector), /* timeout= */ 10 * 1000); 708 if (object != null) { 709 Log.d(TAG, "Found bounds: " + object.getVisibleBounds() 710 + " of object: " + bySelector + ", text: " + object.getText() 711 + " package: " + object.getApplicationPackage()); 712 return object; 713 } else { 714 // Maybe the screen is small. Scroll forward and attempt to click 715 new UiScrollable(new UiSelector().scrollable(true)).scrollForward(); 716 } 717 } catch (Exception ignored) { 718 // do nothing 719 } 720 } 721 if (checkNull) { 722 assertWithMessage("Can't find object " + bySelector).that(object).isNotNull(); 723 } 724 return object; 725 } 726 waitUntilObjectGone(BySelector bySelector)727 private static void waitUntilObjectGone(BySelector bySelector) { 728 if (!sUiDevice.wait(Until.gone(bySelector), WAIT_OBJECT_GONE_TIMEOUT_MS)) { 729 fail("The Object: " + bySelector + "did not disappear within " 730 + WAIT_OBJECT_GONE_TIMEOUT_MS + " milliseconds"); 731 } 732 waitForUiIdle(); 733 } 734 uninstallPackage(String packageName)735 private static void uninstallPackage(String packageName) { 736 SystemUtil.runShellCommand(String.format("pm uninstall %s", packageName)); 737 } 738 uninstallTestPackage()739 private static void uninstallTestPackage() { 740 uninstallPackage(TEST_APK_PACKAGE_NAME); 741 } 742 uninstallInstallerPackage()743 private static void uninstallInstallerPackage() { 744 uninstallPackage(TEST_INSTALLER_PACKAGE_NAME); 745 } 746 747 /** 748 * Install the test apk. 749 */ installTestPackage()750 public static void installTestPackage() throws IOException { 751 installPackage(TEST_APK_NAME); 752 } 753 installPackage(@onNull String apkName)754 private static void installPackage(@NonNull String apkName) throws IOException { 755 Log.d(TAG, "installPackage(): apkName= " + apkName); 756 SystemUtil.runShellCommand("pm install -t " 757 + new File(TEST_APK_LOCATION, apkName).getCanonicalPath()); 758 } 759 isTestPackageInstalled()760 private static boolean isTestPackageInstalled() { 761 return isInstalled(TEST_APK_PACKAGE_NAME); 762 } 763 isInstallerInstalled()764 private static boolean isInstallerInstalled() { 765 return isInstalled(TEST_INSTALLER_PACKAGE_NAME); 766 } 767 isInstalled(@onNull String packageName)768 private static boolean isInstalled(@NonNull String packageName) { 769 Log.d(TAG, "Testing if package " + packageName + " is installed for user " 770 + sContext.getUser()); 771 try { 772 sPackageManager.getPackageInfo(packageName, /* flags= */ 0); 773 return true; 774 } catch (PackageManager.NameNotFoundException e) { 775 Log.v(TAG, "Package " + packageName + " not installed for user " 776 + sContext.getUser() + ": " + e); 777 return false; 778 } 779 } 780 isTestPackageLabelV2Installed()781 private static boolean isTestPackageLabelV2Installed() { 782 return isInstalledAndVerifyAppName(TEST_APK_PACKAGE_NAME, TEST_APK_V2_LABEL); 783 } 784 isInstalledAndVerifyAppName(@onNull String packageName, @NonNull String expectedAppLabel)785 private static boolean isInstalledAndVerifyAppName(@NonNull String packageName, 786 @NonNull String expectedAppLabel) { 787 Log.d(TAG, "Testing if package " + packageName + " is installed for user " 788 + sContext.getUser() + ", with app label " + expectedAppLabel); 789 try { 790 PackageInfo packageInfo = sPackageManager.getPackageInfo( 791 packageName, /* flags= */ 0); 792 CharSequence appLabel = packageInfo.applicationInfo.loadLabel(sPackageManager); 793 return TextUtils.equals(appLabel, expectedAppLabel); 794 } catch (PackageManager.NameNotFoundException e) { 795 Log.v(TAG, "Package " + packageName + " not installed for user " 796 + sContext.getUser() + ": " + e); 797 return false; 798 } 799 } 800 801 private static class InstallerResponseReceiver extends BroadcastReceiver { 802 private CompletableFuture<Integer> mInstallerResponseResult = new CompletableFuture<>(); 803 804 @Override onReceive(Context context, Intent intent)805 public void onReceive(Context context, Intent intent) { 806 final int status = intent.getIntExtra(EXTRA_STATUS, -1); 807 Log.i(TAG, "InstallerResponseReceiver received status: " + status); 808 mInstallerResponseResult.complete(status); 809 } 810 unregisterReceiver(Context context)811 public void unregisterReceiver(Context context) { 812 context.unregisterReceiver(this); 813 } getInstallerResponseResult()814 public int getInstallerResponseResult() throws Exception { 815 return mInstallerResponseResult.get(10, TimeUnit.SECONDS); 816 } 817 resetResult()818 public void resetResult() { 819 mInstallerResponseResult = new CompletableFuture(); 820 } 821 } 822 823 @Nullable getPackageInstallerPackageName()824 private static String getPackageInstallerPackageName() { 825 final Intent intent = new Intent( 826 Intent.ACTION_INSTALL_PACKAGE).setData(Uri.parse("content:")); 827 final ResolveInfo ri = sPackageManager.resolveActivity(intent, /* flags= */ 0); 828 return ri != null ? ri.activityInfo.packageName : null; 829 } 830 isNotSupportedDevice()831 private static boolean isNotSupportedDevice() { 832 return FeatureUtil.isArc() 833 || FeatureUtil.isAutomotive() 834 || FeatureUtil.isTV() 835 || FeatureUtil.isWatch() 836 || FeatureUtil.isVrHeadset(); 837 } 838 } 839