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