Интерфейсы и пакеты

HIDL основан на интерфейсах – абстрактном типе, который используется в объектно-ориентированных языках для определения поведения. Каждый интерфейс является частью пакета.

Пакеты

Названия пакетов могут иметь подуровни, например package.subpackage. Корневой каталог для опубликованных пакетов HIDL – hardware/interfaces или vendor/vendorName (например, vendor/google для устройств Pixel). Название пакета образует один или несколько подкаталогов в корневом каталоге. Все файлы, определяющие пакет, находятся в одном каталоге. Например, поле package android.hardware.example.extension.light@2.0 может находиться в поле hardware/interfaces/example/extension/light/2.0.

В таблице ниже перечислены префиксы пакетов и местоположения:

Префикс пакета Местоположение Типы интерфейсов
android.hardware.* hardware/interfaces/* HAL
android.frameworks.* frameworks/hardware/interfaces/* компоненты Framework и связанные с ними элементы;
android.system.* system/hardware/interfaces/* system/ related
android.hidl.* system/libhidl/transport/* ядро

В каталоге пакета есть файлы с расширением .hal. Каждый файл должен содержать оператор package, в котором указаны название пакета и версия, к которой относится файл. Файл types.hal, если он присутствует, не определяет интерфейс, а определяет типы данных, доступные для каждого интерфейса в пакете.

Определение интерфейса

Все файлы .hal, кроме types.hal, определяют интерфейс. Интерфейс обычно определяется следующим образом:

interface IBar extends IFoo { // IFoo is another interface
    // embedded types
    struct MyStruct {/*...*/};

    // interface methods
    create(int32_t id) generates (MyStruct s);
    close();
};

Интерфейс без явного объявления extends неявно наследуется от android.hidl.base@1.0::IBase (аналогично java.lang.Object в Java). Интерфейс IBase, импортируемый неявно, объявляет несколько зарезервированных методов, которые не должны и не могут быть объявлены повторно в пользовательских интерфейсах или использоваться иным образом. К ним относятся:

  • ping
  • interfaceChain
  • interfaceDescriptor
  • notifySyspropsChanged
  • linkToDeath
  • unlinkToDeath
  • setHALInstrumentation
  • getDebugInfo
  • debug
  • getHashChain

Процесс импорта

Оператор import – это механизм HIDL, позволяющий получить доступ к интерфейсам и типам пакета в другом пакете. Оператор import относится к двум объектам:

  • Импортируемый объект, который может быть пакетом или интерфейсом.
  • Импортируемый объект, который может быть пакетом или интерфейсом.

Импортирующая сущность определяется местоположением оператора import. Если оператор находится внутри пакета types.hal, импортируемый объект виден всему пакету. Это импорт на уровне пакета. Если оператор находится в файле интерфейса, импортируемым объектом является сам интерфейс. Это импорт на уровне интерфейса.

Импортируемый объект определяется значением после ключевого слова import. Значение не обязательно должно быть полным именем. Если какой-либо компонент опущен, он автоматически заполняется информацией из текущего пакета. Для полных значений поддерживаются следующие варианты импорта:

  • Импорт целых пакетов. Если значением является название пакета и его версия (синтаксис описан ниже), то весь пакет импортируется в импортируемый объект.
  • Частичный импорт. Если значение:
    • Интерфейс, пакет types.hal и этот интерфейс импортируются в импортируемый объект.
    • Если в types.hal определен пользовательский тип данных, то в импортируемый объект будет добавлен только он (другие типы из types.hal не импортируются).
  • Импорт только типов. Если значение использует синтаксис частичного импорта, описанный выше, но с ключевым словом types вместо названия интерфейса, импортируются только UDT в types.hal указанного пакета.

Импортирующая организация получает доступ к следующей информации:

  • Общие типы, определенные в файле types.hal импортированного пакета.
  • Интерфейсы импортированного пакета (при импорте всего пакета) или указанного интерфейса (при частичном импорте) для вызова, передачи дескрипторов и/или наследования.

В операторе импорта используется синтаксис полного имени типа, чтобы указать название и версию импортируемого пакета или интерфейса:

import android.hardware.nfc@1.0;            // import a whole package
import android.hardware.example@1.0::IQuux; // import an interface and types.hal
import android.hardware.example@1.0::types; // import just types.hal

Наследование интерфейсов

Интерфейс может быть расширением ранее определенного интерфейса. Существует три типа расширений:

  • Интерфейс может добавлять функциональность другому, используя его API без изменений.
  • Пакет может добавлять функции в другой пакет, используя его API без изменений.
  • Интерфейс может импортировать типы из пакета или из определенного интерфейса.

Интерфейс может расширять только один другой интерфейс (множественное наследование не поддерживается). Каждый интерфейс в пакете с ненулевым номером промежуточной версии должен расширять интерфейс в предыдущей версии пакета. Например, если интерфейс IBar в версии 4.0 пакета derivative основан на интерфейсе IFoo в версии 1.2 пакета original, а версия 1.3 пакета original создана, то версия 4.1 интерфейса IBar не может расширять версию 1.3 интерфейса IFoo. Вместо этого версия 4.1 IBar должна быть расширением версии 4.0 IBar, которая связана с версией 1.2 IFoo. IBar версии 5.0 может расширить IFoo версии 1.3, если это необходимо.

Расширения интерфейса не подразумевают зависимость от библиотеки или включение HAL в сгенерированный код – они просто импортируют структуру данных и определения методов на уровне HIDL. Каждый метод в HAL должен быть реализован в этом HAL.

Расширения поставщиков

В некоторых случаях расширения поставщиков реализуются как подкласс базового объекта, представляющего основной интерфейс, который они расширяют. Один и тот же объект регистрируется под базовым именем и версией HAL, а также под именем и версией HAL расширения (поставщика).

Управление версиями

У пакетов есть версии, а у интерфейсов – версия пакета. Версии обозначаются двумя целыми числами: основная.дополнительная.

  • Основные версии не имеют обратной совместимости. При увеличении номера основной версии номер промежуточной версии сбрасывается до 0.
  • Промежуточные версии обратно совместимы. Увеличение номера дополнительной версии означает, что новая версия полностью обратно совместима с предыдущей. Можно добавлять новые структуры данных и методы, но нельзя изменять существующие структуры данных или сигнатуры методов.

На устройстве может быть одновременно несколько основных или промежуточных версий HAL. Однако промежуточная версия предпочтительнее основной, поскольку клиентский код, работающий с интерфейсом предыдущей промежуточной версии, также работает с более поздними промежуточными версиями того же интерфейса. Подробнее об управлении версиями и расширениях поставщиков можно узнать в статье Управление версиями HIDL.

Сводка по макету интерфейса

В этом разделе приведена сводная информация о том, как управлять пакетом интерфейса HIDL (например, hardware/interfaces). Прежде чем читать эту статью, убедитесь, что вы знакомы с управлением версиями HIDL, хешированием с помощью hidl-gen, общими принципами работы с HIDL и приведенными ниже определениями.

Термин Определение
Двоичный интерфейс приложения (ABI) API и все необходимые двоичные связи.
полное имя (fqName) Название, позволяющее различать типы HIDL. Пример: android.hardware.foo@1.0::IFoo.
пакет Пакет, содержащий интерфейс и типы HIDL. Пример: android.hardware.foo@1.0.
Корневой каталог пакета Корневой пакет, содержащий интерфейсы HIDL. Пример: интерфейс HIDL android.hardware находится в корне пакета android.hardware.foo@1.0.
корневой путь пакета Место в дереве исходного кода Android, куда сопоставляется корень пакета.

Другие определения можно найти в разделе Терминология HIDL.

Каждый файл можно найти по сопоставлению корневого пакета и его полному имени.

Корневые каталоги пакетов указываются в hidl-gen в качестве аргумента -r android.hardware:hardware/interfaces. Например, если пакет – vendor.awesome.foo@1.0::IFoo, а hidl-gen отправляется в -r vendor.awesome:some/device/independent/path/interfaces, то файл интерфейса должен находиться в $ANDROID_BUILD_TOP/some/device/independent/path/interfaces/foo/1.0/IFoo.hal.

На практике поставщику или производителю оригинального оборудования с именем awesome рекомендуется размещать стандартные интерфейсы в vendor.awesome. После выбора пути к пакету его нельзя изменить, поскольку он запекается в ABI интерфейса.

Сопоставление путей к пакетам должно быть уникальным.

Например, если у вас есть -rsome.package:$PATH_A и -rsome.package:$PATH_B, то для согласованности каталога интерфейса $PATH_A должно быть равно $PATH_B (это также значительно упрощает управление версиями интерфейсов).

В корневом каталоге пакета должен быть файл с информацией о версии

Если вы создаете путь к пакету, например -r vendor.awesome:vendor/awesome/interfaces, вам также следует создать файл $ANDROID_BUILD_TOP/vendor/awesome/interfaces/current.txt, который должен содержать хеши интерфейсов, созданных с помощью параметра -Lhash в hidl-gen (подробная информация приведена в разделе Хеширование с помощью hidl-gen).

Интерфейсы должны находиться в независимых от устройства местах.

На практике мы рекомендуем использовать общие интерфейсы для разных ветвей. Это позволяет максимально повторно использовать код и тестировать его на разных устройствах и в разных сценариях.