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