A HAL is not a magic library that knows how hardware works. It is an implementation of a contract, built into an executable or other supported service arrangement, started under a particular identity, registered under a particular name, and called through generated bindings. We will follow those pieces in an actual AOSP example—and discover that a service can implement vibration without driving a motor at all.
Before you start#
Read HALs, Treble, and vendor code, Binder, Soong, init, and Concurrency and lifetime. All source paths below use AOSP android-14.0.0_r1. Browser-only source reading is the required route. Do not install this example over a phone's vendor implementation.
Learning goals#
Connect AIDL, generated server bindings, implementation methods, Soong dependencies, init configuration, VINTF declaration, Binder registration, framework discovery, and a callback. Explain exactly where the example stops short of physical hardware, then design a failure investigation that distinguishes those stages.
Why put a contract between framework and hardware?#
The framework wants operations such as “play this effect” without knowing a board's register layout, actuator model, firmware protocol, or calibration. Vendors want to change those implementation details without forking every framework client. A typed, versioned IPC contract provides the shared boundary. It also lets the implementation live in a different process/security domain and fail independently of a client, though failure can still break the feature.
The contract does not erase physical differences. A cheap actuator may not support amplitude control or composed effects. Capability discovery and defined unsupported-operation behavior let one interface accommodate that variation. The vibrator AIDL documents these semantics; method names alone are insufficient.
Follow seven artifacts, not one file#
- AIDL and generated bindings
Define the contract and generated server/client dispatch.
- Handwritten implementation and build
Vibrator.cpp and Android.bp supply method bodies and native dependencies.
- Init and VINTF artifacts
Declare process startup separately from the provided interface/version.
- main registers and serves
Publish the Binder instance and enter the serving pool.
- Request and callback
The example logs and uses a sleeping worker; physical backend remains absent.
The selected implementation is hardware/interfaces/vibrator/aidl/default/. Here is the complete ownership map:
| Artifact | Role in this example | What its existence does not establish |
|---|---|---|
IVibrator.aidl | Typed operations, capabilities, callback contract | Running service or motor control |
vibrator/aidl/Android.bp | aidl_interface, VINTF stability, versions 1 and 2 | Product installation |
default/include/vibrator-impl/Vibrator.h | C++ implementation class derived from generated server base | Implementation of every method in this header alone |
default/Vibrator.cpp | Method bodies and example behavior | Board-specific driver behavior |
default/Android.bp | Native libraries, binary, init/VINTF packaging | Inclusion in every product |
default/vibrator-default.rc | Init process declaration | Binder service registration |
default/android.hardware.vibrator.xml | Declared AIDL instances/version | Live service or compatibility of an entire system |
Pinned links for the implementation directory and build definition let you inspect each row without a checkout.
Generated code versus handwritten code
The AIDL compiler generates client proxies, server dispatch code, types, and stability/version plumbing. The handwritten Vibrator derives from the generated BnVibrator base. The base receives Binder transactions and invokes the implemented virtual methods. There is no need to hand-encode a Parcel in the implementation's on(...) method.
NDK AIDL methods return ndk::ScopedAStatus; method outputs may be delivered through output parameters. An OK Binder/application status means the declared operation's return contract succeeded. It does not necessarily mean asynchronous hardware work has completed. An unsupported operation, invalid argument, transport death, and later missing completion are different failure categories.
The native vendor example links libbinder_ndk and android.hardware.vibrator-V2-ndk. The framework-side wrapper in this release can use the C++ backend. Different generated backends interoperate through the agreed Binder contract; their C++ class layouts do not have to be identical across processes. See AIDL HAL guidance for the stable vendor-side NDK boundary.
Build and start: why a binary is not yet a service#
In the pinned default/Android.bp, libvibratorexampleimpl supplies the handwritten implementation. The cc_binary named android.hardware.vibrator-service.example has vendor: true, relative_install_path: "hw", init_rc: ["vibrator-default.rc"], and a VINTF fragment. These declare build/installation relationships; a product still has to select the module and package the relevant artifacts.
The actual vibrator-default.rc declares the executable at /vendor/bin/hw/android.hardware.vibrator-service.example, with class hal, user nobody, and group nobody. That is the example's configuration, not a rule that physical HALs all use this identity. A real implementation needs suitable executable labeling, a process domain, service-manager add/find permissions, Binder-call policy, and narrowly permitted access to its device nodes or other backends. An init user line does not grant those accesses.
Three names coexist:
- Build module:
android.hardware.vibrator-service.example. - Init service:
vendor.vibrator-default. - Binder instance:
android.hardware.vibrator.IVibrator/default.
They are not aliases. Asking init to start the module name instead of the init service name is a category error. Finding the process does not establish the Binder instance has been registered.
Register and serve: read main.cpp precisely#
Pinned default/main.cpp performs this sequence:
- Configure the Binder thread pool's maximum additional-thread count as zero.
- Create a shared
Vibratorimplementation object. - Pass its Binder representation and service name to
AServiceManager_addService. - Check registration status.
- Create/register a separate
VibratorManagerwith another vibrator object. - Enter
ABinderProcess_joinThreadPool().
The zero is a useful reading trap: it does not mean no thread handles calls. The main thread joins the Binder pool. Thread-pool APIs distinguish threads the library may start from the caller that joins. To generalize concurrency, inspect all serving threads and callbacks, not just that one integer.
The VINTF fragment declares AIDL version 2 and both IVibrator/default and IVibratorManager/default. Declaration, registration, and serving remain separate: a correctly packaged XML file cannot execute main, and an executable stuck before registration cannot satisfy a waiting client.
What actually happens when on is called?#
Read Vibrator::on. It logs the duration and, if a callback exists, starts a detached thread that sleeps and invokes callback->onComplete(). It then returns OK. off() logs and returns OK. These bodies contain no motor-driver write.
That is why this implementation is an example. The sleep models completion timing; it is not a physical actuator timer or proof of accurate timing. The callback's shared pointer is captured by value, avoiding a raw this capture that could outlive the object. The source even comments on that lifetime issue. It does not follow that arbitrary detached-thread designs are production-ready: cancellation, repeated requests, bounded resources, overflow/input validation, hardware errors, and teardown still need a real design.
getCapabilities() reports a bitmask of supported contract features. perform() accepts only the selected example effects/strengths and returns unsupported for others. A client should use capability and supported-effect information rather than treating every method as unconditionally available.
A real backend, conceptually: validate the request → translate effect/duration to a supported hardware operation → submit via the device's supported kernel/firmware interface → track completion/cancellation → report contract-defined status/callback. The hardware path might use a subsystem ioctl, a suitable sysfs attribute, or another vendor component. There is no universal vibrator node or register sequence. The AOSP example cannot establish the missing vendor path.
Where the framework finds it#
In Android 14 VibratorHalController.cpp, connectHal() first attempts waitForVintfService<Aidl::IVibrator>(). If an AIDL instance is obtained, it creates an AidlHalWrapper; otherwise it explores HIDL alternatives. The wrapper, AidlHalWrapper::on, calls the HAL and translates its status. The JNI controller lives in com_android_server_vibrator_VibratorController.cpp.
This supplies concrete lower-level edges beyond “framework → HAL.” Exact device routing can also involve a vibrator manager, multiple actuators, HIDL versions, cached connection state, and reconnection logic. The source path is not proof that a particular emulator selected the AIDL example.
Practice#
Complete the seven-artifact table with exact path/symbol references, then diagnose this synthetic packet:
product output: service binary and VINTF fragment present
init state: vendor.vibrator-default running
Binder registration: IVibrator/default visible
on(40, callback): returns OK
callback: observed after a delay
physical actuator: none in this test environment
Model answer: packaging/start/registration/request/callback are independently evidenced within the supplied scenario. Physical vibration is not. The example's timer-based implementation explains how all software observations can succeed without a motor. Replace the registration row with “absent”: investigate instance spelling, startup failure, add-service policy, and registration errors before driver timing. Replace the callback row with “missing”: investigate callback capability, server lifetime, execution, and client death rather than assuming on's return meant completion.
Optional platform work: on a separately configured disposable product that deliberately includes the example, build the named module and run its relevant tests. No build, boot, HAL deployment, or physical effect is asserted here. Do not push a replacement into an unrelated vendor partition.
Knowledge check#
- Which artifact starts the process, and which statement registers its Binder object?
- Why can
setThreadPoolMaxThreadCount(0)still lead to incoming calls being served? - Where does this example's
on()drive the motor? - Is a completion callback an ordinary return value from the original transaction?
Answers#
- The init declaration starts the process;
AServiceManager_addServicepublishes the Binder instance. - The main thread enters
ABinderProcess_joinThreadPool; zero additional threads is not zero serving threads. - Nowhere. It uses logging and a sleeping callback thread, not a hardware backend.
- No. It is a later call through a callback Binder object, with its own lifetime and failure behavior.
Checkpoint#
Ready when your trace connects all seven artifacts and the framework wrapper, distinguishes all three names, and stops at the absent physical backend. Revise if XML is treated as executable code, a callback as physical proof, or a build as product deployment. Revisit main.cpp, Vibrator::on, and the init/VINTF files; redraw startup separately from request handling. Device execution remains not yet evidenced without your own identified build and observation.
Sources#
- AOSP Android 14 vibrator
IVibrator.aidland interface build definition — contract and frozen versions. - Pinned example directory,
main.cpp, andVibrator.cpp— registration and nonphysical example behavior. - Pinned
Android.bp,vibrator-default.rc, and VINTF fragment — build/start/declaration roles. - Pinned
VibratorHalController.cppandVibratorHalWrapper.cpp— framework-side discovery and calls. - AOSP AIDL HALs and Binder threading — current contract/backend/threading guidance; distinguish it from the historical example.