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