Реализация системных свойств в виде API

Свойства системы предоставляют удобный способ обмена информацией (обычно конфигурациями) в масштабах всей системы. Каждый раздел может использовать свои собственные системные свойства внутри себя. Проблема может возникнуть, когда доступ к свойствам осуществляется из разных разделов, например, /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 ), которые используют 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-интерфейсы получения и установки.

Примечание. Свойства с префиксом ro. не может использовать доступ ReadWrite .

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.

Тип С++ Джава Ржавчина
логическое значение std::optional<bool> Optional<Boolean> bool
Целое число std::optional<std::int32_t> Optional<Integer> i32
UInt std::optional<std::uint32_t> Optional<Integer> u32
Длинный std::optional<std::int64_t> Optional<Long> i64
Улонг std::optional<std::uint64_t> Optional<Long> u64
Двойной std::optional<double> Optional<Double> f64
Нить std::optional<std::string> Optional<String> String
Перечисление std::optional<{api_name}_values> Optional<{api_name}_values> {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: ["PlatformProperties"],
}

rust_binary {
    name: "rust_client",
    srcs: ["src/main.rs"],
    rustlibs: ["libplatformproperties_rust"],
}

Обратите внимание, что имя библиотеки Rust генерируется путем преобразования имени sysprop_library в нижний регистр с заменой . и - с _ , а затем добавить lib и добавить _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
        );
    }
    …
}
…

Пример С++:

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