• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 android.util.Log
32 import com.android.compatibility.common.util.SystemUtil
33 import com.google.common.truth.Truth.assertThat
34 import com.google.common.truth.Truth.assertWithMessage
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(true, false).map { usePiaV2 ->
75             listOf(
76                 // Check that installer is allowed to explicitly grant FSI
77                 Params(
78                     usePiaV2 = usePiaV2,
79                     name = "fullScreenIntentGranted",
80                     finalPermissionState = mapOf(Manifest.permission.USE_FULL_SCREEN_INTENT to true)
81                 ) {
82                     setFinalState(
83                         Manifest.permission.USE_FULL_SCREEN_INTENT,
84                         PERMISSION_STATE_GRANTED
85                     )
86                 },
87 
88                 // Check that installer is allowed to explicitly deny FSI
89                 Params(
90                     usePiaV2 = usePiaV2,
91                     name = "fullScreenIntentDenied",
92                     finalPermissionState = mapOf(
93                         Manifest.permission.USE_FULL_SCREEN_INTENT to false
94                     )
95                 ) {
96                     setFinalState(
97                         Manifest.permission.USE_FULL_SCREEN_INTENT,
98                         PERMISSION_STATE_DENIED
99                     )
100                 },
101 
102                 // Check that a vanilla session automatically grants/denies FSI to an app
103                 // declaring it
104                 Params(
105                     usePiaV2 = usePiaV2,
106                     name = "fullScreenIntentDefault",
107                     finalPermissionState = mapOf(
108                         Manifest.permission.USE_FULL_SCREEN_INTENT to isFsiDefaultGranted,
109                     ),
110                 ) {
111                     setFinalState(
112                         Manifest.permission.USE_FULL_SCREEN_INTENT,
113                         PERMISSION_STATE_DEFAULT
114                     )
115                 },
116 
117                 // Check that the installer doesn't affect an app that doesn't declare FSI
118                 listOf(
119                     PERMISSION_STATE_GRANTED,
120                     PERMISSION_STATE_DENIED,
121                     PERMISSION_STATE_DEFAULT,
122                 ).map {
123                     Params(
124                         usePiaV2 = usePiaV2,
125                         name = "fullScreenIntentWithoutAppDeclaration${stateToName(it)}",
126                         success = true,
127                         testApkName = TEST_APK_NAME,
128                         finalPermissionState = mapOf(
129                             Manifest.permission.USE_FULL_SCREEN_INTENT to null
130                         )
131                     ) { setFinalState(Manifest.permission.USE_FULL_SCREEN_INTENT, it) }
132                 },
133 
134                 // Check that granting/denying a real runtime permission isn't allowed
135                 listOf(
136                     PERMISSION_STATE_GRANTED,
137                     PERMISSION_STATE_DENIED,
138                 ).map {
139                     Params(
140                         usePiaV2 = usePiaV2,
141                         name = "runtimePermission${stateToName(it)}",
142                         success = false,
143                     ) { setFinalState(Manifest.permission.READ_CALENDAR, it) }
144                 },
145 
146                 // Check that setting a runtime permission to default is ignored (and thus succeeds)
147                 Params(
148                     usePiaV2 = usePiaV2,
149                     name = "runtimePermissionDefault",
150                     finalPermissionState = mapOf(
151                         Manifest.permission.USE_FULL_SCREEN_INTENT to isFsiDefaultGranted,
152                         Manifest.permission.READ_CALENDAR to false,
153                     ),
154                 ) { setFinalState(Manifest.permission.READ_CALENDAR, PERMISSION_STATE_DEFAULT) },
155 
156                 // Check that setting a permission not known to the system isn't allowed
157                 listOf(
158                     PERMISSION_STATE_GRANTED,
159                     PERMISSION_STATE_DENIED,
160                 ).map {
161                     Params(
162                         usePiaV2 = usePiaV2,
163                         name = "unknownPermission${stateToName(it)}",
164                         success = false,
165                     ) { setFinalState(NON_EXISTENT_PERMISSION, it) }
166                 },
167 
168                 // Check that setting an unknown permission to default is ignored
169                 // (and thus succeeds)
170                 Params(
171                     usePiaV2 = usePiaV2,
172                     name = "unknownPermissionDefault",
173                     finalPermissionState = mapOf(
174                         Manifest.permission.USE_FULL_SCREEN_INTENT to isFsiDefaultGranted,
175                     ),
176                 ) { setFinalState(NON_EXISTENT_PERMISSION, PERMISSION_STATE_DEFAULT) },
177 
178                 // Check that setting a runtime/unknown permission with the right permission
179                 // is allowed
180                 Params(
181                     usePiaV2 = usePiaV2,
182                     name = "runtimePermissionGranted",
183                     withInstallGrantRuntimePermissions = true,
184                     finalPermissionState = mapOf(
185                         Manifest.permission.USE_FULL_SCREEN_INTENT to isFsiDefaultGranted,
186                         Manifest.permission.READ_CALENDAR to true,
187                         NON_EXISTENT_PERMISSION to null,
188                     ),
189                 ) {
190                     setFinalState(Manifest.permission.READ_CALENDAR, PERMISSION_STATE_GRANTED)
191                         .setFinalState(NON_EXISTENT_PERMISSION, PERMISSION_STATE_GRANTED)
192                 },
193             ).flatMap { if (it is Collection<*>) it else listOf(it) }
194         }.flatten()
195 
196         data class Params(
197             val usePiaV2: Boolean,
198             val name: String,
199             var success: Boolean = true,
200             val testApkName: String = FULL_SCREEN_INTENT_APK,
201             val withInstallGrantRuntimePermissions: Boolean = false,
202             val finalPermissionState: Map<String, Boolean?> = emptyMap(),
203             val paramsBlock: PackageInstaller.SessionParams.() -> Unit = {},
204         ) {
205             override fun toString(): String {
206                 val sb = StringBuilder(name + "_")
207                 if (success) {
208                     sb.append("Success")
209                 } else {
210                     sb.append("Failure")
211                 }
212                 sb.append("(usePiaV2=$usePiaV2)")
213                 return sb.toString()
214             }
215         }
216 
217         private fun stateToName(state: Int) = when (state) {
218             PERMISSION_STATE_GRANTED -> "Granted"
219             PERMISSION_STATE_DENIED -> "Denied"
220             PERMISSION_STATE_DEFAULT -> "Default"
221             else -> throw IllegalArgumentException("Unknown state: $state")
222         }
223 
224         /** Cycles through all of the states to make sure only latest is kept */
225         private fun PackageInstaller.SessionParams.setFinalState(
226             permissionName: String,
227             state: Int,
228         ) = setPermissionState(permissionName, PERMISSION_STATE_GRANTED)
229             .setPermissionState(permissionName, PERMISSION_STATE_DENIED)
230             .setPermissionState(permissionName, PERMISSION_STATE_DEFAULT)
231             .setPermissionState(permissionName, state)
232     }
233 
234     @Parameterized.Parameter(0)
235     lateinit var params: Params
236 
237     @Before
238     fun validateParams() {
239         if (!params.success) {
240             // Ensure that a test case expecting failure has no permission state to assert
241             assertThat(params.finalPermissionState).isEmpty()
242         }
243     }
244 
245     @Before
246     override fun setUsePiaV2() {
247         Log.i(TAG, "Using Pia V${if (params.usePiaV2) 2 else 1}")
248         usePiaRule.setSettingValue(params.usePiaV2)
249     }
250 
251     @Test
252     fun checkInstall() {
253         val block = {
254             startInstallationViaSession(
255                 apkName = params.testApkName,
256                 paramsBlock = params.paramsBlock,
257             )
258         }
259 
260         if (!params.success) {
261             assertFailsWith(SecurityException::class) { block() }
262             return
263         } else if (params.withInstallGrantRuntimePermissions) {
264             SystemUtil.callWithShellPermissionIdentity(
265                 { block() },
266                 Manifest.permission.INSTALL_GRANT_RUNTIME_PERMISSIONS
267             )
268         } else {
269             block()
270         }
271 
272         clickInstallerUIButton(INSTALL_BUTTON_ID)
273 
274         val result = getInstallSessionResult()
275         assertWithMessage(result.message)
276             .that(result.status)
277             .isEqualTo(PackageInstaller.STATUS_SUCCESS)
278 
279         val packageInfo = assertInstalled(GET_PERMISSIONS_FLAGS)
280         params.finalPermissionState.forEach { (permission, granted) ->
281             assertPermission(packageInfo, permission, granted)
282         }
283     }
284 
285     private fun assertPermission(packageInfo: PackageInfo, name: String, granted: Boolean?) {
286         val permissionIndex = packageInfo.requestedPermissions?.indexOfFirst { it == name } ?: -1
287 
288         if (granted == null) {
289             assertThat(permissionIndex).isEqualTo(-1)
290         } else {
291             val appInfo = pm.getApplicationInfo(
292                 TEST_APK_PACKAGE_NAME,
293                 PackageManager.ApplicationInfoFlags.of(0),
294             )
295 
296             permissionManager.checkPermissionForPreflight(
297                 name,
298                 AttributionSource.Builder(appInfo.uid)
299                     .setPackageName(TEST_APK_PACKAGE_NAME)
300                     .build(),
301             ).let(::assertThat)
302                 .run {
303                     if (granted) {
304                         isEqualTo(PermissionManager.PERMISSION_GRANTED)
305                     } else {
306                         isNotEqualTo(PermissionManager.PERMISSION_GRANTED)
307                     }
308                 }
309         }
310     }
311 }
312