HIDL HALs guarantee the Android core system (aka system.img or the framework) is backwards compatible. While Vendor Test Suite (VTS) tests ensure that HALs work as expected (e.g. 1.1 HAL tests are run on all 1.2 implementations), framework testing is needed to ensure that when a supported HAL (1.0, 1.1, or 1.2) is provided, the framework works properly with that HAL.
For details on HAL interface definition language (HIDL), refer to HIDL, HIDL versioning, and HIDL HAL Deprecation.
About HAL upgrades
There are two types of HAL upgrades: major and minor. Most systems include only one HAL implementation, but multiple implementations are supported. For example:
android.hardware.teleport@1.0 # initial interface android.hardware.teleport@1.1 # minor version upgrade android.hardware.teleport@1.2 # another minor version upgrade ... android.hardware.teleport@2.0 # major version upgrade ...
The system partition typically includes a framework daemon (such as
teleportd
) that manages communication with a specific group of
HAL implementations. Alternatively, systems might instead
include a system library (such as
android.hardware.configstore-utils
) that implements convenient
client behavior. In the example above, teleportd
must work no
matter what version of the HAL is installed on the device.
Google-maintained versions
If major version upgrades (1.0, 2.0, 3.0, etc.) exist, at least one Google-maintained device must maintain an implementation of each major version until that version is deprecated. If no Google-maintained device ships with a specific major version, Google continues to maintain an old implementation of that major version.
Such maintenance adds minor additional overhead because the old implementation (e.g. 1.2) can be kept and not used by default when a new implementation (e.g. 2.0) is created.
Testing minor version upgrades
Testing the backwards compatibility of minor versions in the framework
requires a way to automatically generate minor version implementations. Given
the restrictions around Google-maintained versions, hidl-gen
will only (and can only) generate adapters that take a 1.(x+n) implementation
and provide a 1.x implementation; it cannot generate a 1.0 implementation
from a 2.0 implementation (by definition of a major version).
For example, to run 1.1 tests on a 1.2 implementation, you must be able to
simulate having a 1.1 implementation. The 1.2 interfaces can automatically be
used as 1.1 implementation with some slight differences in behavior (such as
the framework manually checking what version something is or calling
castFrom
on it).
The basic idea is this:
- Install an x.(y+n) interface on an Android mobile device.
- Install and activate an x.y-target adapter.
- Test the device to verify it works as expected when running an older minor version.
These adapters completely hide the fact that the implementation is actually backed by a 1.2 interface and only provides the 1.1 interface (the adapter takes a 1.2 interface and makes it look like a 1.1 interface).
Example workflow
In this example, the Android device runs
android.hardware.foo@1.1::IFoo/default
. To ensure a client works
properly with android.hardware.foo@1.0::IFoo/default
:
- In a terminal, run the following:
$ PACKAGE=android.hidl.allocator@1.0-adapter $ INTERFACE=IAllocator $ INSTANCE=ashmem $ THREAD_COUNT=1 # can see current thread use on `lshal -i -e` $ m -j $PACKAGE $ /data/nativetest64/$PACKAGE/$PACKAGE $INTERFACE $INSTANCE $THREAD_COUNT Trying to adapt down android.hidl.allocator@1.0-adapter/default Press any key to disassociate adapter.
- Restart the client using
adb shell stop
(orstart
) or simply kill the process. - After the test completes, disassociate the adapter.
- Restore system state by restarting the device OR by restarting the client.
Additional targets
hidl-gen
automatically adds additional build targets for the
adapters for every interface specified with hidl_interface
in
the build system. For package a.b.c@x.y
, there is an additional
C++ target a.b.c@x.y-adapter
.
An adapter for a.b.c@x.y
takes as an input some
implementation, a.b.c@x.(y+n)::ISomething/instance-name
, and
must register a.b.c@x.y::ISomething/instance-name
which must
also unregister the y+n
implementation.
Given the following sample interface:
// IFoo.hal package a.b.c@1.0; interface IFoo { doFoo(int32_t a) generates (int64_t b); doSubInterface() generates (IFoo a); };
The code provided by a.b.c@1.0-adapter
is similar to the
sample below:
// autogenerated code // in namespace a::b::c::V1_0::IFoo struct MockFoo { // takes some subclass of V1_0. May be V1_1, V1_2, etc... MockFoo(V1_0::IFoo impl) mImpl(impl) {} Return<int64_t> doFoo(int32_t a) { return this->mImpl->doFoo(a); } Return<V1_0::ICallback> doSubInterface() { // getMockForBinder returns MockCallback instance // that corresponds to a particular binder object // It can't return a new object every time or // clients using interfacesSame will have // divergent behavior when using the mock. auto _hidl_out = this->mImpl->doSubInterface(); return getMockForBinder(_hidl_out); } };
Data values are forwarded exactly into and out of the auto-generated mock class, except for sub interfaces, which are returned. These interfaces must be wrapped in the corresponding most recent callback object.