HIDL Framework Backwards Compatibility Verification

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:

  1. Install an x.(y+n) interface on an Android mobile device.
  2. Install and activate an x.y-target adapter.
  3. 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:

  1. 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.
  2. Restart the client using adb shell stop (or start) or simply kill the process.
  3. After the test completes, disassociate the adapter.
  4. 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.