• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * 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;
18 
19 import static android.safetycenter.SafetyCenterManager.RefreshReason;
20 
21 import android.os.Binder;
22 import android.provider.DeviceConfig;
23 import android.safetycenter.SafetySourceData;
24 import android.safetycenter.SafetySourceIssue;
25 import android.util.ArraySet;
26 import android.util.Log;
27 
28 import androidx.annotation.Nullable;
29 
30 import com.android.modules.utils.build.SdkLevel;
31 import com.android.permission.flags.Flags;
32 import com.android.safetycenter.resources.SafetyCenterResourcesApk;
33 
34 import java.io.PrintWriter;
35 import java.time.Duration;
36 import java.util.Arrays;
37 import java.util.Collections;
38 import java.util.List;
39 
40 /**
41  * A class to access the Safety Center {@link DeviceConfig} flags.
42  *
43  * @hide
44  */
45 public final class SafetyCenterFlags {
46 
47     private static final String TAG = "SafetyCenterFlags";
48 
49     /** {@link DeviceConfig} property name for {@link #getSafetyCenterEnabled()}. */
50     static final String PROPERTY_SAFETY_CENTER_ENABLED = "safety_center_is_enabled";
51 
52     private static final String PROPERTY_NOTIFICATIONS_ENABLED =
53             "safety_center_notifications_enabled";
54 
55     private static final String PROPERTY_NOTIFICATIONS_ALLOWED_SOURCES =
56             "safety_center_notifications_allowed_sources";
57 
58     private static final String PROPERTY_NOTIFICATIONS_MIN_DELAY =
59             "safety_center_notifications_min_delay";
60 
61     private static final String PROPERTY_NOTIFICATIONS_IMMEDIATE_BEHAVIOR_ISSUES =
62             "safety_center_notifications_immediate_behavior_issues";
63 
64     private static final String PROPERTY_NOTIFICATION_RESURFACE_INTERVAL =
65             "safety_center_notification_resurface_interval";
66 
67     private static final String PROPERTY_REPLACE_LOCK_SCREEN_ICON_ACTION =
68             "safety_center_replace_lock_screen_icon_action";
69 
70     private static final String PROPERTY_RESOLVING_ACTION_TIMEOUT_MILLIS =
71             "safety_center_resolve_action_timeout_millis";
72 
73     private static final String PROPERTY_FGS_ALLOWLIST_DURATION_MILLIS =
74             "safety_center_refresh_fgs_allowlist_duration_millis";
75 
76     private static final String PROPERTY_RESURFACE_ISSUE_MAX_COUNTS =
77             "safety_center_resurface_issue_max_counts";
78 
79     private static final String PROPERTY_RESURFACE_ISSUE_DELAYS_MILLIS =
80             "safety_center_resurface_issue_delays_millis";
81 
82     private static final String PROPERTY_UNTRACKED_SOURCES = "safety_center_untracked_sources";
83 
84     private static final String PROPERTY_BACKGROUND_REFRESH_DENIED_SOURCES =
85             "safety_center_background_refresh_denied_sources";
86 
87     private static final String PROPERTY_REFRESH_SOURCES_TIMEOUTS_MILLIS =
88             "safety_center_refresh_sources_timeouts_millis";
89 
90     private static final String PROPERTY_ISSUE_CATEGORY_ALLOWLISTS =
91             "safety_center_issue_category_allowlists";
92 
93     private static final String PROPERTY_ALLOW_STATSD_LOGGING =
94             "safety_center_allow_statsd_logging";
95 
96     private static final String PROPERTY_SHOW_SUBPAGES = "safety_center_show_subpages";
97 
98     private static final String PROPERTY_OVERRIDE_REFRESH_ON_PAGE_OPEN_SOURCES =
99             "safety_center_override_refresh_on_page_open_sources";
100 
101     private static final String PROPERTY_ADDITIONAL_ALLOW_PACKAGE_CERTS =
102             "safety_center_additional_allow_package_certs";
103 
104     private static final Duration FGS_ALLOWLIST_DEFAULT_DURATION = Duration.ofSeconds(20);
105 
106     private static final String PROPERTY_TEMP_HIDDEN_ISSUE_RESURFACE_DELAY_MILLIS =
107             "safety_center_temp_hidden_issue_resurface_delay_millis";
108 
109     private static final String PROPERTY_ACTIONS_TO_OVERRIDE_WITH_DEFAULT_INTENT =
110             "safety_center_actions_to_override_with_default_intent";
111 
112     private static final Duration RESOLVING_ACTION_TIMEOUT_DEFAULT_DURATION =
113             Duration.ofSeconds(10);
114 
115     private static final Duration NOTIFICATIONS_MIN_DELAY_DEFAULT_DURATION = Duration.ofDays(180);
116 
117     private static final String REFRESH_SOURCES_TIMEOUT_DEFAULT =
118             "100:15000,200:60000,300:30000,400:30000,500:30000,600:3600000";
119     private static final Duration REFRESH_SOURCES_TIMEOUT_DEFAULT_DURATION = Duration.ofSeconds(15);
120 
121     private static final String RESURFACE_ISSUE_MAX_COUNT_DEFAULT = "200:0,300:1,400:1";
122     private static final long RESURFACE_ISSUE_MAX_COUNT_DEFAULT_COUNT = 0;
123 
124     private static final String RESURFACE_ISSUE_DELAYS_DEFAULT = "";
125     private static final Duration RESURFACE_ISSUE_DELAYS_DEFAULT_DURATION = Duration.ofDays(180);
126 
127     private static final ArraySet<String> sAllowedNotificationSources =
128             new ArraySet<>(new String[] {"GoogleAppProtectionService"});
129     private static final ArraySet<String> sAllowedNotificationSourcesUPlus =
130             new ArraySet<>(new String[] {"GoogleBackupAndRestore"});
131 
132     private static volatile String sUntrackedSourcesDefault =
133             "AndroidAccessibility,AndroidBackgroundLocation,"
134                     + "AndroidNotificationListener,AndroidPermissionAutoRevoke";
135 
136     private static volatile String sBackgroundRefreshDenyDefault = "";
137 
138     private static volatile String sIssueCategoryAllowlistDefault = "";
139 
140     private static volatile String sRefreshOnPageOpenSourcesDefault = "AndroidBiometrics";
141 
142     private static volatile String sActionsToOverrideWithDefaultIntentDefault = "";
143 
init(SafetyCenterResourcesApk safetyCenterResourcesApk)144     static void init(SafetyCenterResourcesApk safetyCenterResourcesApk) {
145         String untrackedSourcesDefault =
146                 safetyCenterResourcesApk.getOptionalStringByName("config_defaultUntrackedSources");
147         if (untrackedSourcesDefault != null) {
148             sUntrackedSourcesDefault = untrackedSourcesDefault;
149         }
150         String backgroundRefreshDenyDefault =
151                 safetyCenterResourcesApk.getOptionalStringByName(
152                         "config_defaultBackgroundRefreshDeny");
153         if (backgroundRefreshDenyDefault != null) {
154             sBackgroundRefreshDenyDefault = backgroundRefreshDenyDefault;
155         }
156         String issueCategoryAllowlistDefault =
157                 safetyCenterResourcesApk.getOptionalStringByName(
158                         "config_defaultIssueCategoryAllowlist");
159         if (issueCategoryAllowlistDefault != null) {
160             sIssueCategoryAllowlistDefault = issueCategoryAllowlistDefault;
161         }
162         String refreshOnPageOpenSourcesDefault =
163                 safetyCenterResourcesApk.getOptionalStringByName(
164                         "config_defaultRefreshOnPageOpenSources");
165         if (refreshOnPageOpenSourcesDefault != null) {
166             sRefreshOnPageOpenSourcesDefault = refreshOnPageOpenSourcesDefault;
167         }
168         String actionsToOverrideWithDefaultIntentDefault =
169                 safetyCenterResourcesApk.getOptionalStringByName(
170                         "config_defaultActionsToOverrideWithDefaultIntent");
171         if (actionsToOverrideWithDefaultIntentDefault != null) {
172             sActionsToOverrideWithDefaultIntentDefault = actionsToOverrideWithDefaultIntentDefault;
173         }
174     }
175 
176     private static final Duration TEMP_HIDDEN_ISSUE_RESURFACE_DELAY_DEFAULT_DURATION =
177             Duration.ofDays(2);
178 
179     /** Dumps state for debugging purposes. */
dump(PrintWriter fout)180     static void dump(PrintWriter fout) {
181         fout.println("FLAGS");
182         printFlag(fout, PROPERTY_SAFETY_CENTER_ENABLED, getSafetyCenterEnabled());
183         printFlag(fout, PROPERTY_NOTIFICATIONS_ENABLED, getNotificationsEnabled());
184         printFlag(
185                 fout,
186                 PROPERTY_NOTIFICATIONS_ALLOWED_SOURCES,
187                 getNotificationsAllowedSourceIdsFlag());
188         printFlag(fout, PROPERTY_NOTIFICATIONS_MIN_DELAY, getNotificationsMinDelay());
189         printFlag(
190                 fout,
191                 PROPERTY_NOTIFICATIONS_IMMEDIATE_BEHAVIOR_ISSUES,
192                 getImmediateNotificationBehaviorIssues());
193         printFlag(
194                 fout, PROPERTY_NOTIFICATION_RESURFACE_INTERVAL, getNotificationResurfaceInterval());
195         printFlag(fout, PROPERTY_REPLACE_LOCK_SCREEN_ICON_ACTION, getReplaceLockScreenIconAction());
196         printFlag(fout, PROPERTY_RESOLVING_ACTION_TIMEOUT_MILLIS, getResolvingActionTimeout());
197         printFlag(fout, PROPERTY_FGS_ALLOWLIST_DURATION_MILLIS, getFgsAllowlistDuration());
198         printFlag(fout, PROPERTY_UNTRACKED_SOURCES, getUntrackedSourceIds());
199         printFlag(fout, PROPERTY_RESURFACE_ISSUE_MAX_COUNTS, getResurfaceIssueMaxCounts());
200         printFlag(fout, PROPERTY_RESURFACE_ISSUE_DELAYS_MILLIS, getResurfaceIssueDelaysMillis());
201         printFlag(
202                 fout,
203                 PROPERTY_BACKGROUND_REFRESH_DENIED_SOURCES,
204                 getBackgroundRefreshDeniedSourceIds());
205         printFlag(
206                 fout, PROPERTY_REFRESH_SOURCES_TIMEOUTS_MILLIS, getRefreshSourcesTimeoutsMillis());
207         printFlag(fout, PROPERTY_ISSUE_CATEGORY_ALLOWLISTS, getIssueCategoryAllowlists());
208         printFlag(fout, PROPERTY_ALLOW_STATSD_LOGGING, getAllowStatsdLogging());
209         printFlag(fout, PROPERTY_SHOW_SUBPAGES, getShowSubpages());
210         printFlag(
211                 fout,
212                 PROPERTY_OVERRIDE_REFRESH_ON_PAGE_OPEN_SOURCES,
213                 getOverrideRefreshOnPageOpenSourceIds());
214         printFlag(
215                 fout,
216                 PROPERTY_ADDITIONAL_ALLOW_PACKAGE_CERTS,
217                 getAdditionalAllowedPackageCertsString());
218         fout.println();
219     }
220 
printFlag(PrintWriter pw, String key, @Nullable Duration duration)221     private static void printFlag(PrintWriter pw, String key, @Nullable Duration duration) {
222         if (duration == null) {
223             printFlag(pw, key, "null");
224         } else {
225             printFlag(pw, key, duration.toMillis() + " (" + duration + ")");
226         }
227     }
228 
printFlag(PrintWriter pw, String key, Object value)229     private static void printFlag(PrintWriter pw, String key, Object value) {
230         pw.println("\t" + key + "=" + value);
231     }
232 
233     /** Returns whether Safety Center is enabled. */
getSafetyCenterEnabled()234     public static boolean getSafetyCenterEnabled() {
235         if (Flags.safetyCenterEnabledNoDeviceConfig() && SdkLevel.isAtLeastU()) {
236             return true;
237         }
238         return getBoolean(PROPERTY_SAFETY_CENTER_ENABLED, SdkLevel.isAtLeastU());
239     }
240 
241     /** Returns whether Safety Center notifications are enabled. */
getNotificationsEnabled()242     public static boolean getNotificationsEnabled() {
243         return getBoolean(PROPERTY_NOTIFICATIONS_ENABLED, SdkLevel.isAtLeastU());
244     }
245 
246     /**
247      * Returns the IDs of sources that Safety Center can send notifications about, in addition to
248      * those permitted by the current XML config.
249      *
250      * <p>If the ID of a source appears on this list then Safety Center may send notifications about
251      * issues from that source, regardless of (overriding) the XML config. If the ID of a source is
252      * absent from this list, then Safety Center may send such notifications only if the XML config
253      * allows it.
254      *
255      * <p>Note that the {@code areNotificationsAllowed} config attribute is only available on API U+
256      * and therefore this is the only way to enable notifications for sources on Android T.
257      */
getNotificationsAllowedSourceIds()258     public static ArraySet<String> getNotificationsAllowedSourceIds() {
259         ArraySet<String> sources = getNotificationsAllowedSourceIdsFlag();
260         // This is a hack to update the flag value via mainline update. Reasons why we can't do this
261         // via:
262         // remote flag update - these are generally avoided and considered risky
263         // XML config - it would break GTS tests for OEMs that have a separate config copy
264         // default flag value - it would also require a remote flag update
265         if (Flags.odadNotificationsSupported()) {
266             sources.addAll(sAllowedNotificationSources);
267         }
268         if (SdkLevel.isAtLeastU()) {
269             sources.addAll(sAllowedNotificationSourcesUPlus);
270         }
271         return sources;
272     }
273 
getNotificationsAllowedSourceIdsFlag()274     private static ArraySet<String> getNotificationsAllowedSourceIdsFlag() {
275         return getCommaSeparatedStrings(PROPERTY_NOTIFICATIONS_ALLOWED_SOURCES);
276     }
277 
278     /**
279      * Returns the minimum delay before Safety Center can send a notification for an issue with
280      * {@link SafetySourceIssue#NOTIFICATION_BEHAVIOR_DELAYED}.
281      *
282      * <p>The actual delay used may be longer.
283      */
getNotificationsMinDelay()284     public static Duration getNotificationsMinDelay() {
285         return getDuration(
286                 PROPERTY_NOTIFICATIONS_MIN_DELAY, NOTIFICATIONS_MIN_DELAY_DEFAULT_DURATION);
287     }
288 
289     /**
290      * Returns the issue type IDs for which, if otherwise undefined, Safety Center should use {@link
291      * SafetySourceIssue#NOTIFICATION_BEHAVIOR_IMMEDIATELY}.
292      *
293      * <p>If a safety source specifies the notification behavior of an issue explicitly this flag
294      * has no effect, even if the issue matches one of the entries in this flag.
295      *
296      * <p>Entries in this set should be strings of the form "safety_source_id/issue_type_id".
297      */
getImmediateNotificationBehaviorIssues()298     public static ArraySet<String> getImmediateNotificationBehaviorIssues() {
299         return getCommaSeparatedStrings(PROPERTY_NOTIFICATIONS_IMMEDIATE_BEHAVIOR_ISSUES);
300     }
301 
302     /**
303      * Returns the minimum interval that must elapse before Safety Center can resurface a
304      * notification after it was dismissed, or {@code null} (the default) if dismissed notifications
305      * cannot resurface.
306      *
307      * <p>Returns {@code null} if the underlying device config flag is either unset or is set to a
308      * negative value.
309      *
310      * <p>There may be other conditions for resurfacing a notification and the actual delay may be
311      * longer than this.
312      */
313     @Nullable
getNotificationResurfaceInterval()314     public static Duration getNotificationResurfaceInterval() {
315         long millis = getLong(PROPERTY_NOTIFICATION_RESURFACE_INTERVAL, -1);
316         if (millis < 0) {
317             return null;
318         } else {
319             return Duration.ofMillis(millis);
320         }
321     }
322 
323     /**
324      * Returns whether we should replace the lock screen source's {@link
325      * android.safetycenter.SafetySourceStatus.IconAction}.
326      */
getReplaceLockScreenIconAction()327     public static boolean getReplaceLockScreenIconAction() {
328         return getBoolean(PROPERTY_REPLACE_LOCK_SCREEN_ICON_ACTION, true);
329     }
330 
331     /**
332      * Returns the time for which Safety Center will wait for a source to respond to a resolving
333      * action before timing out.
334      */
getResolvingActionTimeout()335     static Duration getResolvingActionTimeout() {
336         return getDuration(
337                 PROPERTY_RESOLVING_ACTION_TIMEOUT_MILLIS,
338                 RESOLVING_ACTION_TIMEOUT_DEFAULT_DURATION);
339     }
340 
341     /**
342      * Returns the time for which an app, upon receiving a Safety Center refresh broadcast, will be
343      * placed on a temporary power allowlist allowing it to start a foreground service from the
344      * background.
345      */
getFgsAllowlistDuration()346     static Duration getFgsAllowlistDuration() {
347         return getDuration(PROPERTY_FGS_ALLOWLIST_DURATION_MILLIS, FGS_ALLOWLIST_DEFAULT_DURATION);
348     }
349 
350     /**
351      * Returns the IDs of sources that should not be tracked, for example because they are
352      * mid-rollout. Broadcasts are still sent to these sources.
353      */
getUntrackedSourceIds()354     static ArraySet<String> getUntrackedSourceIds() {
355         return getCommaSeparatedStrings(PROPERTY_UNTRACKED_SOURCES, sUntrackedSourcesDefault);
356     }
357 
358     /**
359      * Returns the IDs of sources that should only be refreshed when Safety Center is on screen. We
360      * will refresh these sources only on page open and when the scan button is clicked.
361      */
getBackgroundRefreshDeniedSourceIds()362     static ArraySet<String> getBackgroundRefreshDeniedSourceIds() {
363         return getCommaSeparatedStrings(
364                 PROPERTY_BACKGROUND_REFRESH_DENIED_SOURCES, sBackgroundRefreshDenyDefault);
365     }
366 
367     /**
368      * Returns the time for which a Safety Center refresh is allowed to wait for a source to respond
369      * to a refresh request before timing out and marking the refresh as completed, based on the
370      * reason for the refresh.
371      */
getRefreshSourcesTimeout(@efreshReason int refreshReason)372     static Duration getRefreshSourcesTimeout(@RefreshReason int refreshReason) {
373         String refreshSourcesTimeouts = getRefreshSourcesTimeoutsMillis();
374         Long timeout = getLongValueFromStringMapping(refreshSourcesTimeouts, refreshReason);
375         if (timeout != null) {
376             return Duration.ofMillis(timeout);
377         }
378         return REFRESH_SOURCES_TIMEOUT_DEFAULT_DURATION;
379     }
380 
381     /**
382      * Returns a comma-delimited list of colon-delimited pairs where the left value is a {@link
383      * RefreshReason} and the right value is the refresh timeout applied for each source in case of
384      * a refresh.
385      */
getRefreshSourcesTimeoutsMillis()386     private static String getRefreshSourcesTimeoutsMillis() {
387         return getString(PROPERTY_REFRESH_SOURCES_TIMEOUTS_MILLIS, REFRESH_SOURCES_TIMEOUT_DEFAULT);
388     }
389 
390     /**
391      * Returns the number of times an issue of the given {@link SafetySourceData.SeverityLevel}
392      * should be resurfaced.
393      */
getResurfaceIssueMaxCount( @afetySourceData.SeverityLevel int severityLevel)394     public static long getResurfaceIssueMaxCount(
395             @SafetySourceData.SeverityLevel int severityLevel) {
396         String maxCountsConfigString = getResurfaceIssueMaxCounts();
397         Long maxCount = getLongValueFromStringMapping(maxCountsConfigString, severityLevel);
398         if (maxCount != null) {
399             return maxCount;
400         }
401         return RESURFACE_ISSUE_MAX_COUNT_DEFAULT_COUNT;
402     }
403 
404     /**
405      * Returns a comma-delimited list of colon-delimited pairs where the left value is an issue
406      * {@link SafetySourceData.SeverityLevel} and the right value is the number of times an issue of
407      * this {@link SafetySourceData.SeverityLevel} should be resurfaced.
408      */
getResurfaceIssueMaxCounts()409     private static String getResurfaceIssueMaxCounts() {
410         return getString(PROPERTY_RESURFACE_ISSUE_MAX_COUNTS, RESURFACE_ISSUE_MAX_COUNT_DEFAULT);
411     }
412 
413     /**
414      * Returns the time after which a dismissed issue of the given {@link
415      * SafetySourceData.SeverityLevel} will resurface if it has not reached the maximum count for
416      * which a dismissed issue of the given {@link SafetySourceData.SeverityLevel} should be
417      * resurfaced.
418      */
getResurfaceIssueDelay( @afetySourceData.SeverityLevel int severityLevel)419     public static Duration getResurfaceIssueDelay(
420             @SafetySourceData.SeverityLevel int severityLevel) {
421         String delaysConfigString = getResurfaceIssueDelaysMillis();
422         Long delayMillis = getLongValueFromStringMapping(delaysConfigString, severityLevel);
423         if (delayMillis != null) {
424             return Duration.ofMillis(delayMillis);
425         }
426         return RESURFACE_ISSUE_DELAYS_DEFAULT_DURATION;
427     }
428 
429     /**
430      * Returns a comma-delimited list of colon-delimited pairs where the left value is an issue
431      * {@link SafetySourceData.SeverityLevel} and the right value is the time after which a
432      * dismissed issue of this safety source severity level will resurface if it has not reached the
433      * maximum count for which a dismissed issue of this {@link SafetySourceData.SeverityLevel}
434      * should be resurfaced.
435      */
getResurfaceIssueDelaysMillis()436     private static String getResurfaceIssueDelaysMillis() {
437         return getString(PROPERTY_RESURFACE_ISSUE_DELAYS_MILLIS, RESURFACE_ISSUE_DELAYS_DEFAULT);
438     }
439 
440     /**
441      * Returns a comma-delimited list of colon-delimited pairs of SourceId:ActionId. The action IDs
442      * listed by this flag should have their {@code PendingIntent}s overridden with the source's
443      * default intent drawn from Safety Center's config file, if available.
444      */
getActionsToOverrideWithDefaultIntent()445     private static String getActionsToOverrideWithDefaultIntent() {
446         return getString(
447                 PROPERTY_ACTIONS_TO_OVERRIDE_WITH_DEFAULT_INTENT,
448                 sActionsToOverrideWithDefaultIntentDefault);
449     }
450 
451     /** Returns a duration after which a temporarily hidden issue will resurface. */
getTemporarilyHiddenIssueResurfaceDelay()452     public static Duration getTemporarilyHiddenIssueResurfaceDelay() {
453         return getDuration(
454                 PROPERTY_TEMP_HIDDEN_ISSUE_RESURFACE_DELAY_MILLIS,
455                 TEMP_HIDDEN_ISSUE_RESURFACE_DELAY_DEFAULT_DURATION);
456     }
457 
458     /**
459      * Returns whether a safety source is allowed to send issues for the given {@link
460      * SafetySourceIssue.IssueCategory}.
461      */
isIssueCategoryAllowedForSource( @afetySourceIssue.IssueCategory int issueCategory, String safetySourceId)462     public static boolean isIssueCategoryAllowedForSource(
463             @SafetySourceIssue.IssueCategory int issueCategory, String safetySourceId) {
464         List<String> allowlist =
465                 getStringListValueFromStringMapping(
466                         getIssueCategoryAllowlists(), Integer.toString(issueCategory));
467         return allowlist.isEmpty() || allowlist.contains(safetySourceId);
468     }
469 
470     /** Returns a set of package certificates allowlisted for the given package name. */
getAdditionalAllowedPackageCerts(String packageName)471     public static ArraySet<String> getAdditionalAllowedPackageCerts(String packageName) {
472         String property = getAdditionalAllowedPackageCertsString();
473         String allowlistedCertString = getStringValueFromStringMapping(property, packageName);
474         if (allowlistedCertString == null) {
475             return new ArraySet<>();
476         }
477         return new ArraySet<>(allowlistedCertString.split("\\|"));
478     }
479 
480     /**
481      * Returns a comma-delimited list of colon-delimited pairs where the left value is an issue
482      * {@link SafetySourceIssue.IssueCategory} and the right value is a vertical-bar-delimited list
483      * of IDs of safety sources that are allowed to send issues with this category.
484      */
getIssueCategoryAllowlists()485     private static String getIssueCategoryAllowlists() {
486         return getString(PROPERTY_ISSUE_CATEGORY_ALLOWLISTS, sIssueCategoryAllowlistDefault);
487     }
488 
getAdditionalAllowedPackageCertsString()489     private static String getAdditionalAllowedPackageCertsString() {
490         return getString(PROPERTY_ADDITIONAL_ALLOW_PACKAGE_CERTS, "");
491     }
492 
493     /** Returns whether we allow statsd logging. */
getAllowStatsdLogging()494     public static boolean getAllowStatsdLogging() {
495         return getBoolean(PROPERTY_ALLOW_STATSD_LOGGING, true);
496     }
497 
498     /**
499      * Returns a list of action IDs that should be overridden with the source's default intent drawn
500      * from the config for a given source.
501      */
getActionsToOverrideWithDefaultIntentForSource( String safetySourceId)502     public static List<String> getActionsToOverrideWithDefaultIntentForSource(
503             String safetySourceId) {
504         return getStringListValueFromStringMapping(
505                 getActionsToOverrideWithDefaultIntent(), safetySourceId);
506     }
507 
508     /**
509      * Returns whether to show subpages in the Safety Center UI for Android-U instead of the
510      * expand-and-collapse list implementation.
511      */
getShowSubpages()512     static boolean getShowSubpages() {
513         return SdkLevel.isAtLeastU() && getBoolean(PROPERTY_SHOW_SUBPAGES, true);
514     }
515 
516     /**
517      * Returns an array of safety source Ids that will be refreshed on page open, even if
518      * refreshOnPageOpenAllowed is false (the default) in the XML config.
519      */
getOverrideRefreshOnPageOpenSourceIds()520     static ArraySet<String> getOverrideRefreshOnPageOpenSourceIds() {
521         return getCommaSeparatedStrings(
522                 PROPERTY_OVERRIDE_REFRESH_ON_PAGE_OPEN_SOURCES, sRefreshOnPageOpenSourcesDefault);
523     }
524 
getDuration(String property, Duration defaultValue)525     private static Duration getDuration(String property, Duration defaultValue) {
526         return Duration.ofMillis(getLong(property, defaultValue.toMillis()));
527     }
528 
getBoolean(String property, boolean defaultValue)529     private static boolean getBoolean(String property, boolean defaultValue) {
530         // This call requires the READ_DEVICE_CONFIG permission.
531         final long callingId = Binder.clearCallingIdentity();
532         try {
533             return DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_PRIVACY, property, defaultValue);
534         } finally {
535             Binder.restoreCallingIdentity(callingId);
536         }
537     }
538 
getLong(String property, long defaultValue)539     private static long getLong(String property, long defaultValue) {
540         // This call requires the READ_DEVICE_CONFIG permission.
541         final long callingId = Binder.clearCallingIdentity();
542         try {
543             return DeviceConfig.getLong(DeviceConfig.NAMESPACE_PRIVACY, property, defaultValue);
544         } finally {
545             Binder.restoreCallingIdentity(callingId);
546         }
547     }
548 
getCommaSeparatedStrings(String property)549     private static ArraySet<String> getCommaSeparatedStrings(String property) {
550         return getCommaSeparatedStrings(property, "");
551     }
552 
getCommaSeparatedStrings(String property, String defaultValue)553     private static ArraySet<String> getCommaSeparatedStrings(String property, String defaultValue) {
554         return new ArraySet<>(getString(property, defaultValue).split(","));
555     }
556 
getString(String property, String defaultValue)557     private static String getString(String property, String defaultValue) {
558         // This call requires the READ_DEVICE_CONFIG permission.
559         final long callingId = Binder.clearCallingIdentity();
560         try {
561             return DeviceConfig.getString(DeviceConfig.NAMESPACE_PRIVACY, property, defaultValue);
562         } finally {
563             Binder.restoreCallingIdentity(callingId);
564         }
565     }
566 
567     /**
568      * Gets a long value for the provided integer key in a comma separated list of colon separated
569      * pairs of integers and longs.
570      */
571     @Nullable
getLongValueFromStringMapping(String mapping, int key)572     private static Long getLongValueFromStringMapping(String mapping, int key) {
573         String valueString = getStringValueFromStringMapping(mapping, key);
574         if (valueString == null) {
575             return null;
576         }
577         try {
578             return Long.parseLong(valueString);
579         } catch (NumberFormatException e) {
580             Log.w(TAG, "Badly formatted string mapping: " + mapping, e);
581             return null;
582         }
583     }
584 
585     /**
586      * Gets a value for the provided integer key in a comma separated list of colon separated pairs
587      * of integers and strings.
588      */
589     @Nullable
getStringValueFromStringMapping(String mapping, int key)590     private static String getStringValueFromStringMapping(String mapping, int key) {
591         return getStringValueFromStringMapping(mapping, Integer.toString(key));
592     }
593 
594     /**
595      * Gets a value for the provided key in a comma separated list of colon separated key-value
596      * string pairs.
597      */
598     @Nullable
getStringValueFromStringMapping(String mapping, String key)599     private static String getStringValueFromStringMapping(String mapping, String key) {
600         if (mapping.isEmpty()) {
601             return null;
602         }
603         String[] pairsList = mapping.split(",");
604         for (int i = 0; i < pairsList.length; i++) {
605             String[] pair = pairsList[i].split(":", -1 /* allow trailing empty strings */);
606             if (pair.length != 2) {
607                 Log.w(TAG, "Badly formatted string mapping: " + mapping);
608                 continue;
609             }
610             if (pair[0].equals(key)) {
611                 return pair[1];
612             }
613         }
614         return null;
615     }
616 
getStringListValueFromStringMapping(String mapping, String key)617     private static List<String> getStringListValueFromStringMapping(String mapping, String key) {
618         String value = getStringValueFromStringMapping(mapping, key);
619         if (value == null) {
620             return Collections.emptyList();
621         }
622 
623         return Arrays.asList(value.split("\\|"));
624     }
625 
SafetyCenterFlags()626     private SafetyCenterFlags() {}
627 }
628