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