RE paperConfirmed

Reverse engineering paper #11 — where the cluster composites an Android surface

The cluster panel is 1920×720, but an app's surface lands in a 440-pixel band — full width in Full, a 640-pixel card in Small.

BYD LabVerified 5 min read

1920 × 720 cluster panel — Android display 1 the instrument's own drawing — 160 rows the instrument's own drawing — 120 rows Full — 1920 × 440 at (0, 160) full width of the panel Small 640 × 440 at (1280, 160) the panel is not the window — render at the rectangle, or the map comes out stretched

Our own app. Sealion 7 Pilot is developed by BYD Lab. It is held to the same standards as every other app on this site — read with that in mind.

Tested on

Vehicle
BYD Sealion 7 · Morocco
Head-unit version
51.1.4.2606220.1
Android
11
Verified

The instrument cluster does not hand an app a screen. It hands it a band across the middle of one — and an app that renders at the wrong size gets a stretched map rather than an error.

Question#

When the cluster composites an app’s Android surface into its navi area, where on the panel does that surface land, and at what pixel size?

Environment#

Display identity read with dumpsys display over adb. Geometry measured by casting from our own app and capturing the cluster display with screencap -d 1, which returns the Android layer of that display only. The rectangles are constants in Sealion 7 Pilot (ClusterProjectionBounds.kt), and a debug overlay draws the active rectangle, its centre cross and its pixel bounds onto the surface, so a capture reads back against the numbers it claims.

Observation#

Take the display first, because that is where the confusion starts.

The cluster is Android display id 1: "Écran HDMI", local:3, port 3, type EXTERNAL, 1920×720 at 60 Hz, density 213, carrying FLAG_PRESENTATION and FLAG_OWN_CONTENT_ONLY and no FLAG_PRIVATE. A physical, public display — not a vendor virtual one.

Note the trap in the numbering: the Android id is 1, the local name is local:3, on port 3. Notes that say “display 3” and notes that say “display 1” can both be right about the same panel. The head unit is display 0 — a different panel, and not the one measured here.

Cast Full and capture the cluster display: map pixels appear only in rows 160 to 599, black above and below, across the full width. Cast Small and capture again: map pixels only in columns 1280 to 1919 by rows 160 to 599.

Evidence#

Three rectangles, in panel pixels:

Rectangle x y Width Height Centre
Physical panel 0 0 1920 720 960, 360
Full map window 0 160 1920 440 960, 380
Small map window 1280 160 640 440 1600, 380

Interpretation#

The panel is not the window. QNX owns the 1920×720 framebuffer and draws the gauges; it gives the Android layer a 440-pixel band and keeps the 160 rows above and the 120 below. An app that lays out at 1920×720 has put 280 rows of its own content where they will never be seen.

The two map windows are the same band. Full and Small share y = 160 and h = 440 — vertical centre 380 in both; only x and width differ. A Full↔Small change is a move and a resize inside one band, not a new geometry regime, which is what lets a live mode switch (paper #8) keep the same surface: render resolution, map position, camera padding and overlay visibility change, the map engine and the navigation state do not.

Rendering at the rectangle is not optional. The surface has to be laid out and rendered at exactly the active rectangle’s resolution — 1920×440 in Full, 640×440 in Small. Not a 1920×720 viewport with padding, not 640×720, no vertical stretch, no overscan, nothing rendered outside the rectangle. Get it wrong and there is no error to read: the map comes out stretched, and the giveaway is that road labels change aspect between one mode and the other.

The same rule reaches the camera. Paddings are viewport-relative rather than absolute — overview fit margins of 60 px left and right, 30 px top and bottom, follow-camera top padding viewportH × (1 − 2·pct), 0 in flat 2D — so the car sits at the same fraction of the visible band in either window.

Result#

Confirmed: the cluster is a 1920×720 public physical display (Android id 1, local:3), and the surface it composites occupies 1920×440 at (0, 160) in Full and 640×440 at (1280, 160) in Small. Centres 960, 380 and 1600, 380.

Two consequences. The geometry is a contract the app has to meet exactly, and the only way to check it is a capture. And the property that makes any of it reachable is a separate one: display 1 carries no FLAG_PRIVATE, which is what lets a normal app put an activity there at all (paper #6).

Limitations#

One car, one market, one head-unit build, one cluster panel, captured parked.

Only two rectangles are measured. Simple is not one of them. The research record states that whether QNX composites an Android surface at all in display mode 2 was still a hypothesis to confirm on the car at the time of writing, and where its window would be was not recorded. Our own app renders Simple into the Small window: a choice made in code, not a measurement.

screencap -d 1 returns the Android layer of that display and nothing else. It shows where our surface lands, not what QNX draws around it — black rows mean “no Android content”, not “nothing on the cluster”.

The 160-pixel top band and the 120-pixel bottom band are arithmetic on the two measured windows, not values read from a vendor interface, and the rectangles are measured rather than declared: a firmware change could move them, and only a fresh capture would notice. We have not tested what the vendor does with a surface placed outside the active mode’s window; our debug override is always clamped to it. Density 213 is recorded but nothing was checked for scaling against it, and the head-unit display was not measured at all.

Reproduction#

Shell
adb shell dumpsys display
adb shell screencap -p -d 1 /sdcard/cluster-full.png
adb pull /sdcard/cluster-full.png

Find the entry with id 1 and confirm local:3, port 3, EXTERNAL, 1920×720 and the absence of FLAG_PRIVATE.

Then cast a full-screen map to the cluster and capture. Switch to the small mode and capture again. Read the two PNGs for the first and last row and column carrying map content:

Log
Full   rows 160..599, full width   guidance card (24, 176)   stats row bottom 584
Small  cols 1280..1919 x rows 160..599

Road labels must keep the aspect they had in Full. If they do not, the surface is being rendered at a size other than the window and stretched into it.