Implementare le proprietà di sistema come API

Le proprietà di sistema forniscono un modo conveniente per condividere informazioni, di solito configurazioni, a livello di sistema. Ogni partizione può usare internamente le proprie proprietà di sistema. Un problema può verificarsi quando viene eseguito l'accesso alle proprietà tra le partizioni, ad esempio /vendor che accede alle proprietà definite da /system. A partire da Android 8.0, è possibile eseguire l'upgrade di alcune partizioni, ad esempio /system, mentre /vendor non viene modificato. Poiché le proprietà di sistema sono solo un dizionario globale di coppie chiave-valore di stringa senza schema, è difficile stabilizzare le proprietà. La partizione /system potrebbe modificare o rimuovere le proprietà da cui dipende la partizione /vendor senza alcun preavviso.

A partire dalla release di Android 10, le proprietà di sistema accessibili tra le partizioni sono schematizzate in file di descrizione Sysprop, mentre le API per accedere alle proprietà vengono generate come funzioni concrete per C++ e Rust, e classi per Java. Queste API sono più pratiche da usare perché per l'accesso non sono necessarie stringhe magiche (ad esempio ro.build.date) e possono essere digitate in modo statico. La stabilità dell'ABI viene controllata anche al momento della build e la build si interrompe se si verificano modifiche incompatibili. Questo controllo funge da interfacce definite esplicitamente tra le partizioni. Queste API possono anche fornire coerenza tra Rust, Java e C++.

Definisci le proprietà di sistema come API

Definisci le proprietà di sistema come API con i file di descrizione Sysprop (.sysprop), che utilizzano un valore TextFormat di protobuf, con il seguente 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;
}

Un file di descrizione Sysprop contiene un messaggio relativo alle proprietà, che descrive un insieme di proprietà. Il significato dei relativi campi è il seguente.

Campo Significato
owner Imposta la partizione proprietaria delle proprietà: Platform, Vendor o Odm.
module Utilizzato per creare uno spazio dei nomi (C++) o una classe finale statica (Java) in cui vengono inserite le API generate. Ad esempio, com.android.sysprop.BuildProperties sarà lo spazio dei nomi com::android::sysprop::BuildProperties in C++ e la classe BuildProperties nel pacchetto in com.android.sysprop in Java.
prop Elenco di proprietà.

I significati dei campi del messaggio Property sono i seguenti.

Campo Significato
api_name Il nome dell'API generata.
type Il tipo di questa proprietà.
access Readonly: genera solo l'API getter
Writeonce, ReadWrite: genera API getter e setter
Nota: le proprietà con il prefisso ro. non possono utilizzare l'accesso ReadWrite.
scope Internal: solo il proprietario può accedere.
Public: tutti possono accedere, ad eccezione dei moduli NDK.
prop_name Il nome della proprietà di sistema sottostante, ad esempio ro.build.date.
enum_values (Solo Enum, EnumList) Una stringa separata da barre(|) composta da possibili valori enum. Ad esempio, value1|value2.
integer_as_bool (Solo Boolean e BooleanList) Consenti ai settatori di utilizzare 0 e 1 anziché false e true.
legacy_prop_name (Facoltativo, solo per le proprietà Readonly) Il nome legacy della proprietà di sistema sottostante. Quando chiami il getter, l'API getter prova a leggere prop_name e utilizza legacy_prop_name se prop_name non esiste. Utilizza legacy_prop_name quando ritira una proprietà esistente e passa a una nuova proprietà.

Ogni tipo di proprietà è mappato ai seguenti tipi in C++, Java e Rust.

Tipo C++ Java Rust
Booleano std::optional<bool> Optional<Boolean> bool
Numero intero std::optional<std::int32_t> Optional<Integer> i32
UInt std::optional<std::uint32_t> Optional<Integer> u32
Lungo std::optional<std::int64_t> Optional<Long> i64
ULungo std::optional<std::uint64_t> Optional<Long> u64
Doppio std::optional<double> Optional<Double> f64
Stringa std::optional<std::string> Optional<String> String
Enum std::optional<{api_name}_values> Optional<{api_name}_values> {ApiName}Values
Elenco T std::vector<std::optional<T>> List<T> Vec<T>

Ecco un esempio di file di descrizione Sysprop che definisce tre proprietà:

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

Definisci le librerie delle proprietà di sistema

Ora puoi definire moduli sysprop_library con i file di descrizione Sysprop. sysprop_library funge da API per C++, Java e Rust. Il sistema di compilazione genera internamente un rust_library, un java_library e un cc_library per ogni istanza di sysprop_library.

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

Per i controlli delle API, devi includere i file degli elenchi delle API nell'origine. Per farlo, crea dei file API e una directory api. Inserisci la directory api nella stessa directory di Android.bp. I nomi file dell'API sono <module_name>-current.txt, <module_name>-latest.txt. <module_name>-current.txt contiene le firme API dei codici sorgente attuali, mentre <module_name>-latest.txt contiene le ultime firme API bloccate. Il sistema di compilazione controlla se le API vengono modificate confrontando questi file API con i file API generati al momento della build ed emette un messaggio di errore e le istruzioni per aggiornare il file current.txt se current.txt non corrisponde ai codici sorgente. Ecco un esempio di organizzazione file e directory:

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

I moduli client Rust, Java e C++ possono collegarsi a sysprop_library per utilizzare le API generate. Il sistema di compilazione crea collegamenti dai client alle librerie C++, Java e Rust generate, consentendo così ai client di accedere alle API generate.

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

Tieni presente che il nome della libreria Rust viene generato convertendo il nome sysprop_library in minuscolo, sostituendo . e - con _, quindi anteponendo lib e aggiungendo _rust.

Nell'esempio precedente, potevi accedere a proprietà definite nel seguente modo.

Esempio di ruggine:

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

  …
}

Esempio di 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
        );
    }
    …
}
…

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