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_OK
21 import android.content.Intent
22 import android.content.pm.InstallSourceInfo
23 import android.net.Uri
24 import android.platform.test.annotations.AppModeFull
25 import android.platform.test.annotations.RequiresFlagsDisabled
26 import android.platform.test.flag.junit.CheckFlagsRule
27 import android.platform.test.flag.junit.DeviceFlagsValueProvider
28 import android.platform.test.rule.ScreenRecordRule.ScreenRecord
29 import androidx.test.runner.AndroidJUnit4
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.compatibility.common.util.SystemUtil
36 import com.android.xts.root.annotations.RequireAdbRoot
37 import java.util.concurrent.TimeUnit
38 import org.junit.After
39 import org.junit.Assert.assertEquals
40 import org.junit.Assert.assertNotNull
41 import org.junit.Assert.assertNull
42 import org.junit.ClassRule
43 import org.junit.Ignore
44 import org.junit.Rule
45 import org.junit.Test
46 import org.junit.runner.RunWith
47 
48 @RunWith(AndroidJUnit4::class)
49 @AppModeFull(reason = "Instant apps cannot install packages")
50 @ScreenRecord
51 class IntentTest : PackageInstallerTestBase() {
52 
53     @get:Rule
54     val checkFlagsRule: CheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule()
55 
56     companion object {
57         // An invalid package name that exceeds the maximum file name length.
58         const val LONG_PACKAGE_NAME = "android.packageinstaller.install.cts.invalidname." +
59                 "27jEBRNRG3ozwBsGr1sVIM9U0bVTI2TdyIyeRkZgW4JrJefwNIBAmCg4AzqXiCvG6JjqA0u" +
60                 "TCWSFu2YqAVxVdiRKAay19k5VFlSaM7QW9uhvlrLQqsTW01ofFzxNDbp2QfIFHZR6rebKzK" +
61                 "Bz6byQFM0DYQnYMwFWXjWkMPNdqkRLykoFLyBup53G68k2n8wl27jEBRNRG3ozwBsGr"
62         const val NO_INSTALL_APPS_RESTRICTION_TEXT = "This user is not allowed to install apps"
63         const val DISABLED_LAUNCHER_ACTIVITY_PKG_NAME =
64                 "android.packageinstaller.disabledlauncheractivity.cts"
65         const val INSTALL_SUCCESS_TEXT = "App installed."
66 
67         @JvmField
68         @ClassRule
69         @Rule
70         val deviceState = DeviceState()
71     }
72 
73     @After
disableSecureFrpnull74     fun disableSecureFrp() {
75         setSecureFrp(false)
76     }
77 
78     /**
79      * Check that we can install an app via a package-installer intent
80      */
81     @Test
confirmInstallationnull82     fun confirmInstallation() {
83         val installation = startInstallationViaIntent()
84         clickInstallerUIButton(INSTALL_BUTTON_ID)
85 
86         // Install should have succeeded
87         assertEquals(RESULT_OK, installation.get(GLOBAL_TIMEOUT, TimeUnit.MILLISECONDS))
88         assertInstalled()
89         var originatingPackageName: String? = null
90         SystemUtil.runWithShellPermissionIdentity(
91             { originatingPackageName = getInstallSourceInfo().originatingPackageName },
92             Manifest.permission.INSTALL_PACKAGES
93         )
94         assertNotNull(originatingPackageName)
95         assertEquals(context.packageName, originatingPackageName)
96     }
97 
98     /**
99      * Install an app via a package-installer intent, but then cancel it when the package installer
100      * pops open.
101      */
102     @Test
cancelInstallationnull103     fun cancelInstallation() {
104         val installation = startInstallationViaIntent()
105         clickInstallerUIButton(CANCEL_BUTTON_ID)
106 
107         // Install should have been aborted
108         assertEquals(RESULT_CANCELED, installation.get(GLOBAL_TIMEOUT, TimeUnit.MILLISECONDS))
109         assertNotInstalled()
110     }
111 
112     /**
113      * Install an app via a package-installer intent, and assign itself as the installer.
114      */
115     @Test
installWithCallingInstallerPackageNamenull116     fun installWithCallingInstallerPackageName() {
117         val intent = getInstallationIntent()
118         intent.putExtra(Intent.EXTRA_INSTALLER_PACKAGE_NAME, context.opPackageName)
119         val installation = startInstallationViaIntent(intent)
120         clickInstallerUIButton(INSTALL_BUTTON_ID)
121 
122         // Install should have succeeded, and system will use the given installer package name
123         // in EXTRA_INSTALLER_PACKAGE_NAME as the installer.
124         assertEquals(RESULT_OK, installation.get(GLOBAL_TIMEOUT, TimeUnit.MILLISECONDS))
125         assertEquals(context.opPackageName, getInstallSourceInfo().installingPackageName)
126     }
127 
128     /**
129      * Install an app via a package-installer intent, but assign another package as installer
130      * package name.
131      */
132     @Ignore("b/317736655")
133     @Test
installWithAnotherInstallerPackageNamenull134     fun installWithAnotherInstallerPackageName() {
135         val intent = getInstallationIntent()
136         intent.putExtra(Intent.EXTRA_INSTALLER_PACKAGE_NAME, context.opPackageName + ".another")
137         val installation = startInstallationViaIntent(intent)
138         clickInstallerUIButton(INSTALL_BUTTON_ID)
139 
140         // Install should have succeeded, but system won't use the given installer package name
141         // in EXTRA_INSTALLER_PACKAGE_NAME as the installer.
142         assertEquals(RESULT_OK, installation.get(GLOBAL_TIMEOUT, TimeUnit.MILLISECONDS))
143         assertEquals(
144             getInstallSourceInfo().initiatingPackageName,
145                 getInstallSourceInfo().installingPackageName
146         )
147     }
148 
149     /**
150      * Install an app via a package-installer intent, but assign an invalid installer
151      * package name which exceeds the maximum file name length.
152      */
153     @Test
installWithLongInstallerPackageNamenull154     fun installWithLongInstallerPackageName() {
155         val intent = getInstallationIntent()
156         intent.putExtra(Intent.EXTRA_INSTALLER_PACKAGE_NAME, LONG_PACKAGE_NAME)
157         val installation = startInstallationViaIntent(intent)
158         clickInstallerUIButton(INSTALL_BUTTON_ID)
159 
160         // Install should have succeeded, but system won't use the given installer package name
161         // in EXTRA_INSTALLER_PACKAGE_NAME as the installer.
162         assertEquals(RESULT_OK, installation.get(GLOBAL_TIMEOUT, TimeUnit.MILLISECONDS))
163         assertEquals(
164             getInstallSourceInfo().initiatingPackageName,
165                 getInstallSourceInfo().installingPackageName
166         )
167     }
168 
169     /**
170      * Make sure that an already installed app can be reinstalled via a "package" uri
171      */
172     @Test
reinstallViaPackageUrinull173     fun reinstallViaPackageUri() {
174         // Regular install
175         confirmInstallation()
176 
177         // Reinstall
178         val intent = Intent(Intent.ACTION_INSTALL_PACKAGE)
179         intent.data = Uri.fromParts("package", TEST_APK_PACKAGE_NAME, null)
180         intent.putExtra(Intent.EXTRA_RETURN_RESULT, true)
181         intent.flags = Intent.FLAG_GRANT_READ_URI_PERMISSION
182 
183         val reinstall = installDialogStarter.activity.startActivityForResult(intent)
184 
185         clickInstallerUIButton(INSTALL_BUTTON_ID)
186 
187         // Install should have succeeded
188         assertEquals(RESULT_OK, reinstall.get(GLOBAL_TIMEOUT, TimeUnit.MILLISECONDS))
189         assertInstalled()
190     }
191 
192     /**
193      * Check that we can't install an app via a package-installer intent if Secure FRP is enabled
194      */
195     @Test
196     @RequiresFlagsDisabled(android.security.Flags.FLAG_FRP_ENFORCEMENT)
packageNotInstalledSecureFrpnull197     fun packageNotInstalledSecureFrp() {
198         setSecureFrp(true)
199         try {
200             val installation = startInstallationViaIntent()
201             clickInstallerUIButton(INSTALL_BUTTON_ID)
202 
203             // Install should not have succeeded
204             assertNotInstalled()
205         } finally {
206             setSecureFrp(false)
207         }
208     }
209 
210     @Test
211     @RequireAdbRoot(reason = "b/322830652 Required for TestApis to set user restriction")
disallowInstallApps_installFailsnull212     fun disallowInstallApps_installFails() {
213         try {
214             TestApis.devicePolicy().userRestrictions().set(DISALLOW_INSTALL_APPS, true)
215 
216             val installation = startInstallationViaIntent()
217 
218             assertNotNull(
219                 "Error dialog not shown",
220                 uiDevice.wait(
221                     Until.findObject(By.text(NO_INSTALL_APPS_RESTRICTION_TEXT)),
222                     GLOBAL_TIMEOUT
223                 )
224             )
225             clickInstallerUIButton(INSTALL_BUTTON_ID)
226 
227             assertEquals(RESULT_CANCELED, installation.get(GLOBAL_TIMEOUT, TimeUnit.MILLISECONDS))
228         } finally {
229             TestApis.devicePolicy().userRestrictions().set(DISALLOW_INSTALL_APPS, false)
230         }
231     }
232 
233     @Test
launcherActivityDisabled_cannotLaunchAppnull234     fun launcherActivityDisabled_cannotLaunchApp() {
235         val intent = Intent(Intent.ACTION_INSTALL_PACKAGE)
236         intent.data = Uri.fromParts("package", DISABLED_LAUNCHER_ACTIVITY_PKG_NAME, null)
237         intent.putExtra(Intent.EXTRA_RETURN_RESULT, false)
238         intent.flags = Intent.FLAG_GRANT_READ_URI_PERMISSION
239 
240         startInstallationViaIntent(intent)
241         clickInstallerUIButton(INSTALL_BUTTON_ID)
242 
243         // Wait for success dialog
244         assertNotNull(
245             "Success dialog not shown",
246             uiDevice.wait(Until.findObject(By.text(INSTALL_SUCCESS_TEXT)), GLOBAL_TIMEOUT)
247         )
248 
249         // Since the dialog is already visible, no need to wait for long for the "Open" button.
250         assertNull(
251             "Open button should not be shown",
252             uiDevice.wait(Until.findObject(getBySelector(INSTALL_BUTTON_ID)), 5000)
253         )
254     }
255 
256     @Test
installLowTargetSdkApp_installFailedVisiblenull257     fun installLowTargetSdkApp_installFailedVisible() {
258         // We want the InstallFailed dialog to be visible. Thus, pass EXTRA_RETURN_RESULT as false
259         val installIntent = getInstallationIntent(TEST_LOW_TARGET_SDK_APK_NAME)
260         installIntent.putExtra(Intent.EXTRA_RETURN_RESULT, false)
261 
262         val installation = startInstallationViaIntent(installIntent)
263         clickInstallerUIButton(INSTALL_BUTTON_ID)
264 
265         // GPP dialog should be shown. Clicking on "Got it" should cancel the installation
266         val gppDefaultBtn = uiDevice.wait(Until.findObject(By.text("Got it")), FIND_OBJECT_TIMEOUT)
267         if (gppDefaultBtn != null) {
268             gppDefaultBtn.click()
269         }
270 
271         // Click the positive button on the InstallFailed dialog
272         clickInstallerUIButton(INSTALL_BUTTON_ID)
273 
274         assertEquals(RESULT_CANCELED, installation.get(GLOBAL_TIMEOUT, TimeUnit.MILLISECONDS))
275         assertNotInstalled(TEST_LOW_TARGET_SDK_APK_PACKAGE_NAME)
276     }
277 
getInstallSourceInfonull278     private fun getInstallSourceInfo(): InstallSourceInfo {
279         return pm.getInstallSourceInfo(TEST_APK_PACKAGE_NAME)
280     }
281 }
282