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