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