A small Android library for putting your app's video on a car screen — Android Auto and cars with Google built-in — through the official Android for Cars App Library.
You implement one interface (CarCanvasSink) and get a Surface, its size, and the area you may
draw in. The library owns the car-side plumbing: service, session, screen, surface lifecycle,
visible/stable areas, drive-state handoff.
public class MyCarService extends CarCanvasService {
@NonNull @Override
protected CarCanvasSink createSink(@NonNull CarContext ctx) {
return new ExoPlayerSink(ctx, "https://example.com/stream.m3u8");
}
}Plus a manifest entry (see sample/src/main/AndroidManifest.xml).
That is the whole integration.
An app installed any way other than from Google Play will not appear on the car screen. Android
Auto checks the installing package, and apps built with the car app library are exempted from the
"unknown sources" developer toggle. A debug build over adb, an APK from a release page, an F-Droid
install — the host ignores all of them. So:
- Developing: use the Desktop Head Unit, which works with a local debug build.
- Shipping: publish through your own Play listing. Production, closed or internal testing tracks
all give the package
com.android.vendingprovenance. Check withadb shell pm get-install-source <your.package>.
This is a property of the platform, not of this library, and nothing in this repo can work around it. It also means each integrator publishes under their own developer account and is responsible for what their app does on the car screen.
The <category> in your service's intent filter decides both where the host lists the app and
whether it may draw at all.
| Category | Draws a surface | Notes |
|---|---|---|
androidx.car.app.category.NAVIGATION |
yes | What the sample uses. Widest availability today. |
androidx.car.app.category.POI |
yes | Same surface access, different placement. |
| Media template category | no | Templates only — no surface. |
As of September 2026 a dedicated video category exists on cars with Google built-in (declare
android:appCategory="video"), and Google has announced the same for Android Auto "later in 2026",
needing Android 17+ on the phone and a compatible car. When that lands, prefer it: it is the
supported route for video and carries none of the ambiguity of drawing video on a navigation
surface. Until then, and for older phones and cars afterwards, navigation or POI is the only way.
Whichever you pick, your app must still satisfy Google's car app quality guidelines for
that category, and playing video while the car moves is what gets apps and accounts removed. Source
a drive state and pass it to CarCanvasScreen.setDriveState(parked); the library forwards it to
your sink and the sample stops playback. The library deliberately does not read car hardware
itself — that needs a permission you must declare and justify, and "may play" is a policy decision
that belongs to whoever ships the app.
A sink is not tied to ExoPlayer. If your app already encodes or decodes video — a browser casting a
tab, a mirroring app — take the Surface from onSurfaceAvailable and render into it directly
(MediaCodec.configure(..., surface, ...), an ImageReader, GL, whatever you have). Media3 is a
dependency of :sample only; :carcanvas depends on nothing but androidx.car.app.
For fitting content of a different aspect into the head unit's usable area, and for mapping car
touches back to content coordinates, see CarCanvasGeometry.
carcanvas/ the library — CarCanvasService, CarCanvasScreen, CarCanvasSink, CarCanvasGeometry
aidl/ the IPC contract, for clients that talk to the bridge (no other dependencies)
bridge/ the bridge app: owns the car display, serves a client app over AIDL
sample/ a minimal app that plays one stream on the car display
The host ignores any car app not installed from Play (see above). That is fatal for an app you distribute yourself — a browser, a media app with its own store, anything sideloaded.
bridge/ solves it by splitting the problem: the bridge is a tiny app you publish on Play, and it
is the only thing the car ever talks to. Your app binds it over AIDL and supplies the content, and can
be distributed any way you like.
Two modes, because neither covers everything:
URL mode (loadMedia) |
Surface mode (requestSurface) |
|
|---|---|---|
| Your app supplies | a stream URL + headers | frames |
| Who renders | the bridge's ExoPlayer | your app, into the car Surface |
| Cost on the phone | none | a scale + colour convert per frame (no encode — the car host encodes) |
| Works when | you can resolve a real URL | always: MSE/blob players, canvas, a whole page |
A client picks per cast: URL mode when it extracted one, surface mode otherwise. Surface mode passes
the car's Surface across the process boundary, which works because Surface is Parcelable — your
app draws into it exactly as if it owned it.
Access is a signature-level permission, so only an app signed with the bridge's own key may bind it. An open bridge would let any app on the phone draw on a driver's display; if you need a differently-signed client, add an explicit package + certificate allowlist rather than removing the check.
DRM content is out of scope: a licence cannot be delegated to another app's player, and protected frames cannot be drawn into a capture surface.
Build: ./gradlew :sample:assembleDebug (JDK 17, Android SDK 34).
Maven coordinates once published: ai.rplay.cafari.carcanvas:carcanvas.
Apps doing this exist — ihueDashPlayer draws a hidden WebView onto the car screen,
Android-auto-media-player registers as NAVIGATION rather than MEDIA for the same
SurfaceContainer access. What is missing is the reusable piece, which is what this is.
Early. The API compiles and the shape is settled, but it has not been run against a real head unit yet — next step is a Desktop Head Unit pass, then a car.
Apache-2.0. Copyright 2026 rPlay AI.
Contributions welcome — see CONTRIBUTING.md.