Architecture
Layering
Changeling mirrors the layering used by aasdk (the open-source library that implements the head unit side of this same protocol), just from the opposite end:
UsbAccessoryConnection (AOAP bulk pipe as InputStream/OutputStream)
|
UsbBulkTransport (frame header + size, fragmentation, encryption hook)
|
AndroidAutoSession (routes messages to the right channel handler)
|
+---------+------------------+
| | |
ControlChannel MediaAudioChannel (more channels later: input, video, ...)
|
TlsServerSession (bridges an SSLEngine through SSL_HANDSHAKE control messages)
Key packages
net.spitzertech.changeling.usb—UsbAccessoryConnection: permission handling and turning an attachedUsbAccessoryinto streams. This is the only part that talks to Android's USB APIs directly.net.spitzertech.changeling.transport— the wire-format layer:FrameHeader,FrameSize,ChannelId,UsbBulkTransport, andAndroidAutoSession(the orchestrator).net.spitzertech.changeling.transport.crypto—PhoneIdentity(self-signed cert via AndroidKeyStore) andTlsServerSession(drives the TLS handshake through memory buffers instead of a real socket).net.spitzertech.changeling.transport.channels— per-channel protocol handlers (currently justMediaAudioChannel).net.spitzertech.changeling.media—AudioFileDecoder, standard Android decode APIs, nothing Android-Auto-specific.
The frame format
Every message is wrapped in a small frame:
[2-byte header: channel id + flags][size field][payload]
- The header's flag byte encodes frame type (FIRST/MIDDLE/LAST/BULK — BULK meaning "fits in one frame"), message type (CONTROL vs SPECIFIC), and encryption (PLAIN vs ENCRYPTED).
- The size field is 2 bytes (
Short) normally, or 6 bytes (Extended: frame size + total message size) on the first frame of a message — including BULK frames, which are treated as "first and only." - Messages bigger than aasdk's 0x4000-byte max frame payload are split FIRST → MIDDLE* → LAST and reassembled on the other end.
- The payload itself is prefixed with a 2-byte big-endian message ID (e.g.
VERSION_REQUEST = 0x0001), followed by the message body — raw bytes for a few handshake messages (version negotiation), protobuf for everything else.
TLS handshake direction (the part most likely to surprise you)
The head unit is the TLS client; the phone is the TLS server. This is confirmed from aasdk's source (Cryptor::init() calls setConnectState, i.e. aasdk — playing the head-unit role — is the client) and matches Google's leaked Head Unit Integration Guide. TlsServerSession drives a server-mode SSLEngine, bridging handshake bytes through SSL_HANDSHAKE control-channel messages instead of a real Socket.
Message order after the AOAP/USB session is open:
- Head unit sends
VERSION_REQUEST→ phone repliesVERSION_RESPONSE. - Head unit drives a TLS handshake as the client; phone responds as the server (
SSL_HANDSHAKEmessages, unencrypted framing even though the payload is TLS handshake data). - Once the handshake completes, phone sends
AUTH_COMPLETE(now ENCRYPTED framing) followed bySERVICE_DISCOVERY_REQUEST(the phone announcing itself —device_name/device_brand). - Head unit replies
SERVICE_DISCOVERY_RESPONSEwith its channel list and car/head-unit metadata. - Phone sends
CHANNEL_OPEN_REQUESTfor whichever channels it wants (e.g.MEDIA_AUDIO); head unit repliesCHANNEL_OPEN_RESPONSE. - From there, per-channel protocols take over (e.g.
MediaAudioChannel's own SETUP_REQUEST/START_INDICATION/STOP_INDICATION).
Licensing note
The protobuf schema (app/src/main/proto/) is vendored from OpenDsh/aasdk's aasdk_proto directory, which is GPLv3-licensed. This is a deliberate choice — using it made the project GPLv3 as a whole rather than writing an independent, permissively-licensed schema from scratch, in exchange for moving faster. See app/src/main/proto/NOTICE.md in the repo.
What's deliberately not implemented
- The raw AOAP USB control-transfer handshake (
GET_PROTOCOL/SEND_STRING/START) — this is handled entirely by Android's kernelf_accessorygadget driver before any app code runs. Confirmed via research into AOSP source and the kernel driver itself.