1 /* 2 * Copyright (C) 2018 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.server.am; 18 19 import android.annotation.NonNull; 20 import android.content.ContentResolver; 21 import android.database.ContentObserver; 22 import android.net.Uri; 23 import android.net.LocalSocketAddress; 24 import android.net.LocalSocket; 25 import android.os.AsyncTask; 26 import android.os.Build; 27 import android.os.SystemProperties; 28 import android.provider.DeviceConfig; 29 import android.provider.Settings; 30 import android.text.TextUtils; 31 import android.util.Slog; 32 import android.util.proto.ProtoInputStream; 33 import android.util.proto.ProtoOutputStream; 34 35 import com.android.internal.annotations.VisibleForTesting; 36 37 import android.aconfigd.Aconfigd.StorageRequestMessage; 38 import android.aconfigd.Aconfigd.StorageRequestMessages; 39 import android.aconfigd.Aconfigd.StorageReturnMessage; 40 import android.aconfigd.Aconfigd.StorageReturnMessages; 41 import static com.android.aconfig_new_storage.Flags.enableAconfigStorageDaemon; 42 43 import java.io.DataInputStream; 44 import java.io.DataOutputStream; 45 import java.io.BufferedReader; 46 import java.io.File; 47 import java.io.FileReader; 48 import java.io.IOException; 49 import java.util.HashSet; 50 import java.util.HashMap; 51 import java.util.Map; 52 import java.util.List; 53 import java.util.ArrayList; 54 55 /** 56 * Maps system settings to system properties. 57 * <p>The properties are dynamically updated when settings change. 58 * @hide 59 */ 60 public class SettingsToPropertiesMapper { 61 62 private static final String TAG = "SettingsToPropertiesMapper"; 63 64 private static final String SYSTEM_PROPERTY_PREFIX = "persist.device_config."; 65 66 private static final String RESET_PERFORMED_PROPERTY = "device_config.reset_performed"; 67 68 private static final String RESET_RECORD_FILE_PATH = 69 "/data/server_configurable_flags/reset_flags"; 70 71 private static final String SYSTEM_PROPERTY_VALID_CHARACTERS_REGEX = "^[\\w\\.\\-@:]*$"; 72 73 private static final String SYSTEM_PROPERTY_INVALID_SUBSTRING = ".."; 74 75 private static final int SYSTEM_PROPERTY_MAX_LENGTH = 92; 76 77 // experiment flags added to Global.Settings(before new "Config" provider table is available) 78 // will be added under this category. 79 private static final String GLOBAL_SETTINGS_CATEGORY = "global_settings"; 80 81 // Add the global setting you want to push to native level as experiment flag into this list. 82 // 83 // NOTE: please grant write permission system property prefix 84 // with format persist.device_config.global_settings.[flag_name] in system_server.te and grant 85 // read permission in the corresponding .te file your feature belongs to. 86 @VisibleForTesting 87 static final String[] sGlobalSettings = new String[] { 88 Settings.Global.NATIVE_FLAGS_HEALTH_CHECK_ENABLED, 89 }; 90 91 // TODO(b/282593625): Move this constant to DeviceConfig module 92 private static final String NAMESPACE_TETHERING_U_OR_LATER_NATIVE = 93 "tethering_u_or_later_native"; 94 95 // All the flags under the listed DeviceConfig scopes will be synced to native level. 96 // 97 // NOTE: please grant write permission system property prefix 98 // with format persist.device_config.[device_config_scope]. in system_server.te and grant read 99 // permission in the corresponding .te file your feature belongs to. 100 @VisibleForTesting 101 static final String[] sDeviceConfigScopes = new String[] { 102 DeviceConfig.NAMESPACE_ACTIVITY_MANAGER_NATIVE_BOOT, 103 DeviceConfig.NAMESPACE_CAMERA_NATIVE, 104 DeviceConfig.NAMESPACE_CONFIGURATION, 105 DeviceConfig.NAMESPACE_CONNECTIVITY, 106 DeviceConfig.NAMESPACE_EDGETPU_NATIVE, 107 DeviceConfig.NAMESPACE_INPUT_NATIVE_BOOT, 108 DeviceConfig.NAMESPACE_INTELLIGENCE_CONTENT_SUGGESTIONS, 109 DeviceConfig.NAMESPACE_LMKD_NATIVE, 110 DeviceConfig.NAMESPACE_MEDIA_NATIVE, 111 DeviceConfig.NAMESPACE_MGLRU_NATIVE, 112 DeviceConfig.NAMESPACE_NETD_NATIVE, 113 DeviceConfig.NAMESPACE_NNAPI_NATIVE, 114 DeviceConfig.NAMESPACE_PROFCOLLECT_NATIVE_BOOT, 115 DeviceConfig.NAMESPACE_REMOTE_KEY_PROVISIONING_NATIVE, 116 DeviceConfig.NAMESPACE_RUNTIME_NATIVE, 117 DeviceConfig.NAMESPACE_RUNTIME_NATIVE_BOOT, 118 DeviceConfig.NAMESPACE_STATSD_NATIVE, 119 DeviceConfig.NAMESPACE_STATSD_NATIVE_BOOT, 120 DeviceConfig.NAMESPACE_STORAGE_NATIVE_BOOT, 121 DeviceConfig.NAMESPACE_SURFACE_FLINGER_NATIVE_BOOT, 122 DeviceConfig.NAMESPACE_SWCODEC_NATIVE, 123 DeviceConfig.NAMESPACE_VENDOR_SYSTEM_NATIVE, 124 DeviceConfig.NAMESPACE_VENDOR_SYSTEM_NATIVE_BOOT, 125 DeviceConfig.NAMESPACE_VIRTUALIZATION_FRAMEWORK_NATIVE, 126 DeviceConfig.NAMESPACE_WINDOW_MANAGER_NATIVE_BOOT, 127 DeviceConfig.NAMESPACE_MEMORY_SAFETY_NATIVE_BOOT, 128 DeviceConfig.NAMESPACE_MEMORY_SAFETY_NATIVE, 129 DeviceConfig.NAMESPACE_HDMI_CONTROL, 130 NAMESPACE_TETHERING_U_OR_LATER_NATIVE 131 }; 132 133 // All the aconfig flags under the listed DeviceConfig scopes will be synced to native level. 134 // The list is sorted. 135 @VisibleForTesting 136 static final String[] sDeviceConfigAconfigScopes = new String[] { 137 "accessibility", 138 "android_core_networking", 139 "android_stylus", 140 "aoc", 141 "app_widgets", 142 "arc_next", 143 "art_mainline", 144 "art_performance", 145 "attack_tools", 146 "avic", 147 "biometrics", 148 "biometrics_framework", 149 "biometrics_integration", 150 "bluetooth", 151 "brownout_mitigation_audio", 152 "brownout_mitigation_modem", 153 "build", 154 "camera_hal", 155 "camera_platform", 156 "car_framework", 157 "car_perception", 158 "car_security", 159 "car_telemetry", 160 "codec_fwk", 161 "companion", 162 "com_android_adbd", 163 "content_protection", 164 "context_hub", 165 "core_experiments_team_internal", 166 "core_graphics", 167 "core_libraries", 168 "crumpet", 169 "dck_framework", 170 "devoptions_settings", 171 "game", 172 "gpu", 173 "haptics", 174 "hardware_backed_security_mainline", 175 "input", 176 "llvm_and_toolchains", 177 "lse_desktop_experience", 178 "machine_learning", 179 "mainline_modularization", 180 "mainline_sdk", 181 "make_pixel_haptics", 182 "media_audio", 183 "media_drm", 184 "media_reliability", 185 "media_solutions", 186 "media_tv", 187 "nearby", 188 "nfc", 189 "pdf_viewer", 190 "perfetto", 191 "pixel_audio_android", 192 "pixel_biometrics_face", 193 "pixel_bluetooth", 194 "pixel_connectivity_gps", 195 "pixel_continuity", 196 "pixel_sensors", 197 "pixel_system_sw_video", 198 "pixel_watch", 199 "platform_compat", 200 "platform_security", 201 "pmw", 202 "power", 203 "preload_safety", 204 "printing", 205 "privacy_infra_policy", 206 "resource_manager", 207 "responsible_apis", 208 "rust", 209 "safety_center", 210 "sensors", 211 "spoon", 212 "statsd", 213 "system_performance", 214 "system_sw_touch", 215 "system_sw_usb", 216 "test_suites", 217 "text", 218 "threadnetwork", 219 "treble", 220 "tv_system_ui", 221 "usb", 222 "vibrator", 223 "virtual_devices", 224 "virtualization", 225 "wallet_integration", 226 "wear_calling_messaging", 227 "wear_connectivity", 228 "wear_esim_carriers", 229 "wear_frameworks", 230 "wear_health_services", 231 "wear_media", 232 "wear_offload", 233 "wear_security", 234 "wear_system_health", 235 "wear_systems", 236 "wear_sysui", 237 "window_surfaces", 238 "windowing_frontend", 239 }; 240 241 public static final String NAMESPACE_REBOOT_STAGING = "staged"; 242 public static final String NAMESPACE_REBOOT_STAGING_DELIMITER = "*"; 243 244 public static final String NAMESPACE_LOCAL_OVERRIDES = "device_config_overrides"; 245 246 private final String[] mGlobalSettings; 247 248 private final String[] mDeviceConfigScopes; 249 250 private final String[] mDeviceConfigAconfigScopes; 251 252 private final ContentResolver mContentResolver; 253 254 @VisibleForTesting SettingsToPropertiesMapper(ContentResolver contentResolver, String[] globalSettings, String[] deviceConfigScopes, String[] deviceConfigAconfigScopes)255 protected SettingsToPropertiesMapper(ContentResolver contentResolver, 256 String[] globalSettings, 257 String[] deviceConfigScopes, 258 String[] deviceConfigAconfigScopes) { 259 mContentResolver = contentResolver; 260 mGlobalSettings = globalSettings; 261 mDeviceConfigScopes = deviceConfigScopes; 262 mDeviceConfigAconfigScopes = deviceConfigAconfigScopes; 263 } 264 265 @VisibleForTesting updatePropertiesFromSettings()266 void updatePropertiesFromSettings() { 267 for (String globalSetting : mGlobalSettings) { 268 Uri settingUri = Settings.Global.getUriFor(globalSetting); 269 String propName = makePropertyName(GLOBAL_SETTINGS_CATEGORY, globalSetting); 270 if (settingUri == null) { 271 logErr("setting uri is null for globalSetting " + globalSetting); 272 continue; 273 } 274 if (propName == null) { 275 logErr("invalid prop name for globalSetting " + globalSetting); 276 continue; 277 } 278 279 ContentObserver co = new ContentObserver(null) { 280 @Override 281 public void onChange(boolean selfChange) { 282 updatePropertyFromSetting(globalSetting, propName); 283 } 284 }; 285 286 // only updating on starting up when no native flags reset is performed during current 287 // booting. 288 if (!isNativeFlagsResetPerformed()) { 289 updatePropertyFromSetting(globalSetting, propName); 290 } 291 mContentResolver.registerContentObserver(settingUri, false, co); 292 } 293 294 for (String deviceConfigScope : mDeviceConfigScopes) { 295 DeviceConfig.addOnPropertiesChangedListener( 296 deviceConfigScope, 297 AsyncTask.THREAD_POOL_EXECUTOR, 298 (DeviceConfig.Properties properties) -> { 299 String scope = properties.getNamespace(); 300 for (String key : properties.getKeyset()) { 301 String propertyName = makePropertyName(scope, key); 302 if (propertyName == null) { 303 logErr("unable to construct system property for " + scope + "/" 304 + key); 305 return; 306 } 307 setProperty(propertyName, properties.getString(key, null)); 308 309 // for legacy namespaces, they can also be used for trunk stable 310 // purposes. so push flag also into trunk stable slot in sys prop, 311 // later all legacy usage will be refactored and the sync to old 312 // sys prop slot can be removed. 313 String aconfigPropertyName = makeAconfigFlagPropertyName(scope, key); 314 if (aconfigPropertyName == null) { 315 logErr("unable to construct system property for " + scope + "/" 316 + key); 317 return; 318 } 319 setProperty(aconfigPropertyName, properties.getString(key, null)); 320 } 321 }); 322 } 323 324 for (String deviceConfigAconfigScope : mDeviceConfigAconfigScopes) { 325 DeviceConfig.addOnPropertiesChangedListener( 326 deviceConfigAconfigScope, 327 AsyncTask.THREAD_POOL_EXECUTOR, 328 (DeviceConfig.Properties properties) -> { 329 String scope = properties.getNamespace(); 330 for (String key : properties.getKeyset()) { 331 String aconfigPropertyName = makeAconfigFlagPropertyName(scope, key); 332 if (aconfigPropertyName == null) { 333 logErr("unable to construct system property for " + scope + "/" 334 + key); 335 return; 336 } 337 setProperty(aconfigPropertyName, properties.getString(key, null)); 338 } 339 }); 340 } 341 342 // add sys prop sync callback for staged flag values 343 DeviceConfig.addOnPropertiesChangedListener( 344 NAMESPACE_REBOOT_STAGING, 345 AsyncTask.THREAD_POOL_EXECUTOR, 346 (DeviceConfig.Properties properties) -> { 347 348 for (String flagName : properties.getKeyset()) { 349 String flagValue = properties.getString(flagName, null); 350 if (flagName == null || flagValue == null) { 351 continue; 352 } 353 354 int idx = flagName.indexOf(NAMESPACE_REBOOT_STAGING_DELIMITER); 355 if (idx == -1 || idx == flagName.length() - 1 || idx == 0) { 356 logErr("invalid staged flag: " + flagName); 357 continue; 358 } 359 360 String actualNamespace = flagName.substring(0, idx); 361 String actualFlagName = flagName.substring(idx+1); 362 String propertyName = "next_boot." + makeAconfigFlagPropertyName( 363 actualNamespace, actualFlagName); 364 365 setProperty(propertyName, flagValue); 366 } 367 368 // send prop stage request to new storage 369 if (enableAconfigStorageDaemon()) { 370 stageFlagsInNewStorage(properties); 371 } 372 373 }); 374 375 // add prop sync callback for flag local overrides 376 DeviceConfig.addOnPropertiesChangedListener( 377 NAMESPACE_LOCAL_OVERRIDES, 378 AsyncTask.THREAD_POOL_EXECUTOR, 379 (DeviceConfig.Properties properties) -> { 380 if (enableAconfigStorageDaemon()) { 381 setLocalOverridesInNewStorage(properties); 382 } 383 }); 384 } 385 386 /** 387 * apply flag local override in aconfig new storage 388 * @param requests: request proto output stream 389 * @return aconfigd socket return as proto input stream 390 */ sendAconfigdRequests(ProtoOutputStream requests)391 static ProtoInputStream sendAconfigdRequests(ProtoOutputStream requests) { 392 // connect to aconfigd socket 393 LocalSocket client = new LocalSocket(); 394 try{ 395 client.connect(new LocalSocketAddress( 396 "aconfigd", LocalSocketAddress.Namespace.RESERVED)); 397 Slog.d(TAG, "connected to aconfigd socket"); 398 } catch (IOException ioe) { 399 logErr("failed to connect to aconfigd socket", ioe); 400 return null; 401 } 402 403 DataInputStream inputStream = null; 404 DataOutputStream outputStream = null; 405 try { 406 inputStream = new DataInputStream(client.getInputStream()); 407 outputStream = new DataOutputStream(client.getOutputStream()); 408 } catch (IOException ioe) { 409 logErr("failed to get local socket iostreams", ioe); 410 return null; 411 } 412 413 // send requests 414 try { 415 byte[] requests_bytes = requests.getBytes(); 416 outputStream.writeInt(requests_bytes.length); 417 outputStream.write(requests_bytes, 0, requests_bytes.length); 418 Slog.d(TAG, "flag override requests sent to aconfigd"); 419 } catch (IOException ioe) { 420 logErr("failed to send requests to aconfigd", ioe); 421 return null; 422 } 423 424 // read return 425 try { 426 int num_bytes = inputStream.readInt(); 427 ProtoInputStream returns = new ProtoInputStream(inputStream); 428 Slog.d(TAG, "received " + num_bytes + " bytes back from aconfigd"); 429 return returns; 430 } catch (IOException ioe) { 431 logErr("failed to read requests return from aconfigd", ioe); 432 return null; 433 } 434 } 435 436 /** 437 * serialize a flag override request 438 * @param proto 439 */ writeFlagOverrideRequest( ProtoOutputStream proto, String packageName, String flagName, String flagValue, boolean isLocal)440 static void writeFlagOverrideRequest( 441 ProtoOutputStream proto, String packageName, String flagName, String flagValue, 442 boolean isLocal) { 443 long msgsToken = proto.start(StorageRequestMessages.MSGS); 444 long msgToken = proto.start(StorageRequestMessage.FLAG_OVERRIDE_MESSAGE); 445 proto.write(StorageRequestMessage.FlagOverrideMessage.PACKAGE_NAME, packageName); 446 proto.write(StorageRequestMessage.FlagOverrideMessage.FLAG_NAME, flagName); 447 proto.write(StorageRequestMessage.FlagOverrideMessage.FLAG_VALUE, flagValue); 448 proto.write(StorageRequestMessage.FlagOverrideMessage.IS_LOCAL, isLocal); 449 proto.end(msgToken); 450 proto.end(msgsToken); 451 } 452 453 /** 454 * deserialize a flag input proto stream and log 455 * @param proto 456 */ parseAndLogAconfigdReturn(ProtoInputStream proto)457 static void parseAndLogAconfigdReturn(ProtoInputStream proto) throws IOException { 458 while (true) { 459 switch (proto.nextField()) { 460 case (int) StorageReturnMessages.MSGS: 461 long msgsToken = proto.start(StorageReturnMessages.MSGS); 462 switch (proto.nextField()) { 463 case (int) StorageReturnMessage.FLAG_OVERRIDE_MESSAGE: 464 Slog.d(TAG, "successfully handled override requests"); 465 long msgToken = proto.start(StorageReturnMessage.FLAG_OVERRIDE_MESSAGE); 466 proto.end(msgToken); 467 break; 468 case (int) StorageReturnMessage.ERROR_MESSAGE: 469 String errmsg = proto.readString(StorageReturnMessage.ERROR_MESSAGE); 470 Slog.d(TAG, "override request failed: " + errmsg); 471 break; 472 case ProtoInputStream.NO_MORE_FIELDS: 473 break; 474 default: 475 logErr("invalid message type, expecting only flag override return or error message"); 476 break; 477 } 478 proto.end(msgsToken); 479 break; 480 case ProtoInputStream.NO_MORE_FIELDS: 481 return; 482 default: 483 logErr("invalid message type, expect storage return message"); 484 break; 485 } 486 } 487 } 488 489 /** 490 * apply flag local override in aconfig new storage 491 * @param props 492 */ setLocalOverridesInNewStorage(DeviceConfig.Properties props)493 static void setLocalOverridesInNewStorage(DeviceConfig.Properties props) { 494 int num_requests = 0; 495 ProtoOutputStream requests = new ProtoOutputStream(); 496 for (String flagName : props.getKeyset()) { 497 String flagValue = props.getString(flagName, null); 498 if (flagName == null || flagValue == null) { 499 continue; 500 } 501 502 int idx = flagName.indexOf(":"); 503 if (idx == -1 || idx == flagName.length() - 1 || idx == 0) { 504 logErr("invalid local flag override: " + flagName); 505 continue; 506 } 507 String actualNamespace = flagName.substring(0, idx); 508 String fullFlagName = flagName.substring(idx+1); 509 idx = fullFlagName.lastIndexOf("."); 510 if (idx == -1) { 511 logErr("invalid flag name: " + fullFlagName); 512 continue; 513 } 514 String packageName = fullFlagName.substring(0, idx); 515 String realFlagName = fullFlagName.substring(idx+1); 516 writeFlagOverrideRequest(requests, packageName, realFlagName, flagValue, true); 517 ++num_requests; 518 } 519 520 if (num_requests == 0) { 521 return; 522 } 523 524 // send requests to aconfigd and obtain the return byte buffer 525 ProtoInputStream returns = sendAconfigdRequests(requests); 526 527 // deserialize back using proto input stream 528 try { 529 parseAndLogAconfigdReturn(returns); 530 } catch (IOException ioe) { 531 logErr("failed to parse aconfigd return", ioe); 532 } 533 } 534 start(ContentResolver contentResolver)535 public static SettingsToPropertiesMapper start(ContentResolver contentResolver) { 536 SettingsToPropertiesMapper mapper = new SettingsToPropertiesMapper( 537 contentResolver, 538 sGlobalSettings, 539 sDeviceConfigScopes, 540 sDeviceConfigAconfigScopes); 541 mapper.updatePropertiesFromSettings(); 542 return mapper; 543 } 544 545 /** 546 * If native level flags reset has been performed as an attempt to recover from a crash loop 547 * during current device booting. 548 * @return 549 */ isNativeFlagsResetPerformed()550 public static boolean isNativeFlagsResetPerformed() { 551 String value = SystemProperties.get(RESET_PERFORMED_PROPERTY); 552 return "true".equals(value); 553 } 554 555 /** 556 * return an array of native flag categories under which flags got reset during current device 557 * booting. 558 * @return 559 */ getResetNativeCategories()560 public static @NonNull String[] getResetNativeCategories() { 561 if (!isNativeFlagsResetPerformed()) { 562 return new String[0]; 563 } 564 565 String content = getResetFlagsFileContent(); 566 if (TextUtils.isEmpty(content)) { 567 return new String[0]; 568 } 569 570 String[] property_names = content.split(";"); 571 HashSet<String> categories = new HashSet<>(); 572 for (String property_name : property_names) { 573 String[] segments = property_name.split("\\."); 574 if (segments.length < 3) { 575 logErr("failed to extract category name from property " + property_name); 576 continue; 577 } 578 categories.add(segments[2]); 579 } 580 return categories.toArray(new String[0]); 581 } 582 583 /** 584 * system property name constructing rule: "persist.device_config.[category_name].[flag_name]". 585 * If the name contains invalid characters or substrings for system property name, 586 * will return null. 587 * @param categoryName 588 * @param flagName 589 * @return 590 */ 591 @VisibleForTesting makePropertyName(String categoryName, String flagName)592 static String makePropertyName(String categoryName, String flagName) { 593 String propertyName = SYSTEM_PROPERTY_PREFIX + categoryName + "." + flagName; 594 595 if (!propertyName.matches(SYSTEM_PROPERTY_VALID_CHARACTERS_REGEX) 596 || propertyName.contains(SYSTEM_PROPERTY_INVALID_SUBSTRING)) { 597 return null; 598 } 599 600 return propertyName; 601 } 602 603 604 /** 605 * stage flags in aconfig new storage 606 * @param propsToStage 607 */ 608 @VisibleForTesting stageFlagsInNewStorage(DeviceConfig.Properties props)609 static void stageFlagsInNewStorage(DeviceConfig.Properties props) { 610 // write aconfigd requests proto to proto output stream 611 int num_requests = 0; 612 ProtoOutputStream requests = new ProtoOutputStream(); 613 for (String flagName : props.getKeyset()) { 614 String flagValue = props.getString(flagName, null); 615 if (flagName == null || flagValue == null) { 616 continue; 617 } 618 619 int idx = flagName.indexOf("*"); 620 if (idx == -1 || idx == flagName.length() - 1 || idx == 0) { 621 logErr("invalid local flag override: " + flagName); 622 continue; 623 } 624 String actualNamespace = flagName.substring(0, idx); 625 String fullFlagName = flagName.substring(idx+1); 626 627 idx = fullFlagName.lastIndexOf("."); 628 if (idx == -1) { 629 logErr("invalid flag name: " + fullFlagName); 630 continue; 631 } 632 String packageName = fullFlagName.substring(0, idx); 633 String realFlagName = fullFlagName.substring(idx+1); 634 writeFlagOverrideRequest(requests, packageName, realFlagName, flagValue, false); 635 ++num_requests; 636 } 637 638 if (num_requests == 0) { 639 return; 640 } 641 642 // send requests to aconfigd and obtain the return 643 ProtoInputStream returns = sendAconfigdRequests(requests); 644 645 // deserialize back using proto input stream 646 try { 647 parseAndLogAconfigdReturn(returns); 648 } catch (IOException ioe) { 649 logErr("failed to parse aconfigd return", ioe); 650 } 651 } 652 653 /** 654 * system property name constructing rule for aconfig flags: 655 * "persist.device_config.aconfig_flags.[category_name].[flag_name]". 656 * If the name contains invalid characters or substrings for system property name, 657 * will return null. 658 * @param categoryName 659 * @param flagName 660 * @return 661 */ 662 @VisibleForTesting makeAconfigFlagPropertyName(String categoryName, String flagName)663 static String makeAconfigFlagPropertyName(String categoryName, String flagName) { 664 String propertyName = SYSTEM_PROPERTY_PREFIX + "aconfig_flags." + 665 categoryName + "." + flagName; 666 667 if (!propertyName.matches(SYSTEM_PROPERTY_VALID_CHARACTERS_REGEX) 668 || propertyName.contains(SYSTEM_PROPERTY_INVALID_SUBSTRING)) { 669 return null; 670 } 671 672 return propertyName; 673 } 674 setProperty(String key, String value)675 private void setProperty(String key, String value) { 676 // Check if need to clear the property 677 if (value == null) { 678 // It's impossible to remove system property, therefore we check previous value to 679 // avoid setting an empty string if the property wasn't set. 680 if (TextUtils.isEmpty(SystemProperties.get(key))) { 681 return; 682 } 683 value = ""; 684 } else if (value.length() > SYSTEM_PROPERTY_MAX_LENGTH) { 685 logErr("key=" + key + " value=" + value + " exceeds system property max length."); 686 return; 687 } 688 689 try { 690 SystemProperties.set(key, value); 691 } catch (Exception e) { 692 // Failure to set a property can be caused by SELinux denial. This usually indicates 693 // that the property wasn't allowlisted in sepolicy. 694 // No need to report it on all user devices, only on debug builds. 695 logErr("Unable to set property " + key + " value '" + value + "'", e); 696 } 697 } 698 logErr(String msg, Exception e)699 private static void logErr(String msg, Exception e) { 700 if (Build.IS_DEBUGGABLE) { 701 Slog.wtf(TAG, msg, e); 702 } else { 703 Slog.e(TAG, msg, e); 704 } 705 } 706 logErr(String msg)707 private static void logErr(String msg) { 708 if (Build.IS_DEBUGGABLE) { 709 Slog.wtf(TAG, msg); 710 } else { 711 Slog.e(TAG, msg); 712 } 713 } 714 715 @VisibleForTesting getResetFlagsFileContent()716 static String getResetFlagsFileContent() { 717 String content = null; 718 try { 719 File reset_flag_file = new File(RESET_RECORD_FILE_PATH); 720 BufferedReader br = new BufferedReader(new FileReader(reset_flag_file)); 721 content = br.readLine(); 722 723 br.close(); 724 } catch (IOException ioe) { 725 logErr("failed to read file " + RESET_RECORD_FILE_PATH, ioe); 726 } 727 return content; 728 } 729 730 @VisibleForTesting updatePropertyFromSetting(String settingName, String propName)731 void updatePropertyFromSetting(String settingName, String propName) { 732 String settingValue = Settings.Global.getString(mContentResolver, settingName); 733 setProperty(propName, settingValue); 734 } 735 } 736