Introduction: Resurrecting Android Things for Bespoke IoT
Android Things, Google’s embedded operating system for IoT devices, officially reached end-of-life in 2022. While Google no longer provides updates or new hardware support, the underlying architecture and its capabilities for rapid IoT development remain highly attractive for specialized industrial, automotive, and smart TV applications. For projects requiring deep integration with specific, non-standard hardware, the path forward often involves reverse engineering existing Android Things firmware and adapting it to new chipsets and peripherals. This guide delves into the expert-level process of dissecting Android Things firmware to port it onto custom hardware, enabling unique functionalities beyond its original scope.
The complexity of this task lies in understanding the interplay between the Linux kernel, Android’s Hardware Abstraction Layer (HAL), and the framework services, all while navigating device-specific implementations.
Prerequisites for Firmware Adaptation
Before embarking on this reverse engineering journey, a solid foundation in several areas is crucial:
- Linux Kernel Development: Familiarity with kernel compilation, device drivers, and Device Tree Overlays.
- Android OS Architecture: Understanding the AOSP (Android Open Source Project) structure, HALs, Binder IPC, and Android’s init process.
- ARM Assembly & Reverse Engineering Tools: IDA Pro, Ghidra, objdump, readelf, and basic assembly knowledge for analyzing shared libraries.
- Embedded Systems Hardware: Datasheet interpretation, understanding SoC architectures, memory mapping, and common peripheral interfaces (I2C, SPI, UART, GPIO).
- Development Environment: A robust Linux workstation, Android SDK/NDK, AOSP build tools, and toolchains for ARM/ARM64.
Acquiring and Initial Analysis of Android Things Firmware
The first step is to obtain a suitable Android Things firmware image. This can typically be acquired from:
- Official Developer Kits: If you have an original Android Things developer kit (e.g., Raspberry Pi 3, NXP i.MX7D, NXP i.MX8M), you can often download factory images.
- AOSP Repository: While Android Things is EOL, much of its foundation is rooted in AOSP, and studying relevant AOSP branches for embedded devices can provide insights.
- Custom Device Firmware: If your goal is to adapt an existing Android Things product, you might extract firmware directly from the device via debugging interfaces (JTAG/SWD) or by desoldering flash memory.
Once you have a firmware image (often a `.zip` or `.img` file), the initial analysis begins:
Deconstructing the Firmware Image
Most Android firmware images consist of several partitions. We’ll focus on `boot.img`, `system.img`, and `vendor.img`:
# Use binwalk to identify embedded files and file systems if it's a raw image or update package.binwalk -e firmware_image.zip# If it's a sparse image, convert it to a raw image for easier mounting.simg2img system.img system.raw.img# Mount the raw system image.sudo mount -o loop system.raw.img /mnt/android_system# Extract boot.img components (kernel, ramdisk, dtb).python3 split_boot.py boot.img
The `split_boot.py` script will typically give you `kernel`, `ramdisk.img`, and potentially `dtb` (Device Tree Blob) files.
Hardware Identification and Comparison
The core of custom hardware adaptation is understanding the differences between the original Android Things reference board and your target custom hardware. Key components to identify on your custom board include:
- System-on-Chip (SoC): Manufacturer, model, architecture (ARM Cortex-A series).
- Memory: RAM (DDR type, size), eMMC/NAND flash.
- Peripherals: WiFi/Bluetooth modules, Ethernet controller, display controller (MIPI DSI, HDMI), camera interfaces (MIPI CSI), USB host/device controllers, I2C/SPI/UART buses, GPIOs, audio codecs.
- Power Management IC (PMIC): Model, voltage regulators.
Thoroughly examine datasheets and schematics for your custom hardware. Compare these specifications against known reference boards that originally supported Android Things. This comparison will highlight the areas where the firmware needs modification.
Kernel and Device Tree Adaptation
The Linux kernel and its associated Device Tree Blob (DTB) are paramount for hardware interaction. The DTB describes the hardware components present on the board to the kernel.
Decompiling and Modifying the Device Tree
Extract the DTB from `boot.img`. If your `split_boot.py` didn’t yield a direct `.dtb` file, you might find it embedded within the kernel or ramdisk. Once found, decompile it:
# Decompile the DTB to a human-readable Device Tree Source (.dts) file.dtc -I dtb -O dts -o custom_board.dts original_dtb_file.dtb
Now, `custom_board.dts` can be edited. You’ll need to modify this file to reflect your custom hardware:
- SoC Pin Muxing: Adjust GPIOs for your specific peripherals.
- Peripheral Nodes: Add, remove, or modify nodes for I2C devices, SPI devices, UARTs, display panels, network controllers, and any custom hardware.
- Memory Map: Verify and adjust RAM and flash memory addresses if different.
- Power Management: Configure PMIC settings for voltage regulators and power states.
After modifications, recompile the `.dts` back into a `.dtb`:
# Recompile the modified .dts file.dtc -I dts -O dtb -o new_custom_board.dtb custom_board.dts
Kernel Source Adaptation
If your custom hardware uses a different SoC or requires custom drivers not present in the original kernel, you’ll need to build a new kernel. This involves:
- Obtaining the Linux kernel source code matching the original Android Things version or a compatible one from AOSP.
- Adding necessary drivers for your new SoC or peripherals (e.g., display panel drivers, touch screen drivers, custom sensor drivers).
- Updating kernel configuration (`.config`) to enable these new drivers and disable irrelevant ones.
- Compiling the new kernel.
# Example kernel compilation (adjust ARCH, CROSS_COMPILE, and config as needed)export ARCH=arm64export CROSS_COMPILE=aarch64-linux-android-make custom_board_defconfigmake -j$(nproc)
Finally, rebuild `boot.img` with your new kernel and DTB (and potentially a modified ramdisk for init scripts or custom HALs).
Hardware Abstraction Layer (HAL) Customization
Android’s HAL provides a standardized interface for the Android framework to interact with device-specific hardware. When porting to custom hardware, you’ll often need to adapt or create new HAL modules.
Identifying and Analyzing Existing HALs
Explore the `vendor/lib/hw/` (or `vendor/lib64/hw/`) directory on the mounted `system.raw.img` to find existing HAL modules (e.g., `lights.device_name.so`, `sensors.device_name.so`, `power.device_name.so`). Use reverse engineering tools (IDA Pro, Ghidra) to understand how these modules interact with the kernel drivers and hardware.
# Example: Examining a HAL module strings vendor/lib/hw/lights.i.mx7d.so | grep HALS_DEVICE_PATH# Inspecting exportsreadelf -s vendor/lib/hw/lights.i.mx7d.so | grep hal_module_methods
Developing Custom HAL Modules
For custom peripherals (e.g., a unique industrial sensor, a custom display backlight controller), you might need to write a new HAL module or modify an existing one. This involves:
- Defining the HAL interface using C/C++ in accordance with Android’s `libhardware` standards or AIDL (for newer Android versions).
- Implementing the logic to communicate with your custom kernel driver (e.g., via `/dev` nodes, `sysfs` entries, or `ioctl` calls).
- Compiling the HAL module and placing it in the correct location (`/vendor/lib/hw/` or `/system/lib/hw/`).
// Pseudocode for a custom HAL light module#include <hardware/hardware.h>#include <hardware/lights.h>static int open_lights(const struct hw_module_t* module, const char* name, struct hw_device_t** device){ // Open /dev/custom_led_driver // Initialize device struct // ... return 0;}static struct hw_module_methods_t lights_module_methods = { .open = open_lights};struct light_module_t HAL_MODULE_INFO_SYM = { .common = { .tag = HARDWARE_MODULE_TAG, .version_major = 1, .version_minor = 0, .id = LIGHTS_HARDWARE_MODULE_ID, .name = "Custom Board Lights HAL", .author = "RE Lab", .methods = &lights_module_methods, }};
System Partition and Services Adaptation
The `system.img` partition contains the Android framework, system services, and configuration files. While less hardware-specific, some modifications may be necessary:
- Init Scripts: Analyze `init.rc` and `init..rc` files in the root of the ramdisk and `/system/etc/` to understand service startup. Adjust services that depend on specific hardware.
- SELinux Policy: Custom kernel drivers or HAL modules might require new SELinux rules to allow proper interaction. This can be complex and requires understanding Android’s security model.
- Framework Overlays: If UI elements or specific system behaviors need adjustment due to hardware changes (e.g., display resolution, input methods), framework resource overlays (`/vendor/overlay`) might be used.
Flashing and Debugging the Custom Firmware
Once your custom firmware (`boot.img`, `system.img`, `vendor.img`) is assembled, it’s time to flash it to your custom hardware. This is typically done via Fastboot:
# Reboot device into Fastboot modeadb reboot bootloader# Flash individual partitionsfastboot flash boot new_boot.imgfastboot flash system new_system.imgfastboot flash vendor new_vendor.img# Reboot to Androidfastboot reboot
Initial boot-ups are often challenging. Connect a serial console to capture kernel boot logs (`dmesg`) and Android logs (`logcat`) as early as possible. Look for kernel panics, service crashes, or
Android Mobile Specs & Compare Directory
Are you researching mobile hardware properties, processor SoCs, GPU chipsets, or RAM configurations? Access our complete specs catalog to compare up to 5 devices side-by-side!
Compare Devices Specs →