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