Реализуйте системные свойства как 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 ( .sysprop ), использующих текстовый формат 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 ) Устаревшее имя базового системного свойства. При вызове геттера 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>
УЛонг std::optional<std::uint64_t> Optional<Long> Option<u64>
Двойной std::optional<double> Optional<Double> Option<f64>
Нить std::optional<std::string> Optional<String> Option<String>
Перечисление std::optional<{api\_name}\_values> Optional<{api\_name}\_values> Option<{ApiName}Values>
Список Т std::vector<std::optional<T>> List<T> Vec<T>

Вот пример файла описания Sysprop, определяющего три свойства:

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