Zygote
HALs, Treble & Mainlinenerd

From HIDL to AIDL HALs

Android built a bespoke interface language for HALs, used it for five years, then replaced it with the one it already had. The reasons are a lesson in what happens when an interface has to live for a decade.

12 minAPI 8–37 · 17interactivequizverified 2026-10-27

For five years Android had two interface languages: AIDL for framework IPC, and HIDL for HALs. In hindsight the second was a mistake — not because it was badly designed, but because it was a second one. Android now has one.

TL;DR#

  • was created for Treble in Android 8.0 (API 26Android 8.0): a bespoke IDL for vendor interfaces, with versioned packages and .hal files.
  • It was deprecated as of Android 10 and its replacement was already there: , the language the rest of the platform used.
  • AOSP's HAL documentation now says HIDL is deprecated as of Android 13; both statements are true and describe different milestones (deprecation announced vs. no new HIDL).
  • Stable AIDL HALs need two annotations: @VintfStability on every type, and stability: "vintf" on the aidl_interface module.
  • The migration is mechanical for simple HALs (hidl2aidl exists), and painful for HALs that depended on HIDL-specific features.

Mental model#

Imagine a company that builds a private railway to connect its two factories, because no public line went there.

The private railway works. It has its own gauge, its own signalling and its own timetable. Ten years later the public network reaches both factories, and the private line becomes a maintenance burden: a separate set of trains, a separate training programme, a separate set of accidents.

HIDL is the private railway. AIDL is the public line, which was already running through the rest of the platform.

Where the analogy breaks#

The private railway analogy implies the public line was there all along and equally capable. It was not. At the time Treble was designed, AIDL had no stable versioning story for vendor interfaces, no way to express a frozen ABI, and no support in the vendor build. HIDL was not a whim; it was what could be built in the time available.

The big picture#

Loading diagram…
Text description of this diagram

A sequence diagram of a camera capture crossing four process boundaries. The app calls the Camera2 API in its own process, which makes a Binder call into CameraService in system_server, which makes a second call into the vendor camera provider process through the HAL interface, which issues ioctls to the kernel driver, which talks to the image sensor over I2C and MIPI. The frame returns the same way, but carries a buffer handle rather than pixels. The HAL interface between CameraService and the provider is the boundary that HIDL and AIDL both describe — the difference between them is how that interface is specified, not where it sits.

Deep dive#

What HIDL was for#

HIDL was designed to solve a specific problem: how do you specify an interface that will not change for years, between two organisations that will not coordinate?

It had the right instincts for that problem:

  • Versioned packages. android.hardware.foo@1.2 is a distinct package from @1.3.
  • Frozen interfaces. Once published, an interface version cannot change.
  • Minor version rules. A minor bump may add types and methods but not change existing ones.
  • Passthrough generation. The same interface could be used in-process, which made migration possible.

Its grammar was a C++-like language with Java-style annotations, deliberately not C++ because the preprocessor was excluded:

ROOT = PACKAGE IMPORTS PREAMBLE { ITEM ITEM ... }
ITEM = ANNOTATIONS? oneway? identifier(FIELD, FIELD ...) GENERATES?;

The exclusion of the C preprocessor was a design statement: an interface definition should be self-contained and machine-readable, not dependent on a build-time macro soup.

What went wrong#

Four things, none of them fatal alone.

It was a second toolchain. Every HAL needed hidl-gen, HIDL-specific build rules, HIDL service registration and HIDL's own service manager (hwservicemanager) on top of the kernel's hwbinder device. That is a parallel universe next to the AIDL/Binder one the framework already used.

It duplicated AIDL's job. Both generated a stub and a proxy from an interface description. Both parcelled arguments. Both registered a service with a manager. Maintaining two of everything is a cost that compounds.

Its versioning was coarse. A major version was a new package. Adding a method meant a minor bump, which meant a new package name, which meant every client had to be updated to ask for the new name even if it did not care about the new method.

The passthrough mode undermined the boundary. A HAL could be dlopened into the client, which meant the same interface could be a process boundary or not, depending on the device. That is a useful migration tool and a terrible long-term property: the abstraction stops being a guarantee.

What AIDL offered#

AIDL already had the machinery:

  • Stub and Proxy generation shared with every framework interface.
  • A single build system path, so a HAL and a framework service use the same tooling.
  • libbinder_ndk, a stable NDK backend that vendor code can link without depending on the platform's private Binder library.
  • In-place versioning. Interface owners can add methods to the end of an interface and fields to a parcelable without renaming the package.

That last point is the decisive one. HIDL's versioning was a new package per minor version. AIDL's is additive within a version, with an explicit extension mechanism for the cases that need more. For an interface that lives for a decade, "add to the end" is a much cheaper evolution strategy than "fork the package".

The stable AIDL contract#

An ordinary AIDL interface is not automatically usable across the vendor boundary. To be a HAL interface, it needs to be declared stable, which means two things:1

  1. Every type in the interface is annotated @VintfStability.
  2. The aidl_interface build module declares stability: "vintf".

A stable interface is frozen in the same sense a HIDL interface was: it appears in the VINTF manifest, it is checked against the compatibility matrix, and its ABI cannot change incompatibly.

The same HAL in both languages#

The shape is similar; the details are not. A HIDL interface lives in a .hal file with a package declaration and a version in the package name:

package android.hardware.light@2.0;
 
interface ILight {
    generateLightState(uint32_t color) generates (LightState state);
    setLight(Type type, LightState state) generates (Status status);
};

An AIDL HAL lives in an .aidl file under hardware/interfaces/<domain>/aidl/, with the package expressed as a Java-style package and the stability expressed as an annotation:

package android.hardware.light;
 
@VintfStability
@Backing(type="int")
enum Type {
    BACKLIGHT = 0,
    KEYBOARD = 1,
    BUTTONS = 2,
}
 
@VintfStability
interface ILight {
    void setLight(in Type type, in LightState state);
    LightState getLights();
}

Three differences are visible even in this small example. AIDL has no generates clause: return values are ordinary return types, and out-parameters use in/out/inout markers. AIDL uses @VintfStability where HIDL used the package version. And AIDL's enum backing type is explicit, which matters for ABI stability.

Registration and discovery#

Both languages register a service with a service manager, but the details differ.

A HIDL service registers with hwservicemanager and is looked up by interface name and instance:

// HIDL
sp<ILight> light = ILight::getService("default");

An AIDL HAL registers with the regular servicemanager and is looked up by the fully qualified name:

// AIDL
ndk::SpAIBinder binder = AServiceManager_getService("android.hardware.light.ILight/default");
std::shared_ptr<ILight> light = ILight::fromBinder(binder);

The move from hwservicemanager to servicemanager is the visible sign of the unification: there is now one service registry for the device, not two. SELinux still distinguishes HAL services by type, and a HAL service needs the hal_service_type attribute in policy.

VINTF: the same file, a different tag#

The manifest entry changed shape but not purpose. Under HIDL:

<hal format="hidl">
  <name>android.hardware.light</name>
  <version>2.0</version>
  <interface>
    <name>ILight</name>
    <instance>default</instance>
  </interface>
</hal>

Under AIDL:

<hal format="aidl">
  <name>android.hardware.light</name>
  <version>1</version>
  <fqname>ILight/default</fqname>
</hal>

The <fqname> form is more compact. The optionality and version-range semantics live in the compatibility matrix, as before.

The migration in practice#

The migration was not a big-bang rewrite.2 It happened HAL by HAL, release by release, and it is still happening on devices in the field.

The mechanics, roughly:

  1. Run hidl2aidl on the .hal files to generate initial .aidl files.
  2. Fix what the tool cannot: HIDL's generates clauses, its versioned types, and any use of vec<T> or fmq that needs a different AIDL equivalent.
  3. Add @VintfStability to every type that crosses the boundary.
  4. Implement the AIDL service, register it with servicemanager, add the VINTF entry.
  5. Keep the HIDL service alongside for devices that need it, or drop it once the framework stops asking.

Step 5 is where the real cost lives. A vendor may need to support both interfaces on the same device for a transition period, which means two implementations, two VINTF entries and two sets of tests.

How it evolved#

The updatability story9 releases
APIVersionWhat changed
11.0
  • Initial AOSP platform: Linux kernel, Dalvik VM, core libraries
174.2 Jelly Bean
  • SELinux enforcement begins in the kernel (behind the scenes)
194.4 KitKat
  • Kernel moved under a tighter vendor control
236.0 Marshmallow
  • Fingerprint HAL
247.0 Nougat
  • Project Treble groundwork
268.0 Oreo
  • Project Treble: the vendor interface separates vendor and system code
  • HIDL for HALs
289 Pie
  • Mainline groundwork
2910
  • Project Mainline: APEX and updatable system modules
  • Project Mainline starts shipping through Play
3011
  • GKI: the Generic Kernel Image splits the kernel into core + vendor modules

The arc: HIDL introduced (8.0), AIDL HALs added (11), HIDL deprecated and new HAL work expected in AIDL (13). The trend is not "HIDL was wrong" but "a platform with two of everything eventually picks one".

Under the hood#

Curiosity corner#

  • HIDL deliberately had no C preprocessor. The grammar excluded it, which made interface files parseable without a build context and made the generated documentation trustworthy.
  • hwbinder still exists on modern devices. Some legacy HALs are still HIDL, and hwbinder is what carries them.
  • The android.hardware.light HAL is one of the best examples to read. It is small enough to read in one sitting and it exercises enums, parcelables and interface methods.

Try it#

See which HALs a device uses, by language

adb shell cat /vendor/etc/vintf/manifest.xml | grep -c 'format="aidl"' adb shell cat /vendor/etc/vintf/manifest.xml | grep -c 'format="hidl"' adb shell service list | grep "android.hardware"

The two counts are a rough measure of how far a device has migrated. A device with many HIDL entries and few AIDL ones is either old or was built conservatively.

Check yourself#

Check yourself

1 / 12

What was the core technical barrier that kept Android phones on old software before 2017?

0 answered · 0 correct

Sources#

Sources

Every version number, API level, class name and date on this page is traced to one of these. Pages are re-verified against them; last_verified above says when.

  1. docsAIDL for HALssource.android.comaccessed 2026-10-27↩
  2. docsHIDLsource.android.comaccessed 2026-10-27↩
  3. docsHAL typessource.android.comaccessed 2026-10-27↩
  4. docsVendor interface objects (VINTF)source.android.comaccessed 2026-10-27↩
  5. docsHAL dynamic lifecyclesource.android.comaccessed 2026-10-27↩
  6. docsVendor NDK (VNDK)source.android.comaccessed 2026-10-27↩
  7. docsAndroid Interface Definition Language (AIDL) | Background workdeveloper.android.comaccessed 2026-10-27↩