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, orOdm. | 
| module | Used to create a namespace (C++) or static final class (Java) in which
   generated APIs are placed. For example, com.android.sysprop.BuildPropertieswill be namespacecom::android::sysprop::BuildPropertiesin C++,
   and theBuildPropertiesclass in the package incom.android.syspropin 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 onlyWriteonce,ReadWrite: Generates getter and setter APIsNote: Properties with the prefix ro.may not useReadWriteaccess. | 
| 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,EnumListonly) A bar(|)-separated string
   that consists of possible enum values. For example,value1|value2. | 
| integer_as_bool | ( Boolean,BooleanListonly) Make setters use0and1instead offalseandtrue. | 
| legacy_prop_name | (optional, Readonlyproperties only) The legacy name of the
   underlying system property. When calling getter, the getter API tries to readprop_nameand useslegacy_prop_nameifprop_namedoesn't exist. Uselegacy_prop_namewhen
   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: ["libPlatformProperties"],
}
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);
    }
    …
}
…
