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