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