Skip to content

AIDL HALs

Simão Gomes Viana edited this page Mar 16, 2026 · 6 revisions

AIDL HALs - Architecture and Implementation Guide

Android Interface Definition Language (AIDL) is used to define interfaces for communication between Android components. In the context of HALs (Hardware Abstraction Layers), AIDL replaced HIDL as the preferred interface definition language starting from Android 11/12.

Overview

AIDL HALs provide:

  • Type-safe IPC between framework and vendor code
  • Stable ABI through Vintf (Vendor Interface) stability guarantees
  • Multi-language support - C++, Java, Kotlin, and NDK backends
  • Callback support for asynchronous notifications

AIDL File Types

Interface (.aidl)

Defines methods that can be called remotely. Use @VintfStability for HAL interfaces.

Parcelable (Data Structure)

Defines data containers that can be passed across process boundaries. Use @VintfStability and optionally @JavaDerive for auto-generated helpers.

Enum

Use @Backing(type="int") or @Backing(type="byte") to specify the underlying type.

Key Annotations

Annotation Purpose
@VintfStability Marks interface as stable across vendor/framework boundary
@Backing(type="int") Specifies enum backing type
@JavaDerive(toString=true) Auto-generates toString() in Java/Kotlin
@nullable Field or return value can be null
oneway Method returns immediately, no response expected

Directory Structure

hardware/interfaces/<category>/aidl/
├── Android.bp                    # Interface build definition
├── android/hardware/<category>/  # AIDL files
│   ├── IFoo.aidl
│   ├── FooConfig.aidl            # Parcelable
│   └── FooMode.aidl              # Enum
└── default/                      # Default implementation (optional)
    ├── Android.bp
    ├── Foo.cpp
    ├── Foo.h
    ├── service.cpp
    ├── android.hardware.foo-service.default.xml
    └── android.hardware.foo-service.default.rc

Build System (Android.bp)

Interface Definition

Use aidl_interface module type. Key properties:

  • stability: "vintf" - Required for HAL interfaces
  • vendor_available: true - Allows vendor partition usage
  • frozen: true - Locks interface for stability
  • Backend configuration for java/ndk/cpp

HAL Service Binary

Use cc_binary with:

  • vintf_fragments - VINTF manifest file
  • init_rc - Init script
  • vendor: true - Installs to vendor partition
  • Link against generated AIDL library: <interface-name>-V<version>-ndk

C++ Implementation

Header Pattern

  1. Include the generated BnXxx base class header
  2. Inherit from BnXxx (e.g., BnFoo)
  3. Override all interface methods with ndk::ScopedAStatus return type
  4. Use ndk::SharedRefBase::make<>() for instance creation

Implementation Pattern

  1. Use ndk::ScopedAStatus::ok() for success
  2. Use ndk::ScopedAStatus::fromExceptionCode() for errors
  3. Output parameters use pointers (e.g., bool* _aidl_return)

Service Registration

  1. Call ABinderProcess_setThreadPoolMaxThreadCount()
  2. Create service instance with ndk::SharedRefBase::make<>()
  3. Register with AServiceManager_addService() using <descriptor>/default instance name
  4. Call ABinderProcess_joinThreadPool() to run

Important: AServiceManager_addService() returns binder_exception_t, not binder_status_t. Check against EX_NONE, not STATUS_OK:

binder_exception_t status = AServiceManager_addService(service->asBinder().get(), instance.c_str());
CHECK_EQ(status, EX_NONE);

Generated Classes

The AIDL compiler generates:

  • BnXxx - Server base class (native binder)
  • BpXxx - Client proxy
  • IXxx - Interface definition with descriptor constant
  • IXxx.Stub (Java) - Server stub class

VINTF Manifest

Required XML file declaring the HAL:

<manifest version="1.0" type="device">
    <hal format="aidl">
        <name>android.hardware.foo</name>
        <version>1</version>
        <fqname>IFoo/default</fqname>
    </hal>
</manifest>

Init RC Script

Defines how the service starts:

  • class hal - Start with HAL class
  • user/group - Run as system or dedicated user
  • oneshot - For single-execution services

Client Usage (Java/Kotlin)

Getting the Service

  1. Build the fully-qualified name: <DESCRIPTOR>/default
  2. Use ServiceManager.getService() to get IBinder
  3. Use IXxx.Stub.asInterface() to convert to typed interface

Calling Methods

  1. Always check for null service reference
  2. Wrap calls in try-catch for RemoteException and ServiceSpecificException
  3. Handle errors appropriately

Client Usage (C++)

  1. Use AServiceManager_getService() to get binder
  2. Wrap in ndk::SpAIBinder
  3. Use IXxx::fromBinder() to get typed interface
  4. Check for null and call methods through the pointer

Callbacks (Asynchronous Notifications)

Pattern

  1. Define callback interface (e.g., IFooCallback)
  2. Main interface has registerCallback() and unregisterCallback() methods
  3. Client implements callback, server stores reference and calls methods on events
  4. Use oneway for callback methods to avoid blocking

Client Implementation

Inherit from BnFooCallback and override methods. Register with service after getting it.

Exception Handling

Exception Code Use Case
EX_UNSUPPORTED_OPERATION Feature not supported on device
EX_ILLEGAL_ARGUMENT Invalid input parameter
EX_ILLEGAL_STATE Invalid state for operation
EX_SERVICE_SPECIFIC Custom error with status code

C++

return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION);
return ndk::ScopedAStatus::fromServiceSpecificError(ERROR_CODE);

Java/Kotlin

throw new ServiceSpecificException(ERROR_CODE, "message");

Versioning

  1. Set frozen: true after initial release
  2. Add new version to versions_with_info array
  3. Maintain backward compatibility - new versions must work with old clients
  4. Deprecate old methods with @deprecated annotation, don't remove them

Critical: VINTF-stable interfaces (stability: "vintf") must be frozen before the service can register. Without frozen: true and a matching version in versions_with_info, assemble_vintf silently strips the HAL entry from the manifest fragment, producing an empty <manifest version="9.0" type="device"/>. The service then fails with status=-3 (EX_ILLEGAL_ARGUMENT) on AServiceManager_addService. Use m <interface-name>-freeze-api to freeze.

Build Dependency Propagation

When adding a custom AIDL HAL as a dependency to a large module like SurfaceFlinger, be aware that the module's sources may be compiled by multiple build targets — not just the main binary. For example, SurfaceFlinger.cpp is compiled by both libsurfaceflinger and the layertracegenerator tool (via :libsurfaceflinger_sources filegroup). If the AIDL library is only added to one target's shared_libs, the others will fail with a missing header error.

Best practice: Create a dedicated cc_defaults for the custom AIDL deps and have every target that compiles the relevant sources inherit from it. This avoids duplicating the library list across unrelated Android.bp files and ensures new consumers automatically pick up the deps.

// In the module's Android.bp, define a defaults block for custom deps:
cc_defaults {
    name: "surfaceflinger_custom_deps",
    shared_libs: [
        "custom.hardware.display.ltpo-V2-ndk",
        "custom.hardware.biometrics.fingerprint.udfps-V1-ndk",
    ],
}

// Main binary inherits it:
cc_defaults {
    name: "libsurfaceflinger_defaults",
    defaults: ["surfaceflinger_custom_deps"],
    ...
}

// Tool that compiles the same sources also inherits it:
cc_binary {
    name: "layertracegenerator",
    defaults: ["surfaceflinger_custom_deps", ...],
    ...
}

This is especially important for emulator builds, which may build tools (like layertracegenerator) that device builds skip.

Common Patterns

Simple Get/Set

Interface with getter and setter for a boolean or integer value.

Configuration with Parcelable

Complex configuration passed as a parcelable containing multiple fields.

Session-Based

createSession() returns an ISession interface for ongoing operations (common in biometrics).

Event Notifications

Register callback to receive unsolicited events from HAL.

Debugging

Service Availability

service list | grep hardware.foo
dumpsys vintf

Logs

Use LOG_TAG in C++ to identify log source:

logcat -s <LOG_TAG>

Common Issues

Issue Check
Service not found init.rc spelling, VINTF manifest, SELinux
DeadObjectException Service crashed, check logs
Permission denied SELinux policy for service access
Method not found Version mismatch between client and service

Key Headers (C++)

Header Purpose
<android/binder_manager.h> Service manager (register/lookup)
<android/binder_process.h> Thread pool management
<aidl/.../BnXxx.h> Generated server base class
<aidl/.../IXxx.h> Generated interface definition

SELinux

HAL services need appropriate SELinux labels:

  • Service binary labeled as hal_foo_default_exec
  • Service process labeled as hal_foo_default
  • Allow rules for binder communication

SELinux Architecture for AIDL HALs

AIDL HALs use the service_manager (not hwservice_manager like HIDL). The key components are:

  1. Attributes - Define client/server/domain attributes for the HAL
  2. Service type - Declares the service in service_manager
  3. Service context - Maps AIDL interface name to SELinux type
  4. Domain - The process domain for the HAL service
  5. File context - Labels the service binary

Required SELinux Files

File Purpose
public/attributes Declare HAL attributes
public/te_macros Helper macros for attribute creation
dynamic/service.te Service type declaration
dynamic/service_contexts Map interface name → SELinux type
dynamic/hal_<name>.te Binder rules and service association
vendor/hal_<name>_default.te Domain definition for implementation
vendor/file_contexts Label the service binary

SELinux File Structure

device/<org>/sepolicy/common/
├── public/
│   ├── attributes           # HAL attribute declarations
│   └── te_macros            # Custom macros
├── dynamic/                  # Used when vendor partition is prebuilt
│   ├── hal_<name>.te        # Binder call rules + service association
│   ├── service.te           # Service type declarations
│   └── service_contexts     # Interface name → type mapping
├── vendor/                   # Used when building vendor partition
│   ├── hal_<name>_default.te  # Domain definition
│   └── file_contexts        # Binary labeling
├── private/                  # System_ext private policies
└── sepolicy.mk              # Build system integration

Creating SELinux for a New AIDL HAL

Step 1: Add Attribute Declaration (public/attributes)

# SPDX-FileCopyrightText: 2026 The halogenOS Project
# SPDX-License-Identifier: Apache-2.0

hal_attribute_custom(my_hal)

Or manually without macro:

attribute hal_my_hal;
expandattribute hal_my_hal true;
attribute hal_my_hal_client;
expandattribute hal_my_hal_client true;
attribute hal_my_hal_server;
expandattribute hal_my_hal_server false;

Step 2: Declare Service Type (dynamic/service.te)

# SPDX-FileCopyrightText: 2026 The halogenOS Project
# SPDX-License-Identifier: Apache-2.0

type hal_my_hal_service, hal_service_type, service_manager_type;

Step 3: Map Interface to Service Type (dynamic/service_contexts)

# SPDX-FileCopyrightText: 2026 The halogenOS Project
# SPDX-License-Identifier: Apache-2.0

com.example.my.IFoo/default    u:object_r:hal_my_hal_service:s0

The format is: <AIDL descriptor>/<instance> where descriptor is the full package path to the interface.

Step 4: Define Binder Rules (dynamic/hal_my_hal.te)

# SPDX-FileCopyrightText: 2026 The halogenOS Project
# SPDX-License-Identifier: Apache-2.0

# Allow binder IPC from client to server
binder_call(hal_my_hal_client, hal_my_hal_server)

# Associate the HAL attribute with the service type
hal_attribute_service(hal_my_hal, hal_my_hal_service)

Step 5: Define Domain (vendor/hal_my_hal_default.te)

# SPDX-FileCopyrightText: 2026 The halogenOS Project
# SPDX-License-Identifier: Apache-2.0

type hal_my_hal_default, domain;
hal_server_domain(hal_my_hal_default, hal_my_hal)

type hal_my_hal_default_exec, exec_type, vendor_file_type, file_type;
init_daemon_domain(hal_my_hal_default)

binder_call(hal_my_hal_default, servicemanager)

Step 6: Label the Binary (vendor/file_contexts)

# SPDX-FileCopyrightText: 2026 The halogenOS Project
# SPDX-License-Identifier: Apache-2.0

/vendor/bin/hw/com\.example\.my\.foo-service\.default    u:object_r:hal_my_hal_default_exec:s0

Step 7: Build System Integration (sepolicy.mk)

#
# SPDX-FileCopyrightText: 2026 The halogenOS Project
# SPDX-License-Identifier: Apache-2.0
#

ifeq ($(TARGET_COPY_OUT_VENDOR), vendor)
ifeq ($(BOARD_VENDORIMAGE_FILE_SYSTEM_TYPE),)
TARGET_USES_PREBUILT_VENDOR_SEPOLICY ?= true
endif
endif

SYSTEM_EXT_PUBLIC_SEPOLICY_DIRS += \
    device/<org>/sepolicy/common/public

SYSTEM_EXT_PRIVATE_SEPOLICY_DIRS += \
    device/<org>/sepolicy/common/private

ifeq ($(TARGET_USES_PREBUILT_VENDOR_SEPOLICY), true)
SYSTEM_EXT_PRIVATE_SEPOLICY_DIRS += \
    device/<org>/sepolicy/common/dynamic
else
BOARD_VENDOR_SEPOLICY_DIRS += \
    device/<org>/sepolicy/common/dynamic \
    device/<org>/sepolicy/common/vendor
endif

Allowing Clients to Access the HAL

To allow a client (e.g., system_server, platform_app) to use the HAL:

# In private/system_server.te or private/platform_app.te
hal_client_domain(system_server, hal_my_hal)

Device-Specific Implementations

For device-specific HAL implementations, add to the device's sepolicy/vendor/file_contexts:

/vendor/bin/hw/com\.example\.my\.foo-service\.device_name    u:object_r:hal_my_hal_default_exec:s0

The device implementation reuses the same _exec type from common sepolicy - no additional domain rules needed.

Debugging SELinux Issues

# Check if service is labeled correctly
ls -Z /vendor/bin/hw/my-service

# Check service manager contexts
service list

# Check for denials
dmesg | grep avc
logcat -b events -d | grep avc

# Audit2allow for generating rules
audit2allow -p /sys/fs/selinux/policy -i /dev/stdin

Common SELinux Errors

Error Cause Solution
ServiceManager: No match found Missing service_contexts entry Add interface mapping
avc: denied { add } for service Wrong service type Check service.te declaration
avc: denied { find } for service Client not in hal_client_domain Add hal_client_domain()
avc: denied { call } for binder Missing binder_call rules Add binder_call() in hal .te

Custom HAL SELinux Setup

The custom SELinux policy is located at device/custom/sepolicy/common/ and is included globally via product/halogenOS/config/BoardConfigCustom.mk.

Directory Structure

device/custom/sepolicy/common/
├── public/
│   ├── attributes              # hal_attribute_custom() macro usage
│   └── te_macros               # hal_attribute_custom macro definition
├── dynamic/
│   ├── hal_custom_media_codec.te
│   ├── service.te
│   └── service_contexts
├── vendor/
│   ├── file_contexts
│   └── hal_custom_media_codec_default.te
├── private/                    # (empty, for future use)
└── sepolicy.mk

Naming Conventions

Component Pattern Example
Attributes hal_<name>, hal_<name>_client, hal_<name>_server hal_custom_media_codec
Service type hal_<name>_service hal_custom_media_codec_service
Domain hal_<name>_default hal_custom_media_codec_default
Exec type hal_<name>_default_exec hal_custom_media_codec_default_exec

Custom Macro: hal_attribute_custom()

Located in public/te_macros:

define(`hal_attribute_custom', `
attribute hal_$1;
expandattribute hal_$1 true;
attribute hal_$1_client;
expandattribute hal_$1_client true;
attribute hal_$1_server;
expandattribute hal_$1_server false;
')

Usage:

hal_attribute_custom(my_new_hal)

Example: custom.media.codec HAL

AIDL Interface: custom.media.codec.ICodecFeatures

File Content
public/attributes hal_attribute_custom(custom_media_codec)
dynamic/service.te type hal_custom_media_codec_service, hal_service_type, service_manager_type;
dynamic/service_contexts custom.media.codec.ICodecFeatures/default u:object_r:hal_custom_media_codec_service:s0
dynamic/hal_custom_media_codec.te binder_call(...) + hal_attribute_service(...)
vendor/hal_custom_media_codec_default.te Domain + exec type + init_daemon_domain()
vendor/file_contexts Labels custom.media.codec.features-service.default

Adding a New Custom HAL

  1. Add hal_attribute_custom(<hal_name>) to public/attributes
  2. Add service type to dynamic/service.te
  3. Add interface mapping to dynamic/service_contexts
  4. Create dynamic/hal_<hal_name>.te with binder rules
  5. Create vendor/hal_<hal_name>_default.te with domain definition
  6. Add binary label to vendor/file_contexts
  7. For device implementations, add device binary label to device's sepolicy/vendor/file_contexts

Reverse-Engineering Vendor AIDL Interfaces

When a vendor ships a proprietary AIDL HAL as a prebuilt binary, the interface definition can be reconstructed:

Finding the Interface Name

  1. VINTF manifest — Check vendor/etc/vintf/manifest/*.xml for the <hal> entry:

    <hal format="aidl">
        <name>vendor.example.hardware.foo</name>
        <fqname>IFoo/default</fqname>
    </hal>
  2. Demangled symbols — Run nm -DC on the binary and look for BnXxx/BpXxx/IXxx symbols:

    nm -DC service_binary | grep "BnFoo\|IFoo"
  3. Shared library depsreadelf -d shows the AIDL stub library name:

    readelf -d service_binary | grep NEEDED
    # e.g. vendor.example.hardware.foo-V1-ndk.so

    If not listed, the stubs are statically linked.

Extracting Method Names

  1. Log stringsstrings on the binary often reveals method names in log messages:

    strings service_binary | grep -oP 'Tag: \K\w+'
  2. Sysfs paths — Log strings often reference the sysfs nodes each method reads/writes, revealing the purpose of each method.

  3. Parameter types — Format strings like val = %d indicate int parameters, val = %s indicates string.

Using the Vendor HAL from a Custom HAL

Instead of writing directly to sysfs nodes that a vendor HAL also manages (causing conflicts), a custom device-specific HAL can call the vendor HAL via binder:

  1. Reconstruct the minimal AIDL definition with only the methods you need
  2. Build it as an aidl_interface module with matching package name and version
  3. In your HAL implementation, use AServiceManager_getService() to get the vendor HAL
  4. Call methods through the typed interface

Caveat: Transaction codes in AIDL are assigned by method declaration order. If the reconstructed AIDL has methods in a different order than the original, calls will invoke the wrong methods. Verify by testing on-device or by analyzing the onTransact dispatch in the binary.

Clone this wiki locally