System properties provide a convenient way to share information, usually
configurations, system-wide. Each partition can use its own system properties
internally. A problem can happen when properties are accessed across partitions,
such as /vendor
accessing /system
-defined properties. Since Android 8.0,
some partitions, such as /system
, can be upgraded, while /vendor
is left
unchanged. Because system properties are just a global dictionary of string
key-value pairs with no schema, it's difficult to stabilize properties. The
/system
partition could change or remove properties that the /vendor
partition depends on without any notice.
Starting with the Android 10 release, system properties
accessed across partitions are schematized into Sysprop Description files, and
APIs to access properties are generated as concrete functions for C++ and Rust,
and classes for Java. These APIs are more convenient to use because no magic
strings (such as ro.build.date
) are needed for access, and because they can be
statically typed. ABI stability is also checked at build time, and the build
breaks if incompatible changes happen. This check acts as explicitly defined
interfaces between partitions. These APIs can also provide consistency between
Rust, Java and C++.
Define system properties as APIs
Define system properties as APIs with Sysprop Description files (.sysprop
),
which use a TextFormat of protobuf, with the following schema:
// File: sysprop.proto
syntax = "proto3";
package sysprop;
enum Access {
Readonly = 0;
Writeonce = 1;
ReadWrite = 2;
}
enum Owner {
Platform = 0;
Vendor = 1;
Odm = 2;
}
enum Scope {
Public = 0;
Internal = 2;
}
enum Type {
Boolean = 0;
Integer = 1;
Long = 2;
Double = 3;
String = 4;
Enum = 5;
UInt = 6;
ULong = 7;
BooleanList = 20;
IntegerList = 21;
LongList = 22;
DoubleList = 23;
StringList = 24;
EnumList = 25;
UIntList = 26;
ULongList = 27;
}
message Property {
string api_name = 1;
Type type = 2;
Access access = 3;
Scope scope = 4;
string prop_name = 5;
string enum_values = 6;
bool integer_as_bool = 7;
string legacy_prop_name = 8;
}
message Properties {
Owner owner = 1;
string module = 2;
repeated Property prop = 3;
}
One Sysprop Description file contains one properties message, which describes a set of properties. The meaning of its fields are as follows.
Field | Meaning |
---|---|
owner
|
Set to the partition that owns the properties: Platform ,
Vendor , or Odm .
|
module
|
Used to create a namespace (C++) or static final class (Java) in which
generated APIs are placed. For example,
com.android.sysprop.BuildProperties
will be namespace com::android::sysprop::BuildProperties in C++,
and the BuildProperties class in the package in
com.android.sysprop in Java.
|
prop
|
List of properties. |
The meanings of the Property
message fields are as follows.
Field | Meaning |
---|---|
api_name
|
The name of the generated API. |
type
|
The type of this property. |
access
|
Readonly : Generates getter API only
Writeonce , ReadWrite : Generates getter and setter APIs
Note: Properties with the prefix ro. may not use
ReadWrite access.
|
scope
|
Internal : Only the owner can access.
Public : Everyone can access, except for NDK modules.
|
prop_name
|
The name of the underlying system property, for example
ro.build.date .
|
enum_values
|
(Enum , EnumList only) A bar(|)-separated string
that consists of possible enum values. For example, value1|value2 .
|
integer_as_bool
|
(Boolean , BooleanList only) Make setters use
0 and 1 instead of false and
true .
|
legacy_prop_name
|
(optional, Readonly properties only) The legacy name of the
underlying system property. When calling getter, the getter API tries to read
prop_name and uses legacy_prop_name if
prop_name doesn't exist. Use legacy_prop_name when
deprecating an existing property and moving to a new property.
|
Each type of property maps to the following types in C++, Java, and Rust.
Type | C++ | Java | Rust |
---|---|---|---|
Boolean | std::optional<bool>
|
Optional<Boolean>
|
bool
|
Integer | std::optional<std::int32_t>
|
Optional<Integer>
|
i32
|
UInt | std::optional<std::uint32_t>
|
Optional<Integer>
|
u32
|
Long | std::optional<std::int64_t>
|
Optional<Long>
|
i64
|
ULong | std::optional<std::uint64_t>
|
Optional<Long>
|
u64
|
Double | std::optional<double>
|
Optional<Double>
|
f64
|
String | std::optional<std::string>
|
Optional<String>
|
String
|
Enum | std::optional<{api_name}_values>
|
Optional<{api_name}_values>
|
{ApiName}Values
|
T List | std::vector<std::optional<T>>
|
List<T>
|
Vec<T>
|
Here's an example of a Sysprop Description file defining three properties:
# File: android/sysprop/PlatformProperties.sysprop
owner: Platform
module: "android.sysprop.PlatformProperties"
prop {
api_name: "build_date"
type: String
prop_name: "ro.build.date"
scope: Public
access: Readonly
}
prop {
api_name: "date_utc"
type: Integer
prop_name: "ro.build.date_utc"
scope: Internal
access: Readonly
}
prop {
api_name: "device_status"
type: Enum
enum_values: "on|off|unknown"
prop_name: "device.status"
scope: Public
access: ReadWrite
}
Define system properties libraries
You can now define sysprop_library
modules with Sysprop Description files.
sysprop_library
serves as an API for C++, Java and Rust. The build system
internally generates one rust_library
, one java_library
and one cc_library
for each instance of sysprop_library
.
// File: Android.bp
sysprop_library {
name: "PlatformProperties",
srcs: ["android/sysprop/PlatformProperties.sysprop"],
property_owner: "Platform",
vendor_available: true,
}
You must include API lists files in the source for API checks. To do this,
create API files and an api
directory. Put the api
directory in the same
directory as Android.bp
. The API filenames are <module_name>-current.txt
,
<module_name>-latest.txt
. <module_name>-current.txt
holds the API signatures
of current source codes, and <module_name>-latest.txt
holds the latest frozen
API signatures. The build system checks whether the APIs are changed by
comparing these API files with generated API files at build time and emits an
error message and instructions to update current.txt
file if current.txt
doesn't match with the source codes. Here's an example directory and file
organization:
├── api
│ ├── PlatformProperties-current.txt
│ └── PlatformProperties-latest.txt
└── Android.bp
Rust, Java and C++ client modules can link against sysprop_library
to use
generated APIs. The build system creates links from clients to generated C++,
Java and Rust libraries, thus giving clients access to generated APIs.
java_library {
name: "JavaClient",
srcs: ["foo/bar.java"],
libs: ["PlatformProperties"],
}
cc_binary {
name: "cc_client",
srcs: ["baz.cpp"],
shared_libs: ["PlatformProperties"],
}
rust_binary {
name: "rust_client",
srcs: ["src/main.rs"],
rustlibs: ["libplatformproperties_rust"],
}
Note that the Rust library name is generated by converting the sysprop_library
name to lowercase, replacing .
and -
with _
, and then prepending lib
and
appending _rust
.
In the preceding example, you could access defined properties as follows.
Rust example:
use platformproperties::DeviceStatusValues;
fn foo() -> Result<(), Error> {
// Read "ro.build.date_utc". default value is -1.
let date_utc = platformproperties::date_utc()?.unwrap_or_else(-1);
// set "device.status" to "unknown" if "ro.build.date" is not set.
if platformproperties::build_date()?.is_none() {
platformproperties::set_device_status(DeviceStatusValues::UNKNOWN);
}
…
}
Java example:
import android.sysprop.PlatformProperties;
…
static void foo() {
…
// read "ro.build.date_utc". default value is -1
Integer dateUtc = PlatformProperties.date_utc().orElse(-1);
// set "device.status" to "unknown" if "ro.build.date" is not set
if (!PlatformProperties.build_date().isPresent()) {
PlatformProperties.device_status(
PlatformProperties.device_status_values.UNKNOWN
);
}
…
}
…
C++ example:
#include <android/sysprop/PlatformProperties.sysprop.h>
using namespace android::sysprop;
…
void bar() {
…
// read "ro.build.date". default value is "(unknown)"
std::string build_date = PlatformProperties::build_date().value_or("(unknown)");
// set "device.status" to "on" if it's "unknown" or not set
using PlatformProperties::device_status_values;
auto status = PlatformProperties::device_status();
if (!status.has_value() || status.value() == device_status_values::UNKNOWN) {
PlatformProperties::device_status(device_status_values::ON);
}
…
}
…