HIDL C++

Android 8 re-architects the Android OS to define clear interfaces between the device-independent Android platform and device- and vendor-specific code. Android already defines many such interfaces in the form of HAL interfaces, defined as C headers in hardware/libhardware. HIDL replaces these HAL interfaces with stable, versioned interfaces, which can be client- and server-side HIDL interfaces in C++ (described below) or Java.

The pages in this section describe C++ implementations of HIDL interfaces, including details about the files auto-generated from the HIDL .hal files by the hidl-gen compiler, how these files are packaged, and how to integrate these files with the C++ code that uses them.

Client and server implementations

HIDL interfaces have client and server implementations:

  • A client of a HIDL interface is the code that uses the interface by calling methods on it.
  • A server is an implementation of a HIDL interface that receives calls from clients and returns results (if necessary).

In transitioning from libhardware HALs to HIDL HALs, the HAL implementation becomes the server and the process calling into the HAL becomes the client. Default implementations can serve both passthrough and binderized HALs, and can change over time:

Figure 1. Development progression for legacy HALs.

Create the HAL client

Start by including the HAL libraries in the makefile:

  • Make: LOCAL_SHARED_LIBRARIES += android.hardware.nfc@1.0
  • Soong: shared_libs: [ …, android.hardware.nfc@1.0 ]

Next, include the HAL header files:

#include <android/hardware/nfc/1.0/IFoo.h>
…
// in code:
sp<IFoo> client = IFoo::getService();
client->doThing();

Create the HAL server

To create the HAL implementation, you must have the .hal files that represent your HAL and have already generated makefiles for your HAL using -Lmakefile or -Landroidbp on hidl-gen (./hardware/interfaces/update-makefiles.sh does this for internal HAL files and is a good reference). When transferring over HALs from libhardware, you can do a lot of this work easily using c2hal.

To create the necessary files to implement your HAL:

PACKAGE=android.hardware.nfc@1.0
LOC=hardware/interfaces/nfc/1.0/default/
m -j hidl-gen
hidl-gen -o $LOC -Lc++-impl -randroid.hardware:hardware/interfaces \
    -randroid.hidl:system/libhidl/transport $PACKAGE
hidl-gen -o $LOC -Landroidbp-impl -randroid.hardware:hardware/interfaces \
    -randroid.hidl:system/libhidl/transport $PACKAGE

For the HAL to work in passthrough mode, you must have the function HIDL_FETCH_IModuleName residing in /(system|vendor|...)/lib(64)?/hw/android.hardware.package@3.0-impl(OPTIONAL_IDENTIFIER).so where OPTIONAL_IDENTIFIER is a string identifying the passthrough implementation. The passthrough mode requirements are met automatically by the above commands, which also create the android.hardware.nfc@1.0-impl target, but any extension can be used. For instance android.hardware.nfc@1.0-impl-foo uses -foo to differentiate itself.

If a HAL is a minor version or an extension of another HAL, the base HAL should be used to name this binary. For instance, android.hardware.graphics.mapper@2.1 implementations should still be in a binary called android.hardware.graphics.mapper@2.0-impl(OPTIONAL_IDENTIFIER). Usually, the OPTIONAL_IDENTIFIER here would include the actual HAL version. By naming the binary like this, 2.0 clients can retrieve it directly, and 2.1 clients can upcast the implementation.

Next, fill out the stubs with functionality and setup a daemon. Example daemon code (supporting passthrough):

#include <hidl/LegacySupport.h>

int main(int /* argc */, char* /* argv */ []) {
    return defaultPassthroughServiceImplementation<INfc>("nfc");
}

defaultPassthroughServiceImplementation calls dlopen() for the provided -impl library and provides it as a binderized service. Example daemon code (for pure binderized service):

int main(int /* argc */, char* /* argv */ []) {
    // This function must be called before you join to ensure the proper
    // number of threads are created. The threadpool never exceeds
    // size one because of this call.
    ::android::hardware::configureRpcThreadpool(1 /*threads*/, true /*willJoin*/);

    sp<INfc> nfc = new Nfc();
    const status_t status = nfc->registerAsService();
    if (status != ::android::OK) {
        return 1; // or handle error
    }

    // Adds this thread to the threadpool, resulting in one total
    // thread in the threadpool. We could also do other things, but
    // would have to specify 'false' to willJoin in configureRpcThreadpool.
    ::android::hardware::joinRpcThreadpool();
    return 1; // joinRpcThreadpool should never return
}

This daemon usually lives in $PACKAGE + "-service-suffix" (for example, android.hardware.nfc@1.0-service), but it could be anywhere. The sepolicy for a specific class of HALs is the attribute hal_<module> (for instance, hal_nfc). This attribute must be applied to the daemon that runs a particular HAL (if the same process serves multiple HALs, multiple attributes can be applied to it).