In Android system development, we often encounter situations where system properties need to be dynamically modified. Especially for properties that are marked read-only, modifying them can be difficult. This article will take RK3568 Android11 as an example to introduce how to dynamically replace arbitrary ro
attributes as well as the problems and solutions encountered in the process.
Demand background
In the Android system, properties are often used for configuration information, such as device model, hardware version, etc. Sometimes, in order to adapt to different hardware or software requirements, we may need to dynamically modify these properties while the system is running instead of recompiling the entire system.
Plan ideas
My solution is to load a custom properties file when the system starts. The properties in this file will overwrite the properties that already exist in the system. In this way, we can dynamically modify any attribute without modifying the system source code.
Property loading process of Android 11
-
Start the init process : When the Android system starts, the process is started first
init
. This is the first process of the system and is responsible for initializing the system environment and starting other system services. -
Load default properties :
init
The process first loads/system/etc/prop.default
the properties from the file. These properties typically include the hardware and basic configuration of the system.
if (!load_properties_from_file("/system/etc/prop.default", nullptr, &properties)) {
// Try recovery path
if (!load_properties_from_file("/prop.default", nullptr, &properties)) {
// Try legacy path
load_properties_from_file("/default.prop", nullptr, &properties);
}
}
- Load other property files : In addition to the default property file,
init
the process will also load multiple other property files, such as/system/build.prop
,/vendor/build.prop
etc. Properties in these files can override properties in the default properties file.
load_properties_from_file("/system/build.prop", nullptr, &properties);
load_properties_from_file("/vendor/build.prop", nullptr, &properties);
- Loading device-specific properties : In order to support multiple hardware configurations, Android 11 introduces device-specific properties files, such as
/odm/build.prop
,/product/build.prop
etc. The properties in these files can be customized based on specific hardware configurations.
load_properties_from_file("/odm/build.prop", nullptr, &properties);
load_properties_from_file("/product/build.prop", nullptr, &properties);
- Property override : When loading properties, properties loaded later will overwrite properties loaded first. This means that if the same property is defined in multiple properties files, the property value from the last loaded file will be used.
Why can custom properties override default properties?
This is because Android's attribute loading mechanism is designed based on the principle that attributes loaded later overwrite attributes loaded first. This design allows developers and OEM manufacturers to customize the system configuration by adding or modifying property files without modifying the system source code.
For example, we can /product/customize.prop
define a property in ro.product.system.model=rk3568_custom
. Since this file is loaded after other properties files, this property will overwrite the property with the same name defined in the previous file.
Implementation steps:
-
Select the location of the custom properties file : Considering the system startup sequence and partition mounting, we choose to place the custom properties file in the
/product
partition. Because this partition has been mounted in the early startup phase of the system, theinit
process can Access it on startup. We name our custom properties filecustomize.prop
. -
Modify the attribute loading code : In
system/core/init/property_service.cpp
the file, findPropertyLoadBootDefaults
the function. In this function, we added the code to load our custom properties file after loading the other properties files.
void PropertyLoadBootDefaults() {
// TODO(b/117892318): merge prop.default and build.prop files into one
// We read the properties and their values into a map, in order to always allow properties
// loaded in the later property files to override the properties in loaded in the earlier
// property files, regardless of if they are "ro." properties or not.
std::map<std::string, std::string> properties;
if (!load_properties_from_file("/system/etc/prop.default", nullptr, &properties)) {
// Try recovery path
if (!load_properties_from_file("/prop.default", nullptr, &properties)) {
// Try legacy path
load_properties_from_file("/default.prop", nullptr, &properties);
}
}
load_properties_from_file("/system/build.prop", nullptr, &properties);
load_properties_from_file("/system_ext/build.prop", nullptr, &properties);
load_properties_from_file("/vendor/default.prop", nullptr, &properties);
load_properties_from_file("/vendor/build.prop", nullptr, &properties);
if (SelinuxGetVendorAndroidVersion() >= __ANDROID_API_Q__) {
load_properties_from_file("/odm/etc/build.prop", nullptr, &properties);
} else {
load_properties_from_file("/odm/default.prop", nullptr, &properties);
load_properties_from_file("/odm/build.prop", nullptr, &properties);
}
load_properties_from_file("/product/build.prop", nullptr, &properties);
load_properties_from_file("/factory/factory.prop", "ro.*", &properties);
if (access(kDebugRamdiskProp, R_OK) == 0) {
LOG(INFO) << "Loading " << kDebugRamdiskProp;
load_properties_from_file(kDebugRamdiskProp, nullptr, &properties);
}
// 加载自定义的prop文件 start
//const char* customPropPath = "/mnt/private/customize.prop";
const char* customPropPath = "/product/customize.prop";
if (access(customPropPath, R_OK) == 0) {
LOG(INFO) << "[custom_prop] Loading " << customPropPath;
load_properties_from_file(customPropPath, nullptr, &properties);
} else {
LOG(INFO) << customPropPath << " not found, skipping.[custom_prop]";
}
// 加载自定义的prop文件 end
for (const auto& [name, value] : properties) {
std::string error;
if (PropertySet(name, value, &error) != PROP_SUCCESS) {
LOG(ERROR) << "Could not set '" << name << "' to '" << value
<< "' while loading .prop files" << error;
}
}
property_initialize_ro_product_props();
property_derive_build_fingerprint();
update_sys_usb_config();
}
How to test:
We customize.prop
set it in the file ro.product.system.model=rk3568_custom
. After starting the system, we used getprop ro.product.system.model
the command and indeed got it rk3568_custom
, which proved that our modification was successful.
-
Property verification : Use
getprop
the command to verify that the property has been loaded correctly. -
System functional testing : Ensure that the modified system can start normally and run various applications.
-
Log check : Use
logcat
the command to check the system log to ensure there are no errors or warnings related to attribute loading.
Problems encountered:
-
Partition mounting issue : Initially, we tried placing custom properties files on
/mnt/private
the partition. However, in the early stages of system startup, the partition has not yet been mounted, makinginit
the process inaccessible. Therefore, we chose/product
partitioning. -
Property coverage problem : Due to the order in which properties are loaded, properties loaded later will overwrite properties loaded first. Therefore, we ensure that the custom properties file is loaded after other properties files.
in conclusion
Through the above method, we successfully realized the dynamic replacement of arbitrary ro
attributes in the RK3568 Android12 system. This provides greater flexibility for Android system development, allowing us to adapt to different hardware and configuration needs more easily.