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

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

Начиная с выпуска Android 10 системные свойства, доступ к которым осуществляется через разделы, схематизированы в файлы описания Sysprop, а API для доступа к свойствам создаются в виде конкретных функций для C++ и классов для Java. Эти API более удобны в использовании, поскольку для доступа не требуются волшебные строки (такие как ro.build.date ), а также потому, что они могут быть статически типизированы. Стабильность ABI также проверяется во время сборки, и сборка прерывается, если происходят несовместимые изменения. Эта проверка действует как явно определенные интерфейсы между разделами. Эти API также могут обеспечить согласованность между 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-интерфейсы getter и setter

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

Каждый тип свойства сопоставляется со следующими типами в C++ и Java.

Тип С++ Джава
логический std::optional<bool> Optional<Boolean>
Целое число std::optional<std::int32_t> Optional<Integer>
UInt std::optional<std::uint32_t> Optional<Integer>
Длинная std::optional<std::int64_t> Optional<Long>
Улонг std::optional<std::uint64_t> Optional<Long>
Двойной std::optional<double> Optional<Double>
Нить std::optional<std::string> Optional<String>
перечисление std::optional<{api_name}_values> Optional<{api_name}_values>
Список Т std::vector<std::optional<T>> List<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. Система сборки внутри генерирует одну 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

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

java_library {
    name: "JavaClient",
    srcs: ["foo/bar.java"],
    libs: ["PlatformProperties"],
}

cc_binary {
    name: "cc_client",
    srcs: ["baz.cpp"],
    shared_libs: ["PlatformProperties"],
}

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

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