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