Implémentation des propriétés système en tant qu'API, Implémentation des propriétés système en tant qu'API

Les propriétés système constituent un moyen pratique de partager des informations, généralement des configurations, à l'échelle du système. Chaque partition peut utiliser ses propres propriétés système en interne. Un problème peut survenir lorsque les propriétés sont accessibles à travers des partitions, telles que /vendor accédant aux propriétés définies /system . Depuis Android 8.0, certaines partitions, telles que /system , peuvent être mises à niveau, tandis que /vendor reste inchangé. Étant donné que les propriétés système ne sont qu'un dictionnaire global de paires clé/valeur de chaîne sans schéma, il est difficile de stabiliser les propriétés. La partition /system peut modifier ou supprimer les propriétés dont dépend la partition /vendor sans préavis.

À partir de la version Android 10, les propriétés système accessibles sur les partitions sont schématisées dans des fichiers de description Sysprop, et les API permettant d'accéder aux propriétés sont générées sous forme de fonctions concrètes pour C++ et Rust, et de classes pour Java. Ces API sont plus pratiques à utiliser car aucune chaîne magique (telle que ro.build.date ) n'est nécessaire pour l'accès et parce qu'elles peuvent être typées de manière statique. La stabilité d'ABI est également vérifiée au moment de la construction, et la construction s'interrompt si des modifications incompatibles se produisent. Cette vérification agit comme des interfaces explicitement définies entre les partitions. Ces API peuvent également assurer la cohérence entre Rust, Java et C++.

Définir les propriétés du système en tant qu'API

Définissez les propriétés système en tant qu'API avec les fichiers de description Sysprop ( .sysprop ), qui utilisent un TextFormat de protobuf, avec le schéma suivant :

// 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 fichier de description Sysprop contient un message de propriétés, qui décrit un ensemble de propriétés. La signification de ses champs est la suivante.

Champ Signification
owner Définissez sur la partition qui possède les propriétés : Platform , Vendor ou Odm .
module Utilisé pour créer un espace de noms (C++) ou une classe finale statique (Java) dans lequel les API générées sont placées. Par exemple, com.android.sysprop.BuildProperties sera l'espace de noms com::android::sysprop::BuildProperties en C++, et la classe BuildProperties dans le package dans com.android.sysprop en Java.
prop Liste des propriétés.

La signification des champs de message Property est la suivante.

Champ Signification
api_name Le nom de l'API générée.
type Le type de cette propriété.
access Readonly : génère uniquement l'API getter

Writeonce , ReadWrite : génère des API getter et setter

Remarque : Propriétés avec le préfixe ro. ne peut pas utiliser l'accès ReadWrite .

scope Internal : Seul le propriétaire peut y accéder.

Public : Tout le monde peut y accéder, à l'exception des modules NDK.

prop_name Le nom de la propriété système sous-jacente, par exemple ro.build.date .
enum_values ( Enum , EnumList uniquement) Une chaîne séparée par des barres (|) qui se compose de valeurs d'énumération possibles. Par exemple, value1|value2 .
integer_as_bool ( Boolean , BooleanList uniquement) Faites en sorte que les setters utilisent 0 et 1 au lieu de false et true .
legacy_prop_name (facultatif, propriétés Readonly seule uniquement) Nom hérité de la propriété système sous-jacente. Lors de l'appel de getter, l'API getter essaie de lire prop_name et utilise legacy_prop_name si prop_name n'existe pas. Utilisez legacy_prop_name lorsque vous abandonnez une propriété existante et que vous passez à une nouvelle propriété.

Chaque type de propriété correspond aux types suivants en C++, Java et Rust.

Taper C++ Java Rouiller
Booléen std::optional<bool> Optional<Boolean> bool
Entier 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
Chaîne std::optional<std::string> Optional<String> String
Énumération std::optional<{api_name}_values> Optional<{api_name}_values> {ApiName}Values
Liste T std::vector<std::optional<T>> List<T> Vec<T>

Voici un exemple de fichier de description Sysprop définissant trois propriétés :

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

Définition des bibliothèques de propriétés système

Vous pouvez désormais définir des modules sysprop_library avec les fichiers de description Sysprop. sysprop_library sert d'API pour C++, Java et Rust. Le système de construction génère en interne un rust_library , un java_library et un cc_library pour chaque instance de sysprop_library .

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

Vous devez inclure les fichiers de listes d'API dans la source pour les vérifications d'API. Pour ce faire, créez des fichiers API et un répertoire api . Placez le répertoire api dans le même répertoire que Android.bp . Les noms de fichiers API sont <module_name>-current.txt , <module_name>-latest.txt . <module_name>-current.txt contient les signatures API des codes sources actuels, et <module_name>-latest.txt contient les dernières signatures API gelées. Le système de construction vérifie si les API sont modifiées en comparant ces fichiers API avec les fichiers API générés au moment de la construction et émet un message d'erreur et des instructions pour mettre à jour le fichier current.txt si current.txt ne correspond pas aux codes sources. Voici un exemple d'organisation de répertoires et de fichiers :

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

Les modules clients Rust, Java et C++ peuvent se lier à sysprop_library pour utiliser les API générées. Le système de build crée des liens depuis les clients vers les bibliothèques C++, Java et Rust générées, donnant ainsi aux clients l'accès aux API générées.

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

Notez que le nom de la bibliothèque Rust est généré en convertissant le nom sysprop_library en minuscules, en remplaçant . et - avec _ , puis en ajoutant lib et en ajoutant _rust .

Dans l'exemple ci-dessus, vous pouvez accéder aux propriétés définies comme suit.

Exemple de rouille :

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

  …
}

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

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