android-auto 0.3.4

A crate for implementing the android auto protocol.
Documentation

android-auto

Crates.io docs.rs License: LGPL-3.0-or-later

A Rust implementation of the Android Auto protocol — both a reusable library and a working head unit application. Connect an Android phone to a Linux host and get a full Android Auto experience, including wireless connection via Bluetooth.


Table of Contents


What is this?

android-auto is two things in one repository:

  1. A library crate (android-auto on crates.io) that implements the Android Auto wire protocol. You can embed it in your own Rust application to build a custom head unit.
  2. A head unit example application (examples/main/) that demonstrates the library in action, allowing a Linux machine to act as an Android Auto head unit for a connected phone.

How it works

Android Auto uses a binary framing protocol over a TLS-secured transport. The phone (acting as the server) and the head unit (acting as the client) exchange protobuf-encoded messages. At a high level:

  1. Transport layer — wired USB (via the usb feature) or wireless (Wi-Fi + Bluetooth for handoff, via the wireless feature). Bluetooth is used to advertise the head unit and negotiate the wireless connection; actual media data flows over Wi-Fi.
  2. TLS handshake — the head unit authenticates using an X.509 client certificate. This library handles the full TLS negotiation via rustls.
  3. Frame layer — messages may be split across multiple frames (FrameHeaderType). The library reassembles them transparently.
  4. Channel layer — Android Auto multiplexes multiple logical channels over a single connection (video, audio, input, sensors, etc.). Each channel has its own message types, encoded with protobuf.

The library exposes this through a clean async trait interface so that integrators only need to handle the application-level events they care about.


Features

  • Wired USB Android Auto connections (enable with the usb feature)
  • Wireless (Bluetooth-initiated) Android Auto connections (enable with the wireless feature)
  • Full TLS client support via rustls
  • Protobuf message encoding/decoding for all standard Android Auto channels
  • Async-first API built on tokio
  • Configurable head unit identity (HeadUnitInfo, AndroidAutoConfiguration)
  • Video channel trait (AndroidAutoVideoChannelTrait) for custom video rendering backends
  • Audio output and input channel traits (AndroidAutoAudioOutputTrait, AndroidAutoAudioInputTrait)
  • Input channel trait (AndroidAutoInputChannelTrait) for touchscreen and keycode support
  • Sensor channel trait (AndroidAutoSensorTrait) for reporting sensor data to the phone
  • Navigation channel trait (AndroidAutoNavigationTrait) for receiving turn-by-turn updates

Prerequisites

  • Rust (stable, recent version recommended — see rust-version in Cargo.toml)
  • Tokio async runtime (pulled in automatically as a dependency)
  • For wired USB support: enable the usb feature (uses the nusb crate)
  • For wireless support: enable the wireless feature (uses the bluetooth-rust crate) and ensure a system Bluetooth stack is accessible
  • A phone running Android with the Android Auto app installed

Building

Clone the repository and build with Cargo:

git clone https://github.com/uglyoldbob/android-auto.git
cd android-auto
cargo build --release

To enable wired USB support:

cargo build --release --features usb

To enable wireless (Bluetooth-initiated) Android Auto support:

cargo build --release --features wireless

To enable both:

cargo build --release --features usb,wireless

Running the head unit

The head unit is provided as an example application. Run it with:

cargo run --example main --release

Note: the example uses several dev-dependencies (e.g. eframe, openh264, cpal). Make sure all system libraries they require are installed.

Wired connection: plug your phone in via USB and build with the usb feature. The head unit will detect the connection and initiate the Android Auto handshake automatically.

Wireless connection: build with the wireless feature and ensure Bluetooth is enabled on both your host machine and phone. The library starts a Bluetooth server that advertises the head unit; the phone will discover it and negotiate a Wi-Fi session.


Using the library

Add android-auto to your Cargo.toml:

[dependencies]
android-auto = "0.3.3"

# For wired (USB) support:
android-auto = { version = "0.3.3", features = ["usb"] }

# For wireless (Bluetooth-initiated) support:
android-auto = { version = "0.3.3", features = ["wireless"] }

# For both:
android-auto = { version = "0.3.3", features = ["usb", "wireless"] }

Initialization

Call android_auto::setup() once at program startup before doing anything else. It installs the TLS crypto provider and returns an AndroidAutoSetup token that must be passed to run() (and related methods). Requiring this token at the call site is a compile-time guarantee that initialisation is never accidentally skipped:

fn main() {
    let setup = android_auto::setup();
    // pass `setup` to run() later …
}

Minimal example

use android_auto::{
    AndroidAutoConfiguration, AndroidAutoMainTrait, AndroidAutoVideoChannelTrait,
    AndroidAutoAudioOutputTrait, AndroidAutoAudioInputTrait, AndroidAutoInputChannelTrait,
    AndroidAutoSensorTrait, HeadUnitInfo, VideoConfiguration, InputConfiguration,
    SensorInformation, AudioChannelType, SendableAndroidAutoMessage,
};

struct MyHeadUnit;

#[async_trait::async_trait]
impl AndroidAutoSensorTrait for MyHeadUnit {
    fn get_supported_sensors(&self) -> &SensorInformation { todo!() }
    async fn start_sensor(&self, _: android_auto::Wifi::sensor_type::Enum) -> Result<(), ()> { Ok(()) }
}

#[async_trait::async_trait]
impl AndroidAutoAudioOutputTrait for MyHeadUnit {
    async fn open_output_channel(&self, _: AudioChannelType) -> Result<(), ()> { Ok(()) }
    async fn close_output_channel(&self, _: AudioChannelType) -> Result<(), ()> { Ok(()) }
    async fn receive_output_audio(&self, _: AudioChannelType, _: Vec<u8>) {}
    async fn start_output_audio(&self, _: AudioChannelType) {}
    async fn stop_output_audio(&self, _: AudioChannelType) {}
}

#[async_trait::async_trait]
impl AndroidAutoAudioInputTrait for MyHeadUnit {
    async fn open_input_channel(&self) -> Result<(), ()> { Ok(()) }
    async fn close_input_channel(&self) -> Result<(), ()> { Ok(()) }
    async fn start_input_audio(&self) {}
    async fn stop_input_audio(&self) {}
    async fn audio_input_ack(&self, _: u8, _: android_auto::Wifi::AVMediaAckIndication) {}
}

#[async_trait::async_trait]
impl AndroidAutoInputChannelTrait for MyHeadUnit {
    async fn binding_request(&self, _: u32) -> Result<(), ()> { Ok(()) }
    fn retrieve_input_configuration(&self) -> &InputConfiguration { todo!() }
}

#[async_trait::async_trait]
impl AndroidAutoVideoChannelTrait for MyHeadUnit {
    async fn receive_video(&self, _data: Vec<u8>, _timestamp: Option<u64>) {}
    async fn setup_video(&self) -> Result<(), ()> { Ok(()) }
    async fn teardown_video(&self) {}
    async fn wait_for_focus(&self) {}
    async fn set_focus(&self, _focus: bool) {}
    fn retrieve_video_configuration(&self) -> &VideoConfiguration { todo!() }
}

#[async_trait::async_trait]
impl AndroidAutoMainTrait for MyHeadUnit {
    async fn connect(&self) {}
    async fn disconnect(&self) {}
    async fn get_receiver(&self) -> Option<tokio::sync::mpsc::Receiver<SendableAndroidAutoMessage>> {
        None
    }
}

#[tokio::main]
async fn main() {
    let setup = android_auto::setup();

    let config = AndroidAutoConfiguration {
        unit: HeadUnitInfo {
            name: "My Head Unit".to_string(),
            car_model: "My Car".to_string(),
            car_year: "2024".to_string(),
            car_serial: "000000".to_string(),
            left_hand: true,
            head_manufacturer: "ACME".to_string(),
            head_model: "HU-1".to_string(),
            sw_build: "1".to_string(),
            sw_version: "1.0".to_string(),
            native_media: false,
            hide_clock: None,
        },
        custom_certificate: None,
    };

    let mut js = tokio::task::JoinSet::new();
    let _ = Box::new(MyHeadUnit).run(config, &mut js, &setup).await;
}

See the docs.rs documentation and the examples/main/ directory for a complete, working reference implementation.


Architecture

android-auto/
├── src/
│   ├── lib.rs          # Library entry point and protocol logic
│   ├── control.rs      # Control channel handler
│   ├── video.rs        # Video channel handler
│   ├── mediaaudio.rs   # Media audio channel handler
│   ├── speechaudio.rs  # Speech audio channel handler
│   ├── sysaudio.rs     # System audio channel handler
│   ├── avinput.rs      # AV input channel handler
│   ├── input.rs        # Input channel handler
│   ├── sensor.rs       # Sensor channel handler
│   ├── navigation.rs   # Navigation channel handler
│   ├── mediastatus.rs  # Media status channel handler
│   ├── bluetooth.rs    # Bluetooth channel handler
│   ├── common.rs       # Shared utilities
│   ├── cert.rs         # Built-in TLS certificate
│   └── usb.rs          # USB transport (usb feature)
├── examples/
│   └── main/           # Full head unit example application
│       └── main.rs
├── protobuf/           # Protobuf definitions (Bluetooth.proto, Wifi.proto)
└── Cargo.toml

Key types

Type Role
AndroidAutoSetup Proof-of-initialisation token returned by setup(); must be passed to run() and related methods — ensures initialisation is never skipped
AndroidAutoConfiguration Top-level configuration for the head unit (unit: HeadUnitInfo, optional custom certificate)
HeadUnitInfo Static identity information sent to the phone during handshake
BluetoothInformation Bluetooth adapter MAC address used for wireless negotiation
NetworkInformation Wi-Fi network details relayed to the phone for the wireless session
SensorInformation Set of sensor types the head unit reports to the phone
VideoConfiguration Desired video resolution, FPS, and display DPI
InputConfiguration Supported keycodes and optional touchscreen dimensions
AudioChannelType Discriminates between Media, System, and Speech audio channels
AndroidAutoMessage Enum of all message types that can be received over the link
SendableAndroidAutoMessage Wire-ready message sent from the application back to the phone
SendableChannelType Identifies which channel a SendableAndroidAutoMessage targets
FrameHeaderType Whether a packet fits in a single frame or is fragmented (Single, First, Middle, Last)

Key traits

Trait Purpose
AndroidAutoMainTrait Core trait — implement to handle connect/disconnect and provide the message sender; requires all channel traits below
AndroidAutoVideoChannelTrait Receive and render H.264 video frames from the phone
AndroidAutoAudioOutputTrait Receive and play audio for media, system, and speech channels
AndroidAutoAudioInputTrait Capture and stream microphone audio to the phone
AndroidAutoInputChannelTrait Handle touch and keycode input binding
AndroidAutoSensorTrait Report sensor data (e.g. night mode, driving status) to the phone
AndroidAutoNavigationTrait Receive turn-by-turn navigation events from the phone
AndroidAutoWiredTrait Marker trait indicating the implementation supports USB connections (usb feature)
AndroidAutoWirelessTrait Bluetooth + Wi-Fi negotiation for wireless connections (wireless feature)
AndroidAutoBluetoothTrait Low-level Bluetooth adapter configuration

Dependencies

The library relies on:

  • tokio — async runtime
  • rustls — TLS 1.2/1.3 for the secure channel
  • aws-lc-rs — cryptographic backend used by rustls
  • protobuf — message encoding/decoding
  • async-trait — async trait support
  • futures — async combinators
  • serde — serialization for message types
  • log — structured logging
  • nusb (optional, usb feature) — USB device access
  • bluetooth-rust (optional, wireless feature) — Bluetooth RFCOMM for wireless handoff

Contributing

Contributions are welcome! A few guidelines:

  1. Fork the repository and create a feature branch.
  2. Run the tests before submitting: cargo test
  3. Keep it async — the library is built around Tokio; new I/O code should follow the same pattern.
  4. Protobuf changes — if you modify .proto files under protobuf/, regenerate the Rust bindings with protobuf-codegen before committing.
  5. Open a pull request with a clear description of what you changed and why.

If you find a bug or want to request a feature, please open an issue.


License

Licensed under the GNU Lesser General Public License v3.0 or later. See LICENSE for the full text.

This means you can use this library in your own applications — including proprietary or closed-source ones — without being required to release your application's source code. The library itself must remain LGPL.