1 /* <lambda>null2 * Copyright (C) 2022 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 com.android.safetycenter.testing 18 19 import android.Manifest.permission.READ_DEVICE_CONFIG 20 import android.Manifest.permission.WRITE_DEVICE_CONFIG 21 import android.annotation.TargetApi 22 import android.app.job.JobInfo 23 import android.content.Context 24 import android.content.pm.PackageManager 25 import android.content.res.Resources 26 import android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE 27 import android.provider.DeviceConfig 28 import android.provider.DeviceConfig.NAMESPACE_PRIVACY 29 import android.provider.DeviceConfig.Properties 30 import android.safetycenter.SafetyCenterManager.REFRESH_REASON_DEVICE_LOCALE_CHANGE 31 import android.safetycenter.SafetyCenterManager.REFRESH_REASON_DEVICE_REBOOT 32 import android.safetycenter.SafetyCenterManager.REFRESH_REASON_OTHER 33 import android.safetycenter.SafetyCenterManager.REFRESH_REASON_PAGE_OPEN 34 import android.safetycenter.SafetyCenterManager.REFRESH_REASON_PERIODIC 35 import android.safetycenter.SafetyCenterManager.REFRESH_REASON_RESCAN_BUTTON_CLICK 36 import android.safetycenter.SafetyCenterManager.REFRESH_REASON_SAFETY_CENTER_ENABLED 37 import android.safetycenter.SafetySourceData 38 import com.android.safetycenter.testing.Coroutines.TIMEOUT_LONG 39 import com.android.safetycenter.testing.ShellPermissions.callWithShellPermissionIdentity 40 import java.time.Duration 41 import kotlin.reflect.KProperty 42 43 /** A class that facilitates working with Safety Center flags. */ 44 object SafetyCenterFlags { 45 46 /** Flag that determines whether Safety Center is enabled. */ 47 private val isEnabledFlag = 48 Flag("safety_center_is_enabled", defaultValue = false, BooleanParser()) 49 50 /** Flag that determines whether Safety Center can send notifications. */ 51 private val notificationsFlag = 52 Flag("safety_center_notifications_enabled", defaultValue = false, BooleanParser()) 53 54 /** 55 * Flag that determines the minimum delay before Safety Center can send a notification for an 56 * issue with [SafetySourceIssue.NOTIFICATION_BEHAVIOR_DELAYED]. 57 * 58 * The actual delay used may be longer. 59 */ 60 private val notificationsMinDelayFlag = 61 Flag( 62 "safety_center_notifications_min_delay", 63 defaultValue = Duration.ofHours(2), 64 DurationParser() 65 ) 66 67 /** 68 * Flag containing a comma delimited list of IDs of sources that Safety Center can send 69 * notifications about, in addition to those permitted by the current XML config. 70 */ 71 private val notificationsAllowedSourcesFlag = 72 Flag( 73 "safety_center_notifications_allowed_sources", 74 defaultValue = emptySet(), 75 SetParser(StringParser()) 76 ) 77 78 /** 79 * Flag containing a comma-delimited list of the issue type IDs for which, if otherwise 80 * undefined, Safety Center should use [SafetySourceIssue.NOTIFICATION_BEHAVIOR_IMMEDIATELY]. 81 */ 82 private val immediateNotificationBehaviorIssuesFlag = 83 Flag( 84 "safety_center_notifications_immediate_behavior_issues", 85 defaultValue = emptySet(), 86 SetParser(StringParser()) 87 ) 88 89 /** 90 * Flag for the minimum interval which must elapse before Safety Center can resurface a 91 * notification after it was dismissed. A negative [Duration] (the default) means that dismissed 92 * notifications cannot resurface. 93 * 94 * There may be other conditions for resurfacing a notification and the actual delay may be 95 * longer than this. 96 */ 97 private val notificationResurfaceIntervalFlag = 98 Flag( 99 "safety_center_notification_resurface_interval", 100 defaultValue = Duration.ofDays(-1), 101 DurationParser() 102 ) 103 104 /** 105 * Flag that determines whether we should show error entries for sources that timeout when 106 * refreshing them. 107 */ 108 private val showErrorEntriesOnTimeoutFlag = 109 Flag("safety_center_show_error_entries_on_timeout", defaultValue = false, BooleanParser()) 110 111 /** Flag that determines whether we should replace the IconAction of the lock screen source. */ 112 private val replaceLockScreenIconActionFlag = 113 Flag("safety_center_replace_lock_screen_icon_action", defaultValue = true, BooleanParser()) 114 115 /** 116 * Flag that determines the time for which a Safety Center refresh is allowed to wait for a 117 * source to respond to a refresh request before timing out and marking the refresh as finished, 118 * depending on the refresh reason. 119 */ 120 private val refreshSourceTimeoutsFlag = 121 Flag( 122 "safety_center_refresh_sources_timeouts_millis", 123 defaultValue = getAllRefreshTimeoutsMap(TIMEOUT_LONG), 124 MapParser(IntParser(), DurationParser()) 125 ) 126 127 /** 128 * Flag that determines the time for which Safety Center will wait for a source to respond to a 129 * resolving action before timing out. 130 */ 131 private val resolveActionTimeoutFlag = 132 Flag( 133 "safety_center_resolve_action_timeout_millis", 134 defaultValue = TIMEOUT_LONG, 135 DurationParser() 136 ) 137 138 /** Flag that determines a duration after which a temporarily hidden issue will resurface. */ 139 private val tempHiddenIssueResurfaceDelayFlag = 140 Flag( 141 "safety_center_temp_hidden_issue_resurface_delay_millis", 142 defaultValue = Duration.ofDays(2), 143 DurationParser() 144 ) 145 146 /** 147 * Flag that determines the time for which Safety Center will wait before starting dismissal of 148 * resolved issue UI 149 */ 150 private val hideResolveUiTransitionDelayFlag = 151 Flag( 152 "safety_center_hide_resolved_ui_transition_delay_millis", 153 defaultValue = Duration.ofMillis(400), 154 DurationParser() 155 ) 156 157 /** 158 * Flag containing a comma delimited lists of source IDs that we won't track when deciding if a 159 * broadcast is completed. We still send broadcasts to (and handle API calls from) these sources 160 * as normal. 161 */ 162 private val untrackedSourcesFlag = 163 Flag( 164 "safety_center_untracked_sources", 165 defaultValue = emptySet(), 166 SetParser(StringParser()) 167 ) 168 169 /** 170 * Flag containing a map (a comma separated list of colon separated pairs) where the key is an 171 * issue [SafetySourceData.SeverityLevel] and the value is the number of times an issue of this 172 * [SafetySourceData.SeverityLevel] should be resurfaced. 173 */ 174 private val resurfaceIssueMaxCountsFlag = 175 Flag( 176 "safety_center_resurface_issue_max_counts", 177 defaultValue = emptyMap(), 178 MapParser(IntParser(), LongParser()) 179 ) 180 181 /** 182 * Flag containing a map (a comma separated list of colon separated pairs) where the key is an 183 * issue [SafetySourceData.SeverityLevel] and the value is the time after which a dismissed 184 * issue of this [SafetySourceData.SeverityLevel] will resurface if it has not reached the 185 * maximum count for which a dismissed issue of this [SafetySourceData.SeverityLevel] should be 186 * resurfaced. 187 */ 188 private val resurfaceIssueDelaysFlag = 189 Flag( 190 "safety_center_resurface_issue_delays_millis", 191 defaultValue = emptyMap(), 192 MapParser(IntParser(), DurationParser()) 193 ) 194 195 /** 196 * Flag containing a map (a comma separated list of colon separated pairs) where the key is an 197 * issue [SafetySourceIssue.IssueCategory] and the value is a vertical-bar-delimited list of IDs 198 * of safety sources that are allowed to send issues with this category. 199 */ 200 private val issueCategoryAllowlistsFlag = 201 Flag( 202 "safety_center_issue_category_allowlists", 203 defaultValue = emptyMap(), 204 MapParser(IntParser(), SetParser(StringParser(), delimiter = "|")) 205 ) 206 207 /** 208 * Flag that represents a comma delimited list of IDs of sources that should only be refreshed 209 * when Safety Center is on screen. We will refresh these sources only on page open and when the 210 * scan button is clicked. 211 */ 212 private val backgroundRefreshDeniedSourcesFlag = 213 Flag( 214 "safety_center_background_refresh_denied_sources", 215 defaultValue = emptySet(), 216 SetParser(StringParser()) 217 ) 218 219 /** 220 * Flag that determines whether statsd logging is allowed. 221 * 222 * This is useful to allow testing statsd logs in some specific tests, while keeping the other 223 * tests from polluting our statsd logs. 224 */ 225 private val allowStatsdLoggingFlag = 226 Flag("safety_center_allow_statsd_logging", defaultValue = false, BooleanParser()) 227 228 /** 229 * The Package Manager flag used while toggling the QS tile component. 230 * 231 * This is to make sure that the SafetyCenter is not killed while toggling the QS tile component 232 * during the tests, which causes flakiness in them. 233 */ 234 private val qsTileComponentSettingFlag = 235 Flag( 236 "safety_center_qs_tile_component_setting_flags", 237 defaultValue = PackageManager.DONT_KILL_APP, 238 IntParser() 239 ) 240 241 /** 242 * Flag that determines whether to show subpages in the Safety Center UI instead of the 243 * expand-and-collapse list. 244 */ 245 private val showSubpagesFlag = 246 Flag("safety_center_show_subpages", defaultValue = false, BooleanParser()) 247 248 private val overrideRefreshOnPageOpenSourcesFlag = 249 Flag( 250 "safety_center_override_refresh_on_page_open_sources", 251 defaultValue = setOf(), 252 SetParser(StringParser()) 253 ) 254 255 /** 256 * Flag that enables both one-off and periodic background refreshes in 257 * [SafetyCenterBackgroundRefreshJobService]. 258 */ 259 private val backgroundRefreshIsEnabledFlag = 260 Flag( 261 "safety_center_background_refresh_is_enabled", 262 // do not set defaultValue to true, do not want background refreshes running 263 // during other tests 264 defaultValue = false, 265 BooleanParser() 266 ) 267 268 /** 269 * Flag that determines how often periodic background refreshes are run in 270 * [SafetyCenterBackgroundRefreshJobService]. See [JobInfo.setPeriodic] for details. 271 * 272 * Note that jobs may take longer than this to be scheduled, or may possibly never run, 273 * depending on whether the other constraints on the job get satisfied. 274 */ 275 private val periodicBackgroundRefreshIntervalFlag = 276 Flag( 277 "safety_center_periodic_background_interval_millis", 278 defaultValue = Duration.ofDays(1), 279 DurationParser() 280 ) 281 282 /** Flag for allowlisting additional certificates for a given package. */ 283 private val allowedAdditionalPackageCertsFlag = 284 Flag( 285 "safety_center_additional_allow_package_certs", 286 defaultValue = emptyMap(), 287 MapParser(StringParser(), SetParser(StringParser(), delimiter = "|")) 288 ) 289 290 /** 291 * Flag that determines whether background refreshes require charging in 292 * [SafetyCenterBackgroundRefreshJobService]. See [JobInfo.setRequiresCharging] for details. 293 */ 294 private val backgroundRefreshRequiresChargingFlag = 295 Flag("safety_center_background_requires_charging", defaultValue = false, BooleanParser()) 296 297 /** Every Safety Center flag. */ 298 private val FLAGS: List<Flag<*>> = 299 listOf( 300 isEnabledFlag, 301 notificationsFlag, 302 notificationsAllowedSourcesFlag, 303 notificationsMinDelayFlag, 304 immediateNotificationBehaviorIssuesFlag, 305 notificationResurfaceIntervalFlag, 306 showErrorEntriesOnTimeoutFlag, 307 replaceLockScreenIconActionFlag, 308 refreshSourceTimeoutsFlag, 309 resolveActionTimeoutFlag, 310 tempHiddenIssueResurfaceDelayFlag, 311 hideResolveUiTransitionDelayFlag, 312 untrackedSourcesFlag, 313 resurfaceIssueMaxCountsFlag, 314 resurfaceIssueDelaysFlag, 315 issueCategoryAllowlistsFlag, 316 allowedAdditionalPackageCertsFlag, 317 backgroundRefreshDeniedSourcesFlag, 318 allowStatsdLoggingFlag, 319 qsTileComponentSettingFlag, 320 showSubpagesFlag, 321 overrideRefreshOnPageOpenSourcesFlag, 322 backgroundRefreshIsEnabledFlag, 323 periodicBackgroundRefreshIntervalFlag, 324 backgroundRefreshRequiresChargingFlag 325 ) 326 327 /** Returns whether the device supports Safety Center. */ 328 fun Context.deviceSupportsSafetyCenter() = 329 resources.getBoolean( 330 Resources.getSystem().getIdentifier("config_enableSafetyCenter", "bool", "android") 331 ) 332 333 /** A property that allows getting and setting the [isEnabledFlag]. */ 334 var isEnabled: Boolean by isEnabledFlag 335 336 /** A property that allows getting and setting the [notificationsFlag]. */ 337 var notificationsEnabled: Boolean by notificationsFlag 338 339 /** A property that allows getting and setting the [notificationsAllowedSourcesFlag]. */ 340 var notificationsAllowedSources: Set<String> by notificationsAllowedSourcesFlag 341 342 /** A property that allows getting and setting the [notificationsMinDelayFlag]. */ 343 var notificationsMinDelay: Duration by notificationsMinDelayFlag 344 345 /** A property that allows getting and setting the [immediateNotificationBehaviorIssuesFlag]. */ 346 var immediateNotificationBehaviorIssues: Set<String> by immediateNotificationBehaviorIssuesFlag 347 348 /** A property that allows getting and setting the [notificationResurfaceIntervalFlag]. */ 349 var notificationResurfaceInterval: Duration by notificationResurfaceIntervalFlag 350 351 /** A property that allows getting and setting the [showErrorEntriesOnTimeoutFlag]. */ 352 var showErrorEntriesOnTimeout: Boolean by showErrorEntriesOnTimeoutFlag 353 354 /** A property that allows getting and setting the [replaceLockScreenIconActionFlag]. */ 355 var replaceLockScreenIconAction: Boolean by replaceLockScreenIconActionFlag 356 357 /** A property that allows getting and setting the [refreshSourceTimeoutsFlag]. */ 358 var refreshTimeouts: Map<Int, Duration> by refreshSourceTimeoutsFlag 359 360 /** A property that allows getting and setting the [resolveActionTimeoutFlag]. */ 361 var resolveActionTimeout: Duration by resolveActionTimeoutFlag 362 363 /** A property that allows getting and setting the [tempHiddenIssueResurfaceDelayFlag]. */ 364 var tempHiddenIssueResurfaceDelay: Duration by tempHiddenIssueResurfaceDelayFlag 365 366 /** A property that allows getting and setting the [hideResolveUiTransitionDelayFlag]. */ 367 var hideResolvedIssueUiTransitionDelay: Duration by hideResolveUiTransitionDelayFlag 368 369 /** A property that allows getting and setting the [untrackedSourcesFlag]. */ 370 var untrackedSources: Set<String> by untrackedSourcesFlag 371 372 /** A property that allows getting and setting the [resurfaceIssueMaxCountsFlag]. */ 373 var resurfaceIssueMaxCounts: Map<Int, Long> by resurfaceIssueMaxCountsFlag 374 375 /** A property that allows getting and setting the [resurfaceIssueDelaysFlag]. */ 376 var resurfaceIssueDelays: Map<Int, Duration> by resurfaceIssueDelaysFlag 377 378 /** A property that allows getting and setting the [issueCategoryAllowlistsFlag]. */ 379 var issueCategoryAllowlists: Map<Int, Set<String>> by issueCategoryAllowlistsFlag 380 381 var allowedAdditionalPackageCerts: Map<String, Set<String>> by allowedAdditionalPackageCertsFlag 382 383 /** A property that allows getting and setting the [backgroundRefreshDeniedSourcesFlag]. */ 384 var backgroundRefreshDeniedSources: Set<String> by backgroundRefreshDeniedSourcesFlag 385 386 /** A property that allows getting and setting the [allowStatsdLoggingFlag]. */ 387 var allowStatsdLogging: Boolean by allowStatsdLoggingFlag 388 389 /** A property that allows getting and setting the [showSubpagesFlag]. */ 390 var showSubpages: Boolean by showSubpagesFlag 391 392 /** A property that allows getting and setting the [overrideRefreshOnPageOpenSourcesFlag]. */ 393 var overrideRefreshOnPageOpenSources: Set<String> by overrideRefreshOnPageOpenSourcesFlag 394 395 /** A property that allows getting and settings the [backgroundRefreshIsEnabledFlag]. */ 396 var backgroundRefreshIsEnabled: Boolean by backgroundRefreshIsEnabledFlag 397 398 /** A property that allows getting and settings the [periodicBackgroundRefreshIntervalFlag]. */ 399 var periodicBackgroundRefreshInterval: Duration by periodicBackgroundRefreshIntervalFlag 400 401 /** A property that allows getting and settings the [backgroundRefreshRequiresChargingFlag]. */ 402 var backgroundRefreshRequiresCharging: Boolean by backgroundRefreshRequiresChargingFlag 403 404 /** 405 * Returns a snapshot of all the Safety Center flags. 406 * 407 * This snapshot is only taken once and cached afterwards. [setup] must be called at least once 408 * prior to modifying any flag for the snapshot to be taken with the right values. 409 */ 410 @Volatile lateinit var snapshot: Properties 411 412 private val lazySnapshot: Properties by lazy { 413 callWithShellPermissionIdentity(READ_DEVICE_CONFIG) { 414 DeviceConfig.getProperties(NAMESPACE_PRIVACY, *FLAGS.map { it.name }.toTypedArray()) 415 } 416 } 417 418 /** 419 * Takes a snapshot of all Safety Center flags and sets them up to their default values. 420 * 421 * This doesn't apply to [isEnabled] as it is handled separately by [SafetyCenterTestHelper]: 422 * there is a listener that listens to changes to this flag in system server, and we need to 423 * ensure we wait for it to complete when modifying this flag. 424 */ 425 fun setup() { 426 snapshot = lazySnapshot 427 FLAGS.filter { it.name != isEnabledFlag.name } 428 .forEach { writeDeviceConfigProperty(it.name, it.defaultStringValue) } 429 } 430 431 /** 432 * Resets the Safety Center flags based on the existing [snapshot] captured during [setup]. 433 * 434 * This doesn't apply to [isEnabled] as it is handled separately by [SafetyCenterTestHelper]: 435 * there is a listener that listens to changes to this flag in system server, and we need to 436 * ensure we wait for it to complete when modifying this flag. 437 */ 438 fun reset() { 439 // Write flags one by one instead of using `DeviceConfig#setProperties` as the latter does 440 // not work when DeviceConfig sync is disabled and does not take uninitialized values into 441 // account. 442 FLAGS.filter { it.name != isEnabledFlag.name } 443 .forEach { 444 val key = it.name 445 val value = snapshot.getString(key, /* defaultValue */ null) 446 writeDeviceConfigProperty(key, value) 447 } 448 } 449 450 /** Sets the [refreshTimeouts] for all refresh reasons to the given [refreshTimeout]. */ 451 fun setAllRefreshTimeoutsTo(refreshTimeout: Duration) { 452 refreshTimeouts = getAllRefreshTimeoutsMap(refreshTimeout) 453 } 454 455 /** Returns the [isEnabledFlag] value of the Safety Center flags snapshot. */ 456 fun Properties.isSafetyCenterEnabled() = 457 getBoolean(isEnabledFlag.name, /* defaultValue */ false) 458 459 @TargetApi(UPSIDE_DOWN_CAKE) 460 private fun getAllRefreshTimeoutsMap(refreshTimeout: Duration): Map<Int, Duration> = 461 mapOf( 462 REFRESH_REASON_PAGE_OPEN to refreshTimeout, 463 REFRESH_REASON_RESCAN_BUTTON_CLICK to refreshTimeout, 464 REFRESH_REASON_DEVICE_REBOOT to refreshTimeout, 465 REFRESH_REASON_DEVICE_LOCALE_CHANGE to refreshTimeout, 466 REFRESH_REASON_SAFETY_CENTER_ENABLED to refreshTimeout, 467 REFRESH_REASON_OTHER to refreshTimeout, 468 REFRESH_REASON_PERIODIC to refreshTimeout 469 ) 470 471 private interface Parser<T> { 472 fun parseFromString(stringValue: String): T 473 474 fun toString(value: T): String = value.toString() 475 } 476 477 private class StringParser : Parser<String> { 478 override fun parseFromString(stringValue: String) = stringValue 479 } 480 481 private class BooleanParser : Parser<Boolean> { 482 override fun parseFromString(stringValue: String) = stringValue.toBoolean() 483 } 484 485 private class IntParser : Parser<Int> { 486 override fun parseFromString(stringValue: String) = stringValue.toInt() 487 } 488 489 private class LongParser : Parser<Long> { 490 override fun parseFromString(stringValue: String) = stringValue.toLong() 491 } 492 493 private class DurationParser : Parser<Duration> { 494 override fun parseFromString(stringValue: String) = Duration.ofMillis(stringValue.toLong()) 495 496 override fun toString(value: Duration) = value.toMillis().toString() 497 } 498 499 private class SetParser<T>( 500 private val elementParser: Parser<T>, 501 private val delimiter: String = "," 502 ) : Parser<Set<T>> { 503 override fun parseFromString(stringValue: String) = 504 stringValue.split(delimiter).map(elementParser::parseFromString).toSet() 505 506 override fun toString(value: Set<T>) = 507 value.joinToString(delimiter, transform = elementParser::toString) 508 } 509 510 private class MapParser<K, V>( 511 private val keyParser: Parser<K>, 512 private val valueParser: Parser<V>, 513 private val entriesDelimiter: String = ",", 514 private val pairDelimiter: String = ":" 515 ) : Parser<Map<K, V>> { 516 override fun parseFromString(stringValue: String) = 517 stringValue.split(entriesDelimiter).associate { pair -> 518 val (keyString, valueString) = pair.split(pairDelimiter) 519 keyParser.parseFromString(keyString) to valueParser.parseFromString(valueString) 520 } 521 522 override fun toString(value: Map<K, V>) = 523 value 524 .map { 525 "${keyParser.toString(it.key)}${pairDelimiter}${valueParser.toString(it.value)}" 526 } 527 .joinToString(entriesDelimiter) 528 } 529 530 private class Flag<T>( 531 val name: String, 532 private val defaultValue: T, 533 private val parser: Parser<T> 534 ) { 535 val defaultStringValue = parser.toString(defaultValue) 536 537 operator fun getValue(thisRef: Any?, property: KProperty<*>): T = 538 readDeviceConfigProperty(name)?.let(parser::parseFromString) ?: defaultValue 539 540 operator fun setValue(thisRef: Any?, property: KProperty<*>, value: T) { 541 writeDeviceConfigProperty(name, parser.toString(value)) 542 } 543 } 544 545 private fun readDeviceConfigProperty(name: String): String? = 546 callWithShellPermissionIdentity(READ_DEVICE_CONFIG) { 547 DeviceConfig.getProperty(NAMESPACE_PRIVACY, name) 548 } 549 550 private fun writeDeviceConfigProperty(name: String, stringValue: String?) { 551 callWithShellPermissionIdentity(WRITE_DEVICE_CONFIG) { 552 val valueWasSet = 553 DeviceConfig.setProperty( 554 NAMESPACE_PRIVACY, 555 name, 556 stringValue, /* makeDefault */ 557 false 558 ) 559 require(valueWasSet) { "Could not set $name to: $stringValue" } 560 } 561 } 562 } 563