1 /*
<lambda>null2  * Copyright (C) 2023 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.install.cts
18 
19 import android.Manifest
20 import android.content.AttributionSource
21 import android.content.pm.PackageInfo
22 import android.content.pm.PackageInstaller
23 import android.content.pm.PackageInstaller.SessionParams.PERMISSION_STATE_DEFAULT
24 import android.content.pm.PackageInstaller.SessionParams.PERMISSION_STATE_DENIED
25 import android.content.pm.PackageInstaller.SessionParams.PERMISSION_STATE_GRANTED
26 import android.content.pm.PackageManager
27 import android.content.pm.PermissionInfo
28 import android.permission.PermissionManager
29 import android.platform.test.annotations.AppModeFull
30 import android.platform.test.rule.ScreenRecordRule.ScreenRecord
31 import com.android.compatibility.common.util.SystemUtil
32 import com.google.common.truth.Truth.assertThat
33 import com.google.common.truth.Truth.assertWithMessage
34 import java.io.File
35 import kotlin.test.assertFailsWith
36 import org.junit.Before
37 import org.junit.BeforeClass
38 import org.junit.Test
39 import org.junit.runner.RunWith
40 import org.junit.runners.Parameterized
41 
42 @RunWith(Parameterized::class)
43 @AppModeFull(reason = "Instant apps cannot create installer sessions")
44 @ScreenRecord
45 class SessionParamsPermissionStateTest : PackageInstallerTestBase() {
46 
47     companion object {
48         private const val FULL_SCREEN_INTENT_APK = "CtsEmptyTestApp_FullScreenIntent.apk"
49         private const val NON_EXISTENT_PERMISSION = "android.cts.NON_EXISTENT_PERMISSION"
50         private val GET_PERMISSIONS_FLAGS =
51             PackageManager.PackageInfoFlags.of(PackageManager.GET_PERMISSIONS.toLong())
52 
53         private val permissionManager = context.getSystemService(PermissionManager::class.java)!!
54 
55         private val isFsiDefaultGranted by lazy {
56             context.packageManager
57                 .getPermissionInfo(Manifest.permission.USE_FULL_SCREEN_INTENT, 0)
58                 .protection == PermissionInfo.PROTECTION_NORMAL
59         }
60 
61         @JvmStatic
62         @BeforeClass
63         fun verifyNoGrantRuntimePermission() {
64             // Ensure the test doesn't have the grant runtime permission
65             assertThat(
66                 context.checkSelfPermission(
67                     Manifest.permission.INSTALL_GRANT_RUNTIME_PERMISSIONS
68                 )
69             ).isEqualTo(PackageManager.PERMISSION_DENIED)
70         }
71 
72         @JvmStatic
73         @Parameterized.Parameters(name = "{0}")
74         fun parameters() = listOf(
75             // Check that installer is allowed to explicitly grant FSI
76             Params(
77                 name = "fullScreenIntentGranted",
78                 finalPermissionState = mapOf(Manifest.permission.USE_FULL_SCREEN_INTENT to true)
79             ) {
80                 setFinalState(
81                     Manifest.permission.USE_FULL_SCREEN_INTENT,
82                     PERMISSION_STATE_GRANTED
83                 )
84             },
85 
86             // Check that installer is allowed to explicitly deny FSI
87             Params(
88                 name = "fullScreenIntentDenied",
89                 finalPermissionState = mapOf(Manifest.permission.USE_FULL_SCREEN_INTENT to false)
90             ) {
91                 setFinalState(
92                     Manifest.permission.USE_FULL_SCREEN_INTENT,
93                     PERMISSION_STATE_DENIED
94                 )
95             },
96 
97             // Check that a vanilla session automatically grants/denies FSI to an app declaring it
98             Params(
99                 name = "fullScreenIntentDefault",
100                 finalPermissionState = mapOf(
101                     Manifest.permission.USE_FULL_SCREEN_INTENT to isFsiDefaultGranted,
102                 ),
103             ) {
104                 setFinalState(
105                     Manifest.permission.USE_FULL_SCREEN_INTENT,
106                     PERMISSION_STATE_DEFAULT
107                 )
108             },
109 
110             // Check that the installer doesn't affect an app that doesn't declare FSI
111             listOf(
112                 PERMISSION_STATE_GRANTED,
113                 PERMISSION_STATE_DENIED,
114                 PERMISSION_STATE_DEFAULT,
115             ).map {
116                 Params(
117                     name = "fullScreenIntentWithoutAppDeclaration${stateToName(it)}",
118                     success = true,
119                     testApkName = TEST_APK_NAME,
120                     finalPermissionState = mapOf(Manifest.permission.USE_FULL_SCREEN_INTENT to null)
121                 ) { setFinalState(Manifest.permission.USE_FULL_SCREEN_INTENT, it) }
122             },
123 
124             // Check that granting/denying a real runtime permission isn't allowed
125             listOf(
126                 PERMISSION_STATE_GRANTED,
127                 PERMISSION_STATE_DENIED,
128             ).map {
129                 Params(
130                     name = "runtimePermission${stateToName(it)}",
131                     success = false,
132                 ) { setFinalState(Manifest.permission.READ_CALENDAR, it) }
133             },
134 
135             // Check that setting a runtime permission to default is ignored (and thus succeeds)
136             Params(
137                 name = "runtimePermissionDefault",
138                 finalPermissionState = mapOf(
139                     Manifest.permission.USE_FULL_SCREEN_INTENT to isFsiDefaultGranted,
140                     Manifest.permission.READ_CALENDAR to false,
141                 ),
142             ) { setFinalState(Manifest.permission.READ_CALENDAR, PERMISSION_STATE_DEFAULT) },
143 
144             // Check that setting a permission not known to the system isn't allowed
145             listOf(
146                 PERMISSION_STATE_GRANTED,
147                 PERMISSION_STATE_DENIED,
148             ).map {
149                 Params(
150                     name = "unknownPermission${stateToName(it)}",
151                     success = false,
152                 ) { setFinalState(NON_EXISTENT_PERMISSION, it) }
153             },
154 
155             // Check that setting an unknown permission to default is ignored (and thus succeeds)
156             Params(
157                 name = "unknownPermissionDefault",
158                 finalPermissionState = mapOf(
159                     Manifest.permission.USE_FULL_SCREEN_INTENT to isFsiDefaultGranted,
160                 ),
161             ) { setFinalState(NON_EXISTENT_PERMISSION, PERMISSION_STATE_DEFAULT) },
162 
163             // Check that setting a runtime/unknown permission with the right permission is allowed
164             Params(
165                 name = "runtimePermissionGranted",
166                 withInstallGrantRuntimePermissions = true,
167                 finalPermissionState = mapOf(
168                     Manifest.permission.USE_FULL_SCREEN_INTENT to isFsiDefaultGranted,
169                     Manifest.permission.READ_CALENDAR to true,
170                     NON_EXISTENT_PERMISSION to null,
171                 ),
172             ) {
173                 setFinalState(Manifest.permission.READ_CALENDAR, PERMISSION_STATE_GRANTED)
174                     .setFinalState(NON_EXISTENT_PERMISSION, PERMISSION_STATE_GRANTED)
175             },
176         ).flatMap { if (it is Collection<*>) it else listOf(it) }
177 
178         data class Params(
179             val name: String,
180             var success: Boolean = true,
181             val testApkName: String = FULL_SCREEN_INTENT_APK,
182             val withInstallGrantRuntimePermissions: Boolean = false,
183             val finalPermissionState: Map<String, Boolean?> = emptyMap(),
184             val paramsBlock: PackageInstaller.SessionParams.() -> Unit = {},
185         ) {
186             override fun toString() = "${name}_${if (success) "Success" else "Failure"}"
187         }
188 
189         private fun stateToName(state: Int) = when (state) {
190             PERMISSION_STATE_GRANTED -> "Granted"
191             PERMISSION_STATE_DENIED -> "Denied"
192             PERMISSION_STATE_DEFAULT -> "Default"
193             else -> throw IllegalArgumentException("Unknown state: $state")
194         }
195 
196         /** Cycles through all of the states to make sure only latest is kept */
197         private fun PackageInstaller.SessionParams.setFinalState(
198             permissionName: String,
199             state: Int
200         ) = setPermissionState(permissionName, PERMISSION_STATE_GRANTED)
201             .setPermissionState(permissionName, PERMISSION_STATE_DENIED)
202             .setPermissionState(permissionName, PERMISSION_STATE_DEFAULT)
203             .setPermissionState(permissionName, state)
204     }
205 
206     @Parameterized.Parameter(0)
207     lateinit var params: Params
208 
209     @Before
210     fun validateParams() {
211         if (!params.success) {
212             // Ensure that a test case expecting failure has no permission state to assert
213             assertThat(params.finalPermissionState).isEmpty()
214         }
215     }
216 
217     @Test
218     fun checkInstall() {
219         val block = {
220             startInstallationViaSession(
221                 apkName = params.testApkName,
222                 paramsBlock = params.paramsBlock,
223             )
224         }
225 
226         if (!params.success) {
227             assertFailsWith(SecurityException::class) { block() }
228             return
229         } else if (params.withInstallGrantRuntimePermissions) {
230             SystemUtil.callWithShellPermissionIdentity(
231                 { block() },
232                 Manifest.permission.INSTALL_GRANT_RUNTIME_PERMISSIONS
233             )
234         } else {
235             block()
236         }
237 
238         clickInstallerUIButton(INSTALL_BUTTON_ID)
239 
240         val result = getInstallSessionResult()
241         assertWithMessage(result.message)
242             .that(result.status)
243             .isEqualTo(PackageInstaller.STATUS_SUCCESS)
244 
245         val packageInfo = assertInstalled(GET_PERMISSIONS_FLAGS)
246         params.finalPermissionState.forEach { (permission, granted) ->
247             assertPermission(packageInfo, permission, granted)
248         }
249     }
250 
251     private fun assertPermission(packageInfo: PackageInfo, name: String, granted: Boolean?) {
252         val permissionIndex = packageInfo.requestedPermissions!!.indexOfFirst { it == name }
253 
254         if (granted == null) {
255             assertThat(permissionIndex).isEqualTo(-1)
256         } else {
257             val appInfo = pm.getApplicationInfo(
258                 TEST_APK_PACKAGE_NAME,
259                 PackageManager.ApplicationInfoFlags.of(0),
260             )
261 
262             permissionManager.checkPermissionForPreflight(
263                 name,
264                 AttributionSource.Builder(appInfo.uid)
265                     .setPackageName(TEST_APK_PACKAGE_NAME)
266                     .build(),
267             ).let(::assertThat)
268                 .run {
269                     if (granted) {
270                         isEqualTo(PermissionManager.PERMISSION_GRANTED)
271                     } else {
272                         isNotEqualTo(PermissionManager.PERMISSION_GRANTED)
273                     }
274                 }
275         }
276     }
277 }
278