• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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