An open-source implementation of the Android Auto phone-side app. This app runs on your phone and projects to a car's head unit over USB, replacing Google's proprietary com.google.android.projection.gearhead APK.
This project is in early development. The protocol handshake and video projection are working with a real head unit. The phone screen is successfully displayed on the car's head unit for several seconds before disconnecting (video stability is being improved).
- USB AOA accessory mode detection and connection
- TLS 1.2 mutual authentication (phone as server)
- Version negotiation (protocol v1.7)
- Service discovery (request/response)
- Channel open on target channels (video, audio, input, sensor)
- Ping/pong keepalive (bidirectional)
- Audio focus request/response handling
- Navigation focus request/response handling
- Voice session request handling
- Graceful shutdown handling
- Priority write queue (control messages over video)
- Wait for audio focus grant before sending audio (500ms timeout per HUIG)
- Bluetooth pairing exchange (BluetoothPairingRequest/Response)
- Handle multiple USB reconnections without AOAP re-init
- H.264 encoding via MediaCodec (800x480 @ 30fps, Baseline profile)
- MediaProjection screen capture (with user permission dialog)
- Video channel setup (SETUP → CONFIG → FOCUS → START flow)
- Zero-based timestamps in microseconds
- SPS/PPS prepended to keyframes (Annex B format)
- Flow control (max_unacked tracking, backpressure)
- Frame pacing (consistent 33ms intervals)
- Stable long-running video (currently disconnects after ~7 seconds)
- Resolution negotiation from head unit's service discovery
- Adaptive bitrate based on connection quality
- Input channel open and binding request
- Touch event parsing (single and multi-touch)
- Key event parsing (buttons, media keys)
- Coordinate mapping (head unit → phone resolution)
- TouchInjector with MotionEvent creation
- Inject touch events into VirtualDisplay
- Key event injection into Android system
- Audio channel open and setup
- Wait for audio focus grant before sending audio (500ms timeout per HUIG)
- Send AUDIO_FOCUS RELEASE on initial connect, then GAIN when playing
- Phone audio capture (MediaProjection AudioPlaybackCapture)
- PCM/AAC encoding and streaming to head unit
- Microphone input from head unit (voice commands)
- Multiple audio channels (media, system, speech, guidance)
- Audio silence streaming to keep channel active
- Sensor channel open
- Sensor start request/response handling
- Night mode data parsing and sending
- Driving status data parsing and sending
- GPS location forwarding
- Compass heading
- Car speed
- RPM
- Odometer (total + trip mileage)
- Fuel level and range
- Parking brake state
- Gear position (P/R/N/D/1-10)
- OBD-II diagnostics
- Environment (temperature, pressure, rain)
- HVAC (target/current temperature)
- Dead reckoning
- Passenger presence
- Door state (hood, boot, individual doors)
- Light state (headlights, indicators, hazards)
- Tire pressure
- Accelerometer (3-axis)
- Gyroscope (3-axis)
- GPS satellite data
- Respond to head unit sensor requests proactively
- Resolution negotiation from head unit's service discovery
- Adaptive bitrate based on connection quality
- Support for 720p, 1080p, 1440p, 4K resolutions
- Portrait mode resolutions (720x1280, 1080x1920, etc.)
- UI config updates (theme, insets)
- Bluetooth pairing coordination (A2DP, HFP)
- Navigation turn-by-turn to instrument cluster
- Navigation state (maneuvers, lanes, distances, current position)
- Media status (now playing info)
- Media playback metadata (track, artist, album)
- Media browser (browse phone media library from head unit)
- Phone status (call state notifications)
- Generic notifications (subscribe/unsubscribe system)
- Vendor extensions
- Wireless Android Auto (WiFi + Bluetooth handoff)
- Channel close notification
- Car connected devices request/response
- User switch request/response
- Battery status notification
- Call availability status
- Service discovery update (dynamic channel changes)
- Input feedback (haptic/visual feedback to head unit)
- Microphone request/response (voice input from head unit)
- Audio underflow notification
- Radio service (AM/FM/HD/DAB tuning, presets, RDS)
USE AT YOUR OWN RISK. This software is provided "as is", without warranty of any kind.
- This software may cause unexpected behaviour with your car's head unit
- This software may damage your phone or head unit — the authors accept no liability
- DO NOT use this application while driving
- DO NOT interact with this application while operating a vehicle
- This application is intended for development and testing purposes only
- Always pull over and stop your vehicle before interacting with any phone application
- The authors are not responsible for any accidents, injuries, or damages resulting from the use of this software
USB Plug-in → MainActivity → ProjectionService
↓
UsbAoaTransport (USB AOA accessory mode)
↓
MessageFramer (16KB frame fragmentation)
↓
InBandTls (TLSv1.2 via SSLEngine)
↓
ProtocolEngine (AAP state machine)
↓
┌───────────┼───────────┐
Video Input Audio
(H.264) (touch/keys) (PCM)
- Channel assignment assumes order — We assign the first
av_channelin SERVICE_DISCOVERY_RESPONSE as video and the second as audio. This works with the car head unit (channel 1 = video) but fails with openauto (channel 4 = audio, not video). Fix: parse thestream_typefield insideav_channelto distinguishVIDEO(3)fromAUDIO(1). - Video stability — Connection drops after extended streaming due to head unit USB buffer overflow. See test results below.
- Duplicate device listing on head unit — The head unit's smartphone page shows our app as two separate entries (one for Android Auto, one for Bluetooth) instead of a single entry with both capabilities. This is caused by Android 12+ blocking access to the real Bluetooth MAC address (returns
02:00:00:00:00:00). Workaround: write the real address to a config file viaadb shell "echo $(adb shell settings get secure bluetooth_address) > /sdcard/Android/data/org.openandroidauto/files/bt_address.txt". Need a UI settings screen to let the user enter their BT MAC manually. - Voice assistant button not handled — When the driver presses the voice/assistant button on the head unit, we receive a VOICE_SESSION_REQUEST and attempt to launch a voice assistant (Dicio or system default). However, the launched assistant doesn't receive audio from the head unit's microphone yet.
Test pattern (color bars) at 800x480, I-frame interval 1 second:
| FPS | Bitrate | Fragment | Duration | Frames | Status |
|---|---|---|---|---|---|
| 30 | 2Mbps | No | ~3s | ~90 | ❌ Too fast |
| 15 | 2Mbps | No | ~33s | ~500 | |
| 10 | 2Mbps | No | ~93s | ~930 | |
| 30 | 2Mbps | Yes (2KB) | 5-25s | 150-750 | |
| 30 | 500Kbps | Yes (2KB) | ~54s | ~1691 | |
| 15 | 250Kbps | Yes (2KB) | ~67s+ | 1000+ | |
| 30 | 250Kbps | Yes (2KB), I=5s | ~20s | ~600 | ❌ Worse with long I-frame |
| 15 | 250Kbps | No | ~13s | ~200 | ❌ Fragmentation helped here |
Root cause: Head unit USB receive buffer overflows with sustained high throughput. Lower data rate = longer connection.
Best confirmed config: 10fps, 2Mbps, no fragmentation = 93 seconds. Fragment-before-encrypt implementation is broken (head unit can't reassemble) — needs further investigation.
./gradlew assembleDebugRequires Android SDK with platform 35.
./gradlew testDebugUnitTest113 unit and integration tests covering protocol, framing, TLS, channel logic, video state machine, sensor handling, and touch input.
openauto is a third-party head unit emulator that speaks the full Android Auto protocol. We use it to verify our protocol implementation without needing a real car.
- Docker installed and running
- Phone connected via ADB (USB or wireless)
- App installed on phone:
./gradlew assembleDebug && adb install -r app/build/outputs/apk/debug/app-debug.apk
cd thirdparty/openauto
docker build -f Dockerfile.headless -t openauto-headless .This builds openauto with all dependencies (Qt5, boost, protobuf, OpenSSL) in a Debian container. Takes ~5 minutes on first build.
docker run --rm -p 5100:5000 -e QT_QPA_PLATFORM=offscreen \
openauto-headless timeout 60 /src/build/bin/autoappopenauto listens on port 5000 inside the container, mapped to port 5100 on the host. It runs in headless mode (no display needed).
adb reverse tcp:5000 tcp:5100This makes the phone's localhost:5000 tunnel to the computer's localhost:5100 (openauto). Our app connects to localhost:5000 as a TCP client when no USB accessory is found.
adb shell am start -n org.openandroidauto/.MainActivityThe app will:
- Fail to find a USB accessory
- Connect to
localhost:5000(openauto via adb reverse) - Perform the full protocol handshake (VERSION → TLS → AUTH → SERVICE_DISCOVERY)
- Open channels (video, audio, input, sensor)
- Start streaming video (test pattern)
You should see in the openauto output:
[OpenAuto] handleNewClient() - Handle WIFI Client Connection
[OpenAuto] [AndroidAutoEntity] Send Version Request.
[OpenAuto] [AndroidAutoEntity] onVersionResponse()
[OpenAuto] [AndroidAutoEntity] Beginning SSL handshake.
[OpenAuto] [AndroidAutoEntity] Handshake completed.
[OpenAuto] [AndroidAutoEntity] onServiceDiscoveryRequest()
[OpenAuto] [AndroidAutoEntity] onAudioFocusRequest()
[OpenAuto] [AudioMediaSinkService] onChannelOpenRequest()
[OpenAuto] [VideoMediaSinkService] onChannelOpenRequest() (if video focus granted)
adb pull /sdcard/Android/data/org.openandroidauto/files/aa_log.txt
cat aa_log.txtThe log file persists on the phone between USB switches (useful when testing with a real car head unit).
# Assumes openauto image already built and app installed
docker run --rm -p 5100:5000 -e QT_QPA_PLATFORM=offscreen openauto-headless timeout 20 /src/build/bin/autoapp &
sleep 3 && adb reverse tcp:5000 tcp:5100 && adb shell am start -n org.openandroidauto/.MainActivity- openauto uses protocol v1.6; our app responds with v1.7 (both are accepted)
- openauto logs
Message Id not Handled: 4for AUTH_COMPLETE — this is a known openauto quirk, not an error - The connection should remain stable indefinitely (no timeout/disconnect)
- Video is not displayed (headless mode) but the protocol exchange is fully validated
- uglyoldbob/android-auto (Rust, LGPL-3.0) — protocol implementation with protobuf definitions
- opencardev/aasdk (C++, GPL-3.0) — updated protocol library with full protobuf definitions
- opencardev/openauto (C++, GPL-3.0) — head-unit emulator (Crankshaft-NG)
- tomasz-grobelny/AACS (C++, GPL-3.0) — phone-side AA implementation for ODROID
- headunit-revived (Kotlin, AGPL-3.0) — head-unit side implementation
- f1xpl/aasdk (C++, GPL-3.0) — original protocol library
- GAL protocol research — protocol notes, Wireshark dissector, and cached Head Unit Integration Guide
The Android Auto protocol was reverse-engineered across several projects. Each built on the previous:
| Project | Year | Proto Files | Role |
|---|---|---|---|
| f1xpl/aasdk | 2018 | 1 monolithic (Wifi.proto) |
Original RE — core protocol, video, audio, input, sensors |
| AACS | 2020 | 28 (split by message) | Phone-side impl — minimal coverage for video projection |
| opencardev/aasdk | 2024 | 254 (hierarchical by service) | Definitive reference — full protocol with all services |
What opencardev/aasdk adds over AACS:
- Radio service (AM/FM/HD/DAB tuning, presets, RDS, traffic)
- Navigation status (full turn-by-turn: maneuvers, lanes, distances, cues)
- Phone status (call state notifications)
- Media browser (browse phone media library from head unit)
- Media playback status (now playing metadata)
- Generic notifications (subscribe/unsubscribe system)
- WiFi projection (wireless AA credentials and access point config)
- GAL verification (Google Automotive Link testing/debugging)
- Instrument cluster (secondary display input)
- UI config (theme day/night, insets, display configuration)
- Battery status, user switch, toll card, EV connector types
- Service discovery update (dynamic channel changes)
- Extended video resolutions (1440p, portrait variants)
- Extended control messages (26 types vs 13)
Key structural differences:
- AACS uses
priority+channel_idin ChannelOpenRequest; aasdk usespriority(sint32) +service_id - AACS ServiceDiscoveryResponse is just a channel list; aasdk adds HeadUnitInfo, DriverPosition, PingConfiguration, ConnectionConfiguration
- aasdk separates media into sink (head unit receives) and source (head unit sends) with distinct message IDs
The thirdparty/aasdk/protobuf/ directory is the authoritative protocol reference for this project.
Android Auto uses mutual TLS. The phone acts as the TLS server and must present a certificate signed by the Google Automotive Link CA (baked into head unit firmware). Without the correct private key, the head unit rejects the connection with AUTH_COMPLETE status=-3.
The phone presents a 2-cert chain:
- CarService cert —
O=CarService, signed by the Google Automotive Link CA - Google Automotive Link CA — self-signed root,
O=Google Automotive Link(valid 2014-2044)
- The head unit has the Google Automotive Link CA public key baked into its firmware and trusts it
- During TLS, the phone presents the CarService cert (which is signed by that CA)
- The phone proves ownership of the cert by signing the TLS handshake with the corresponding private key
- The head unit verifies the signature matches the cert's public key, and that the cert chains back to the trusted CA
Google appears to rotate the cert+key embedded in the Android Auto APK approximately every 8 months (matching the cert's validity period). This may be a deliberate measure to limit the usefulness of extracted keys — if a head unit checks certificate expiry, an old extracted key would stop working. Users of the official app receive fresh certs via app updates. If this theory is correct, a user who never updates the official app could eventually be rejected by head units that enforce expiry. Not all head units may check expiry — this behaviour is model-dependent.
The private key is AES-256-CBC encrypted inside the Android Auto APK. The head unit validates the phone's cert against the Google Automotive Link CA — any cert signed by that CA is accepted.
The key is embedded (encrypted) in the Android Auto APK and can be decrypted using the APK's own algorithm. This requires any Android device with ADB access (no root, no Google Play Services needed) to run the decryption, because Android's Base64 decoder behaves differently from the desktop JVM.
Requirements:
- The Android Auto APK (pull from a phone with
adb pull, or download from APKPure/APKMirror) - Any Android device with ADB access to run the decryption (no root, no Google Play Services needed)
- Android SDK (
d8build tool,adb)
Process:
- Pull the AA APK from a phone:
adb pull $(adb shell pm path com.google.android.projection.gearhead | grep base | cut -d: -f2) aa.apk - Decompile with JADX to find the cert provider class
- Extract the binary data (256-byte KDF salt + ~1712-byte encrypted key + cert PEMs)
- Compile the decryption Java class to DEX and run on-device with
dalvikvm
Finding the cert provider class (step 2):
The class names are obfuscated and change between APK versions, but the structure is always the same. Search JADX for "-----BEGIN CERTIFICATE-----" — you'll find a small class implementing an interface with three methods:
a()→ returns aString(the CarService cert PEM)b()→ returns abyte[](~1712 bytes — the AES-encrypted private key)c()→ returns abyte[](256 bytes — the KDF salt)
Known class names by version:
| APK Version | Cert provider class | Salt+key class | Decryption class |
|---|---|---|---|
| v6.4 | SslWrapper (fields o, p) |
same class | SslWrapper.m23915f() |
| v16.8 | ivo / rqi |
ivq / rql (fields b, c) |
ivq.d() |
The decryption function is in a nearby class — search for "AES/CBC/PKCS5Padding" to find it. It takes the cert provider interface as a parameter.
Note: The decryption step (step 4) only needs dalvikvm — any Android device with ADB works, no root or Google Play Services required. The GApps requirement is only for step 1 (pulling the APK, since the AA app is distributed via Play Store).
Note: You don't modify or run the decompiled APK code. Instead, you write a standalone Decrypt.java class that reimplements the decryption logic, reads the extracted byte arrays from files, and has its own main() entry point. The decompiled source is only used as a reference to understand the algorithm and copy out the byte arrays. See tools/decrypt_key_from_apk.md for the complete Decrypt.java source.
Critical JADX bug: JADX decompiles the KDF helper as byte b = bArr2[i2] & 255; but it must be int b = bArr2[i2] & 255;. The byte type truncates back to signed, producing garbage output. Fix this to int and the decryption works.
The KDF (tweakBytes/ap) function:
static void tweakBytes(byte[] bArr, byte[] bArr2, byte[] bArr3) {
for (int i = 0; i < bArr.length; i++) {
for (int i2 = 0; i2 < 48; i2++) {
int b = bArr2[i2] & 255; // MUST be int, not byte
bArr2[i2] = (byte) (((((b >> 7) | (b + b)) + 33) ^ bArr3[i2 % bArr3.length]) ^ bArr[i]);
}
}
}After AES decryption, the T() function extracts the key:
- Skip first 28 bytes, trim last 26 bytes
- Base64 decode (URL_SAFE, flag=2) the middle portion
- Result is PKCS#8 DER encoded RSA private key
Note: Must run on Android (not desktop JVM) due to android.util.Base64 vs java.util.Base64 differences. The desktop JVM's Base64.getUrlDecoder() rejects standard base64 characters (+, /) and newlines that Android's decoder accepts. Use Base64.getMimeDecoder() on desktop, or run the decryption on-device with dalvikvm:
# Compile to DEX and run on any Android device with ADB access
javac Decrypt.java -d out
d8 out/Decrypt.class --output dex_out
adb push dex_out/classes.dex /data/local/tmp/decrypt.dex
adb shell "dalvikvm -cp /data/local/tmp/decrypt.dex Decrypt"This outputs the PKCS#8 private key in base64. Wrap it in PEM headers and place at app/src/main/assets/carservice_key.pem.
See tools/decrypt_key_from_apk.md for the full step-by-step guide.
Since some head units may not check certificate expiry, a previously extracted cert+key pair (even expired) may still work. Sources:
- Contact opengal_proxy author — email
som@marekkraus.sk(see gamelaster/opengal_proxy) - Extract from a rooted phone with GApps — use Frida to hook
KeyFactory.generatePrivate()(requires both root AND Google Play Services on the same device):frida -U -n "com.google.android.projection.gearhead" -l tools/dump_key_frida.js - Community — see AACS#15 for discussion
Once obtained, place the cert+key at app/src/main/assets/carservice_key.pem.
See tools/dump_key.sh and tools/dump_key_frida.js for runtime extraction scripts.
This project is licensed under the GNU General Public License v3.0.
This project includes protocol buffer definitions from aasdk (GPLv3, Copyright © 2018 f1x.studio / Michal Szwaj) as a git submodule.