Implementa propiedades del sistema como APIs

Las propiedades del sistema proporcionan una forma conveniente de compartir información, por lo general, configuraciones, en todo el sistema. Cada partición puede usar sus propias propiedades del sistema de forma interna. Puede ocurrir un problema cuando se accede a las propiedades en diferentes particiones, como cuando /vendor accede a las propiedades definidas por /system. Desde Android 8.0, algunas particiones, como /system, se pueden actualizar, mientras que /vendor permanece sin cambios. Debido a que las propiedades del sistema son solo un diccionario global de pares clave-valor de cadenas sin esquema, es difícil estabilizar las propiedades. La partición /system podría cambiar o quitar propiedades de las que depende la partición /vendor sin previo aviso.

A partir de la versión de Android 10, las propiedades del sistema a las que se accede en las particiones se esquematizan en archivos de descripción de Sysprop, y las APIs para acceder a las propiedades se generan como funciones concretas para C++ y Rust, y clases para Java. Estas APIs son más convenientes de usar porque no se necesitan cadenas mágicas (como ro.build.date) para el acceso y porque se pueden escribir de forma estática. La estabilidad de la ABI también se verifica en el momento de la compilación, y la compilación se interrumpe si se producen cambios incompatibles. Esta verificación actúa como interfaces definidas de forma explícita entre las particiones. Estas APIs también pueden proporcionar coherencia entre Rust, Java y C++.

Define las propiedades del sistema como APIs

Define las propiedades del sistema como APIs con archivos de descripción de Sysprop (.sysprop), que usan un TextFormat de protobuf, con el siguiente esquema:

// 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;
}

Un archivo de descripción de Sysprop contiene un mensaje de propiedades, que describe un conjunto de propiedades. El significado de sus campos es el siguiente:

Campo Significado
owner Se establece en la partición que posee las propiedades: Platform, Vendor o Odm.
module Se usa para crear un espacio de nombres (C++) o una clase final estática (Java) en la que se colocan las APIs generadas. Por ejemplo, com.android.sysprop.BuildProperties será el espacio de nombres com::android::sysprop::BuildProperties en C++ y la clase BuildProperties en el paquete en com.android.sysprop en Java.
prop Es una lista de propiedades.

Los significados de los campos de mensajes de Property son los siguientes:

Campo Significado
api_name Nombre de la API generada.
type Es el tipo de esta propiedad.
access Readonly: Genera solo la API de getter.
Writeonce, ReadWrite: Genera APIs de getter y setter.
Nota: Las propiedades con el prefijo ro. no pueden usar el acceso de ReadWrite.
scope Internal: Solo el propietario puede acceder.
Public: Todos pueden acceder, excepto los módulos del NDK.
prop_name Nombre de la propiedad del sistema subyacente, por ejemplo, ro.build.date.
enum_values (Enum y EnumList solamente) Es una cadena separada por barras(|) que consta de los valores de enumeración posibles. Por ejemplo, value1|value2.
integer_as_bool (Solo Boolean y BooleanList) Haz que los métodos de configuración usen 0 y 1 en lugar de false y true.
legacy_prop_name (opcional, solo para propiedades de Readonly) Es el nombre heredado de la propiedad del sistema subyacente. Cuando se llama al getter, la API del getter intenta leer prop_name y usa legacy_prop_name si prop_name no existe. Usa legacy_prop_name cuando dejes de usar una propiedad existente y te cambies a una nueva.

Cada tipo de propiedad se asigna a los siguientes tipos en C++, Java y Rust.

Tipo C++ Java Rust
Booleano std::optional<bool> Optional<Boolean> bool
Entero std::optional<std::int32_t> Optional<Integer> i32
UInt std::optional<std::uint32_t> Optional<Integer> u32
Largo 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
Lista de T std::vector<std::optional<T>> List<T> Vec<T>

A continuación, se muestra un ejemplo de un archivo de descripción de Sysprop que define tres propiedades:

# 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
}

Cómo definir bibliotecas de propiedades del sistema

Ahora puedes definir módulos sysprop_library con archivos de descripción de Sysprop. sysprop_library funciona como una API para C++, Java y Rust. Internamente, el sistema de compilación genera un rust_library, un java_library y un cc_library para cada instancia de sysprop_library.

// File: Android.bp
sysprop_library {
    name: "PlatformProperties",
    srcs: ["android/sysprop/PlatformProperties.sysprop"],
    property_owner: "Platform",
    vendor_available: true,
}

Debes incluir archivos de listas de APIs en el código fuente para las verificaciones de APIs. Para ello, crea archivos de API y un directorio api. Coloca el directorio api en el mismo directorio que Android.bp. Los nombres de los archivos de la API son <module_name>-current.txt y <module_name>-latest.txt. <module_name>-current.txt contiene las firmas de la API de los códigos fuente actuales, y <module_name>-latest.txt contiene las firmas de la API congeladas más recientes. El sistema de compilación verifica si las APIs cambiaron comparando estos archivos de API con los archivos de API generados en el momento de la compilación y emite un mensaje de error y las instrucciones para actualizar el archivo current.txt si current.txt no coincide con los códigos fuente. Esta es una organización de ejemplo de directorios y archivos:

├── api
│   ├── PlatformProperties-current.txt
│   └── PlatformProperties-latest.txt
└── Android.bp

Los módulos cliente de Rust, Java y C++ pueden vincularse con sysprop_library para usar las APIs generadas. El sistema de compilación crea vínculos desde los clientes a las bibliotecas generadas de C++, Java y Rust, lo que les da acceso a las APIs generadas.

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"],
}

Ten en cuenta que el nombre de la biblioteca de Rust se genera convirtiendo el nombre de sysprop_library a minúsculas, reemplazando . y - por _, y, luego, anteponiendo lib y agregando _rust.

En el ejemplo anterior, podrías acceder a las propiedades definidas de la siguiente manera.

Ejemplo de Rust:

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);
  }

  
}

Ejemplo de Java:

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
        );
    }
    
}

Ejemplo en C++:

#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);
    }
    
}