1 /* 2 * Copyright (C) 2018 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.packageinstaller.install.cts 17 18 import android.Manifest 19 import android.app.Activity.RESULT_CANCELED 20 import android.app.Activity.RESULT_FIRST_USER 21 import android.app.Activity.RESULT_OK 22 import android.content.Intent 23 import android.content.pm.InstallSourceInfo 24 import android.net.Uri 25 import android.platform.test.annotations.AppModeFull 26 import android.platform.test.annotations.RequiresFlagsDisabled 27 import android.platform.test.flag.junit.CheckFlagsRule 28 import android.platform.test.flag.junit.DeviceFlagsValueProvider 29 import android.platform.test.rule.ScreenRecordRule.ScreenRecord 30 import androidx.test.uiautomator.By 31 import androidx.test.uiautomator.Until 32 import com.android.bedstead.harrier.DeviceState 33 import com.android.bedstead.nene.TestApis 34 import com.android.bedstead.nene.userrestrictions.CommonUserRestrictions.DISALLOW_INSTALL_APPS 35 import com.android.bedstead.nene.userrestrictions.CommonUserRestrictions.DISALLOW_INSTALL_UNKNOWN_SOURCES 36 import com.android.compatibility.common.util.SystemUtil 37 import com.android.xts.root.annotations.RequireAdbRoot 38 import com.google.testing.junit.testparameterinjector.TestParameterInjector 39 import java.util.concurrent.TimeUnit 40 import org.junit.After 41 import org.junit.Assert.assertEquals 42 import org.junit.Assert.assertNotNull 43 import org.junit.Assert.assertNull 44 import org.junit.ClassRule 45 import org.junit.Ignore 46 import org.junit.Rule 47 import org.junit.Test 48 import org.junit.runner.RunWith 49 50 @RunWith(TestParameterInjector::class) 51 @AppModeFull(reason = "Instant apps cannot install packages") 52 @ScreenRecord 53 class IntentTest : PackageInstallerTestBase() { 54 55 @get:Rule 56 val checkFlagsRule: CheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule() 57 58 companion object { 59 // An invalid package name that exceeds the maximum file name length. 60 const val LONG_PACKAGE_NAME = "android.packageinstaller.install.cts.invalidname." + 61 "27jEBRNRG3ozwBsGr1sVIM9U0bVTI2TdyIyeRkZgW4JrJefwNIBAmCg4AzqXiCvG6JjqA0u" + 62 "TCWSFu2YqAVxVdiRKAay19k5VFlSaM7QW9uhvlrLQqsTW01ofFzxNDbp2QfIFHZR6rebKzK" + 63 "Bz6byQFM0DYQnYMwFWXjWkMPNdqkRLykoFLyBup53G68k2n8wl27jEBRNRG3ozwBsGr" 64 const val NO_INSTALL_APPS_RESTRICTION_TEXT = "This user is not allowed to install apps" 65 const val DISABLED_LAUNCHER_ACTIVITY_PKG_NAME = 66 "android.packageinstaller.disabledlauncheractivity.cts" 67 const val INSTALL_SUCCESS_TEXT = "App installed." 68 const val TEST_VERIFIER_APK_NAME = "CtsSufficientVerifierReject.apk" 69 const val TEST_VERIFIER_PACKAGE_NAME = "android.packageinstaller.sufficientverifierreject" 70 const val TEST_REJECTED_BY_VERIFIER_APK_NAME = "CtsEmptyTestApp_RejectedByVerifier.apk" 71 const val TEST_REJECTED_BY_VERIFIER_PACKAGE_NAME = 72 "android.packageinstaller.emptytestapp.rejectedbyverifier.cts" 73 const val TEST_APK_V2_NAME = "CtsEmptyTestAppV2.apk" 74 75 @JvmField 76 @ClassRule 77 @Rule 78 val deviceState = DeviceState() 79 } 80 81 @After disableSecureFrpnull82 fun disableSecureFrp() { 83 setSecureFrp(false) 84 } 85 86 /** 87 * Check that we can install an app via a package-installer intent 88 */ 89 @Test confirmInstallationnull90 fun confirmInstallation() { 91 val installation = startInstallationViaIntent() 92 clickInstallerUIButton(INSTALL_BUTTON_ID) 93 94 // Install should have succeeded 95 assertEquals(RESULT_OK, installation.get(GLOBAL_TIMEOUT, TimeUnit.MILLISECONDS)) 96 assertInstalled() 97 var originatingPackageName: String? = null 98 SystemUtil.runWithShellPermissionIdentity( 99 { originatingPackageName = getInstallSourceInfo().originatingPackageName }, 100 Manifest.permission.INSTALL_PACKAGES 101 ) 102 assertNotNull(originatingPackageName) 103 assertEquals(context.packageName, originatingPackageName) 104 } 105 106 /** 107 * Install an app via a package-installer intent, but then cancel it when the package installer 108 * pops open. 109 */ 110 @Test cancelInstallationnull111 fun cancelInstallation() { 112 val installation = startInstallationViaIntent() 113 clickInstallerUIButton(CANCEL_BUTTON_ID) 114 115 // Install should have been aborted 116 assertEquals(RESULT_CANCELED, installation.get(GLOBAL_TIMEOUT, TimeUnit.MILLISECONDS)) 117 assertNotInstalled() 118 } 119 120 @Test failedInstallation_requireFailureDialognull121 fun failedInstallation_requireFailureDialog() { 122 installPackage(TEST_APK_V2_NAME) 123 124 // Install a lower version of the same app to trigger an install failure 125 // We want the InstallFailed dialog to be visible. Thus, pass EXTRA_RETURN_RESULT as false 126 val intent = getInstallationIntent() 127 intent.putExtra(Intent.EXTRA_RETURN_RESULT, false) 128 val installation = startInstallationViaIntent(intent) 129 130 clickInstallerUIButton(INSTALL_BUTTON_ID) 131 132 // Click the positive button on InstallFailed dialog. 133 clickInstallerUIButton(INSTALL_BUTTON_ID) 134 135 assertEquals(RESULT_CANCELED, installation.get(GLOBAL_TIMEOUT, TimeUnit.MILLISECONDS)) 136 } 137 138 @Test failedInstallation_noRequireFailureDialognull139 fun failedInstallation_noRequireFailureDialog() { 140 // The InstallFailed dialog isn't shown here as the default intent used by 141 // startInstallationViaIntent contains EXTRA_RETURN_RESULT set to true 142 143 installPackage(TEST_APK_V2_NAME) 144 145 val installation = startInstallationViaIntent() 146 clickInstallerUIButton(INSTALL_BUTTON_ID) 147 148 assertEquals(RESULT_FIRST_USER, installation.get(GLOBAL_TIMEOUT, TimeUnit.MILLISECONDS)) 149 } 150 151 /** 152 * Install an app via a package-installer intent, and assign itself as the installer. 153 */ 154 @Test installWithCallingInstallerPackageNamenull155 fun installWithCallingInstallerPackageName() { 156 val intent = getInstallationIntent() 157 intent.putExtra(Intent.EXTRA_INSTALLER_PACKAGE_NAME, context.opPackageName) 158 val installation = startInstallationViaIntent(intent) 159 clickInstallerUIButton(INSTALL_BUTTON_ID) 160 161 // Install should have succeeded, and system will use the given installer package name 162 // in EXTRA_INSTALLER_PACKAGE_NAME as the installer. 163 assertEquals(RESULT_OK, installation.get(GLOBAL_TIMEOUT, TimeUnit.MILLISECONDS)) 164 assertEquals(context.opPackageName, getInstallSourceInfo().installingPackageName) 165 } 166 167 /** 168 * Install an app via a package-installer intent, but assign another package as installer 169 * package name. 170 */ 171 @Ignore("b/317736655") 172 @Test installWithAnotherInstallerPackageNamenull173 fun installWithAnotherInstallerPackageName() { 174 val intent = getInstallationIntent() 175 intent.putExtra(Intent.EXTRA_INSTALLER_PACKAGE_NAME, context.opPackageName + ".another") 176 val installation = startInstallationViaIntent(intent) 177 clickInstallerUIButton(INSTALL_BUTTON_ID) 178 179 // Install should have succeeded, but system won't use the given installer package name 180 // in EXTRA_INSTALLER_PACKAGE_NAME as the installer. 181 assertEquals(RESULT_OK, installation.get(GLOBAL_TIMEOUT, TimeUnit.MILLISECONDS)) 182 assertEquals( 183 getInstallSourceInfo().initiatingPackageName, 184 getInstallSourceInfo().installingPackageName 185 ) 186 } 187 188 /** 189 * Install an app via a package-installer intent, but assign an invalid installer 190 * package name which exceeds the maximum file name length. 191 */ 192 @Test installWithLongInstallerPackageNamenull193 fun installWithLongInstallerPackageName() { 194 val intent = getInstallationIntent() 195 intent.putExtra(Intent.EXTRA_INSTALLER_PACKAGE_NAME, LONG_PACKAGE_NAME) 196 val installation = startInstallationViaIntent(intent) 197 clickInstallerUIButton(INSTALL_BUTTON_ID) 198 199 // Install should have succeeded, but system won't use the given installer package name 200 // in EXTRA_INSTALLER_PACKAGE_NAME as the installer. 201 assertEquals(RESULT_OK, installation.get(GLOBAL_TIMEOUT, TimeUnit.MILLISECONDS)) 202 assertEquals( 203 getInstallSourceInfo().initiatingPackageName, 204 getInstallSourceInfo().installingPackageName 205 ) 206 } 207 208 /** 209 * Make sure that an already installed app can be reinstalled via a "package" uri 210 */ 211 @Test reinstallViaPackageUrinull212 fun reinstallViaPackageUri() { 213 // Regular install 214 confirmInstallation() 215 216 // Reinstall 217 val intent = Intent(Intent.ACTION_INSTALL_PACKAGE) 218 intent.data = Uri.fromParts("package", TEST_APK_PACKAGE_NAME, null) 219 intent.putExtra(Intent.EXTRA_RETURN_RESULT, true) 220 intent.flags = Intent.FLAG_GRANT_READ_URI_PERMISSION 221 222 val reinstall = installDialogStarter.activity.startActivityForResult(intent) 223 224 clickInstallerUIButton(INSTALL_BUTTON_ID) 225 226 // Install should have succeeded 227 assertEquals(RESULT_OK, reinstall.get(GLOBAL_TIMEOUT, TimeUnit.MILLISECONDS)) 228 assertInstalled() 229 } 230 231 /** 232 * Check that we can't install an app via a package-installer intent if Secure FRP is enabled 233 */ 234 @Test 235 @RequiresFlagsDisabled(android.security.Flags.FLAG_FRP_ENFORCEMENT) packageNotInstalledSecureFrpnull236 fun packageNotInstalledSecureFrp() { 237 setSecureFrp(true) 238 try { 239 val installation = startInstallationViaIntent() 240 clickInstallerUIButton(INSTALL_BUTTON_ID) 241 242 // Install should not have succeeded 243 assertNotInstalled() 244 } finally { 245 setSecureFrp(false) 246 } 247 } 248 249 @Test 250 @RequireAdbRoot(reason = "b/322830652 Required for TestApis to set user restriction") disallowInstallApps_installFailsnull251 fun disallowInstallApps_installFails() { 252 try { 253 TestApis.devicePolicy().userRestrictions().set(DISALLOW_INSTALL_APPS, true) 254 255 val installation = startInstallationViaIntent() 256 257 assertNotNull( 258 "Error dialog not shown", 259 uiDevice.wait( 260 Until.findObject(By.text(NO_INSTALL_APPS_RESTRICTION_TEXT)), 261 GLOBAL_TIMEOUT 262 ) 263 ) 264 clickInstallerUIButton(INSTALL_BUTTON_ID) 265 266 assertEquals(RESULT_CANCELED, installation.get(GLOBAL_TIMEOUT, TimeUnit.MILLISECONDS)) 267 } finally { 268 TestApis.devicePolicy().userRestrictions().set(DISALLOW_INSTALL_APPS, false) 269 } 270 } 271 272 @Test 273 @RequireAdbRoot(reason = "b/322830652 Required for TestApis to set user restriction") disallowInstallApps_installFromTrustedSource_installFailsnull274 fun disallowInstallApps_installFromTrustedSource_installFails() { 275 try { 276 TestApis.devicePolicy().userRestrictions().set(DISALLOW_INSTALL_APPS, true) 277 278 instrumentation.uiAutomation.adoptShellPermissionIdentity( 279 Manifest.permission.INSTALL_PACKAGES 280 ) 281 var installation = startInstallationViaIntent() 282 283 assertNotNull( 284 "Error dialog not shown", 285 uiDevice.wait( 286 Until.findObject(By.text(NO_INSTALL_APPS_RESTRICTION_TEXT)), 287 GLOBAL_TIMEOUT 288 ) 289 ) 290 clickInstallerUIButton(INSTALL_BUTTON_ID) 291 292 assertEquals(RESULT_CANCELED, installation.get(GLOBAL_TIMEOUT, TimeUnit.MILLISECONDS)) 293 } finally { 294 TestApis.devicePolicy().userRestrictions().set(DISALLOW_INSTALL_APPS, false) 295 instrumentation.uiAutomation.dropShellPermissionIdentity() 296 } 297 } 298 299 @Test 300 @RequireAdbRoot(reason = "b/322830652 Required for TestApis to set user restriction") disallowInstallUnknownSources_installFromTrustedSource_installSucceedsnull301 fun disallowInstallUnknownSources_installFromTrustedSource_installSucceeds() { 302 try { 303 TestApis.devicePolicy().userRestrictions().set(DISALLOW_INSTALL_UNKNOWN_SOURCES, true) 304 305 instrumentation.uiAutomation.adoptShellPermissionIdentity( 306 Manifest.permission.INSTALL_PACKAGES 307 ) 308 var installation = startInstallationViaIntent() 309 310 clickInstallerUIButton(INSTALL_BUTTON_ID) 311 312 assertEquals(RESULT_OK, installation.get(GLOBAL_TIMEOUT, TimeUnit.MILLISECONDS)) 313 } finally { 314 TestApis.devicePolicy().userRestrictions().set(DISALLOW_INSTALL_UNKNOWN_SOURCES, false) 315 instrumentation.uiAutomation.dropShellPermissionIdentity() 316 } 317 } 318 319 @Test launcherActivityDisabled_cannotLaunchAppnull320 fun launcherActivityDisabled_cannotLaunchApp() { 321 val intent = Intent(Intent.ACTION_INSTALL_PACKAGE) 322 intent.data = Uri.fromParts("package", DISABLED_LAUNCHER_ACTIVITY_PKG_NAME, null) 323 intent.putExtra(Intent.EXTRA_RETURN_RESULT, false) 324 intent.flags = Intent.FLAG_GRANT_READ_URI_PERMISSION 325 326 startInstallationViaIntent(intent) 327 clickInstallerUIButton(INSTALL_BUTTON_ID) 328 329 // Wait for success dialog 330 assertNotNull( 331 "Success dialog not shown", 332 uiDevice.wait(Until.findObject(By.text(INSTALL_SUCCESS_TEXT)), GLOBAL_TIMEOUT) 333 ) 334 335 // Since the dialog is already visible, no need to wait for long for the "Open" button. 336 assertNull( 337 "Open button should not be shown", 338 uiDevice.wait(Until.findObject(getBySelector(INSTALL_BUTTON_ID)), 5000) 339 ) 340 } 341 342 /** 343 * Using a sufficient verifier, test whether InstallFailed dialog is shown when the sufficient 344 * verifier rejects installation of a test app. 345 */ 346 @Test installRejectedByVerifier_installFailedVisiblenull347 fun installRejectedByVerifier_installFailedVisible() { 348 uninstallPackage(TEST_VERIFIER_PACKAGE_NAME) 349 uninstallPackage(TEST_REJECTED_BY_VERIFIER_PACKAGE_NAME) 350 351 installPackage(TEST_VERIFIER_APK_NAME) 352 assertInstalled(TEST_VERIFIER_PACKAGE_NAME) 353 354 // We want the InstallFailed dialog to be visible. Thus, pass EXTRA_RETURN_RESULT as false 355 val installIntent = getInstallationIntent(TEST_REJECTED_BY_VERIFIER_APK_NAME) 356 installIntent.putExtra(Intent.EXTRA_RETURN_RESULT, false) 357 358 val installation = startInstallationViaIntent(installIntent) 359 clickInstallerUIButton(INSTALL_BUTTON_ID) 360 361 // Click the positive button on the InstallFailed dialog 362 clickInstallerUIButton(INSTALL_BUTTON_ID) 363 364 assertEquals(RESULT_CANCELED, installation.get(GLOBAL_TIMEOUT, TimeUnit.MILLISECONDS)) 365 assertNotInstalled(TEST_REJECTED_BY_VERIFIER_PACKAGE_NAME) 366 } 367 getInstallSourceInfonull368 private fun getInstallSourceInfo(): InstallSourceInfo { 369 return pm.getInstallSourceInfo(TEST_APK_PACKAGE_NAME) 370 } 371 } 372