Android Automotive (AAOS) Emulator Reference
Purpose: setting up an AOSP automotive emulator as a virtual environment for prototyping a vehicle HMI/platform, and understanding precisely where that emulator stops being a valid proxy for Qualcomm automotive silicon.
Caveat on freshness: AOSP lunch target names, goldfish codec coverage, and Qualcomm's access/portal model all change between releases. Verify branch-specific details against lunch output and your own build rather than treating names here as stable.
1. Choosing your path
| Path | What it is | When to use |
|---|---|---|
| A. App on AAOS | Prebuilt system images from Android Studio SDK Manager ("Automotive with Play Store" / "with Google APIs") | Building an app that runs on someone else's head unit. Fast, no source build. Cannot sign with the platform key, so signature-level car permissions and custom VHAL properties are off-limits. |
| B. Platform / HMI prototyping | Build AOSP automotive from source, run the emulator it produces | Changing system UI, adding vehicle properties, modeling your own hardware. This is what "prototype car" normally means. |
| C. OEM add-on images | Polestar, Volvo, GM, Honda etc. publish images via a custom SDK update-site URL | Targeting a specific OEM stack. |
This document assumes Path B.
2. Host requirements
Emulator only (Path A)
- x86_64 host with hardware virtualization: KVM (Linux), AEHD or WHPX (Windows — HAXM is dead), Hypervisor.framework (macOS)
- ~16 GB RAM, ~60 GB disk
- Discrete GPU strongly recommended — automotive images render multiple large surfaces; software rendering is painful
AOSP source build (Path B)
- x86_64 Linux. Not negotiable.
- Realistically 64 GB RAM, 16+ cores, 400–500 GB SSD
- First build is several hours; set up
ccacheimmediately
Architecture constraints (Path B specifically)
You cannot build AOSP on macOS. Darwin host support for full platform builds was removed around the Android 12/13 era and has not returned. An Apple Silicon Mac is not a build machine under any configuration.
Linux-on-arm64 as an AOSP host has seen incremental work (aarch64 host toolchain, host_cross plumbing) but is under-exercised versus x86_64 Linux, with prebuilt-binary gaps in automotive-specific parts of the tree. Don't stake a project on it.
Guest arch is a separate question you control. You're cross-compiling anyway. Car arm64 emulator targets exist (sdk_car_arm64-style), so you can generate arm64 automotive images yourself. The caveat is maturity, not existence: x86_64 car targets get vastly more testing from Google, OEMs, and everyone's CI. arm64 car targets are lightly trodden, and graphics is where it shows — gfxstream/Vulkan paths, secondary displays, cluster surfaces. Expect to file the bug rather than find it fixed.
Hybrid workflow: build sdk_car_arm64 on x86_64 Linux, run the image on an Apple Silicon Mac using the SDK's darwin-aarch64 emulator binary. Works, but watch version skew between an SDK-shipped emulator and a self-built image. Running an arm64 guest on the x86_64 build host means TCG emulation — unusably slow for HMI work.
Recommendation: build sdk_car_x86_64 and run on the Linux host unless you have a concrete arch-sensitive reason (NDK code, vendor HAL binaries, native media pipeline). For HMI prototyping, vehicle property modeling, and multi-display layout, target arch is irrelevant to what you're testing — and arch-sensitive work is better validated on a reference board anyway.
3. Building from AOSP
Sync an automotive-capable branch, then lunch something along the lines of:
sdk_car_x86_64-<release>-userdebug
Older branches used aosp_car_x86_64-userdebug; target naming changed with trunk-based development, so check lunch output for your branch. Variants also exist for portrait (sdk_car_portrait_*) and cluster/multi-display configurations.
After m, launch with emulator from the built environment.
4. Emulator configuration checklist
4.1 Build-time: your device/product tree
device/<yourco>/<yourcar>/
AndroidProducts.mk # registers your lunch target
<yourcar>.mk # inherits device/generic/car/emulator/*
BoardConfig.mk
overlay/ # static resource overlays
sepolicy/ # domains for any new service
vhal/ # your property config
car_audio_configuration.xml
car_ux_restrictions_map.xml
power_policy.xml
Critical discipline: inherit from device/generic/car, don't fork it. Everything you add should be an overlay or an additional package, so the same tree can later be pointed at a vendor board.
4.2 Car service configuration XMLs
These declare "what the car does" and are the highest-value thing to get right early. They are resource overlays on CarService.
car_audio_configuration.xml— zones (driver / front passenger / rear), audio contexts (music, navigation, voice, alarm, call), and the ducking/focus interaction matrix. Multi-zone is what surprises people; model it before HMI work.car_ux_restrictions_map.xml— per driving state (parked / idling / moving): no video, no keyboard, limited string length, no setup. Your HMI must respond to these; this file is what you test against.power_policy.xml— component enablement per power state. Emulator fidelity is poor here, but the config still needs to exist.- Occupant zone / user config — maps users and displays to seats.
4.3 VHAL property config
Vendor property IDs in the VENDOR range, with:
- change mode (
ON_CHANGE/CONTINUOUS) - access (
READ/WRITE/READ_WRITE) - area IDs
- default values
Treat this as your production interface contract, because it is — you'll reimplement it against the real bus later.
4.4 AVD / runtime: config.ini
Located at ~/.android/avd/<name>.avd/config.ini.
hw.lcd.width=1920
hw.lcd.height=1080
hw.lcd.density=160
hw.display1.width=1920 # cluster
hw.display1.height=720
hw.display1.density=160
hw.display2.width=1280 # rear seat entertainment
hw.display2.height=800
hw.display2.density=160
hw.ramSize=8192
Multi-display also requires enabling the MultiDisplay feature in ~/.android/advancedFeatures.ini.
Notes:
- Automotive skins commonly run 1024×768, 1080p landscape, or tall portrait (Tesla-style).
- Density matching your real head unit matters more than resolution — density mismatch is the usual cause of an HMI that looks right in the emulator and wrong on glass.
- Give it 4–8 GB RAM; automotive system UI plus several apps will thrash a 2 GB VM.
- Cluster is usually a separate secondary display with its own driving-relevant surface.
4.5 RROs (runtime resource overlays)
Theming for CarSystemUI, CarLauncher, framework resources. Never edit framework resources directly — RROs are the only approach that survives contact with a vendor tree later.
4.6 SELinux, permissions, signing
- Write sepolicy for any new service as you go, and run enforcing on the emulator. Deferring this is the single most common source of "worked on emulator, dead on hardware."
- Privapp permission allowlists for your packages.
- Platform key signing for anything touching signature-level car permissions.
5. Driving vehicle data in the emulator
Everything vehicle-side flows through the Vehicle HAL (VHAL). The emulator ships a reference/emulated VHAL you can drive several ways:
| Method | Use |
|---|---|
| Extended Controls → Car Data panel in the emulator UI | Basic speed, gear, ignition, fuel |
adb shell cmd car_service inject-vhal-event | Scripted property injection; also subcommands for driving state and UX restrictions |
adb shell dumpsys car_service | Inspect current property state |
vhal_emulator.py (older AOSP branches) | Protobuf over a forwarded socket; handy for scripted sensor replay |
Driver-distraction/UX-restriction testing matters: your app must gracefully degrade above a speed threshold or it won't pass.
For a real prototype, the meaningful work is adding your own vendor properties to the VHAL and defining a bridge. Replaying recorded CAN logs into the emulated VHAL is the common approach, since the emulator has no real bus.
6. Known emulator limitations
- Google Play images are locked down (no
adb root) — debugging system behavior requires userdebug AOSP or an OEM image. - Vulkan/GPU driver issues cause blank or corrupted secondary displays more often than expected.
-gpu swiftshader_indirectis the fallback diagnostic. - Boot/suspend behavior (garage mode, deep sleep, fast-boot resume) is not modeled faithfully. Power-state testing eventually needs hardware.
- No real timing, no real audio focus contention with a real amplifier, no real touch latency. The emulator validates logic and layout, not experience.
7. The emulator is not a model of the SoC
7.1 Two build lanes that never merge
| Lane | Contents |
|---|---|
| Emulator lane | Public AOSP, device/generic/car, no vendor code. Target is goldfish/ranchu — a synthetic virtual board with virtio devices, a reference VHAL, gfxstream graphics. |
| Target lane | Qualcomm's BSP for the actual silicon. Entirely different device tree, kernel, vendor HALs, firmware blobs. |
When you lunch sdk_car_x86_64, you are building for that virtual machine — not for SA8155P or SA8295P. You cannot build an emulator image "for" an automotive SoC.
Your job: keep everything you write above the vendor boundary — car service configs, HMI, RROs, apps, custom VHAL properties — shared between both lanes, so the emulator remains a meaningful proxy.
7.2 What the emulator actually is
A conformant implementation of the same interfaces — MediaCodec, Codec2, gralloc, surface binding. You test your code against a contract, not against silicon.
Two implementations of one contract can differ arbitrarily in performance and still agree on structure: which process work happens in, what buffers cross which boundary, what happens on the failure path, what call sequence is required. Your integration code is written against structure. Architecture difference is a difference in how the contract is fulfilled; bugs in your code are bugs in how you use the contract. Those are largely orthogonal.
8. Qualcomm: what you can and can't get
8.1 BSP access
For the target lane: not publicly, and not without a commercial relationship.
Qualcomm's automotive Android platform (QAAOS, under Snapdragon Digital Chassis / Cockpit) is distributed through gated channels — CreatePoint and Qualcomm Package Manager — requiring an NDA, a license agreement, and typically purchase of a development kit or engagement through a Tier 1. Public web material is marketing plus high-level SDK docs. Some pieces are open (kernel trees and various Snapdragon components on CodeLinaro), but the automotive BSP as a whole, display/camera/audio HALs, DSP firmware, and hypervisor configuration are not.
Even with access, "all the information" overstates it. Automotive Snapdragon platforms typically run Android as a guest VM under a hypervisor, with instrument cluster and safety-critical functions on a separate VM (often QNX). Partitioning, VM resource allocation, and inter-VM communication are configured through vendor tooling with no emulator equivalent at all. That is the single biggest fidelity gap, and no amount of emulator configuration closes it.
Practical consequence: if you don't already have a Qualcomm or Tier 1 relationship, start procurement now. It runs on a months-long timeline, in parallel with emulator work, not after it.
8.2 Do the SoCs include display? Yes.
Snapdragon automotive SoCs include an on-die display subsystem: the DPU/MDSS ("Snapdragon Display Engine"), with multiple hardware display pipes, layer mixers, and DSI/DP/eDP outputs. SA8295P is marketed largely on this — driving many concurrent displays is the whole point for a multi-screen cockpit.
What is not on-die: the long-haul link to remote panels. Automotive cable runs generally need FPD-Link or GMSL serializers between SoC and display.
8.3 Why gralloc lives in the display HAL
Qualcomm's gralloc implementation lives in the display HAL package (hardware/qcom/display, the libgralloc/gralloc4 component). It is a single vendor allocator serving all buffer consumers — display, GPU, camera, video — because those consumers have mutually-constraining alignment and format requirements and something must arbitrate.
The Venus-specific coupling is explicit in the code: media/msm_media_info.h defines macros such as VENUS_Y_STRIDE, VENUS_UV_SCANLINES, VENUS_BUFFER_SIZE, which gralloc calls when sizing a buffer whose usage flags indicate a video producer/consumer. Allocating a graphic buffer for decode output therefore has the display HAL's allocator computing Venus's stride/scanline alignment.
Why structured this way: the display controller is usually the most constrained consumer in the chain. If a buffer will be scanned out, its alignment and compression format must satisfy the DPU, so allocation policy lives with the component that knows those rules. UBWC — Qualcomm's bandwidth-compression format — is the sharpest case: whether a buffer is UBWC-compressed depends on the combination of producer and consumer usage flags, and only the vendor allocator knows the compatibility matrix.
9. Media / codecs: what transfers and what doesn't
9.1 You cannot emulate a Venus/Iris VPU
A hardware codec block is IP + firmware + kernel driver + vendor Codec2 HAL, all closed. Emulating its behavior would require a cycle-level model plus the firmware image. That model doesn't exist publicly and Qualcomm doesn't ship one. Under NDA you get a hardware dev kit, not a VPU simulator. Nobody in the industry validates codec behavior in an emulator.
9.2 But the emulator is not swcodec-only
device/generic/goldfish ships Codec2 decoders — c2.goldfish.h264.decoder, c2.goldfish.vp8.decoder, c2.goldfish.vp9.decoder — that run in the hardware codec process (media.hwcodec / android.hardware.media.c2@...-service), not media.swcodec. They forward work to the host through gfxstream and decode with the host's real accelerator (VAAPI on Linux, VideoToolbox on macOS), with output landing in gralloc buffers bindable directly to a surface.
The hardware-codec code path is live. Meaningfully exercisable:
- Codec discovery and selection — your code actually picks a C2 hardware component, not a fallback
MediaCodecstate machine, async callbacks, flush/reset/reconfigure- Surface output and gralloc buffer handoff, including video on a secondary display in a multi-display setup
- Format negotiation, resolution changes, adaptive playback plumbing
- Concurrency logic — how your app reacts when codec instantiation fails
Coverage is narrow: roughly H.264, VP8, VP9. HEVC and AV1 hardware paths vary by branch and host driver; encoders are much thinner than decoders. Check the codec list on your actual build (adb shell dumpsys media.codec) rather than assuming.
9.3 Why host-HW beats swcodec even though both are "wrong"
media.swcodec and media.hwcodec are not two implementations of one path — they are two different paths through your stack:
| swcodec | goldfish C2 (hw) | Qualcomm Venus/Iris | |
|---|---|---|---|
| Process / SELinux domain | media.swcodec | hwcodec service | hwcodec service |
| Buffer type | mostly CPU-mapped | gralloc / graphic buffers | gralloc (UBWC) |
| Output to surface | often copy | bind, no CPU touch | bind, no CPU touch |
| Allocation fails under load | rarely | yes | yes, aggressively |
| Async/callback timing | different shape | asymmetric like real HW | asymmetric |
Developing on swcodec silently acquires swcodec-only dependencies: assuming you can getOutputImage() and read pixels, assuming allocation always succeeds, assuming a timing shape that doesn't hold. Those pass locally and fail on hardware — the worst possible ordering.
Goldfish C2 is wrong about magnitudes and right about shape. swcodec is wrong about shape. Shape is what your integration code touches.
On ISA: for media specifically the arch difference matters less than it sounds. Decode doesn't run on the CPU ISA in either case — it's a fixed-function block (Venus on target, host VAAPI/VideoToolbox on the emulator). Your code sits in ART or in NDK code calling an arch-independent API. Where ISA does enter — vendor color formats, stride/alignment assumptions, UBWC, NEON in your own native processing — is exactly the non-transferable list below. Not a coincidence: the things sensitive to architecture are the things you can't validate here.
9.4 What does NOT transfer to SA8155 / SA8295
- Performance and latency — throughput, decode-to-display latency, power. Host GPU numbers are noise relative to target silicon.
- Concurrency limits — how many simultaneous 1080p/4K instances before allocation fails. The automotive question with rear-seat entertainment + cluster + main display. Emulator limits are the host's, not Qualcomm's.
- Secure video — Widevine L1, TrustZone-backed secure buffers, secure decoder variants. Emulator gives L3 and nothing else.
- Tunneled playback — requires real HWC and vendor sideband handling. No emulator equivalent.
- Buffer alignment, stride, min buffer counts, vendor color formats — UBWC especially. A pipeline that works on goldfish gralloc can break outright on UBWC output.
vendor.qti.*Codec2 parameters — low-latency mode, custom rate control, picture-order settings. Don't exist on the emulator.- Error resilience — corrupt/truncated streams fail differently per silicon; often where automotive tuner/streaming integrations break.
- HDR, interlaced, unusual profiles/levels.
9.5 Rules that follow
- Keep hardware codecs enabled on the emulator. Don't force swcodec — you want the C2 hardware path exercised, because that's where plumbing bugs live and they're cheap to find early.
- Query codec capabilities, never assume:
MediaCodecList/CodecCapabilitiesat runtime, graceful degradation on instantiation failure, no hardcoded component names. - Never hardcode a color format. Qualcomm exposes vendor formats (
QOMX_*/ private UBWC variants) with no emulator equivalent. - Treat gralloc buffers as opaque handles to pass between components, not as memory you reason about. Code written that way ports; code that touches pixel layout doesn't.
- Get producer/consumer usage flags right. The emulator won't punish you for getting them wrong; on Qualcomm, wrong flags mean either a UBWC buffer handed to something that can't read it, or silent loss of compression and a bandwidth blowout.
9.6 Bug classes the emulator's HW path genuinely catches
- SELinux denials for the hwcodec domain
- Codec selection logic picking a component that doesn't exist on target
- Missing or wrong flush/reset/reconfigure sequencing
- Surface lifecycle bugs — releasing while the codec holds buffers
- Multi-display: video on the passenger screen while cluster renders; display hotplug during playback
- Unhandled
MediaCodec.CodecExceptionon instantiation failure - UX-restriction integration: video must blank above a speed threshold, and that logic lives in your app, not the codec
Every one ships to hardware if not found here. None requires accurate silicon. All are ~30 seconds to reproduce on an emulator and ~30 minutes on a bench rig.
10. CAN: AOSP does not handle it
There is no CAN stack in AOSP, no CAN API in the framework, and nothing in android.car exposing raw frames. Android deliberately does not know CAN exists. Apps see typed, permission-gated properties — PERF_VEHICLE_SPEED, GEAR_SELECTION, HVAC_TEMPERATURE_SET — and nothing below.
10.1 The boundary is the VHAL
App → CarPropertyManager → CarService → VHAL (HIDL/AIDL)
─────────── AOSP, public ───────────┤
│ ← the line
─────────── vendor, closed ─────────┤
translation layer → CAN / Ethernet / MCU IPC
Everything below the VHAL belongs to the OEM and Tier 1. AOSP's contribution is defining property semantics and units so apps are portable across vehicles.
10.2 Production topologies
Android rarely touches a bus directly:
- Separate VM owns the bus. On Snapdragon cockpit platforms, a hypervisor runs Android alongside a safety VM (frequently QNX) owning the vehicle interfaces. VHAL talks to it over inter-VM transport — virtio-vsock, shared memory, Gunyah/QVM channels. Android never sees a CAN controller.
- Companion MCU. A microcontroller handles CAN/LIN and speaks to the SoC over SPI/UART with proprietary framing. VHAL wraps that link.
- SocketCAN in the Android kernel. The kernel has a mature SocketCAN stack (
can0,PF_CANsockets,can-utils), so a vendor native daemon can open a socket and translate. Common on dev boards and lower-cost platforms; less often in shipping premium cockpits.
Increasingly this is Automotive Ethernet with SOME-IP rather than CAN for anything high-bandwidth, with CAN/CAN-FD retained for legacy and low-rate signals.
10.3 The translation layer
Signal definitions come from a DBC file (Vector's CAN database format): message ID, start bit, bit length, endianness, scale, offset, min/max, unit. The vendor daemon uses it to unpack frames into physical values and map them to VHAL property IDs. Much of this code is generated from the DBC rather than hand-written.
Two things that catch people:
- Mapping is rarely 1:1. One VHAL property may need several CAN signals fused, plus validity/quality bits, plus staleness timeouts. Conversely, one CAN message carries many properties.
- Direction is asymmetric. Reads are cheap; writes go through a gateway ECU that enforces policy. Android is treated as untrusted by the vehicle network — typically it cannot write powertrain or safety signals at all, and even HVAC writes are requests the climate ECU may reject.
10.4 Testing CAN without a bus
- Structure for testability. Make DBC-decode a pure, host-buildable library with no Android dependencies. Unit-test against recorded frames on the build machine — no emulator, no target, runs in CI in milliseconds. This is where most CAN bugs actually are.
- Virtual CAN on the Linux host:
Point a host-side build of your translation daemon atsudo ip link add dev vcan0 type vcansudo ip link set up vcan0canplayer -I recorded.log vcan0=can0
vcan0and verify it emits the right VHAL property updates. - Inject at the VHAL level in the emulator (
adb shell cmd car_service inject-vhal-event) for HMI work, where you don't care how the value arrived. - Record real traces early.
candump -lon a mule vehicle or bench rig gives replayable ground truth.
Tooling: cantools (Python, DBC-aware), SavvyCAN (free GUI), Vector CANoe/CANalyzer (industry standard, expensive, likely what your Tier 1 uses).
11. Test strategy
The failure mode to guard against is not using the emulator — it's misreading what a pass means. "Video played on the emulator" means the plumbing is connected. It says nothing about whether three concurrent 1080p streams fit the VPU budget on SA8155.
Make the tier explicit for every test:
| Tier | Where | What | Cadence |
|---|---|---|---|
| 1 | Emulator | Functional, structural, integration; API sequences, error handling, lifecycle, HMI layout, UX restrictions | CI on every commit |
| 2 | Snapdragon dev kit / bench rig | Performance, concurrency limits, secure video, tunneling, latency, thermal, power states, UBWC/stride correctness | Slower cadence; gates release |
Expect false alarms in the other direction too: a glitch that is really a host VAAPI driver quirk, not your code. When something looks wrong on the emulator and only there, check the host stack before spending a day on it.
The analogy: a flight simulator has a real cockpit, real switch positions, real checklists, real failure procedures — and approximated aerodynamics. Pilots train procedures in it. Nobody certifies an airframe in it. The emulator is the cockpit; your procedures (API sequences, error handling, lifecycle) are what you rehearse. The airframe still has to be tested for real.
12. Hardware plan
For an actual prototype vehicle you will eventually need a reference board — a Snapdragon automotive dev kit, an OEM-provided dev unit, or at minimum a bench rig with a real display and a CAN interface.
Usual pattern: emulator for daily iteration and CI, hardware for integration and UX validation.
If media is central to your prototype, this pulls dev-kit procurement earlier — it becomes a blocker rather than a nice-to-have, because secure video, concurrency budgets, tunneled playback, and latency targets cannot be signed off in the emulator.
13. Open items to confirm for your project
- Which Snapdragon part (SA8155P / SA8295P / other) — determines display pipe counts, UBWC generation, VPU budget
- Qualcomm / Tier 1 commercial relationship and QAAOS access — start now, months-long timeline
- Whether the vehicle interface is CAN, CAN-FD, or Automotive Ethernet/SOME-IP
- Hypervisor topology: which VM owns the bus, what inter-VM transport VHAL will use
- Available DBC files and recorded CAN traces
- Actual codec list on your built emulator image (
adb shell dumpsys media.codec) - Actual lunch targets available on your AOSP branch