Как реализовать системные свойства в виде API

Системные свойства (sysprops) – это удобный способ обмена информацией, обычно конфигурациями, в масштабе всей системы. Каждый раздел может использовать собственные системные свойства. Проблема может возникнуть, когда доступ к свойствам осуществляется через разделы, например когда /vendor обращается к свойствам, определенным в /system. Начиная с Android 8.0 некоторые разделы, например /system, можно обновлять, а раздел /vendor остается неизменным. Поскольку системные свойства представляют собой глобальный словарь пар "ключ-значение" без схемы, их сложно стабилизировать. Раздел /system может изменить или удалить свойства, от которых зависит раздел /vendor, без предварительного уведомления.

В Android 10 и более поздних версиях системные свойства, доступ к которым осуществляется через разделы, схематизируются в файлы описания sysprop, а API для доступа к свойствам генерируются как конкретные функции для C++ и Rust и классы для Java. Эти API удобнее в использовании, поскольку для доступа к ним не нужны специальные строки (например, ro.build.date) и они могут быть статически типизированы. Стабильность ABI также проверяется во время сборки. Если обнаруживаются несовместимые изменения, сборка прерывается. Эта проверка действует как явно определенный интерфейс между разделами. Эти API также могут обеспечить согласованность между Rust, Java и C++.

Определять системные свойства как API

Определите системные свойства как API с файлами описания системных свойств (.sysprop), в которых используется формат TextFormat протокола Protobuf со следующей схемой:

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

Файл описания sysprop содержит сообщение о свойствах, которое описывает набор свойств. Значения полей приведены ниже.

Поле Значение
owner Укажите раздел, которому принадлежат свойства: platform, vendor или odm.
module Используется для создания пространства имен (C++) или статического финального класса (Java), в котором размещаются сгенерированные API. Например, com.android.sysprop.BuildProperties это пространство имен com::android::sysprop::BuildProperties в C++ и класс BuildProperties в пакете com.android.sysprop в Java.
prop Список свойств.

Поля сообщения Property имеют следующие значения:

Поле Значение
api_name Название сгенерированного API.
type Тип этого свойства.
access Readonly – генерирует только API-метод получения.
Writeonce, ReadWrite – генерирует API-методы получения и установки.
scope Internal: доступ только у владельца.
Public: доступ есть у всех, кроме модулей NDK.
prop_name Название свойства базовой системы, например ro.build.date.
enum_values (Enum, EnumList) Строка, разделенная вертикальной чертой(|), которая состоит из возможных значений перечисления. Пример: value1|value2.
integer_as_bool (Только для Boolean и BooleanList.) Сделайте так, чтобы сеттеры использовали 0 и 1 вместо false и true.
legacy_prop_name (необязательное, только для свойств Readonly) Устаревшее название базового системного свойства. При вызове функции getter API пытается прочитать значение prop_name и использует legacy_prop_name, если prop_name не существует. Используйте legacy_prop_name, когда вы отказываетесь от существующего ресурса и переходите на новый.

Каждому типу свойства соответствует определенный тип в C++, Java и Rust:

Тип C++ (допускает значение NULL) Java (допускается значение null) Rust (необязательное или допускающее значение NULL)
Логическое значение std::optional<bool> Optional<Boolean> Option<bool>
Целое число std::optional<std::int32_t> Optional<Integer> Option<i32>
UInt std::optional<std::uint32_t> Optional<Integer> Option<u32>
Длинный std::optional<std::int64_t> Optional<Long> Option<i64>
ULong std::optional<std::uint64_t> Optional<Long> Option<u64>
Double std::optional<double> Optional<Double> Option<f64>
String std::optional<std::string> Optional<String> Option<String>
Перечисление std::optional<{api\_name}\_values> Optional<{api\_name}\_values> Option<{ApiName}Values>
T List std::vector<std::optional<T>> List<T> Vec<T>

Вот пример файла Sysprop Description, в котором определены три свойства:

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

Как определить библиотеки системных свойств

Вы можете определить модули sysprop_library с помощью файлов описания Sysprop. sysprop_library – это API для C++, Java и Rust. Система сборки автоматически создает по одному объекту rust_library, java_library и cc_library для каждого экземпляра sysprop_library.

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

В исходном коде для проверки API должны быть файлы со списками API. Для этого создайте файлы API и каталог api. Поместите каталог api в тот же каталог, где находится Android.bp. Названия файлов API: <module_name>-current.txt и <module_name>-latest.txt. <module_name>-current.txt содержит сигнатуры API текущих исходных кодов, а <module_name>-latest.txt – последние замороженные сигнатуры API. Система сборки проверяет, были ли изменены API, сравнивая эти файлы API с файлами API, сгенерированными во время сборки, и выдает сообщение об ошибке и инструкции по обновлению файла current.txt, если current.txt не соответствует исходным кодам. Пример организации каталогов и файлов:

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

Клиентские модули Rust, Java и C++ могут ссылаться на sysprop_library, чтобы использовать сгенерированные API. Система сборки создает ссылки от клиентов на сгенерированные библиотеки C++, Java и Rust, предоставляя клиентам доступ к сгенерированным API.

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

В предыдущем примере вы можете получить доступ к определенным свойствам следующим образом.

Пример на 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);
  }

  …
}

Пример для 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
        );
    }
    …
}
…

Пример на 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);
    }
    …
}
…