• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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