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
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
Java and C++.
Defining 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
Note: Properties with the prefix |
scope
|
Internal : Only the owner can access.
|
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++ and Java.
Type | C++ | Java |
---|---|---|
Boolean | std::optional<bool>
|
Optional<Boolean>
|
Integer | std::optional<std::int32_t>
|
Optional<Integer>
|
UInt | std::optional<std::uint32_t>
|
Optional<Integer>
|
Long | std::optional<std::int64_t>
|
Optional<Long>
|
ULong | std::optional<std::uint64_t>
|
Optional<Long>
|
Double | std::optional<double>
|
Optional<Double>
|
String | std::optional<std::string>
|
Optional<String>
|
Enum | std::optional<{api_name}_values>
|
Optional<{api_name}_values>
|
T List | std::vector<std::optional<T>>
|
List<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
}
Defining system properties libraries
You can now define sysprop_library
modules with Sysprop Description files.
sysprop_library
serves as an API for both C++ and Java. The build system
internally generates 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
Both Java and C++ client modules can link against sysprop_library
to use
generated APIs. The build system creates links from clients to generated C++ and
Java 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"],
}
In the above example, you could access defined properties as follows.
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);
}
…
}
…