HIDL requires every interface written in HIDL be versioned. After a HAL interface is published, it is frozen and any further changes must be made to a new version of that interface. While a given published interface may not be modified, it can be extended by another interface.
HIDL code is organized in user-defined types, interfaces, and packages:
types.hal
..hal
file.The data-type definition file types.hal
contains only UDTs (all
package-level UDTs are kept in a single file). Representations in the target
language are available to all interfaces in the package.
A HIDL package (such as android.hardware.nfc
), after being
published for a given version (such as 1.0
), is immutable; it
cannot be changed. Modifications to the interfaces in the package or any
changes to its UDTs can take place only in another package.
In HIDL, versioning applies at the package level, not at the interface level, and all interfaces and UDTs in a package share the same version. Package versions follow semantic versioning without the patch level and build-metadata components. Within a given package, a minor version bump implies the new version of the package is backwards-compatible with the old package and a major version bump implies the new version of the package is not backwards-compatible with the old package.
Conceptually, a package can relate to another package in one of several ways:
types.hal
.For a well structured interface, adding new types of functionality that are not part of the original design should require a modification to the HIDL interface. Conversely, if you can or expect to make a change on both sides of the interface that introduces new functionality without changing the interface itself, then the interface is not structured.
Treble supports separately-compiled vendor and system components in which the
vendor.img
on a device and the system.img
can be
compiled separately. All interactions between vendor.img
and
system.img
must be explicitly and thoroughly defined so they can
continue to work for many years. This includes many API surfaces, but a major
surface is the IPC mechanism HIDL uses for interprocess communication on the
system.img
/vendor.img
boundary.
All data passed through HIDL must be explicitly defined. To ensure an implementation and client can continue to work together even when compiled separately or developed on independently, data must adhere to the following requirements:
If opaque data is used, it must be read only by one side of the HIDL
interface. For example, if vendor.img
code gives a component on the
system.img
a string message or vec<uint8_t>
data, that data cannot be parsed by the system.img
itself; it can
only be passed back to vendor.img
to interpret. When
passing a value from vendor.img
to vendor code on
system.img
or to another device, the format of the data and how it
is to be interpreted must be exactly described and is still part of the
interface.
You should be able to write an implementation or client of a HAL using only the .hal files (i.e. you should not need to look at the Android source or public standards). We recommend specifying the exact required behavior. Statements such as "an implementation may do A or B" encourage implementations to become intertwined with the clients they are developed with.
HIDL includes core and vendor packages.
Core HIDL interfaces are those specified by Google. The packages they belong
to start with android.hardware.
and are named by subsystem,
potentially with nested levels of naming. For example, the NFC package is named
android.hardware.nfc
and the camera package is
android.hardware.camera
. In general, a core package has the name
android.hardware.
[name1
].[name2
]….
HIDL packages have a version in addition to their name. For example, the package
android.hardware.camera
may be at version 3.4
; this is
important, as the version of a package affects its placement in the source tree.
All core packages are placed under hardware/interfaces/
in the
build system. The package
android.hardware.
[name1
].[name2
]…
at version $m.$n
is under
hardware/interfaces/name1/name2/
…/$m.$n/
; package
android.hardware.camera
version 3.4
is in directory
hardware/interfaces/camera/3.4/.
A hard-coded mapping exists
between the package prefix android.hardware.
and the path
hardware/interfaces/
.
Non-core (vendor) packages are those produced by the SoC vendor or ODM. The
prefix for non-core packages is vendor.$(VENDOR).hardware.
where
$(VENDOR)
refers to an SoC vendor or OEM/ODM. This maps to the path
vendor/$(VENDOR)/interfaces
in the tree (this mapping is also
hard-coded).
In HIDL, every UDT has a fully-qualified name that consists of the UDT name,
the package name where the UDT is defined, and the package version. The
fully-qualified name is used only when instances of the type are declared and
not where the type itself is defined. For example, assume package
android.hardware.nfc,
version 1.0
defines a struct
named NfcData
. At the site of the declaration (whether in
types.hal
or within an interface's declaration), the declaration
simply states:
struct NfcData { vec<uint8_t> data; };
When declaring an instance of this type (whether within a data structure or as a method parameter), use the fully-qualified type name:
android.hardware.nfc@1.0::NfcData
The general syntax is
PACKAGE@VERSION::UDT
, where:
PACKAGE
is the dot-separated name of a HIDL package
(e.g., android.hardware.nfc
).VERSION
is the dot-separated major.minor-version
format of the package (e.g., 1.0
).UDT
is the dot-separated name of a HIDL UDT.
Since HIDL supports nested UDTs and HIDL interfaces can contain UDTs (a type of
nested declaration), dots are used to access the names.For example, if the following nested declaration was defined in the common
types file in package android.hardware.example
version
1.0
:
// types.hal package android.hardware.example@1.0; struct Foo { struct Bar { // … }; Bar cheers; };
The fully-qualified name for Bar
is
android.hardware.example@1.0::Foo.Bar
. If, in addition to being in
the above package, the nested declaration were in an interface called
IQuux
:
// IQuux.hal package android.hardware.example@1.0; interface IQuux { struct Foo { struct Bar { // … }; Bar cheers; }; doSomething(Foo f) generates (Foo.Bar fb); };
The fully-qualified name for Bar
is
android.hardware.example@1.0::IQuux.Foo.Bar
.
In both cases, Bar
can be referred to as Bar
only
within the scope of the declaration of Foo
. At the package or
interface level, you must refer to Bar
via Foo
:
Foo.Bar
, as in the declaration of method doSomething
above. Alternatively, you could declare the method more verbosely as:
// IQuux.hal doSomething(android.hardware.example@1.0::IQuux.Foo f) generates (android.hardware.example@1.0::IQuux.Foo.Bar fb);
If a UDT is an enum type, then each value of the enum type has a
fully-qualified name that starts with the fully-qualified name of the enum type,
followed by a colon, then followed by the name of the enum value. For example,
assume package android.hardware.nfc,
version 1.0
defines an enum type NfcStatus
:
enum NfcStatus { STATUS_OK, STATUS_FAILED };
When referring to STATUS_OK
, the fully qualified name is:
android.hardware.nfc@1.0::NfcStatus:STATUS_OK
The general syntax is
PACKAGE@VERSION::UDT:VALUE
,
where:
PACKAGE@VERSION::UDT
is the
exact same fully qualified name for the enum type.VALUE
is the value's name.A fully-qualified UDT name does not need to be specified. A UDT name can safely omit the following:
@1.0::IFoo.Type
IFoo.Type
HIDL attempts to complete the name using auto-interference rules (lower rule number means higher priority).
If no package and version is provided, a local name lookup is attempted. Example:
interface Nfc { typedef string NfcErrorMessage; send(NfcData d) generates (@1.0::NfcStatus s, NfcErrorMessage m); };
NfcErrorMessage
is looked up locally, and the typedef
above it is found. NfcData
is also looked up locally, but as it is
not defined locally, rule 2 and 3 are used. @1.0::NfcStatus
provides a version, so rule 1 does not apply.
If rule 1 fails and a component of the fully-qualified name is missing
(package, version, or package and version), the component is autofilled with
information from the current package. The HIDL compiler then looks in the
current file (and all imports) to find the autofilled fully-qualified name.
Using the example above, assume the declaration of ExtendedNfcData
was made in the same package (android.hardware.nfc
) at the same
version (1.0
) as NfcData
, as follows:
struct ExtendedNfcData { NfcData base; // … additional members };
The HIDL compiler fills out the package name and version name from the
current package to produce the fully-qualified UDT name
android.hardware.nfc@1.0::NfcData
. As the name exists in the
current package (assuming it is imported properly), it is used for the
declaration.
A name in the current package is imported only if one of the following is true:
import
statement.types.hal
in the current packageThe same process is followed if NfcData
was qualified by only
the version number:
struct ExtendedNfcData { // autofill the current package name (android.hardware.nfc) @1.0::NfcData base; // … additional members };
If rule 2 fails to produce a match (the UDT is not defined in the current
package), the HIDL compiler scans for a match within all imported packages.
Using the above example, assume ExtendedNfcData
is declared in
version 1.1
of package android.hardware.nfc
,
1.1
imports 1.0
as it should (see
Package-Level Extensions), and the definition
specifies only the UDT name:
struct ExtendedNfcData { NfcData base; // … additional members };
The compiler looks for any UDT named NfcData
and finds one in
android.hardware.nfc
at version 1.0
, resulting in a
fully-qualified UDT of android.hardware.nfc@1.0::NfcData
. If more
than one match is found for a given partially-qualified UDT, the HIDL compiler
throws an error.
Using rule 2, an imported type defined in the current package is favored over an imported type from another package:
// hardware/interfaces/foo/1.0/types.hal package android.hardware.foo@1.0; struct S {}; // hardware/interfaces/foo/1.0/IFooCallback.hal package android.hardware.foo@1.0; interface IFooCallback {}; // hardware/interfaces/bar/1.0/types.hal package android.hardware.bar@1.0; typedef string S; // hardware/interfaces/bar/1.0/IFooCallback.hal package android.hardware.bar@1.0; interface IFooCallback {}; // hardware/interfaces/bar/1.0/IBar.hal package android.hardware.bar@1.0; import android.hardware.foo@1.0; interface IBar { baz1(S s); // android.hardware.bar@1.0::S baz2(IFooCallback s); // android.hardware.foo@1.0::IFooCallback };
S
is interpolated as
android.hardware.bar@1.0::S
, and is found in
bar/1.0/types.hal
(because types.hal
is automatically
imported).IFooCallback
is interpolated as
android.hardware.bar@1.0::IFooCallback
using rule 2, but it
cannot be found because bar/1.0/IFooCallback.hal
is not imported
automatically (as types.hal
is). Thus, rule 3 resolves it to
android.hardware.foo@1.0::IFooCallback
instead, which is imported
via import android.hardware.foo@1.0;
).Every HIDL package contains a types.hal
file containing UDTs
that are shared among all interfaces participating in that package. HIDL types
are always public; regardless of whether a UDT is declared in
types.hal
or within an interface declaration, these types are
accessible outside of the scope where they are defined. types.hal
is not meant to describe the public API of a package, but rather to host UDTs
used by all interfaces within the package. Due to the nature of HIDL, all UDTs
are a part of the interface.
types.hal
consists of UDTs and import
statements.
Because types.hal
is made available to every interface of the
package (it is an implicit import), these import
statements are
package-level by definition. UDTs in types.hal
may also incorporate
UDTs and interfaces thus imported.
For example, for an IFoo.hal
:
package android.hardware.foo@1.0; // whole package import import android.hardware.bar@1.0; // types only import import android.hardware.baz@1.0::types; // partial imports import android.hardware.qux@1.0::IQux.Quux; // partial imports import android.hardware.quuz@1.0::Quuz;
The following are imported:
android.hidl.base@1.0::IBase
(implicitly)android.hardware.foo@1.0::types
(implicitly)android.hardware.bar@1.0
(including all
interfaces and its types.hal
)types.hal
from android.hardware.baz@1.0::types
(interfaces in android.hardware.baz@1.0
are not imported)IQux.hal
and types.hal
from
android.hardware.qux@1.0
Quuz
from android.hardware.quuz@1.0
(assuming
Quuz
is defined in types.hal
, the entire
types.hal
file is parsed, but types other than Quuz
are not imported).Each interface within a package resides in its own file. The package the
interface belongs to is declared at the top of the interface using the
package
statement. Following the package declaration, zero or more
interface-level imports (partial or whole-package) may be listed. For example:
package android.hardware.nfc@1.0;
In HIDL, interfaces can inherit from other interfaces using the
extends
keyword. For an interface to extend another interface, it
must have access to it via an import
statement. The name of the
interface being extended (the base interface) follows the rules for type-name
qualification explained above. An interface may inherit only from one interface;
HIDL does not support multiple inheritance.
The uprev versioning examples below use the following package:
// types.hal package android.hardware.example@1.0 struct Foo { struct Bar { vec<uint32_t> val; }; }; // IQuux.hal package android.hardware.example@1.0 interface IQuux { fromFooToBar(Foo f) generates (Foo.Bar b); }
To define a package package@major.minor
, either A or all of B
must be true:
Rule A | "Is a start minor version": All previous minor versions,
package@major.0 , package@major.1 , …,
package@major.(minor-1) must not be defined.
|
---|
Rule B | All of the following is true:
|
---|
Because of rule A:
android.hardware.biometrics.fingerprint
starts at
@2.1
.)android.hardware.foo@1.0
is not defined" means
the directory hardware/interfaces/foo/1.0
should not even exist.
However, rule A does not affect a package with the same package name but a
different major version (for example,
android.hardware.camera.device
has both @1.0
and
@3.2
defined; @3.2
doesn't need to interact with
@1.0
.) Hence, @3.2::IExtFoo
can extend
@1.0::IFoo
.
Provided the package name is different,
package@major.minor::IBar
may extend from an interface with a
different name (for example, android.hardware.bar@1.0::IBar
can
extend android.hardware.baz@2.2::IBaz
). If an interface does not
explicitly declare a super type with the extend
keyword, it will
extend android.hidl.base@1.0::IBase
(except IBase
itself).
B.2 and B.3 must be followed at the same time. For example, even if
android.hardware.foo@1.1::IFoo
extends
android.hardware.foo@1.0::IFoo
to pass rule B.2, if an
android.hardware.foo@1.1::IExtBar
extends
android.hardware.foo@1.0::IBar
, this is still not a valid uprev.
To uprev android.hardware.example@1.0
(defined above) to
@1.1
:
// types.hal package android.hardware.example@1.1; import android.hardware.example@1.0; // IQuux.hal package android.hardware.example@1.1 interface IQuux extends @1.0::IQuux { fromBarToFoo(Foo.Bar b) generates (Foo f); }
This is a package-level import
of version 1.0
of
android.hardware.example
in types.hal
. While no new
UDTs are added in version 1.1
of the package, references to UDTs in
version 1.0
are still needed, hence the package-level import
in types.hal
. (The same effect could have been achieved with an
interface-level import in IQuux.hal
.)
In extends @1.0::IQuux
in the declaration of
IQuux
, we specified the version of IQuux
that is being
inherited (disambiguation is required because IQuux
is used to
declare an interface and to inherit from an interface). As declarations are
simply names that inherit all package and version attributes at the site of the
declaration, the disambiguation must be in the name of the base interface; we
could have used the fully-qualified UDT as well, but that would have been
redundant.
The new interface IQuux
does not re-declare method
fromFooToBar()
it inherits from @1.0::IQuux
; it simply
lists the new method it adds fromBarToFoo()
. In HIDL, inherited
methods may not be declared again in the child interfaces, so
the IQuux
interface cannot declare the fromFooToBar()
method explicitly.
Sometimes interface names must rename the extending interface. We recommend that enum extensions, structs, and unions have the same name as what they extend unless they are sufficiently different to warrant a new name. Examples:
// in parent hal file enum Brightness : uint32_t { NONE, WHITE }; // in child hal file extending the existing set with additional similar values enum Brightness : @1.0::Brightness { AUTOMATIC }; // extending the existing set with values that require a new, more descriptive name: enum Color : @1.0::Brightness { HW_GREEN, RAINBOW };
If a method can have a new semantic name (for instance
fooWithLocation
) then that is preferred. Otherwise, it should be
named similarly to what it is extending. For example, the method
foo_1_1
in @1.1::IFoo
may replace the functionality
of the foo
method in @1.0::IFoo
if there is no better
alternative name.
HIDL versioning occurs at the package level; after a package is published, it is immutable (its set of interfaces and UDTs cannot be changed). Packages can relate to each other in several ways, all of which are expressible via a combination of interface-level inheritance and building of UDTs by composition.
However, one type of relationship is strictly-defined and must be enforced: Package-level backwards-compatible inheritance. In this scenario, the parent package is the package being inherited from and the child package is the one extending the parent. Package-level backwards-compatible inheritance rules are as follows:
These rules can be implemented using HIDL interface-level inheritance and UDT composition, but require meta-level knowledge to know these relationships constitute a backwards-compatible package extension. This knowledge is inferred as follows:
If a package meets this requirement, hidl-gen
enforces
backwards-compatibility rules.