1 /* 2 * Copyright (C) 2024 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.permissionui.cts 18 19 import android.Manifest 20 import android.app.AppOpsManager 21 import android.app.Instrumentation 22 import android.app.ecm.EnhancedConfirmationManager 23 import android.content.Context 24 import android.content.pm.PackageManager 25 import android.os.Build 26 import android.os.Process 27 import android.permission.flags.Flags 28 import android.platform.test.annotations.AppModeFull 29 import android.platform.test.annotations.RequiresFlagsEnabled 30 import android.platform.test.flag.junit.CheckFlagsRule 31 import android.platform.test.flag.junit.DeviceFlagsValueProvider 32 import androidx.test.filters.SdkSuppress 33 import androidx.test.platform.app.InstrumentationRegistry 34 import androidx.test.uiautomator.By 35 import com.android.compatibility.common.util.CddTest 36 import com.android.compatibility.common.util.SystemUtil.callWithShellPermissionIdentity 37 import com.android.compatibility.common.util.SystemUtil.eventually 38 import com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity 39 import org.junit.Assert.assertEquals 40 import org.junit.Assert.assertFalse 41 import org.junit.Assert.assertNotNull 42 import org.junit.Assert.assertNull 43 import org.junit.Assert.assertTrue 44 import org.junit.Assume 45 import org.junit.Before 46 import org.junit.Rule 47 import org.junit.Test 48 49 @AppModeFull(reason = "Instant apps cannot install packages") 50 @SdkSuppress(minSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM, codeName = "VanillaIceCream") 51 @RequiresFlagsEnabled(Flags.FLAG_ENHANCED_CONFIRMATION_MODE_APIS_ENABLED) 52 @CddTest(requirement = "9.18/C-0-1") 53 class EnhancedConfirmationManagerTest : BaseUsePermissionTest() { 54 private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() 55 private val context: Context = instrumentation.targetContext <lambda>null56 private val ecm by lazy { context.getSystemService(EnhancedConfirmationManager::class.java)!! } <lambda>null57 private val appOpsManager by lazy { context.getSystemService(AppOpsManager::class.java)!! } <lambda>null58 private val packageManager by lazy { context.packageManager } 59 60 @Rule 61 @JvmField 62 val mCheckFlagsRule: CheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule() 63 64 @Before assumeNotAutoTvOrWearnull65 fun assumeNotAutoTvOrWear() { 66 Assume.assumeFalse(packageManager.hasSystemFeature(PackageManager.FEATURE_LEANBACK)) 67 Assume.assumeFalse( 68 packageManager.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE) 69 ) 70 Assume.assumeFalse(packageManager.hasSystemFeature(PackageManager.FEATURE_WATCH)) 71 } 72 73 @RequiresFlagsEnabled(Flags.FLAG_ENHANCED_CONFIRMATION_MODE_APIS_ENABLED) 74 @Test installedAppStartsWithModeDefaultnull75 fun installedAppStartsWithModeDefault() { 76 installPackageWithInstallSourceAndMetadataFromStore(APP_APK_NAME_LATEST) 77 eventually { 78 runWithShellPermissionIdentity { 79 assertEquals( 80 getAppEcmState(context, appOpsManager, APP_PACKAGE_NAME), 81 AppOpsManager.MODE_DEFAULT 82 ) 83 } 84 } 85 } 86 87 @RequiresFlagsEnabled(Flags.FLAG_ENHANCED_CONFIRMATION_MODE_APIS_ENABLED) 88 @Test givenStoreAppThenIsNotRestrictedFromProtectedSettingnull89 fun givenStoreAppThenIsNotRestrictedFromProtectedSetting() { 90 installPackageWithInstallSourceAndMetadataFromStore(APP_APK_NAME_LATEST) 91 runWithShellPermissionIdentity { 92 eventually { assertFalse(ecm.isRestricted(APP_PACKAGE_NAME, PROTECTED_SETTING)) } 93 } 94 } 95 96 @RequiresFlagsEnabled(Flags.FLAG_ENHANCED_CONFIRMATION_MODE_APIS_ENABLED) 97 @Test givenLocalAppThenIsRestrictedFromProtectedSettingnull98 fun givenLocalAppThenIsRestrictedFromProtectedSetting() { 99 installPackageWithInstallSourceAndMetadataFromLocalFile(APP_APK_NAME_LATEST) 100 runWithShellPermissionIdentity { 101 eventually { assertTrue(ecm.isRestricted(APP_PACKAGE_NAME, PROTECTED_SETTING)) } 102 } 103 } 104 105 @RequiresFlagsEnabled(Flags.FLAG_ENHANCED_CONFIRMATION_MODE_APIS_ENABLED) 106 @Test givenDownloadedThenAppIsRestrictedFromProtectedSettingnull107 fun givenDownloadedThenAppIsRestrictedFromProtectedSetting() { 108 installPackageWithInstallSourceAndMetadataFromDownloadedFile(APP_APK_NAME_LATEST) 109 runWithShellPermissionIdentity { 110 eventually { assertTrue(ecm.isRestricted(APP_PACKAGE_NAME, PROTECTED_SETTING)) } 111 } 112 } 113 114 @RequiresFlagsEnabled(Flags.FLAG_ENHANCED_CONFIRMATION_MODE_APIS_ENABLED) 115 @Test givenExplicitlyRestrictedAppThenIsRestrictedFromProtectedSettingnull116 fun givenExplicitlyRestrictedAppThenIsRestrictedFromProtectedSetting() { 117 installPackageWithInstallSourceAndMetadataFromStore(APP_APK_NAME_LATEST) 118 eventually { 119 runWithShellPermissionIdentity { 120 assertEquals( 121 getAppEcmState(context, appOpsManager, APP_PACKAGE_NAME), 122 AppOpsManager.MODE_DEFAULT 123 ) 124 } 125 } 126 runWithShellPermissionIdentity { 127 eventually { assertFalse(ecm.isRestricted(APP_PACKAGE_NAME, PROTECTED_SETTING)) } 128 setAppEcmState(context, appOpsManager, APP_PACKAGE_NAME, AppOpsManager.MODE_ERRORED) 129 eventually { assertTrue(ecm.isRestricted(APP_PACKAGE_NAME, PROTECTED_SETTING)) } 130 } 131 } 132 133 @RequiresFlagsEnabled(Flags.FLAG_ENHANCED_CONFIRMATION_MODE_APIS_ENABLED) 134 @Test givenRestrictedAppThenIsNotRestrictedFromNonProtectedSettingnull135 fun givenRestrictedAppThenIsNotRestrictedFromNonProtectedSetting() { 136 installPackageWithInstallSourceAndMetadataFromDownloadedFile(APP_APK_NAME_LATEST) 137 runWithShellPermissionIdentity { 138 eventually { assertFalse(ecm.isRestricted(APP_PACKAGE_NAME, NON_PROTECTED_SETTING)) } 139 } 140 } 141 142 @RequiresFlagsEnabled(Flags.FLAG_ENHANCED_CONFIRMATION_MODE_APIS_ENABLED) 143 @Test givenRestrictedAppThenClearRestrictionNotAllowedByDefaultnull144 fun givenRestrictedAppThenClearRestrictionNotAllowedByDefault() { 145 installPackageWithInstallSourceAndMetadataFromDownloadedFile(APP_APK_NAME_LATEST) 146 runWithShellPermissionIdentity { 147 eventually { assertFalse(ecm.isClearRestrictionAllowed(APP_PACKAGE_NAME)) } 148 } 149 } 150 151 @RequiresFlagsEnabled(Flags.FLAG_ENHANCED_CONFIRMATION_MODE_APIS_ENABLED) 152 @Test givenRestrictedAppWhenClearRestrictionThenNotRestrictedFromProtectedSettingnull153 fun givenRestrictedAppWhenClearRestrictionThenNotRestrictedFromProtectedSetting() { 154 installPackageWithInstallSourceAndMetadataFromDownloadedFile(APP_APK_NAME_LATEST) 155 runWithShellPermissionIdentity { 156 eventually { assertTrue(ecm.isRestricted(APP_PACKAGE_NAME, PROTECTED_SETTING)) } 157 ecm.setClearRestrictionAllowed(APP_PACKAGE_NAME) 158 eventually { assertTrue(ecm.isClearRestrictionAllowed(APP_PACKAGE_NAME)) } 159 ecm.clearRestriction(APP_PACKAGE_NAME) 160 eventually { assertFalse(ecm.isRestricted(APP_PACKAGE_NAME, PROTECTED_SETTING)) } 161 } 162 } 163 164 @RequiresFlagsEnabled(Flags.FLAG_ENHANCED_CONFIRMATION_MODE_APIS_ENABLED) 165 @Test createRestrictedSettingDialogIntentReturnsIntentnull166 fun createRestrictedSettingDialogIntentReturnsIntent() { 167 installPackageWithInstallSourceAndMetadataFromDownloadedFile(APP_APK_NAME_LATEST) 168 169 val intent = ecm.createRestrictedSettingDialogIntent(APP_PACKAGE_NAME, PROTECTED_SETTING) 170 171 assertNotNull(intent) 172 } 173 174 @RequiresFlagsEnabled(Flags.FLAG_ENHANCED_CONFIRMATION_MODE_APIS_ENABLED) 175 @Test grantDialogBlocksRestrictedPermissionsOfSameGroupTogethernull176 fun grantDialogBlocksRestrictedPermissionsOfSameGroupTogether() { 177 installPackageWithInstallSourceFromDownloadedFileAndAllowHardRestrictedPerms( 178 APP_APK_NAME_LATEST 179 ) 180 val permissionAndExpectedGrantResults = 181 arrayOf( 182 GROUP_2_PERMISSION_1_RESTRICTED to false, 183 GROUP_2_PERMISSION_2_RESTRICTED to false 184 ) 185 186 requestAppPermissionsAndAssertResult(*permissionAndExpectedGrantResults) { 187 click(By.res(ALERT_DIALOG_OK_BUTTON), TIMEOUT_MILLIS) 188 } 189 assertTrue(isClearRestrictionAllowed(APP_PACKAGE_NAME)) 190 191 requestAppPermissionsAndAssertResult( 192 *permissionAndExpectedGrantResults, 193 waitForWindowTransition = false 194 ) { 195 assertNoEcmDialogShown() 196 } 197 assertTrue(isClearRestrictionAllowed(APP_PACKAGE_NAME)) 198 } 199 200 @RequiresFlagsEnabled(Flags.FLAG_ENHANCED_CONFIRMATION_MODE_APIS_ENABLED) 201 @Test grantDialogBlocksRestrictedGroupsThenRequestsUnrestrictedGroupsDespiteOutOfOrderRequestnull202 fun grantDialogBlocksRestrictedGroupsThenRequestsUnrestrictedGroupsDespiteOutOfOrderRequest() { 203 installPackageWithInstallSourceFromDownloadedFileAndAllowHardRestrictedPerms( 204 APP_APK_NAME_LATEST 205 ) 206 207 requestAppPermissionsAndAssertResult( 208 GROUP_3_PERMISSION_1_UNRESTRICTED to false, 209 GROUP_2_PERMISSION_1_RESTRICTED to false, 210 GROUP_3_PERMISSION_2_UNRESTRICTED to false, 211 GROUP_2_PERMISSION_2_RESTRICTED to false, 212 waitForWindowTransition = false 213 ) { 214 doAndWaitForWindowTransition { click(By.res(ALERT_DIALOG_OK_BUTTON), TIMEOUT_MILLIS) } 215 doAndWaitForWindowTransition { clickPermissionRequestDenyButton() } 216 } 217 assertTrue(isClearRestrictionAllowed(APP_PACKAGE_NAME)) 218 219 requestAppPermissionsAndAssertResult( 220 GROUP_3_PERMISSION_1_UNRESTRICTED to true, 221 GROUP_3_PERMISSION_2_UNRESTRICTED to true, 222 waitForWindowTransition = false 223 ) { 224 doAndWaitForWindowTransition { clickPermissionRequestAllowForegroundButton() } 225 } 226 assertTrue(isClearRestrictionAllowed(APP_PACKAGE_NAME)) 227 } 228 229 @RequiresFlagsEnabled(Flags.FLAG_ENHANCED_CONFIRMATION_MODE_APIS_ENABLED) 230 @Test grantDialogBlocksRestrictedGroupsThenRequestsUnrestrictedHighPriorityGroupsnull231 fun grantDialogBlocksRestrictedGroupsThenRequestsUnrestrictedHighPriorityGroups() { 232 installPackageWithInstallSourceFromDownloadedFileAndAllowHardRestrictedPerms( 233 APP_APK_NAME_LATEST 234 ) 235 236 requestAppPermissionsAndAssertResult( 237 GROUP_3_PERMISSION_1_UNRESTRICTED to true, 238 GROUP_2_PERMISSION_1_RESTRICTED to false, 239 waitForWindowTransition = false 240 ) { 241 doAndWaitForWindowTransition { click(By.res(ALERT_DIALOG_OK_BUTTON), TIMEOUT_MILLIS) } 242 doAndWaitForWindowTransition { clickPermissionRequestAllowForegroundButton() } 243 } 244 assertTrue(isClearRestrictionAllowed(APP_PACKAGE_NAME)) 245 } 246 247 @RequiresFlagsEnabled(Flags.FLAG_ENHANCED_CONFIRMATION_MODE_APIS_ENABLED) 248 @Test grantDialogBlocksRestrictedGroupsThenRequestsUnrestrictedLowPriorityGroupsnull249 fun grantDialogBlocksRestrictedGroupsThenRequestsUnrestrictedLowPriorityGroups() { 250 installPackageWithInstallSourceFromDownloadedFileAndAllowHardRestrictedPerms( 251 APP_APK_NAME_LATEST 252 ) 253 254 requestAppPermissionsAndAssertResult( 255 GROUP_4_PERMISSION_1_UNRESTRICTED to true, 256 GROUP_2_PERMISSION_1_RESTRICTED to false, 257 waitForWindowTransition = false 258 ) { 259 doAndWaitForWindowTransition { click(By.res(ALERT_DIALOG_OK_BUTTON), TIMEOUT_MILLIS) } 260 doAndWaitForWindowTransition { clickPermissionRequestAllowForegroundButton() } 261 } 262 assertTrue(isClearRestrictionAllowed(APP_PACKAGE_NAME)) 263 } 264 <lambda>null265 private fun isClearRestrictionAllowed(packageName: String) = callWithShellPermissionIdentity { 266 ecm.isClearRestrictionAllowed(packageName) 267 } 268 assertNoEcmDialogShownnull269 private fun assertNoEcmDialogShown() { 270 assertNull( 271 "expected to not see dialog", 272 waitFindObjectOrNull(By.res(ALERT_DIALOG_OK_BUTTON), UNEXPECTED_TIMEOUT_MILLIS.toLong()) 273 ) 274 } 275 276 companion object { 277 private const val GROUP_2_PERMISSION_1_RESTRICTED = Manifest.permission.SEND_SMS 278 private const val GROUP_2_PERMISSION_2_RESTRICTED = Manifest.permission.READ_SMS 279 private const val GROUP_3_PERMISSION_1_UNRESTRICTED = 280 Manifest.permission.ACCESS_FINE_LOCATION 281 private const val GROUP_3_PERMISSION_2_UNRESTRICTED = 282 Manifest.permission.ACCESS_COARSE_LOCATION 283 private const val GROUP_4_PERMISSION_1_UNRESTRICTED = Manifest.permission.BODY_SENSORS 284 285 private const val NON_PROTECTED_SETTING = "example_setting_which_is_not_protected" 286 private const val PROTECTED_SETTING = "android:bind_accessibility_service" 287 288 @Throws(PackageManager.NameNotFoundException::class) setAppEcmStatenull289 private fun setAppEcmState( 290 context: Context, 291 appOpsManager: AppOpsManager, 292 packageName: String, 293 mode: Int 294 ) = 295 appOpsManager.setMode( 296 AppOpsManager.OPSTR_ACCESS_RESTRICTED_SETTINGS, 297 getPackageUid(context, packageName), 298 packageName, 299 mode 300 ) 301 302 @Throws(PackageManager.NameNotFoundException::class) 303 private fun getAppEcmState( 304 context: Context, 305 appOpsManager: AppOpsManager, 306 packageName: String 307 ) = 308 appOpsManager.noteOpNoThrow( 309 AppOpsManager.OPSTR_ACCESS_RESTRICTED_SETTINGS, 310 getPackageUid(context, packageName), 311 packageName, 312 context.attributionTag, 313 /* message */ null 314 ) 315 316 @Throws(PackageManager.NameNotFoundException::class) 317 private fun getPackageUid(context: Context, packageName: String) = 318 getApplicationInfoAsUser(context, packageName).uid 319 320 @Throws(PackageManager.NameNotFoundException::class) 321 private fun getApplicationInfoAsUser(context: Context, packageName: String) = 322 packageManager.getApplicationInfoAsUser( 323 packageName, 324 /* flags */ 0, 325 Process.myUserHandle() 326 ) 327 } 328 } 329