master ohmu/fastfetch-openbsd / src / detection / displayserver / displayserver_android.c
ohmu  ·  2026-10-15
   1#include "displayserver.h"
   2#include "common/android/api.h"
   3#include "common/android/binder.h"
   4#include "common/android/dex.h"
   5#include "common/arrutil.h"
   6#include "common/debug.h"
   7#include "common/settings.h"
   8#include "common/strutil.h"
   9#include "common/processing.h"
  10#include "linux/displayserver_linux.h"
  11
  12#include <math.h>
  13
  14// ---------------------------------------------------------------------------------------------
  15// The binder route
  16// ---------------------------------------------------------------------------------------------
  17//
  18// `cmd display get-displays` and `dumpsys display` used to be the routes here, and both are a child
  19// process -- that fork was all of what this module cost: it took 16.0-26.5 ms on the test device,
  20// where `cmd display get-displays` itself takes 9.2 ms and a bare fork 8.6 ms. `cmd` does not exist
  21// before Android 13 either, and `dumpsys` is behind android.permission.DUMP, so on Android 11 and 12
  22// an app UID had no display at all. `dumpsys` is gone: `display` is the service it sat on,
  23// android.hardware.display.IDisplayManager, it lives in system_server, an app UID may call it, and
  24// one `getDisplayInfo(0)` answers the whole `DisplayInfo` the dump prints, field for field. With the
  25// fork gone the module took 2.8-3.2 ms where `cmd` took 16.0-26.5 ms, and Android 11 -- the release
  26// `cmd` could not serve at all -- has been read this way on a device. What is left is dominated by
  27// the dex read below, not by the transaction.
  28//
  29// `cmd display get-displays` stays behind the walk as a fallback, for the case where it is the dex
  30// read that fails rather than the transaction -- see the `cmd` section below the walk.
  31//
  32// What it costs is the parse. The reply is a Java Parcelable and its field set moves with every
  33// release and every vendor fork -- `DisplayInfo` writes 34 fields on Android 11, 42 on 13, 57 on 16
  34// and 57 on 17 -- so neither a fixed offset nor a sequence written against one release survives. The
  35// sequence is read out of the device's own `framework.jar` instead, from the `iget` order of
  36// `DisplayInfo.writeToParcel`, and the walk below follows it: a field the release does not write is
  37// simply not in the sequence, which is what makes every one of them optional by construction. See
  38// common/android/dex.h for how the sequence is recovered and what it does not say.
  39//
  40// What the sequence does not say is how each field is framed: `writeString` and `writeString8` are
  41// both a `Ljava/lang/String;` on the wire and are not the same length. The type descriptor settles
  42// the primitives and both array shapes, a handful of names settle the rest, and every nested
  43// Parcelable is anchored on its class name. A layout this does not recognise fails the route rather
  44// than answering, which is what the `cmd` fallback behind it is for: a build whose layout is not one
  45// this parser reads reports nothing rather than a plausible number out of the middle of a different
  46// field, and `cmd` still has the record in text.
  47//
  48// Android 17 moved three of the things this parse hangs on, and all three are read the same way -- out
  49// of the running build's own jar, so nothing here is a table written against a release:
  50//
  51//  * `address` arrived as a `DisplayAddress$StablePhysical` where every release up to 16 sent a
  52//    `DisplayAddress$Physical`, and the new class writes the port beside the id -- two fields where
  53//    the old one writes one. Both sequences are read and the class name on the wire picks between
  54//    them, so Android 11 to 17 are all read by the walk below.
  55//  * `frameRateVelocityMapping` is new and is a `writeTypedList`, a count and then one body per
  56//    element, each behind the marker the typed writes use and with no class name anywhere. The
  57//    element's own sequence says how long it is.
  58//  * `DeviceProductInfo.mEdidStructureMetadata` is new and is a `writeTypedObject`: the same marker
  59//    and the same absent class name, one body behind it.
  60
  61#define FF_DISPLAY_ANDROID_SERVICE "display"
  62#define FF_DISPLAY_ANDROID_DESCRIPTOR "android.hardware.display.IDisplayManager"
  63
  64// The jar the reply's classes live in, and their dex type descriptors. All of them are asked for in
  65// one lookup: resolving a descriptor is dominated by a pass over the dex type table, and one pass
  66// answers every request whose class is named in it -- see common/android/dex.h.
  67#define FF_DISPLAY_ANDROID_JAR "/system/framework/framework.jar"
  68
  69#define FF_DISPLAY_ANDROID_CLASS_INFO "Landroid/view/DisplayInfo;"
  70#define FF_DISPLAY_ANDROID_CLASS_ADDRESS "Landroid/view/DisplayAddress$Physical;"
  71#define FF_DISPLAY_ANDROID_CLASS_STABLE_ADDRESS "Landroid/view/DisplayAddress$StablePhysical;"
  72#define FF_DISPLAY_ANDROID_CLASS_PRODUCT_INFO "Landroid/hardware/display/DeviceProductInfo;"
  73#define FF_DISPLAY_ANDROID_CLASS_EDID_STRUCTURE "Landroid/hardware/display/DeviceProductInfo$EdidStructureMetadata;"
  74#define FF_DISPLAY_ANDROID_CLASS_MANUFACTURE_DATE "Landroid/hardware/display/DeviceProductInfo$ManufactureDate;"
  75#define FF_DISPLAY_ANDROID_CLASS_HDR_CAPABILITIES "Landroid/view/Display$HdrCapabilities;"
  76#define FF_DISPLAY_ANDROID_CLASS_MODE "Landroid/view/Display$Mode;"
  77#define FF_DISPLAY_ANDROID_CLASS_FRAME_RATE_CATEGORY_RATE "Landroid/view/FrameRateCategoryRate;"
  78#define FF_DISPLAY_ANDROID_CLASS_VELOCITY_POINT "Landroid/view/FrameRateVelocityPoint;"
  79#define FF_DISPLAY_ANDROID_CLASS_CUTOUT "Landroid/view/DisplayCutout;"
  80
  81// The Java class names `writeParcelable` writes in front of each nested body, which is the anchor the
  82// walk hangs on. It writes the *runtime* class, so the abstract `DisplayAddress` a `DisplayInfo`
  83// declares arrives as whichever subclass the framework put there -- its `$Physical` up to Android 16
  84// and its `$StablePhysical` from 17 on, which is why one field needs two names.
  85#define FF_DISPLAY_ANDROID_NAME_ADDRESS "android.view.DisplayAddress$Physical"
  86#define FF_DISPLAY_ANDROID_NAME_STABLE_ADDRESS "android.view.DisplayAddress$StablePhysical"
  87#define FF_DISPLAY_ANDROID_NAME_PRODUCT_INFO "android.hardware.display.DeviceProductInfo"
  88#define FF_DISPLAY_ANDROID_NAME_MANUFACTURE_DATE "android.hardware.display.DeviceProductInfo$ManufactureDate"
  89#define FF_DISPLAY_ANDROID_NAME_HDR_CAPABILITIES "android.view.Display$HdrCapabilities"
  90#define FF_DISPLAY_ANDROID_NAME_FRAME_RATE_CATEGORY_RATE "android.view.FrameRateCategoryRate"
  91
  92// `IDisplayManager$Stub`, as the device's own dex declares it. `getDisplayInfo` is the first method
  93// and has been since the interface was introduced -- it is an `@UnsupportedAppUsage` API and that
  94// order has never moved -- and `getDisplayIds` is the second. They are not counted out of the AOSP
  95// source: a vendor fork inserts methods freely, and the seven a fork added on the test device all sit
  96// behind these two.
  97#define FF_DISPLAY_ANDROID_TRANSACTION_GET_DISPLAY_INFO 1u
  98#define FF_DISPLAY_ANDROID_TRANSACTION_GET_DISPLAY_IDS 2u
  99
 100// One display's reply is 2404 bytes on the test device, most of it the two mode lists, and it grows
 101// with the display. A reply that does not fit is a failure rather than a truncation.
 102#define FF_DISPLAY_ANDROID_REPLY_SIZE 8192
 103#define FF_DISPLAY_ANDROID_PARCEL_SIZE 256
 104
 105// How many displays are read at most. The reply buffer holds one display, so this only bounds the id
 106// list and the array the parsed records are held in until every one of them has parsed.
 107#define FF_DISPLAY_ANDROID_MAX_DISPLAYS 8
 108
 109// How many fields each write sequence can hold. A sequence that does not fit is reported as empty
 110// rather than truncated, which fails the route -- half a sequence would walk half a record. See
 111// common/android/dex.h.
 112#define FF_DISPLAY_ANDROID_INFO_FIELDS 64
 113#define FF_DISPLAY_ANDROID_NESTED_FIELDS 8
 114#define FF_DISPLAY_ANDROID_MODE_FIELDS 12
 115
 116// The two instance fields of `DisplayCutout` that lengthen its body, in the order the request writes
 117// their results. `mCutoutPathParserInfo` arrived in Android 13 and adds nine writes, `mSideOverrides`
 118// in 16 and adds one; Android 11 declares neither.
 119typedef enum FFDisplayAndroidCutoutField : uint32_t {
 120    FF_DISPLAY_ANDROID_CUTOUT_PATH_PARSER_INFO,
 121    FF_DISPLAY_ANDROID_CUTOUT_SIDE_OVERRIDES,
 122    FF_DISPLAY_ANDROID_CUTOUT_FIELD_COUNT,
 123} FFDisplayAndroidCutoutField;
 124
 125static const char* const ffDisplayAndroidCutoutFields[FF_DISPLAY_ANDROID_CUTOUT_FIELD_COUNT] = {
 126    "mCutoutPathParserInfo",
 127    "mSideOverrides",
 128};
 129
 130// The write sequence of every class the reply is made of, read out of the device's own jar. It is
 131// what turns the parse from a sequence written against one release into a walk of the release that
 132// is running.
 133typedef struct FFDisplayAndroidLayout {
 134    FFDexParcelField displayInfo[FF_DISPLAY_ANDROID_INFO_FIELDS];
 135    uint32_t displayInfoCount;
 136    FFDexParcelField address[FF_DISPLAY_ANDROID_NESTED_FIELDS];
 137    uint32_t addressCount;
 138    FFDexParcelField stableAddress[FF_DISPLAY_ANDROID_NESTED_FIELDS];
 139    uint32_t stableAddressCount;
 140    FFDexParcelField productInfo[FF_DISPLAY_ANDROID_NESTED_FIELDS];
 141    uint32_t productInfoCount;
 142    FFDexParcelField edidStructureMetadata[FF_DISPLAY_ANDROID_NESTED_FIELDS];
 143    uint32_t edidStructureMetadataCount;
 144    FFDexParcelField manufactureDate[FF_DISPLAY_ANDROID_NESTED_FIELDS];
 145    uint32_t manufactureDateCount;
 146    FFDexParcelField hdrCapabilities[FF_DISPLAY_ANDROID_NESTED_FIELDS];
 147    uint32_t hdrCapabilitiesCount;
 148    FFDexParcelField mode[FF_DISPLAY_ANDROID_MODE_FIELDS];
 149    uint32_t modeCount;
 150    FFDexParcelField frameRateCategoryRate[FF_DISPLAY_ANDROID_NESTED_FIELDS];
 151    uint32_t frameRateCategoryRateCount;
 152    FFDexParcelField velocityPoint[FF_DISPLAY_ANDROID_NESTED_FIELDS];
 153    uint32_t velocityPointCount;
 154
 155    // `DisplayCutout` is the one class whose body is not field-driven -- `writeCutoutToParcel` reads
 156    // everything through compiler-synthesised accessors, so there is no `iget` to walk. What says how
 157    // long its body is, is which fields the class declares, and that is what is asked for here. They
 158    // are one array rather than two members because the request writes them as a run.
 159    bool cutoutFields[FF_DISPLAY_ANDROID_CUTOUT_FIELD_COUNT];
 160} FFDisplayAndroidLayout;
 161
 162// Reads every sequence above, and `DisplayCutout`'s declared fields, out of the dex entries of
 163// `jarPath` -- in one lookup, because the walk behind it is what a lookup costs and the class data it
 164// reads along the way already holds everything asked for here.
 165//
 166// A class the jar does not declare leaves an empty sequence, and that is an answer rather than a
 167// failure for every class a release added part way through: `FrameRateCategoryRate` arrived in
 168// Android 15 and `StablePhysical`, `EdidStructureMetadata` and `FrameRateVelocityPoint` in 17, and a
 169// jar that does not declare one of them does not write the field either, so the walk never asks.
 170// What *is* required is the sequences a record cannot be walked without: `DisplayInfo` itself, a
 171// `DisplayAddress` -- either of the two shapes, since which one a build sends is what the name on the
 172// wire says -- a `DeviceProductInfo` and its date, the HDR capabilities and a mode.
 173//
 174// A jar that cannot be read at all fails too, which is one of the two cases the `cmd` fallback
 175// behind this route exists for. Which is the point of the dex being the source: a build this parser
 176// cannot read declines to answer, rather than reporting a plausible number read out of the middle of
 177// a different field.
 178static bool displayLayoutLoad(const char* jarPath, FFDisplayAndroidLayout* layout) {
 179    const FFDexParcelRequest requests[] = {
 180        {
 181            .classDescriptor = FF_DISPLAY_ANDROID_CLASS_INFO,
 182            .methodName = "writeToParcel",
 183            .fields = layout->displayInfo,
 184            .capacity = FF_DISPLAY_ANDROID_INFO_FIELDS,
 185            .count = &layout->displayInfoCount,
 186        },
 187        {
 188            .classDescriptor = FF_DISPLAY_ANDROID_CLASS_ADDRESS,
 189            .methodName = "writeToParcel",
 190            .fields = layout->address,
 191            .capacity = FF_DISPLAY_ANDROID_NESTED_FIELDS,
 192            .count = &layout->addressCount,
 193        },
 194        {
 195            // Android 17's shape of the same field. A build that does not declare the class leaves the
 196            // sequence empty, which is how the walk knows the field can only be the older one.
 197            .classDescriptor = FF_DISPLAY_ANDROID_CLASS_STABLE_ADDRESS,
 198            .methodName = "writeToParcel",
 199            .fields = layout->stableAddress,
 200            .capacity = FF_DISPLAY_ANDROID_NESTED_FIELDS,
 201            .count = &layout->stableAddressCount,
 202        },
 203        {
 204            .classDescriptor = FF_DISPLAY_ANDROID_CLASS_PRODUCT_INFO,
 205            .methodName = "writeToParcel",
 206            .fields = layout->productInfo,
 207            .capacity = FF_DISPLAY_ANDROID_NESTED_FIELDS,
 208            .count = &layout->productInfoCount,
 209        },
 210        {
 211            // Android 17's addition to `DeviceProductInfo`. Same as above: an empty sequence means the
 212            // release does not carry the field, and it is not in the product info walk either.
 213            .classDescriptor = FF_DISPLAY_ANDROID_CLASS_EDID_STRUCTURE,
 214            .methodName = "writeToParcel",
 215            .fields = layout->edidStructureMetadata,
 216            .capacity = FF_DISPLAY_ANDROID_NESTED_FIELDS,
 217            .count = &layout->edidStructureMetadataCount,
 218        },
 219        {
 220            .classDescriptor = FF_DISPLAY_ANDROID_CLASS_MANUFACTURE_DATE,
 221            .methodName = "writeToParcel",
 222            .fields = layout->manufactureDate,
 223            .capacity = FF_DISPLAY_ANDROID_NESTED_FIELDS,
 224            .count = &layout->manufactureDateCount,
 225        },
 226        {
 227            .classDescriptor = FF_DISPLAY_ANDROID_CLASS_HDR_CAPABILITIES,
 228            .methodName = "writeToParcel",
 229            .fields = layout->hdrCapabilities,
 230            .capacity = FF_DISPLAY_ANDROID_NESTED_FIELDS,
 231            .count = &layout->hdrCapabilitiesCount,
 232        },
 233        {
 234            .classDescriptor = FF_DISPLAY_ANDROID_CLASS_MODE,
 235            .methodName = "writeToParcel",
 236            .fields = layout->mode,
 237            .capacity = FF_DISPLAY_ANDROID_MODE_FIELDS,
 238            .count = &layout->modeCount,
 239        },
 240        {
 241            .classDescriptor = FF_DISPLAY_ANDROID_CLASS_FRAME_RATE_CATEGORY_RATE,
 242            .methodName = "writeToParcel",
 243            .fields = layout->frameRateCategoryRate,
 244            .capacity = FF_DISPLAY_ANDROID_NESTED_FIELDS,
 245            .count = &layout->frameRateCategoryRateCount,
 246        },
 247        {
 248            // Android 17's `frameRateVelocityMapping` is a list of these, and this is the sequence that
 249            // says how long one element is.
 250            .classDescriptor = FF_DISPLAY_ANDROID_CLASS_VELOCITY_POINT,
 251            .methodName = "writeToParcel",
 252            .fields = layout->velocityPoint,
 253            .capacity = FF_DISPLAY_ANDROID_NESTED_FIELDS,
 254            .count = &layout->velocityPointCount,
 255        },
 256        {
 257            // The one class whose body is not field-driven. `writeCutoutToParcel` reads everything
 258            // through compiler-synthesised accessors, so there is no `iget` to walk and no class name
 259            // on the wire either -- what says how long its body is, is which fields the class
 260            // declares. Asking here rather than by a call of its own is the point: the walk that
 261            // answers the seven sequences above is already holding this class's field list, and a
 262            // second call would repeat it from the mapping down for 7.0 ms of a 23.5 ms lookup.
 263            .classDescriptor = FF_DISPLAY_ANDROID_CLASS_CUTOUT,
 264            .instanceFieldNames = ffDisplayAndroidCutoutFields,
 265            .instanceFieldResults = layout->cutoutFields,
 266            .instanceFieldCount = ARRAY_SIZE(ffDisplayAndroidCutoutFields),
 267        },
 268    };
 269
 270    const char* error = ffDexParcelFields(jarPath, requests, ARRAY_SIZE(requests));
 271    if (error != nullptr) {
 272        FF_DEBUG("Reading the display layout out of %s failed: %s", jarPath, error);
 273        return false;
 274    }
 275    if (layout->displayInfoCount == 0 || (layout->addressCount == 0 && layout->stableAddressCount == 0)
 276        || layout->productInfoCount == 0 || layout->manufactureDateCount == 0 || layout->hdrCapabilitiesCount == 0
 277        || layout->modeCount == 0) {
 278        FF_DEBUG("The classes a display is made of are not all declared in %s", jarPath);
 279        return false;
 280    }
 281
 282    FF_DEBUG(
 283        "Display layout: %u fields in DisplayInfo, %u in a mode, address %s, product info %u + edid %u, velocity point %u, cutout path parser info %s, side overrides %s",
 284        layout->displayInfoCount, layout->modeCount,
 285        layout->addressCount > 0 ? (layout->stableAddressCount > 0 ? "physical and stable" : "physical") : "stable",
 286        layout->productInfoCount, layout->edidStructureMetadataCount, layout->velocityPointCount,
 287        layout->cutoutFields[FF_DISPLAY_ANDROID_CUTOUT_PATH_PARSER_INFO] ? "yes" : "no",
 288        layout->cutoutFields[FF_DISPLAY_ANDROID_CUTOUT_SIDE_OVERRIDES] ? "yes" : "no"
 289    );
 290    return true;
 291}
 292
 293// `DisplayInfo.type`, i.e. `Display.TYPE_*`. A dump prints these as `INTERNAL` / `EXTERNAL` /
 294// `WIFI`; the numbers are what the parcel carries.
 295typedef enum FFDisplayAndroidType : int32_t {
 296    FF_DISPLAY_ANDROID_TYPE_UNKNOWN = 0,
 297    FF_DISPLAY_ANDROID_TYPE_INTERNAL = 1,
 298    FF_DISPLAY_ANDROID_TYPE_EXTERNAL = 2,
 299    FF_DISPLAY_ANDROID_TYPE_WIFI = 3,
 300    FF_DISPLAY_ANDROID_TYPE_OVERLAY = 4,
 301    FF_DISPLAY_ANDROID_TYPE_VIRTUAL = 5,
 302} FFDisplayAndroidType;
 303
 304// The `Parcel.writeValue` tags `DeviceProductInfo` uses. The framework writes one of these in front
 305// of the value; the numbers are its own.
 306typedef enum FFDisplayAndroidValue : int32_t {
 307    FF_DISPLAY_ANDROID_VALUE_NULL = -1,
 308    FF_DISPLAY_ANDROID_VALUE_STRING = 0,
 309    FF_DISPLAY_ANDROID_VALUE_INTEGER = 1,
 310    FF_DISPLAY_ANDROID_VALUE_PARCELABLE = 4,
 311} FFDisplayAndroidValue;
 312
 313// A cursor over one reply. Every read is bounds checked and every mismatch raises `failed` rather
 314// than returning a value: once a field has been read at the wrong place nothing behind it can be
 315// trusted, and the only safe answer left is to report nothing.
 316typedef struct FFDisplayAndroidReader {
 317    const uint8_t* data;
 318    size_t size;
 319    size_t offset;
 320    bool failed;
 321} FFDisplayAndroidReader;
 322
 323static bool readerSkip(FFDisplayAndroidReader* reader, size_t bytes) {
 324    if (bytes > reader->size - reader->offset) {
 325        reader->failed = true;
 326        return false;
 327    }
 328    reader->offset += bytes;
 329    return true;
 330}
 331
 332static int32_t readerI32(FFDisplayAndroidReader* reader) {
 333    if (reader->offset + sizeof(int32_t) > reader->size) {
 334        reader->failed = true;
 335        return 0;
 336    }
 337    int32_t value = 0;
 338    memcpy(&value, reader->data + reader->offset, sizeof(int32_t));
 339    reader->offset += sizeof(int32_t);
 340    return value;
 341}
 342
 343static uint32_t readerU32(FFDisplayAndroidReader* reader) {
 344    return (uint32_t) readerI32(reader);
 345}
 346
 347static uint64_t readerU64(FFDisplayAndroidReader* reader) {
 348    if (reader->offset + sizeof(uint64_t) > reader->size) {
 349        reader->failed = true;
 350        return 0;
 351    }
 352    uint64_t value = 0;
 353    memcpy(&value, reader->data + reader->offset, sizeof(uint64_t));
 354    reader->offset += sizeof(uint64_t);
 355    return value;
 356}
 357
 358static float readerF32(FFDisplayAndroidReader* reader) {
 359    const uint32_t raw = readerU32(reader);
 360    float value = 0;
 361    memcpy(&value, &raw, sizeof(value));
 362    return value;
 363}
 364
 365// A `writeBoolean`, which is an int32. Anything but 0 or 1 is a field read at the wrong place.
 366static bool readerBool(FFDisplayAndroidReader* reader) {
 367    const int32_t value = readerI32(reader);
 368    if (value != 0 && value != 1) {
 369        reader->failed = true;
 370        return false;
 371    }
 372    return value == 1;
 373}
 374
 375// Whether the string16 at `offset` holds `expected`, which is a class name. The length has to be the
 376// name's own and every code unit has to spell it, so a body that happens to start with a plausible
 377// length does not pass for one.
 378static bool readerClassNameAt(const FFDisplayAndroidReader* reader, size_t offset, const char* expected) {
 379    const size_t length = strlen(expected);
 380    if (length > 128 || offset + sizeof(int32_t) + (length + 1) * 2 > reader->size) {
 381        return false;
 382    }
 383    int32_t declared = 0;
 384    memcpy(&declared, reader->data + offset, sizeof(int32_t));
 385    if ((size_t) declared != length) {
 386        return false;
 387    }
 388    const uint8_t* text = reader->data + offset + sizeof(int32_t);
 389    for (size_t i = 0; i < length; ++i) {
 390        if (text[i * 2] != (uint8_t) expected[i] || text[i * 2 + 1] != 0) {
 391            return false;
 392        }
 393    }
 394    return true;
 395}
 396
 397// The class name in front of a Parcelable body, which `writeParcelable` writes and which is the
 398// anchor this whole parser hangs on: a field that moved puts something else here, and a name that
 399// does not decode is how the parser finds out instead of reading a length out of the middle of a
 400// body.
 401static bool readerClassName(FFDisplayAndroidReader* reader, const char* expected) {
 402    if (!readerClassNameAt(reader, reader->offset, expected)) {
 403        reader->failed = true;
 404        return false;
 405    }
 406    return readerSkip(reader, sizeof(int32_t) + (((strlen(expected) + 1) * 2 + 3) & ~(size_t) 3));
 407}
 408
 409// A `writeString`, which is UTF-16 despite the name: an int32 count of code units, the code units, a
 410// NUL terminator and the padding behind it. A count of -1 is how the same field spells a null String
 411// and is not an error; `result` may be null where the text is only in the way.
 412static bool readerString16(FFDisplayAndroidReader* reader, FFstrbuf* result) {
 413    const int32_t length = readerI32(reader);
 414    if (reader->failed) {
 415        return false;
 416    }
 417    if (length < 0) {
 418        return true;
 419    }
 420    const size_t payload = ((size_t) length * 2 + 2 + 3) & ~(size_t) 3;
 421    if (payload > reader->size - reader->offset) {
 422        reader->failed = true;
 423        return false;
 424    }
 425    if (result != nullptr) {
 426        for (int32_t i = 0; i < length; ++i) {
 427            uint32_t codePoint = (uint32_t) reader->data[reader->offset + (size_t) i * 2]
 428                | ((uint32_t) reader->data[reader->offset + (size_t) i * 2 + 1] << 8);
 429            if (codePoint >= 0xD800 && codePoint <= 0xDBFF && i + 1 < length) {
 430                // Anything outside the basic plane arrives as a surrogate pair, and the low half is
 431                // what turns it back into one code point. A high surrogate without one is malformed
 432                // and is passed through as it stands rather than guessed at.
 433                const uint32_t low = (uint32_t) reader->data[reader->offset + (size_t) (i + 1) * 2]
 434                    | ((uint32_t) reader->data[reader->offset + (size_t) (i + 1) * 2 + 1] << 8);
 435                if (low >= 0xDC00 && low <= 0xDFFF) {
 436                    codePoint = 0x10000u + ((codePoint - 0xD800u) << 10) + (low - 0xDC00u);
 437                    ++i;
 438                }
 439            }
 440            ffStrbufAppendUtf32CodePoint(result, codePoint);
 441        }
 442    }
 443    reader->offset += payload;
 444    return true;
 445}
 446
 447// A `writeString8`, which is the one that really is UTF-8: an int32 *byte* count, that many bytes, a
 448// NUL and the padding. This is what `DisplayInfo.name` and `DisplayInfo.uniqueId` use -- the built-in
 449// display's name is 12 bytes of UTF-8 where the same text is 4 code units as a string16.
 450static bool readerString8(FFDisplayAndroidReader* reader, FFstrbuf* result) {
 451    const int32_t length = readerI32(reader);
 452    if (reader->failed) {
 453        return false;
 454    }
 455    if (length < 0) {
 456        return true;
 457    }
 458    const size_t payload = ((size_t) length + 1 + 3) & ~(size_t) 3;
 459    if (payload > reader->size - reader->offset) {
 460        reader->failed = true;
 461        return false;
 462    }
 463    if (result != nullptr) {
 464        ffStrbufAppendNS(result, (uint32_t) length, (const char*) reader->data + reader->offset);
 465    }
 466    reader->offset += payload;
 467    return true;
 468}
 469
 470// An array of primitives: a count and the values. A count of -1 is a null array, which carries none.
 471static bool readerSkipArray(FFDisplayAndroidReader* reader, size_t elementSize) {
 472    const int32_t count = readerI32(reader);
 473    if (reader->failed) {
 474        return false;
 475    }
 476    if (count < 0) {
 477        return true;
 478    }
 479    if (count > 1024) {
 480        reader->failed = true;
 481        return false;
 482    }
 483    return readerSkip(reader, (size_t) count * elementSize);
 484}
 485
 486// A `writeValue` whose value is an Integer, or a null which reads as zero.
 487static int32_t readerValueInteger(FFDisplayAndroidReader* reader) {
 488    switch ((FFDisplayAndroidValue) readerI32(reader)) {
 489        case FF_DISPLAY_ANDROID_VALUE_NULL:
 490            return 0;
 491        case FF_DISPLAY_ANDROID_VALUE_INTEGER:
 492            return readerI32(reader);
 493        default:
 494            reader->failed = true;
 495            return 0;
 496    }
 497}
 498
 499// A `writeValue` whose value is a String, an Integer or a null. Nothing here wants the value; the
 500// shapes it does not know are what it refuses to guess a length for.
 501static bool readerSkipValue(FFDisplayAndroidReader* reader) {
 502    switch ((FFDisplayAndroidValue) readerI32(reader)) {
 503        case FF_DISPLAY_ANDROID_VALUE_NULL:
 504            return true;
 505        case FF_DISPLAY_ANDROID_VALUE_STRING:
 506            return readerString16(reader, nullptr);
 507        case FF_DISPLAY_ANDROID_VALUE_INTEGER:
 508            (void) readerI32(reader);
 509            return !reader->failed;
 510        default:
 511            reader->failed = true;
 512            return false;
 513    }
 514}
 515
 516// One field whose dex type descriptor settles how it is framed. `[I` and `[F` are the two array
 517// shapes the record carries and report their element count; the rest report their value.
 518typedef struct FFDisplayAndroidScalar {
 519    int32_t i32;
 520    float f32;
 521    int32_t count;
 522} FFDisplayAndroidScalar;
 523
 524// Reads one field whose descriptor settles its framing -- an `I`, an `F`, a `J`, a `Z`, an `[I` or
 525// an `[F`. A descriptor with no rule here is a failure rather than a guess: `Ljava/lang/String;`
 526// alone can be a `writeString` or a `writeString8`, the two are not the same length on the wire, and
 527// stepping over it as if it were either would move every field behind it.
 528static bool readerScalar(FFDisplayAndroidReader* reader, const char* type, FFDisplayAndroidScalar* value) {
 529    *value = (FFDisplayAndroidScalar) {};
 530    if (ffStrEquals(type, "I")) {
 531        value->i32 = readerI32(reader);
 532        return !reader->failed;
 533    }
 534    if (ffStrEquals(type, "F")) {
 535        value->f32 = readerF32(reader);
 536        return !reader->failed;
 537    }
 538    if (ffStrEquals(type, "J")) {
 539        (void) readerU64(reader);
 540        return !reader->failed;
 541    }
 542    if (ffStrEquals(type, "Z")) {
 543        value->i32 = readerBool(reader) ? 1 : 0;
 544        return !reader->failed;
 545    }
 546    if (ffStrEquals(type, "[I") || ffStrEquals(type, "[F")) {
 547        const int32_t count = readerI32(reader);
 548        if (reader->failed || count > 1024) {
 549            reader->failed = true;
 550            return false;
 551        }
 552        // A negative count is a null array and carries no elements: `writeIntArray(null)` and
 553        // `writeFloatArray(null)` both write a bare -1 where the count would be. Android 11 has one
 554        // of these in the record -- `DeviceProductInfo.mRelativeAddress` is an `[I` there and is null
 555        // on the built-in display. `readerSkipArray` spells the same rule for the arrays it steps
 556        // over, and a null is a value everywhere else in this walk too.
 557        if (count < 0) {
 558            return true;
 559        }
 560        value->count = count;
 561        return readerSkip(reader, (size_t) count * (ffStrEquals(type, "[I") ? sizeof(int32_t) : sizeof(float)));
 562    }
 563    reader->failed = true;
 564    return false;
 565}
 566
 567// The class name in front of a nested body, when which class it is decides how long the body is.
 568// `writeParcelable` writes the runtime class, so a field declared an abstract type arrives as
 569// whichever subclass the framework put there: `DisplayInfo.address` is a `DisplayAddress`, whose body
 570// is one field on `$Physical` and two on the `$StablePhysical` Android 17 added. The name is read
 571// first and the index of the one it matched is reported, so the caller walks the sequence that
 572// belongs to it -- and a name that matches none of them fails, because there is nothing else that
 573// says where the body ends.
 574static bool readerParcelableHeadAny(FFDisplayAndroidReader* reader, const char* const* names, uint32_t nameCount, uint32_t* index, bool* present) {
 575    const int32_t length = readerI32(reader);
 576    if (reader->failed) {
 577        return false;
 578    }
 579    if (length < 0) {
 580        *present = false;
 581        return true;
 582    }
 583    reader->offset -= sizeof(int32_t);
 584    *present = true;
 585    for (uint32_t i = 0; i < nameCount; ++i) {
 586        if (readerClassNameAt(reader, reader->offset, names[i])) {
 587            *index = i;
 588            return readerClassName(reader, names[i]);
 589        }
 590    }
 591    reader->failed = true;
 592    return false;
 593}
 594
 595// The same head for the fields that carry one class only. A null field is a single -1 where the class
 596// name would be, which is a value and not a failure.
 597static bool readerParcelableHead(FFDisplayAndroidReader* reader, const char* expected, bool* present) {
 598    uint32_t index = 0;
 599    return readerParcelableHeadAny(reader, &expected, 1, &index, present);
 600}
 601
 602// A `Rect` written by `writeTypedObject` or by `writeTypedArray`: the non-null marker the typed write
 603// puts in front of the body and then four ints. A null is a marker and no ints behind it.
 604static bool readerRect(FFDisplayAndroidReader* reader) {
 605    const int32_t marker = readerI32(reader);
 606    if (reader->failed) {
 607        return false;
 608    }
 609    return marker < 0 || readerSkip(reader, 4 * sizeof(int32_t));
 610}
 611
 612// A nested Parcelable written by `writeTypedObject`, walked out of the sequence the jar gives for its
 613// own class. What is on the wire is a marker and the body, with no class name in front of it -- the
 614// reader is handed the `Creator` to build the value with, so the parcel does not have to carry one,
 615// and that is the whole difference between this and `writeParcelable`. The device's own
 616// `Parcel.writeTypedObject` writes a zero marker for a null and a one before every body, so anything
 617// else is a field read at the wrong place rather than a value.
 618static bool readerTypedObject(FFDisplayAndroidReader* reader, const FFDexParcelField* fields, uint32_t count) {
 619    const int32_t marker = readerI32(reader);
 620    if (reader->failed) {
 621        return false;
 622    }
 623    if (marker == 0) {
 624        return true;
 625    }
 626    if (marker != 1) {
 627        reader->failed = true;
 628        return false;
 629    }
 630    for (uint32_t i = 0; i < count; ++i) {
 631        FFDisplayAndroidScalar value = {};
 632        if (!readerScalar(reader, fields[i].type, &value)) {
 633            return false;
 634        }
 635    }
 636    return true;
 637}
 638
 639// A `writeTypedList`: a count and then one `writeTypedObject` per element, all of them the same class,
 640// which is why one sequence answers the whole list. A count of -1 is a null list and carries none.
 641static bool readerTypedObjectList(FFDisplayAndroidReader* reader, const FFDexParcelField* fields, uint32_t count) {
 642    const int32_t size = readerI32(reader);
 643    if (reader->failed) {
 644        return false;
 645    }
 646    if (size < 0) {
 647        return true;
 648    }
 649    // A non-empty list whose elements this parser has no sequence for is a failure rather than an empty
 650    // walk: the elements are still on the wire, and stepping over none of them would put every field
 651    // behind the list at the wrong place, which is worse than reporting nothing.
 652    if (size > 64 || (size > 0 && count == 0)) {
 653        reader->failed = true;
 654        return false;
 655    }
 656    for (int32_t i = 0; i < size; ++i) {
 657        if (!readerTypedObject(reader, fields, count)) {
 658            return false;
 659        }
 660    }
 661    return true;
 662}
 663
 664// `writeValue(mManufactureDate)`, the only Parcelable a `writeValue` carries here, and the one place
 665// the parcel does something the AOSP sources do not describe: on Android 13 and later the tag is
 666// followed by the size of everything behind it -- the class name and the body -- before the class
 667// name, which is why a plain `tag + class name` walk lands four bytes early; Android 11 has no size
 668// at all. Neither layout is assumed -- a value that decodes as the class name has no size, anything
 669// else is stepped over -- and where there is one the cursor is snapped to the end it promises, so a
 670// body this parser reads differently can not move the fields behind it. The size counts from its own
 671// end, not from its start.
 672static bool readerManufactureDate(FFDisplayAndroidReader* reader, const FFDisplayAndroidLayout* layout, uint16_t* year, uint16_t* week) {
 673    // `mManufactureDate` is `@Nullable`, so a display with no date writes a bare `VAL_NULL` and
 674    // nothing behind it -- and a date is not what the display is being read for. A missing one is
 675    // skipped rather than failed: everything the module needs is still in the record, and dropping
 676    // the display over an absent date would answer nothing at all. Only a tag that is neither a
 677    // Parcelable nor a null fails, because that is a framing this walk does not know how to step over.
 678    const int32_t tag = readerI32(reader);
 679    if (tag == FF_DISPLAY_ANDROID_VALUE_NULL) {
 680        *year = 0;
 681        *week = 0;
 682        return true;
 683    }
 684    if (tag != FF_DISPLAY_ANDROID_VALUE_PARCELABLE) {
 685        reader->failed = true;
 686        return false;
 687    }
 688
 689    size_t bodyEnd = 0;
 690    if (!readerClassNameAt(reader, reader->offset, FF_DISPLAY_ANDROID_NAME_MANUFACTURE_DATE)) {
 691        const int32_t declared = readerI32(reader);
 692        if (declared <= 0) {
 693            reader->failed = true;
 694            return false;
 695        }
 696        bodyEnd = reader->offset + (size_t) declared;
 697    }
 698    if (!readerClassName(reader, FF_DISPLAY_ANDROID_NAME_MANUFACTURE_DATE)) {
 699        return false;
 700    }
 701
 702    // `writeValue(mWeek)` and `writeValue(mYear)`, each an Integer or a null. Nothing is reported
 703    // unless both are there, so a class that stops writing one leaves the date at nothing rather
 704    // than at half of it.
 705    uint16_t weekValue = 0;
 706    uint16_t yearValue = 0;
 707    for (uint32_t i = 0; i < layout->manufactureDateCount; ++i) {
 708        const int32_t value = readerValueInteger(reader);
 709        if (reader->failed || value < 0 || value > 3000) {
 710            reader->failed = true;
 711            return false;
 712        }
 713        if (ffStrEquals(layout->manufactureDate[i].name, "mWeek")) {
 714            if (value > 53) {
 715                reader->failed = true;
 716                return false;
 717            }
 718            weekValue = (uint16_t) value;
 719        } else if (ffStrEquals(layout->manufactureDate[i].name, "mYear")) {
 720            yearValue = (uint16_t) value;
 721        }
 722    }
 723    *week = weekValue;
 724    *year = yearValue;
 725
 726    if (bodyEnd > 0) {
 727        if (bodyEnd < reader->offset || bodyEnd > reader->size) {
 728            reader->failed = true;
 729            return false;
 730        }
 731        reader->offset = bodyEnd;
 732    }
 733    return true;
 734}
 735
 736// `deviceProductInfo`, which is where a display's manufacture date is carried. Nothing else in it is
 737// used, but it sits in front of `name`, the density and the rotation, so it has to be walked whether
 738// or not its values are wanted -- and it is null on some displays.
 739//
 740// Three of its fields are handed to `writeValue`, which frames them by their runtime type rather than
 741// by their declared one: `mProductId` is declared a `String` and goes through `writeValue` where
 742// `mName` next to it goes through `writeString`, so those are named rather than dispatched by type.
 743// The connection type is `mRelativeAddress`, an int array, on Android 11 and `mConnectionToSinkType`,
 744// an int, on 13 and later; the descriptor settles that one, which is what makes the two read the same
 745// code. Android 17 added `mVideoInputType`, an int, which the descriptor settles the same way, and
 746// `mEdidStructureMetadata`, a `writeTypedObject` -- six fields up to 16, eight from 17 on.
 747static bool readerDeviceProductInfo(FFDisplayAndroidReader* reader, const FFDisplayAndroidLayout* layout, uint16_t* year, uint16_t* week) {
 748    bool present = false;
 749    if (!readerParcelableHead(reader, FF_DISPLAY_ANDROID_NAME_PRODUCT_INFO, &present)) {
 750        return false;
 751    }
 752    if (!present) {
 753        return true;
 754    }
 755
 756    for (uint32_t i = 0; i < layout->productInfoCount; ++i) {
 757        const FFDexParcelField* field = &layout->productInfo[i];
 758        if (ffStrEquals(field->name, "mName") || ffStrEquals(field->name, "mManufacturerPnpId")) {
 759            if (!readerString16(reader, nullptr)) {
 760                return false;
 761            }
 762        } else if (ffStrEquals(field->name, "mProductId") || ffStrEquals(field->name, "mModelYear")) {
 763            if (!readerSkipValue(reader)) {
 764                return false;
 765            }
 766        } else if (ffStrEquals(field->name, "mManufactureDate")) {
 767            if (!readerManufactureDate(reader, layout, year, week)) {
 768                return false;
 769            }
 770        } else if (ffStrEquals(field->name, "mEdidStructureMetadata")) {
 771            // Android 17. Named rather than left to the descriptor for the same reason the fields above
 772            // are: `writeTypedObject` and `writeParcelable` are both a `L...;` on the wire and are not
 773            // the same length, and this one is the typed write -- a marker, the body, no class name.
 774            if (!readerTypedObject(reader, layout->edidStructureMetadata, layout->edidStructureMetadataCount)) {
 775                return false;
 776            }
 777        } else {
 778            FFDisplayAndroidScalar value = {};
 779            if (!readerScalar(reader, field->type, &value)) {
 780                return false;
 781            }
 782        }
 783    }
 784    return true;
 785}
 786
 787// `address`, which is null on some displays and a `DisplayAddress` on others: its `$Physical` up to
 788// Android 16, whose body is one `long`, and its `$StablePhysical` from 17 on, whose body is that
 789// `long` and the port beside it. Which one is on the wire is what the class name says, and either
 790// sequence comes out of the jar rather than being assumed, so a release that changes either body is
 791// read rather than misread.
 792static bool readerDisplayAddress(FFDisplayAndroidReader* reader, const FFDisplayAndroidLayout* layout) {
 793    static const char* const names[] = {
 794        FF_DISPLAY_ANDROID_NAME_ADDRESS,
 795        FF_DISPLAY_ANDROID_NAME_STABLE_ADDRESS,
 796    };
 797    bool present = false;
 798    uint32_t index = 0;
 799    if (!readerParcelableHeadAny(reader, names, ARRAY_SIZE(names), &index, &present)) {
 800        return false;
 801    }
 802    if (!present) {
 803        return true;
 804    }
 805    const bool stable = index != 0;
 806    const FFDexParcelField* fields = stable ? layout->stableAddress : layout->address;
 807    const uint32_t count = stable ? layout->stableAddressCount : layout->addressCount;
 808    for (uint32_t i = 0; i < count; ++i) {
 809        FFDisplayAndroidScalar value = {};
 810        if (!readerScalar(reader, fields[i].type, &value)) {
 811            return false;
 812        }
 813    }
 814    return true;
 815}
 816
 817// A nested Parcelable whose values this module does not use, walked because the fields behind it sit
 818// on the far side of it. `frameRateCategoryRate` is the only one of those left before `uniqueId`.
 819static bool readerSkippedParcelable(FFDisplayAndroidReader* reader, const char* expected, const FFDexParcelField* fields, uint32_t count) {
 820    bool present = false;
 821    if (!readerParcelableHead(reader, expected, &present)) {
 822        return false;
 823    }
 824    if (!present) {
 825        return true;
 826    }
 827    for (uint32_t i = 0; i < count; ++i) {
 828        FFDisplayAndroidScalar value = {};
 829        if (!readerScalar(reader, fields[i].type, &value)) {
 830            return false;
 831        }
 832    }
 833    return true;
 834}
 835
 836// `hdrCapabilities`, whose only value this module uses is how many HDR types the display supports --
 837// an empty list is the display saying it can not do HDR at all.
 838static bool readerHdrCapabilities(FFDisplayAndroidReader* reader, const FFDisplayAndroidLayout* layout, int32_t* hdrTypeCount) {
 839    bool present = false;
 840    if (!readerParcelableHead(reader, FF_DISPLAY_ANDROID_NAME_HDR_CAPABILITIES, &present)) {
 841        return false;
 842    }
 843    if (!present) {
 844        return true;
 845    }
 846    for (uint32_t i = 0; i < layout->hdrCapabilitiesCount; ++i) {
 847        FFDisplayAndroidScalar value = {};
 848        if (!readerScalar(reader, layout->hdrCapabilities[i].type, &value)) {
 849            return false;
 850        }
 851        if (ffStrEquals(layout->hdrCapabilities[i].name, "mSupportedHdrTypes")) {
 852            if (value.count > 16) {
 853                reader->failed = true;
 854                return false;
 855            }
 856            *hdrTypeCount = value.count;
 857        }
 858    }
 859    return true;
 860}
 861
 862// `displayCutout`, the largest nested structure in the record and the one that is easiest to get
 863// wrong: it is serialized by `DisplayCutout$ParcelableWrapper.writeCutoutToParcel`, because
 864// `DisplayCutout` has no `writeToParcel` of its own, and that method reads everything through
 865// `-$$Nest$` accessors the compiler synthesised -- so there is no `iget` to walk and no class name on
 866// the wire either, and the write sequence the jar gives for every other class says nothing here.
 867//
 868// What says how long the body is, is which fields the class declares. Android 11 declares three --
 869// `mBounds`, `mSafeInsets`, `mWaterfallInsets` -- Android 13 adds `mCutoutPathParserInfo`, which is
 870// nine more writes, and Android 16 adds `mSideOverrides`, which is one more. Both are read from the
 871// jar rather than from a version number. None of it is reported; it is walked because `rotation` and
 872// everything behind it sit on the far side of it.
 873static bool readerDisplayCutout(FFDisplayAndroidReader* reader, const FFDisplayAndroidLayout* layout) {
 874    const int32_t marker = readerI32(reader);
 875    if (reader->failed) {
 876        return false;
 877    }
 878    if (marker != 1) {
 879        // 0 for a null cutout and -1 for `DisplayCutout.NO_CUTOUT`, which is a display with none at
 880        // all. Anything else is a field read at the wrong place.
 881        if (marker == 0 || marker == -1) {
 882            return true;
 883        }
 884        reader->failed = true;
 885        return false;
 886    }
 887
 888    if (!readerRect(reader)) { // mSafeInsets
 889        return false;
 890    }
 891    const int32_t bounds = readerI32(reader); // mBounds, a typed array of Rects
 892    if (reader->failed || bounds < 0 || bounds > 64) {
 893        reader->failed = true;
 894        return false;
 895    }
 896    for (int32_t i = 0; i < bounds; ++i) {
 897        if (!readerRect(reader)) {
 898            return false;
 899        }
 900    }
 901    if (!readerRect(reader)) { // mWaterfallInsets
 902        return false;
 903    }
 904
 905    const bool* cutout = layout->cutoutFields;
 906    if (cutout[FF_DISPLAY_ANDROID_CUTOUT_PATH_PARSER_INFO]) {
 907        // the display size the cutout was built for and then the physical one
 908        if (!readerSkip(reader, 4 * sizeof(int32_t))) {
 909            return false;
 910        }
 911        const float density = readerF32(reader);
 912        if (reader->failed || !(density > 0) || density > 1000) {
 913            reader->failed = true;
 914            return false;
 915        }
 916        // the cutout spec, e.g. `M -45,0 L -45,142 L 45,142 L 45,0 Z`, and then the rotation, the
 917        // scale and the physical pixel ratio, which are three plain four byte fields
 918        if (!readerString16(reader, nullptr) || !readerSkip(reader, 3 * sizeof(int32_t))) {
 919            return false;
 920        }
 921    }
 922    if (cutout[FF_DISPLAY_ANDROID_CUTOUT_SIDE_OVERRIDES]) {
 923        return readerSkipArray(reader, sizeof(int32_t)); // mSideOverrides
 924    }
 925    return true;
 926}
 927
 928// One entry of `supportedModes`, which is `Display$Mode`.
 929typedef struct FFDisplayAndroidMode {
 930    int32_t id;
 931    uint32_t width;
 932    uint32_t height;
 933    float refreshRate;
 934} FFDisplayAndroidMode;
 935
 936// Walks a `Display$Mode[]`: a count and then that many bare bodies, with no non-null marker per
 937// element, which is what `writeTypedArray` would put there -- the framework writes the length with
 938// `writeInt` and then one `writeToParcel` per element. The two ids the record carries are looked up
 939// as it goes, so the list does not have to be kept.
 940//
 941// The body is walked out of the jar's own sequence rather than read as a fixed run: `Display$Mode`
 942// writes four fields on Android 11, five on 13 and eight on 16, and the fourth -- the one the
 943// refresh rate is in -- is `mRefreshRate` on the first two and `mPeakRefreshRate` on the third.
 944static bool readerModeList(FFDisplayAndroidReader* reader, const FFDisplayAndroidLayout* layout, int32_t activeId, int32_t defaultId, FFDisplayAndroidMode* active, FFDisplayAndroidMode* preferred) {
 945    const int32_t count = readerI32(reader);
 946    if (reader->failed || count < 0 || count > 64) {
 947        reader->failed = true;
 948        return false;
 949    }
 950    for (int32_t i = 0; i < count; ++i) {
 951        FFDisplayAndroidMode mode = {};
 952        for (uint32_t j = 0; j < layout->modeCount; ++j) {
 953            const FFDexParcelField* field = &layout->mode[j];
 954            FFDisplayAndroidScalar value = {};
 955            if (!readerScalar(reader, field->type, &value)) {
 956                return false;
 957            }
 958            if (ffStrEquals(field->name, "mModeId")) {
 959                mode.id = value.i32;
 960            } else if (ffStrEquals(field->name, "mWidth")) {
 961                mode.width = (uint32_t) value.i32;
 962            } else if (ffStrEquals(field->name, "mHeight")) {
 963                mode.height = (uint32_t) value.i32;
 964            } else if (ffStrEquals(field->name, "mRefreshRate") || ffStrEquals(field->name, "mPeakRefreshRate")) {
 965                mode.refreshRate = value.f32;
 966            }
 967        }
 968        if (reader->failed
 969            || mode.id < 0 || mode.width == 0 || mode.width > 32768 || mode.height == 0 || mode.height > 32768
 970            || !(mode.refreshRate > 0) || mode.refreshRate > 1000) {
 971            reader->failed = true;
 972            return false;
 973        }
 974        if (active != nullptr && mode.id == activeId) {
 975            *active = mode;
 976        }
 977        if (preferred != nullptr && mode.id == defaultId) {
 978            *preferred = mode;
 979        }
 980    }
 981    return !reader->failed;
 982}
 983
 984// One parsed record. It is held until every display has parsed, because a record this parser does not
 985// recognise says the layout is not the one it was written for, and half an answer from a layout it
 986// does not understand is worse than falling back.
 987typedef struct FFDisplayAndroidInfo {
 988    uint32_t width;
 989    uint32_t height;
 990    uint32_t preferredWidth;
 991    uint32_t preferredHeight;
 992    double preferredRefreshRate;
 993    double refreshRate;
 994    uint32_t density;
 995    double physicalXDpi;
 996    double physicalYDpi;
 997    uint32_t rotation;
 998    uint32_t displayId;
 999    int32_t type;
1000    int32_t modeId;
1001    int32_t defaultModeId;
1002    int32_t hdrTypeCount;
1003    float renderFrameRate;
1004    bool forceSdr;
1005    uint16_t manufactureYear;
1006    uint16_t manufactureWeek;
1007    uint64_t id;
1008    FFstrbuf name;
1009} FFDisplayAndroidInfo;
1010
1011// One `DisplayInfo` walk in progress: the record being filled, the two modes the mode list is
1012// resolved against, and the id string -- which is not part of the record, it is only read to pull the
1013// display's id out of it, so it is held here rather than there.
1014typedef struct FFDisplayAndroidWalk {
1015    FFDisplayAndroidInfo* info;
1016    FFstrbuf* uniqueId;
1017    FFDisplayAndroidMode active;
1018    FFDisplayAndroidMode preferred;
1019} FFDisplayAndroidWalk;
1020
1021// `uniqueId` identifies the display across reboots, e.g. `local:4630946557703207059` for the panel
1022// and `virtual:...` for a virtual one. The prefix says what kind of display it is, and the number
1023// behind the colon is the id.
1024static uint64_t displayUniqueId(const char* uniqueId) {
1025    const char* colon = strchr(uniqueId, ':');
1026    return (uint64_t) strtoull(colon != nullptr ? colon + 1 : uniqueId, nullptr, 10);
1027}
1028
1029// One field of the `DisplayInfo` write sequence. The name says which value the field carries; the
1030// type descriptor says how it is framed, and settles that for every primitive and both array shapes.
1031//
1032// Six things the descriptor does not settle, and each is named:
1033//
1034//  * `address`, whose runtime class decides how long its body is. See readerDisplayAddress.
1035//  * `displayCutout`, which has no write sequence to follow at all. See readerDisplayCutout.
1036//  * `deviceProductInfo`, whose `mEdidStructureMetadata` is a typed write inside a named write.
1037//  * `frameRateCategoryRate`, a Parcelable whose values this module does not use.
1038//  * `frameRateVelocityMapping`, a `writeTypedList` -- a count and then a body per element, all of
1039//    them the class the jar's own sequence was read for. Android 17 added it in place of the
1040//    `appsSupportedModes` that 16 wrote, which was an array of Parcelables written by a loop rather
1041//    than by a `write*Array`, and which `supportedModes` is still the shape of.
1042//  * The `String` fields. `DisplayInfo` writes its own through `writeString8` -- a byte count and
1043//    UTF-8 -- while `DeviceProductInfo` writes its through `writeString`, which is a code-unit count
1044//    and UTF-16; the descriptor is `Ljava/lang/String;` either way. Within one class the framing is
1045//    the class's habit rather than the field's, so a `String` this does not know is read the way the
1046//    ones it does are, which is what a field a later release adds gets.
1047//
1048// A *name* this does not know is stepped over by its type, which is what makes a field a later
1049// release added cost its bytes and nothing else -- `displayGroupId`, `renderFrameRate`,
1050// `hasArrSupport`, `supportedRefreshRates`, `userPreferredModeId`, `appsSupportedModes`, `isForceSdr`
1051// and `committedState` are all of them. A *type* this does not know is a failure: the descriptor is
1052// the only thing that says how long the field is, and a `String` alone can be two different lengths.
1053static bool displayInfoField(FFDisplayAndroidReader* reader, const FFDexParcelField* field, const FFDisplayAndroidLayout* layout, FFDisplayAndroidWalk* walk) {
1054    FFDisplayAndroidInfo* info = walk->info;
1055    const char* name = field->name;
1056
1057    if (ffStrEquals(name, "address")) {
1058        return readerDisplayAddress(reader, layout);
1059    }
1060    if (ffStrEquals(name, "deviceProductInfo")) {
1061        return readerDeviceProductInfo(reader, layout, &info->manufactureYear, &info->manufactureWeek);
1062    }
1063    if (ffStrEquals(name, "displayCutout")) {
1064        return readerDisplayCutout(reader, layout);
1065    }
1066    if (ffStrEquals(name, "hdrCapabilities")) {
1067        return readerHdrCapabilities(reader, layout, &info->hdrTypeCount);
1068    }
1069    if (ffStrEquals(name, "frameRateCategoryRate")) {
1070        return readerSkippedParcelable(reader, FF_DISPLAY_ANDROID_NAME_FRAME_RATE_CATEGORY_RATE, layout->frameRateCategoryRate, layout->frameRateCategoryRateCount);
1071    }
1072    if (ffStrEquals(name, "frameRateVelocityMapping")) {
1073        return readerTypedObjectList(reader, layout->velocityPoint, layout->velocityPointCount);
1074    }
1075    if (ffStrEquals(name, "supportedModes") || ffStrEquals(name, "appsSupportedModes")) {
1076        // The two ids the list is resolved against are written in front of it on every release seen.
1077        // One that moved behind it would leave the list resolved against nothing, so the walk says so
1078        // rather than reporting the mode of whatever id happens to sit at the sentinel.
1079        const bool wanted = ffStrEquals(name, "supportedModes");
1080        if (wanted && (info->modeId == INT32_MIN || info->defaultModeId == INT32_MIN)) {
1081            reader->failed = true;
1082            return false;
1083        }
1084        return readerModeList(reader, layout, info->modeId, info->defaultModeId,
1085            wanted ? &walk->active : nullptr, wanted ? &walk->preferred : nullptr);
1086    }
1087    if (ffStrEquals(field->type, "Ljava/lang/String;")) {
1088        if (ffStrEquals(name, "name")) {
1089            return readerString8(reader, &info->name);
1090        }
1091        if (ffStrEquals(name, "uniqueId")) {
1092            return readerString8(reader, walk->uniqueId);
1093        }
1094        return readerString8(reader, nullptr); // `ownerPackageName`, and anything a later release adds
1095    }
1096
1097    FFDisplayAndroidScalar value = {};
1098    if (!readerScalar(reader, field->type, &value)) {
1099        return false;
1100    }
1101    if (ffStrEquals(name, "type")) {
1102        info->type = value.i32;
1103    } else if (ffStrEquals(name, "displayId")) {
1104        info->displayId = (uint32_t) value.i32;
1105    } else if (ffStrEquals(name, "logicalWidth")) {
1106        info->width = (uint32_t) value.i32;
1107    } else if (ffStrEquals(name, "logicalHeight")) {
1108        info->height = (uint32_t) value.i32;
1109    } else if (ffStrEquals(name, "rotation")) {
1110        info->rotation = (uint32_t) value.i32;
1111    } else if (ffStrEquals(name, "modeId")) {
1112        info->modeId = value.i32;
1113    } else if (ffStrEquals(name, "defaultModeId")) {
1114        info->defaultModeId = value.i32;
1115    } else if (ffStrEquals(name, "renderFrameRate")) {
1116        if (!(value.f32 > 0) || value.f32 > 1000) {
1117            reader->failed = true;
1118            return false;
1119        }
1120        info->renderFrameRate = value.f32;
1121    } else if (ffStrEquals(name, "logicalDensityDpi")) {
1122        info->density = (uint32_t) value.i32;
1123    } else if (ffStrEquals(name, "physicalXDpi")) {
1124        info->physicalXDpi = value.f32;
1125    } else if (ffStrEquals(name, "physicalYDpi")) {
1126        info->physicalYDpi = value.f32;
1127    } else if (ffStrEquals(name, "isForceSdr")) {
1128        info->forceSdr = value.i32 != 0;
1129    }
1130    return true;
1131}
1132
1133// How much of a reply a failure prints. A record is 2404 bytes on the test device and most of it is
1134// the two mode lists, so this covers one whole reply -- and the cap is there only so that a reply
1135// which is not one cannot fill the log.
1136#define FF_DISPLAY_ANDROID_DEBUG_DUMP_SIZE 4096
1137
1138#ifndef NDEBUG
1139// The reply is read by shape, so a reply that fails leaves nothing behind to diagnose it with: the
1140// bytes are the only evidence. Each line carries the text beside the bytes, because what the walk
1141// hangs on is the class name in front of a nested body -- UTF-16, and the one thing that a length
1142// read out of the middle of a different field cannot be told from without looking at it.
1143static void debugDumpReply(const uint8_t* data, size_t size) {
1144    const size_t limit = size < FF_DISPLAY_ANDROID_DEBUG_DUMP_SIZE ? size : FF_DISPLAY_ANDROID_DEBUG_DUMP_SIZE;
1145    for (size_t offset = 0; offset < limit; offset += 16) {
1146        char hex[16 * 3 + 1];
1147        char text[16 + 1];
1148        size_t used = 0;
1149        for (size_t i = 0; i < 16 && offset + i < limit; ++i) {
1150            used += (size_t) snprintf(hex + used, sizeof(hex) - used, "%02x ", data[offset + i]);
1151            text[i] = data[offset + i] >= 32 && data[offset + i] < 127 ? (char) data[offset + i] : '.';
1152            text[i + 1] = '\0';
1153        }
1154        FF_DEBUG("  +0x%04zx  %-47s | %s", offset, hex, text);
1155    }
1156}
1157    #define FF_DISPLAY_ANDROID_DEBUG_DUMP(data, size) debugDumpReply(data, size)
1158#else
1159    #define FF_DISPLAY_ANDROID_DEBUG_DUMP(data, size) ((void) 0)
1160#endif
1161
1162// One `getDisplayInfo` reply: the exception code the stub writes, the non-null marker
1163// `writeTypedObject` writes in front of the Parcelable, and the record itself -- walked in the order
1164// the device's own `DisplayInfo.writeToParcel` writes it, out of the sequence `displayLayoutLoad`
1165// read from that device's jar.
1166static bool displayInfoParse(const uint8_t* data, size_t size, const FFDisplayAndroidLayout* layout, FFDisplayAndroidInfo* info) {
1167    FFDisplayAndroidReader reader = { .data = data, .size = size, .offset = 0, .failed = false };
1168
1169    const int32_t exception = readerI32(&reader);
1170    if (exception != 0) {
1171        FF_DEBUG("getDisplayInfo raised exception %d", exception);
1172        return false;
1173    }
1174    if (readerI32(&reader) != 1) {
1175        FF_DEBUG("getDisplayInfo answered no display");
1176        FF_DISPLAY_ANDROID_DEBUG_DUMP(data, size);
1177        return false;
1178    }
1179
1180    // Sentinels, so a field the release does not write can not be mistaken for one that was read as
1181    // zero. Every one of these is in the sequence on every release seen and the check below is what
1182    // says so; the two that are not -- `renderFrameRate` and `isForceSdr` -- have a defined fallback
1183    // and are the only ones left out of it.
1184    info->type = -1;
1185    info->displayId = UINT32_MAX;
1186    info->width = 0;
1187    info->height = 0;
1188    info->rotation = UINT32_MAX;
1189    info->modeId = INT32_MIN;
1190    info->defaultModeId = INT32_MIN;
1191    info->hdrTypeCount = 0;
1192    info->renderFrameRate = 0;
1193    info->density = 0;
1194    info->physicalXDpi = 0;
1195    info->physicalYDpi = 0;
1196    info->forceSdr = false;
1197
1198    FF_STRBUF_AUTO_DESTROY uniqueId = ffStrbufCreate();
1199    FFDisplayAndroidWalk walk = { .info = info, .uniqueId = &uniqueId };
1200
1201    // `uniqueId` is the last field this module reads. Everything behind it -- `removeMode`, the
1202    // brightness, the corners and shapes, the throttling -- would have to be walked for nothing, so
1203    // the walk stops here rather than implementing four more nested shapes no value comes out of.
1204    bool complete = false;
1205    for (uint32_t i = 0; i < layout->displayInfoCount; ++i) {
1206        const FFDexParcelField* field = &layout->displayInfo[i];
1207        if (!displayInfoField(&reader, field, layout, &walk)) {
1208            // Which field, out of how many, what the jar says it is and how far the walk got before it
1209            // gave up -- followed by the reply itself, which is what says whether the layout moved or
1210            // this parser is reading a shape it was not written for.
1211            FF_DEBUG(
1212                "getDisplayInfo(%u) answered a %s (%s) this parser does not recognise -- field %u of %u, cursor at %zu of %zu",
1213                info->displayId, field->name, field->type, i + 1, layout->displayInfoCount, reader.offset, reader.size
1214            );
1215            FF_DISPLAY_ANDROID_DEBUG_DUMP(data, size);
1216            return false;
1217        }
1218        if (ffStrEquals(field->name, "uniqueId")) {
1219            complete = true;
1220            break;
1221        }
1222    }
1223    if (!complete) {
1224        FF_DEBUG("getDisplayInfo(%u) answered a record with no uniqueId", info->displayId);
1225        FF_DISPLAY_ANDROID_DEBUG_DUMP(data, size);
1226        return false;
1227    }
1228
1229    if (info->type < 0 || info->type > FF_DISPLAY_ANDROID_TYPE_VIRTUAL
1230        || info->displayId == UINT32_MAX
1231        || info->width == 0 || info->width > 65536 || info->height == 0 || info->height > 65536
1232        || info->rotation > 3
1233        || info->modeId < 0 || info->defaultModeId < -1
1234        || info->density > 4000
1235        || !(info->physicalXDpi >= 0) || info->physicalXDpi > 2000
1236        || !(info->physicalYDpi >= 0) || info->physicalYDpi > 2000
1237        // A display with no name is still a display -- the key falls back to a number -- so only a
1238        // name that could not have come from a string is a reason to drop the record.
1239        || info->name.length > 256
1240        || uniqueId.length > 256) {
1241        FF_DEBUG(
1242            "getDisplayInfo(%u) answered type %d, %u x %u, rotation %u, mode %d, density %u, %f x %f dpi",
1243            info->displayId, info->type, info->width, info->height, info->rotation, info->modeId, info->density,
1244            info->physicalXDpi, info->physicalYDpi
1245        );
1246        FF_DISPLAY_ANDROID_DEBUG_DUMP(data, size);
1247        return false;
1248    }
1249
1250    info->preferredWidth = walk.preferred.width;
1251    info->preferredHeight = walk.preferred.height;
1252    info->preferredRefreshRate = walk.preferred.refreshRate;
1253    // The nominal rate of the active mode, which is what `Display.getRefreshRate()` reports and what
1254    // every other platform reports. `renderFrameRate` is only used when the list has no entry for
1255    // `modeId`, because it is a render cadence that follows the content rather than a property of the
1256    // display -- and it is not written at all before Android 15. `refreshRateOverride` is deliberately
1257    // not consulted either: it is what an app asked the framework to hold the panel at, not a
1258    // property of the display.
1259    info->refreshRate = walk.active.refreshRate > 0 ? walk.active.refreshRate : info->renderFrameRate;
1260    info->id = displayUniqueId(uniqueId.chars);
1261    return true;
1262}
1263
1264static FFDisplayType displayType(int32_t type) {
1265    switch ((FFDisplayAndroidType) type) {
1266        case FF_DISPLAY_ANDROID_TYPE_INTERNAL:
1267            return FF_DISPLAY_TYPE_BUILTIN;
1268        case FF_DISPLAY_ANDROID_TYPE_EXTERNAL:
1269        case FF_DISPLAY_ANDROID_TYPE_WIFI:
1270            // A WIFI display is a wireless sink, which is as external as a wired one
1271            return FF_DISPLAY_TYPE_EXTERNAL;
1272        default:
1273            return FF_DISPLAY_TYPE_UNKNOWN;
1274    }
1275}
1276
1277// `hdrCapabilities.mSupportedHdrTypes` is the display's own answer and an empty list means it can not
1278// do HDR at all. `isForceSdr` is the framework having turned every capability of this display off and
1279// `persist.sys.hdr_mode` is the vendor's switch, so `Supported` and `Enabled` stay two different
1280// questions: the list is what the panel can do, those two are whether anything is asking for it.
1281//
1282// A non-empty list does not mean the display does HDR. The list is the answer of the vendor's composer
1283// HAL, which SurfaceFlinger and DisplayManagerService pass through unchanged, and it is wrong on the
1284// test device (vivo PD2505, Android 17): `dumpsys display` reports `mSupportedHdrTypes=[1, 2, 3]` --
1285// Dolby Vision, HDR10 and HLG -- for a Samsung U32J59x that does not do HDR at all, and the same
1286// three types hang off every one of its forty-odd modes, down to `640x480@60`, which is a VESA DMT
1287// mode and not a CTA-861 one. An answer derived per mode from the sink's EDID HDR Static Metadata
1288// block would not look like that, and no Samsung monitor claims Dolby Vision. Nothing on the device
1289// contradicts it either: SurfaceFlinger dumps every mode of that display as `hdrOutputType=INVALID`
1290// where the built-in panel's are `SYSTEM`, its only color mode is `ColorMode::NATIVE`, and `cmd
1291// display get-reported-hdr-types 4` repeats the same `[1, 2, 3]`. The raw EDID would settle it and is
1292// out of reach: `/sys/class/drm/card0-DP-1/edid` exists but is denied by SELinux, and `adb root` is
1293// refused on a retail build. So the list is reported as it arrives -- it is the only answer the
1294// platform gives, and there is nothing here to cross check it against.
1295static FFDisplayHdrStatus displayHdrStatus(const FFDisplayAndroidInfo* info) {
1296    if (info->hdrTypeCount <= 0) {
1297        return FF_DISPLAY_HDR_STATUS_UNSUPPORTED;
1298    }
1299    FF_STRBUF_AUTO_DESTROY hdrMode = ffStrbufCreate();
1300    if (!info->forceSdr && ffSettingsGetAndroidProperty("persist.sys.hdr_mode", &hdrMode) && ffStrbufToUInt(&hdrMode, 0) > 0) {
1301        return FF_DISPLAY_HDR_STATUS_ENABLED;
1302    }
1303    return FF_DISPLAY_HDR_STATUS_SUPPORTED;
1304}
1305
1306// Reads every enabled display over binder. Returns false without appending anything when the service,
1307// the transaction or the layout is not what this parser was written for, which is also what hands the
1308// module over to the `cmd` fallback.
1309static bool detectWithBinder(FFDisplayServerResult* ds) {
1310    // The write sequence first. It is what the whole parse is made of, and a build whose jar cannot be
1311    // read has nothing to walk -- so failing here costs one file open rather than a round trip to a
1312    // service whose answer could not be read anyway.
1313    FFDisplayAndroidLayout layout = {};
1314    if (!displayLayoutLoad(FF_DISPLAY_ANDROID_JAR, &layout)) {
1315        return false;
1316    }
1317
1318    [[gnu::cleanup(ffBinderClose)]] FFBinder binder = { .fd = -1 };
1319    const char* error = ffBinderOpen(&binder);
1320    if (error != nullptr) {
1321        FF_DEBUG("Reading the display over binder failed: %s", error);
1322        return false;
1323    }
1324
1325    // Released on every path out, including the early returns below.
1326    [[gnu::cleanup(ffBinderServiceHandleRelease)]] FFBinderServiceHandle service = { .binder = &binder };
1327    error = ffBinderLookupService(&binder, FF_DISPLAY_ANDROID_SERVICE, FF_BINDER_SM_GET_SERVICE, &service.handle);
1328    if (error != nullptr) {
1329        FF_DEBUG("Looking up the \"%s\" service failed: %s", FF_DISPLAY_ANDROID_SERVICE, error);
1330        return false;
1331    }
1332
1333    uint8_t parcelBuffer[FF_DISPLAY_ANDROID_PARCEL_SIZE];
1334    uint8_t replyBuffer[FF_DISPLAY_ANDROID_REPLY_SIZE];
1335
1336    // `getDisplayIds(false)` is the list of the enabled displays, which is the set a display dump
1337    // prints. It is a separate call rather than a loop over 0..n because the ids are not contiguous,
1338    // and it is twelve bytes.
1339    //
1340    // The argument is written unconditionally although the method takes none on Android 11: the
1341    // generated stub reads the parameters its own signature declares and leaves the rest of the
1342    // parcel alone, so the extra int is dropped on those releases.
1343    FFBinderParcel parcel = ffBinderParcelCreate(parcelBuffer, sizeof(parcelBuffer));
1344    ffBinderParcelPutInterfaceToken(&parcel, FF_DISPLAY_ANDROID_DESCRIPTOR);
1345    ffBinderParcelPutI32(&parcel, 0); // includeDisabled
1346
1347    FFBinderReply reply = ffBinderReplyCreate(replyBuffer, sizeof(replyBuffer));
1348    error = ffBinderTransact(&binder, service.handle, FF_DISPLAY_ANDROID_TRANSACTION_GET_DISPLAY_IDS, 0, &parcel, &reply);
1349    if (error != nullptr) {
1350        FF_DEBUG("getDisplayIds failed: %s", error);
1351        return false;
1352    }
1353    if (ffBinderReplyIsStatus(&reply)) {
1354        FF_DEBUG("getDisplayIds answered status %d instead of a parcel", (int) ffBinderReadI32(reply.data, reply.size, 0));
1355        return false;
1356    }
1357    const int32_t exception = ffBinderReadI32(reply.data, reply.size, 0);
1358    if (exception != 0) {
1359        FF_DEBUG("getDisplayIds raised exception %d", exception);
1360        return false;
1361    }
1362    const int32_t count = ffBinderReadI32(reply.data, reply.size, sizeof(int32_t));
1363    if (count <= 0 || count > FF_DISPLAY_ANDROID_MAX_DISPLAYS) {
1364        FF_DEBUG("getDisplayIds answered %d displays", count);
1365        FF_DISPLAY_ANDROID_DEBUG_DUMP(reply.data, reply.size);
1366        return false;
1367    }
1368
1369    int32_t displayIds[FF_DISPLAY_ANDROID_MAX_DISPLAYS] = {};
1370    for (int32_t i = 0; i < count; ++i) {
1371        displayIds[i] = ffBinderReadI32(reply.data, reply.size, (size_t) (2 + i) * sizeof(int32_t));
1372        if (displayIds[i] < 0) {
1373            FF_DEBUG("getDisplayIds answered display id %d", displayIds[i]);
1374            FF_DISPLAY_ANDROID_DEBUG_DUMP(reply.data, reply.size);
1375            return false;
1376        }
1377    }
1378
1379    // Every display has to parse before any of them is reported, so the records are held here until
1380    // the loop has been through all of them.
1381    FFDisplayAndroidInfo infos[FF_DISPLAY_ANDROID_MAX_DISPLAYS] = {};
1382    int32_t parsed = 0;
1383    for (; parsed < count; ++parsed) {
1384        FFDisplayAndroidInfo* info = &infos[parsed];
1385        ffStrbufInit(&info->name);
1386
1387        FFBinderParcel request = ffBinderParcelCreate(parcelBuffer, sizeof(parcelBuffer));
1388        ffBinderParcelPutInterfaceToken(&request, FF_DISPLAY_ANDROID_DESCRIPTOR);
1389        ffBinderParcelPutI32(&request, displayIds[parsed]);
1390
1391        reply = ffBinderReplyCreate(replyBuffer, sizeof(replyBuffer));
1392        error = ffBinderTransact(&binder, service.handle, FF_DISPLAY_ANDROID_TRANSACTION_GET_DISPLAY_INFO, 0, &request, &reply);
1393        if (error != nullptr) {
1394            FF_DEBUG("getDisplayInfo(%d) failed: %s", displayIds[parsed], error);
1395            break;
1396        }
1397        if (ffBinderReplyIsStatus(&reply)) {
1398            FF_DEBUG("getDisplayInfo(%d) answered status %d instead of a parcel", displayIds[parsed], (int) ffBinderReadI32(reply.data, reply.size, 0));
1399            break;
1400        }
1401        if (!displayInfoParse(reply.data, reply.size, &layout, info)) {
1402            FF_DEBUG("getDisplayInfo(%d) answered a record this parser does not recognise", displayIds[parsed]);
1403            break;
1404        }
1405    }
1406
1407    if (parsed != count) {
1408        for (int32_t i = 0; i <= parsed && i < count; ++i) {
1409            ffStrbufDestroy(&infos[i].name);
1410        }
1411        return false;
1412    }
1413
1414    for (int32_t i = 0; i < count; ++i) {
1415        FFDisplayAndroidInfo* info = &infos[i];
1416
1417        // The physical dpi describes the panel itself and does not change with the logical display
1418        // size, so the physical size has to be derived from the native resolution. Nothing in the
1419        // record carries it directly.
1420        uint32_t physicalWidth = 0, physicalHeight = 0;
1421        if (info->physicalXDpi > 0) {
1422            physicalWidth = (uint32_t) ((info->preferredWidth ? info->preferredWidth : info->width) * 25.4 / info->physicalXDpi + 0.5);
1423        }
1424        if (info->physicalYDpi > 0) {
1425            physicalHeight = (uint32_t) ((info->preferredHeight ? info->preferredHeight : info->height) * 25.4 / info->physicalYDpi + 0.5);
1426        }
1427
1428        FFDisplayResult* display = ffdsAppendDisplay(ds,
1429            info->width,
1430            info->height,
1431            info->refreshRate,
1432            info->density * 96 / 160,
1433            info->preferredWidth,
1434            info->preferredHeight,
1435            info->preferredRefreshRate,
1436            info->rotation,
1437            &info->name, // moved
1438            displayType(info->type),
1439            info->displayId == 0, // Display 0 is the default one
1440            info->id,
1441            physicalWidth,
1442            physicalHeight,
1443            "binder");
1444        if (display == nullptr) {
1445            // `ffdsAppendDisplay` moves `name` only when it keeps the display, so it is still ours
1446            ffStrbufDestroy(&info->name);
1447            continue;
1448        }
1449        display->manufactureYear = info->manufactureYear;
1450        display->manufactureWeek = info->manufactureWeek;
1451        display->hdrStatus = displayHdrStatus(info);
1452    }
1453    return ds->displays.length > 0;
1454}
1455
1456// ---------------------------------------------------------------------------------------------
1457// The `cmd` fallback
1458// ---------------------------------------------------------------------------------------------
1459//
1460// The route behind the walk, for a build whose `framework.jar` can not be read at all or whose parcel
1461// layout is not one the walk knows. `cmd display get-displays` prints the `DisplayInfo` of every
1462// display as text, one record per line, so the same fields are there to read with no dex and no
1463// parcel -- at the price of a fork, which is what the walk above exists to avoid.
1464//
1465// It needs no permission, which is what makes it usable for an app UID, but the subcommand was only
1466// added to `DisplayManagerShellCommand` in Android 13: Android 11 and 12 answer `Unknown command:
1467// get-displays` on stdout with exit code 255 and this route reports nothing. Those two releases are
1468// read by the walk instead, so the fallback only has to cover 13 and newer.
1469//
1470// The record layout has changed across releases, and every difference is accepted rather than version
1471// checked:
1472//
1473//  * The mode list is printed as `modes [...]` up to Android 14 and as `supportedModes [...]` from
1474//    Android 15 on, which also prints `appsSupportedModes [...]` right behind it. Both spellings are
1475//    searched for.
1476//  * `renderFrameRate` is printed from Android 15 on. Before that the active mode's fps is the only
1477//    refresh rate the dump carries. The active mode's fps is preferred even where it exists, see
1478//    the comment on `refreshRate` below.
1479//  * `displayGroupId` is printed from Android 12 on, the physical dpi behind `density` from
1480//    Android 11 on, and `isForceSdr` from Android 15 on.
1481//
1482// A record is one line and the `DisplayInfo{` inside it is what gets parsed, so the `Display id 0: `
1483// prefix the command prints in front of each record is stepped over by the same search that finds it.
1484// `marker` and `platformApi` are the two things a second command would differ in; only `cmd` is wired
1485// up, `dumpsys display` prints the same records behind `mBaseDisplayInfo=` and is not a route here.
1486static bool detectWithCommand(FFDisplayServerResult* ds, char* const argv[], const char* marker, const char* platformApi) {
1487    FF_STRBUF_AUTO_DESTROY buf = ffStrbufCreate();
1488    FFProcessHandle handle;
1489    // `cmd` forwards its stdin to the service over binder, and the kernel rejects the whole
1490    // transaction when that fd is a terminal, which it is whenever fastfetch runs in a terminal.
1491    // Detaching the child from our stdin is only needed here, so the low level API is called instead
1492    // of `ffProcessAppendStdOut`.
1493    if (ffProcessSpawn(argv, FF_PROCESS_OUTPUT_STDOUT_BIT, ffGetNullFD(), &handle) != nullptr) {
1494        return false; // `/system/bin/cmd` is not there before Android 13
1495    }
1496
1497    if (ffProcessReadOutput(&handle, &buf) != nullptr || buf.length == 0) {
1498        return false;
1499    }
1500    ffStrbufTrimRightSpace(&buf);
1501
1502    uint32_t index = 0;
1503    while ((index = ffStrbufNextIndexS(&buf, index, marker)) < buf.length) {
1504        index += strlen(marker);
1505
1506        uint32_t nextIndex = ffStrbufNextIndexC(&buf, index, '\n');
1507        buf.chars[nextIndex] = '\0';
1508        const char* info = buf.chars + index;
1509
1510        // 0: DisplayInfo{"Builtin display", displayId 0, ..., real 1440 x 3168, ..., mode 2,
1511        //    renderFrameRate 60.000004, ..., defaultMode 4, ..., supportedModes [{id=2,
1512        //    width=1440, height=3168, fps=60.000004, ...}], ..., hdrCapabilities
1513        //    HdrCapabilities{mSupportedHdrTypes=[1, 2, 3, 4], ...}, isForceSdr false, ...,
1514        //    rotation 0, ..., type INTERNAL, uniqueId "local:4630946557703207059", ...,
1515        //    density 560 (560.0 x 560.0) dpi, ..., deviceProductInfo DeviceProductInfo{...,
1516        //    manufactureDate=ManufactureDate{week=27, year=2006}, ...}, ...}
1517        const char* field = strstr(info, "DisplayInfo{\"");
1518        FF_STRBUF_AUTO_DESTROY name = ffStrbufCreateA(64);
1519        if (field) {
1520            field += strlen("DisplayInfo{\"");
1521            const char* nameEnd = strchr(field, '"');
1522            if (nameEnd) {
1523                ffStrbufAppendNS(&name, (uint32_t) (nameEnd - field), field);
1524            }
1525        }
1526
1527        // `real` is the size the display currently uses, which is smaller than the panel's when the
1528        // framework emulates a smaller display size
1529        unsigned width = 0, height = 0;
1530        if ((field = strstr(info, ", real ")) && sscanf(field, ", real %u x %u", &width, &height) < 2) {
1531            width = height = 0;
1532        }
1533
1534        // `renderFrameRate` is printed from Android 15 on. It is documented as "a divisor of the
1535        // active mode refresh rate", so it is the rate the display is currently *rendering* at and
1536        // can be lower than the mode it is set to. It is therefore only used for a record whose
1537        // mode list can not be read, where a possibly divided rate still beats none.
1538        double renderFrameRate = 0;
1539        if ((field = strstr(info, ", renderFrameRate ")) && sscanf(field, ", renderFrameRate %lf", &renderFrameRate) < 1) {
1540            renderFrameRate = 0;
1541        }
1542
1543        unsigned activeMode = 0, defaultMode = 0;
1544        if ((field = strstr(info, ", mode ")) && sscanf(field, ", mode %u", &activeMode) < 1) {
1545            activeMode = 0;
1546        }
1547        if ((field = strstr(info, ", defaultMode ")) && sscanf(field, ", defaultMode %u", &defaultMode) < 1) {
1548            defaultMode = 0;
1549        }
1550
1551        // The modes are listed with their resolution in the natural orientation, which is also how
1552        // the preferred values are reported on the other platforms. `defaultMode` is the mode the
1553        // display itself prefers, so it carries the panel's native resolution.
1554        //
1555        // One entry is a `{...}`, and the three fields are searched for inside it rather than matched
1556        // as a single prefix: Android 15 prints `parentModeId`, `sfModeId` and `flags` between `id`
1557        // and `width`, where `sscanf("{id=%u, width=%u, ...")` stops after the id and reports one
1558        // conversion -- measured on the test device, that is a preferred resolution of 0 x 0 and a
1559        // rate that falls back to `renderFrameRate`. Everything up to the entry's own closing brace
1560        // belongs to it, so a field past that brace is the next entry's and ends the walk.
1561        uint32_t preferredWidth = 0, preferredHeight = 0;
1562        double preferredRefreshRate = 0, activeModeRefreshRate = 0;
1563        field = strstr(info, ", supportedModes [");
1564        if (field == nullptr) {
1565            field = strstr(info, ", modes ["); // Android 14 and older
1566        }
1567        while (field && (field = strstr(field, "{id="))) {
1568            // {id=2, ..., width=1440, height=3168, fps=60.000004, ...
1569            const char* end = strchr(field, '}');
1570            const char* modeWidthField = strstr(field, ", width=");
1571            const char* modeHeightField = strstr(field, ", height=");
1572            const char* modeFpsField = strstr(field, ", fps=");
1573            unsigned id = 0, modeWidth = 0, modeHeight = 0;
1574            double fps = 0;
1575            if (end == nullptr || modeWidthField == nullptr || modeWidthField > end ||
1576                modeHeightField == nullptr || modeHeightField > end ||
1577                modeFpsField == nullptr || modeFpsField > end) {
1578                break; // Not a mode entry this parser can read
1579            }
1580            if (sscanf(field, "{id=%u", &id) < 1 ||
1581                sscanf(modeWidthField, ", width=%u", &modeWidth) < 1 ||
1582                sscanf(modeHeightField, ", height=%u", &modeHeight) < 1 ||
1583                sscanf(modeFpsField, ", fps=%lf", &fps) < 1) {
1584                break;
1585            }
1586            if (id == activeMode) {
1587                activeModeRefreshRate = fps;
1588            }
1589            if (id == defaultMode) {
1590                preferredWidth = modeWidth;
1591                preferredHeight = modeHeight;
1592                preferredRefreshRate = fps;
1593            }
1594            if (activeModeRefreshRate > 0 && preferredWidth > 0) {
1595                break; // Both are in, and the same list is printed a second time from Android 15 on
1596            }
1597            ++field;
1598        }
1599
1600        // The nominal rate of the active mode, which is what `Display.getRefreshRate()` reports
1601        // (`refreshRateOverride` if it is set, the mode's own rate otherwise) and what every other
1602        // platform reports. `renderFrameRate` is deliberately not preferred: it is a render
1603        // cadence that follows the content rather than a property of the display, and it does not
1604        // exist before Android 15, so using it would make the reported rate change with the
1605        // Android version as well as with what is on screen.
1606        double refreshRate = activeModeRefreshRate;
1607        if (refreshRate <= 0) {
1608            refreshRate = renderFrameRate;
1609        }
1610
1611        unsigned rotation = 0;
1612        if ((field = strstr(info, ", rotation ")) && sscanf(field, ", rotation %u", &rotation) < 1) {
1613            rotation = 0;
1614        }
1615
1616        FFDisplayType type = FF_DISPLAY_TYPE_UNKNOWN;
1617        if ((field = strstr(info, ", type "))) {
1618            field += strlen(", type ");
1619            if (ffStrStartsWith(field, "INTERNAL")) {
1620                type = FF_DISPLAY_TYPE_BUILTIN;
1621            } else if (ffStrStartsWith(field, "EXTERNAL") || ffStrStartsWith(field, "WIFI")) {
1622                // A WIFI display is a wireless sink, which is as external as a wired one
1623                type = FF_DISPLAY_TYPE_EXTERNAL;
1624            }
1625        }
1626
1627        unsigned density = 0;
1628        double physicalXDpi = 0, physicalYDpi = 0;
1629        if ((field = strstr(info, ", density "))) {
1630            // `density 640 (501.0411 x 509.28604) dpi`, the physical dpi is only printed since
1631            // Android 11
1632            if (sscanf(field, ", density %u (%lf x %lf) dpi", &density, &physicalXDpi, &physicalYDpi) < 1) {
1633                density = 0;
1634            }
1635        }
1636
1637        // The physical dpi describes the panel itself and does not change with the logical display
1638        // size, so the physical size has to be derived from the native resolution
1639        uint32_t physicalWidth = 0, physicalHeight = 0;
1640        if (physicalXDpi > 0) {
1641            physicalWidth = (uint32_t) ((preferredWidth ? preferredWidth : width) * 25.4 / physicalXDpi + 0.5);
1642        }
1643        if (physicalYDpi > 0) {
1644            physicalHeight = (uint32_t) ((preferredHeight ? preferredHeight : height) * 25.4 / physicalYDpi + 0.5);
1645        }
1646
1647        // `uniqueId` identifies the display across reboots, e.g. `local:4630946557703207059` on a
1648        // physical display and `virtual:...` on a virtual one
1649        uint64_t id = 0;
1650        if ((field = strstr(info, ", uniqueId \""))) {
1651            field += strlen(", uniqueId \"");
1652            const char* uniqueIdEnd = strchr(field, '"');
1653            const char* digits = uniqueIdEnd ? memchr(field, ':', (size_t) (uniqueIdEnd - field)) : nullptr;
1654            id = (uint64_t) strtoull(digits ? digits + 1 : field, nullptr, 10);
1655        }
1656
1657        uint16_t manufactureYear = 0, manufactureWeek = 0;
1658        if ((field = strstr(info, ", deviceProductInfo "))) {
1659            // `manufactureDate=ManufactureDate{week=27, year=2006}`, either field may be `null`
1660            const char* date = strstr(field, "manufactureDate=ManufactureDate{");
1661            unsigned year = 0, week = 0;
1662            if (date && sscanf(date + strlen("manufactureDate=ManufactureDate{"), "week=%u, year=%u", &week, &year) == 2) {
1663                manufactureYear = (uint16_t) year;
1664                manufactureWeek = (uint16_t) week;
1665            } else if ((field = strstr(field, ", modelYear=")) && sscanf(field, ", modelYear=%u", &year) == 1) {
1666                // A display reports either the date of manufacture or the model year
1667                manufactureYear = (uint16_t) year;
1668            }
1669        }
1670
1671        // `displayId` sits inside the record, not in front of it: the id in the prefix the command
1672        // prints is what the marker search stepped over, and the field itself is what this reads
1673        unsigned displayId = 0;
1674        if ((field = strstr(info, ", displayId ")) && sscanf(field, ", displayId %u", &displayId) < 1) {
1675            displayId = 0;
1676        }
1677        bool primary = displayId == 0; // Display 0 is the default one
1678
1679        // Android counts density in dpi with 160 as the 1x baseline, fastfetch uses 96
1680        FFDisplayResult* display = ffdsAppendDisplay(ds,
1681            width,
1682            height,
1683            refreshRate,
1684            density * 96 / 160,
1685            preferredWidth,
1686            preferredHeight,
1687            preferredRefreshRate,
1688            rotation,
1689            &name,
1690            type,
1691            primary,
1692            id,
1693            physicalWidth,
1694            physicalHeight,
1695            platformApi);
1696        if (display) {
1697            display->manufactureYear = manufactureYear;
1698            display->manufactureWeek = manufactureWeek;
1699
1700            // Reported for every display, not only for the built-in one: `hdrCapabilities` is a
1701            // field of the `DisplayInfo` record itself, so it describes that display and nothing
1702            // else, and the other platforms report HDR per display too (EDID on Linux, the
1703            // advanced color info per target on Windows). An external display or a wireless sink
1704            // carries the field as well.
1705            //
1706            // `hdrCapabilities HdrCapabilities{mSupportedHdrTypes=[1, 2, 3, 4], ...}` is printed
1707            // since Android 11, where an empty list means that the display can not do HDR at all.
1708            // The two fallbacks below it are device wide vendor properties, which is the price of
1709            // answering for a record that does not print the field.
1710            //
1711            // This is the same field and the same vendor answer the walk above reads, so the same
1712            // caveat applies: a non-HDR display can arrive with a non-empty list, see
1713            // displayHdrStatus.
1714            FF_STRBUF_AUTO_DESTROY buffer = ffStrbufCreate();
1715            field = strstr(info, "hdrCapabilities HdrCapabilities{mSupportedHdrTypes=[");
1716            if (field) {
1717                field += strlen("hdrCapabilities HdrCapabilities{mSupportedHdrTypes=[");
1718                display->hdrStatus = *field == ']' ? FF_DISPLAY_HDR_STATUS_UNSUPPORTED : FF_DISPLAY_HDR_STATUS_SUPPORTED;
1719            } else if (ffSettingsGetAndroidProperty("ro.surface_flinger.has_HDR_display", &buffer)) {
1720                display->hdrStatus = ffStrbufIgnCaseEqualS(&buffer, "true") ? FF_DISPLAY_HDR_STATUS_SUPPORTED : FF_DISPLAY_HDR_STATUS_UNSUPPORTED;
1721            } else {
1722                display->hdrStatus = FF_DISPLAY_HDR_STATUS_UNKNOWN;
1723            }
1724
1725            if (display->hdrStatus == FF_DISPLAY_HDR_STATUS_SUPPORTED) {
1726                // `persist.sys.hdr_mode` is non-zero while HDR is turned on, and `isForceSdr
1727                // true` means that the framework disabled every HDR capability of this display.
1728                // Note that `ffSettingsGetAndroidProperty` appends, so the value needs its own
1729                // buffer.
1730                FF_STRBUF_AUTO_DESTROY hdrMode = ffStrbufCreate();
1731                if (ffSettingsGetAndroidProperty("persist.sys.hdr_mode", &hdrMode) &&
1732                    ffStrbufToUInt(&hdrMode, 0) > 0 &&
1733                    !strstr(info, ", isForceSdr true")) {
1734                    display->hdrStatus = FF_DISPLAY_HDR_STATUS_ENABLED;
1735                }
1736            }
1737        }
1738
1739        // The last display of the dump is not followed by a newline, so the loop must not step
1740        // past the end of the buffer (`ffStrbufNextIndexC` returns the length when it finds none)
1741        index = nextIndex < buf.length ? nextIndex + 1 : buf.length;
1742    }
1743
1744    // A command that produced no `DisplayInfo` record has to be reported as a failure, so that the
1745    // caller can tell "this build answers differently" from "this build answered nothing"
1746    return ds->displays.length > 0;
1747}
1748
1749static bool detectWithCmd(FFDisplayServerResult* ds) {
1750    return detectWithCommand(ds,
1751        (char*[]) { "/system/bin/cmd", "display", "get-displays", nullptr },
1752        "Display id ",
1753        "cmd");
1754}
1755
1756// Several vendors embed the UI name and its version in `ro.build.display.id` without any
1757// separator, e.g. `MyOS12.0.14_A2121` or `RedMagicOS10.0.24_NX779J`.
1758static bool detectDEFromDisplayId(FFDisplayServerResult* ds, const char* const* names, uint32_t count) {
1759    FF_STRBUF_AUTO_DESTROY displayId = ffStrbufCreate();
1760    if (!ffSettingsGetAndroidProperty("ro.build.display.id", &displayId)) {
1761        return false;
1762    }
1763
1764    for (uint32_t i = 0; i < count; i++) {
1765        uint32_t length = (uint32_t) strlen(names[i]);
1766        if (!ffStrbufStartsWithS(&displayId, names[i])) {
1767            continue;
1768        }
1769
1770        ffStrbufSubstrBeforeFirstC(&displayId, '_'); // Drop the model suffix
1771        if (displayId.length > length) {
1772            ffStrbufInsertNC(&displayId, length, 1, ' ');
1773        }
1774        ffStrbufSet(&ds->dePrettyName, &displayId);
1775        return true;
1776    }
1777
1778    return false;
1779}
1780
1781static bool detectDE(FFDisplayServerResult* ds) {
1782    FF_STRBUF_AUTO_DESTROY buffer = ffStrbufCreate();
1783    FF_STRBUF_AUTO_DESTROY brand = ffStrbufCreate();
1784
1785    // `ffSettingsGetAndroidProperty` appends to the given buffer, so the brand has to be read
1786    // into its own buffer exactly once: reading it into a shared buffer repeatedly would
1787    // concatenate the values and break every comparison against it.
1788    ffSettingsGetAndroidProperty("ro.product.brand", &brand);
1789
1790    // vivo reports the marketing name and version in `ro.vivo.os.build.display.id`,
1791    // separated by an underscore (`Funtouch OS_10`, `OriginOS 5`), and the build number
1792    // in `ro.vivo.product.version`.
1793    if (ffSettingsGetAndroidProperty("ro.vivo.os.build.display.id", &ds->dePrettyName)) {
1794        ffStrbufReplaceAllC(&ds->dePrettyName, '_', ' ');
1795        if (ffSettingsGetAndroidProperty("ro.vivo.product.version", &buffer)) {
1796            ffStrbufAppendC(&ds->dePrettyName, ' ');
1797            ffStrbufAppend(&ds->dePrettyName, &buffer);
1798        }
1799        return true;
1800    }
1801
1802    // HarmonyOS 2.0 - 4.x is built on top of the Android framework and keeps reporting an
1803    // EMUI version, but it is HarmonyOS. `ro.build.ohos.devicetype` is set only by those
1804    // builds, and `hw_sc.build.platform.version` is the HarmonyOS version, while
1805    // `ro.build.version.emui` only carries the EMUI compatibility version
1806    // (HarmonyOS 2.0 == EMUI 12, 3.0 == 13, 4.0 == 14, 4.2 == 14.2, 4.3 == 15).
1807    if (ffSettingsGetAndroidProperty("ro.build.ohos.devicetype", &buffer)) {
1808        ffStrbufClear(&buffer);
1809        if (!ffSettingsGetAndroidProperty("hw_sc.build.platform.version", &buffer) &&
1810            ffSettingsGetAndroidProperty("ro.huawei.build.display.id", &buffer)) {
1811            // A few builds leave `hw_sc.build.platform.version` unset and only expose the
1812            // version through `ro.huawei.build.display.id`, e.g. `JKM-AL00 2.0.0.263(C00E260R4P3)`
1813            ffStrbufSubstrAfterFirstC(&buffer, ' ');
1814            ffStrbufSubstrBeforeFirstC(&buffer, '(');
1815            ffStrbufSubstrBeforeLastC(&buffer, '.');
1816        }
1817        if (buffer.length > 0) {
1818            ffStrbufSetF(&ds->dePrettyName, "HarmonyOS %s", buffer.chars);
1819        } else {
1820            ffStrbufSetStatic(&ds->dePrettyName, "HarmonyOS");
1821        }
1822        return true;
1823    }
1824
1825    // HarmonyOS NEXT (5.0 and newer) drops the Android framework. It is the only Huawei
1826    // family reporting `ro.build.display.id` as `System 104.5.0.001(60J9)`, and none of its
1827    // properties carries the marketing version, so it can only be named without one.
1828    if (ffStrbufIgnCaseEqualS(&brand, "HUAWEI") &&
1829        ffSettingsGetAndroidProperty("ro.build.display.id", &buffer) &&
1830        ffStrbufStartsWithS(&buffer, "System ")) {
1831        ffStrbufSetStatic(&ds->dePrettyName, "HarmonyOS NEXT");
1832        return true;
1833    }
1834
1835    // HONOR reports MagicOS / MagicUI in `ro.build.version.magic`. MagicUI 3.x stores a
1836    // bare version number (`3.0.1`) instead of a `MagicUI_x.y.z` string.
1837    if (ffSettingsGetAndroidProperty("ro.build.version.magic", &ds->dePrettyName)) {
1838        ffStrbufReplaceAllC(&ds->dePrettyName, '_', ' ');
1839        if (!ffStrbufStartsWithS(&ds->dePrettyName, "Magic")) {
1840            ffStrbufPrependS(&ds->dePrettyName, "MagicUI ");
1841        }
1842        return true;
1843    }
1844
1845    if (ffSettingsGetAndroidProperty("ro.build.version.emui", &ds->dePrettyName)) {
1846        ffStrbufReplaceAllC(&ds->dePrettyName, '_', ' ');
1847        return true;
1848    }
1849
1850    // Xiaomi HyperOS. `ro.mi.os.version.incremental` is `OS1.0.10.0.TLDCNXM`, while
1851    // `ro.build.version.incremental` still starts with `V816` on HyperOS 1.0, so it can
1852    // not be used to tell HyperOS and MIUI apart.
1853    if (ffSettingsGetAndroidProperty("ro.mi.os.version.name", &ds->dePrettyName)) {
1854        ffStrbufClear(&ds->dePrettyName);
1855        if (ffSettingsGetAndroidProperty("ro.mi.os.version.incremental", &ds->dePrettyName) &&
1856            ffStrbufStartsWithS(&ds->dePrettyName, "OS")) {
1857            ffStrbufSubstrAfter(&ds->dePrettyName, 1); // Drop the leading "OS"
1858            ffStrbufPrependS(&ds->dePrettyName, "HyperOS ");
1859        } else {
1860            ffStrbufSetStatic(&ds->dePrettyName, "HyperOS");
1861        }
1862        return true;
1863    }
1864
1865    // Black Shark runs JOYUI, a MIUI fork, and therefore also sets
1866    // `ro.miui.ui.version.name`. `ro.build.version.incremental` is `V11.0.4.0.JOYUI`.
1867    if (ffStrbufIgnCaseEqualS(&brand, "blackshark")) {
1868        ffStrbufClear(&ds->dePrettyName);
1869        if (ffSettingsGetAndroidProperty("ro.build.version.incremental", &ds->dePrettyName)) {
1870            ffStrbufSubstrBeforeLastC(&ds->dePrettyName, '.'); // Drop the trailing "JOYUI"
1871            if (ffStrbufStartsWithS(&ds->dePrettyName, "V")) {
1872                ffStrbufSubstrAfter(&ds->dePrettyName, 0);
1873            }
1874            ffStrbufPrependS(&ds->dePrettyName, "JOYUI ");
1875        } else {
1876            ffStrbufSetStatic(&ds->dePrettyName, "JOYUI");
1877        }
1878        return true;
1879    }
1880
1881    // MIUI. `ro.build.version.incremental` is `V14.0.1.0.TJJCNXM` on stable builds and a
1882    // bare release date (`21.11.17`) on beta builds.
1883    if (ffSettingsGetAndroidProperty("ro.miui.ui.version.name", &buffer)) {
1884        ffStrbufClear(&ds->dePrettyName);
1885        if (ffSettingsGetAndroidProperty("ro.build.version.incremental", &ds->dePrettyName)) {
1886            if (ffStrbufStartsWithS(&ds->dePrettyName, "V")) {
1887                ffStrbufSubstrAfter(&ds->dePrettyName, 0);
1888            }
1889            ffStrbufPrependS(&ds->dePrettyName, "MiUI ");
1890        } else {
1891            ffStrbufSetStatic(&ds->dePrettyName, "MiUI");
1892        }
1893        return true;
1894    }
1895
1896    // realme UI is a ColorOS fork and reports both `ro.build.version.realmeui` and
1897    // `ro.build.version.oplusrom`; the former is the realme UI version.
1898    if (ffSettingsGetAndroidProperty("ro.build.version.realmeui", &ds->dePrettyName)) {
1899        if (ffStrbufStartsWithS(&ds->dePrettyName, "V")) {
1900            ffStrbufSubstrAfter(&ds->dePrettyName, 0);
1901        }
1902        ffStrbufPrependS(&ds->dePrettyName, "realme UI ");
1903        return true;
1904    }
1905
1906    // ColorOS 12 and newer report `ro.build.version.oplusrom`, ColorOS 11 and older
1907    // report `ro.build.version.opporom`.
1908    if (ffSettingsGetAndroidProperty("ro.build.version.oplusrom", &ds->dePrettyName) ||
1909        ffSettingsGetAndroidProperty("ro.build.version.opporom", &ds->dePrettyName)) {
1910        if (ffStrbufStartsWithS(&ds->dePrettyName, "V")) {
1911            ffStrbufSubstrAfter(&ds->dePrettyName, 0);
1912        }
1913        ffStrbufPrependS(&ds->dePrettyName, "ColorOS ");
1914        return true;
1915    }
1916
1917    if (ffSettingsGetAndroidProperty("ro.oxygen.version", &ds->dePrettyName)) {
1918        ffStrbufPrependS(&ds->dePrettyName, "OxygenOS ");
1919        return true;
1920    }
1921
1922    // HydrogenOS is the Chinese counterpart of OxygenOS and reports `ro.rom.version`.
1923    if (ffStrbufEqualS(&brand, "OnePlus") &&
1924        ffSettingsGetAndroidProperty("ro.rom.version", &ds->dePrettyName)) {
1925        ffStrbufPrependS(&ds->dePrettyName, "H2OS ");
1926        return true;
1927    }
1928
1929    if (ffSettingsGetAndroidProperty("ro.build.version.oneui", &ds->dePrettyName)) {
1930        // [ro.build.version.oneui]: [50101] => One UI 5.1.1
1931        // Samsung encodes the version with a base-100 carry: `major * 10000 + minor * 100 + patch`
1932        uint32_t version = (uint32_t) ffStrbufToUInt(&ds->dePrettyName, 0);
1933        uint32_t major = version / 10000;
1934        uint32_t minor = version / 100 % 100;
1935        uint32_t patch = version % 100;
1936        if (major == 0) {
1937            // Unexpected format, show the raw value
1938            ffStrbufPrependS(&ds->dePrettyName, "OneUI ");
1939        } else if (patch > 0) {
1940            ffStrbufSetF(&ds->dePrettyName, "OneUI %u.%u.%u", major, minor, patch);
1941        } else {
1942            ffStrbufSetF(&ds->dePrettyName, "OneUI %u.%u", major, minor);
1943        }
1944        return true;
1945    }
1946
1947    // Flyme. `ro.build.display.id` is `Flyme 10.5.0.1A`, where the trailing `A` marks a
1948    // stable release. `ro.flyme.version.id` holds the same string, except on Flyme 12,
1949    // where it is an Android build id instead.
1950    if (ffStrbufIgnCaseEqualS(&brand, "meizu")) {
1951        if (!ffSettingsGetAndroidProperty("ro.build.display.id", &ds->dePrettyName) ||
1952            !ffStrbufStartsWithS(&ds->dePrettyName, "Flyme")) {
1953            ffStrbufSetStatic(&ds->dePrettyName, "Flyme");
1954        }
1955        return true;
1956    }
1957
1958    // SmartisanOS. `ro.smartisan.version` is `4.2.6-201808311713-user-511` or
1959    // `6.6.6.2_TNT-201904101033-user-oce`.
1960    if (ffSettingsGetAndroidProperty("ro.smartisan.version", &ds->dePrettyName)) {
1961        ffStrbufSubstrBeforeFirstC(&ds->dePrettyName, '_');
1962        ffStrbufSubstrBeforeFirstC(&ds->dePrettyName, '-');
1963        ffStrbufPrependS(&ds->dePrettyName, "SmartisanOS ");
1964        return true;
1965    }
1966
1967    // ZUI / ZUXOS. `ro.com.zui.version` is the internal ZUI version (`17.0` for
1968    // ZUXOS 1.1.10.138), while `ro.build.display.id` embeds the marketing name and
1969    // version: `TB321FU_CN_OPEN_USER_Q00011.0_V_ZUXOS_1.1.10.138_ST_250626`.
1970    if (ffSettingsGetAndroidProperty("ro.com.zui.version", &ds->dePrettyName)) {
1971        FF_STRBUF_AUTO_DESTROY displayId = ffStrbufCreate();
1972        const char* name = nullptr;
1973        if (ffSettingsGetAndroidProperty("ro.build.display.id", &displayId)) {
1974            if (ffStrbufSubstrAfterFirstS(&displayId, "_ZUXOS_")) {
1975                name = "ZUXOS";
1976            } else if (ffStrbufSubstrAfterFirstS(&displayId, "_ZUI_")) {
1977                name = "ZUI";
1978            }
1979            if (name) {
1980                ffStrbufSubstrBeforeFirstC(&displayId, '_'); // Drop the trailing "_ST_250626"
1981                ffStrbufSetF(&ds->dePrettyName, "%s %s", name, displayId.chars);
1982                return true;
1983            }
1984        }
1985        ffStrbufPrependS(&ds->dePrettyName, "ZUI "); // Moto builds only expose `ro.com.zui.version`
1986        return true;
1987    }
1988
1989    // 360 OS (QiKU) reports `ro.build.uiversion` as `360UI:V3.0`.
1990    if (ffSettingsGetAndroidProperty("ro.build.uiversion", &ds->dePrettyName)) {
1991        uint32_t index = ffStrbufFirstIndexC(&ds->dePrettyName, ':');
1992        if (index < ds->dePrettyName.length) {
1993            // `360UI:V3.0` -> `360UI 3.0`
1994            ffStrbufRemoveSubstr(&ds->dePrettyName, index, index + 1);
1995            if (ds->dePrettyName.chars[index] == 'V') {
1996                ffStrbufRemoveSubstr(&ds->dePrettyName, index, index + 1);
1997            }
1998            ffStrbufInsertNC(&ds->dePrettyName, index, 1, ' ');
1999        }
2000        return true;
2001    }
2002
2003    // LeEco EUI reports `ro.letv.release.version` as `6.0.030S`, where the trailing `S`
2004    // marks a stable release.
2005    if (ffSettingsGetAndroidProperty("ro.letv.release.version", &ds->dePrettyName)) {
2006        ffStrbufPrependS(&ds->dePrettyName, "EUI ");
2007        return true;
2008    }
2009
2010    // nubia / ZTE
2011    if (ffStrbufIgnCaseEqualS(&brand, "nubia") || ffStrbufIgnCaseEqualS(&brand, "zte")) {
2012        // ObricUI reports `ro.os.ota.version`, e.g.
2013        // `1.8.0.2-20260204-125753-RELEASE-user-pacific-b911`
2014        if (ffSettingsGetAndroidProperty("ro.os.ota.version", &ds->dePrettyName)) {
2015            ffStrbufSubstrBeforeFirstC(&ds->dePrettyName, '-');
2016            ffStrbufPrependS(&ds->dePrettyName, "ObricUI ");
2017            return true;
2018        }
2019
2020        // MyOS, NebulaAIOS and RedMagicOS embed the name and version in `ro.build.display.id`
2021        static const char* const displayIdNames[] = { "RedMagicOS", "NebulaAIOS", "MyOS" };
2022        if (detectDEFromDisplayId(ds, displayIdNames, ARRAY_SIZE(displayIdNames))) {
2023            return true;
2024        }
2025
2026        // The older nubiaUI stores them in `ro.build.nubia.rom.name` and `.code`
2027        if (ffSettingsGetAndroidProperty("ro.build.nubia.rom.name", &ds->dePrettyName)) {
2028            if (ffSettingsGetAndroidProperty("ro.build.nubia.rom.code", &buffer)) {
2029                if (ffStrbufStartsWithS(&buffer, "V")) {
2030                    ffStrbufSubstrAfter(&buffer, 0); // `V1.0` -> `1.0`
2031                }
2032                ffStrbufAppendC(&ds->dePrettyName, ' ');
2033                ffStrbufAppend(&ds->dePrettyName, &buffer);
2034            }
2035            return true;
2036        }
2037
2038        // MiFavor is the UI of the pre-MyOS ZTE phones; `ro.build.MiFavor_version` is a
2039        // bare version number (`4.0`). Note that MyOS, NebulaAIOS and RedMagicOS reuse
2040        // this property for their own version, so they are checked first.
2041        if (ffSettingsGetAndroidProperty("ro.build.MiFavor_version", &ds->dePrettyName)) {
2042            ffStrbufPrependS(&ds->dePrettyName, "MiFavor ");
2043            return true;
2044        }
2045    }
2046
2047    // LineageOS and other AOSP-based distributions
2048    if (ffSettingsGetAndroidProperty("ro.lineage.build.version", &ds->dePrettyName)) {
2049        ffStrbufPrependS(&ds->dePrettyName, "LineageOS ");
2050        return true;
2051    }
2052    if (ffSettingsGetAndroidProperty("org.pixelexperience.version.display", &ds->dePrettyName)) {
2053        // PixelExperience_Plus_whyred-13.0-20230325-0421-OFFICIAL
2054        ffStrbufSubstrBeforeFirstC(&ds->dePrettyName, '-');
2055        ffStrbufSubstrBeforeLastC(&ds->dePrettyName, '_');
2056        ffStrbufReplaceAllC(&ds->dePrettyName, '_', ' ');
2057        return true;
2058    }
2059
2060    if (ffStrbufEqualS(&brand, "asus") &&
2061        ffSettingsGetAndroidProperty("ro.build.version.incremental", &ds->dePrettyName)) {
2062        return true;
2063    }
2064    if (ffSettingsGetAndroidProperty("ro.build.display.id", &ds->dePrettyName)) {
2065        // Google Pixel and other devices running native Android
2066        return true;
2067    }
2068
2069    return false;
2070}
2071
2072void ffConnectDisplayServerImpl(FFDisplayServerResult* ds) {
2073    // Keep the same display-server preference as Linux: Wayland provides the
2074    // richest output information, followed by XCB and XRandR.
2075    if (instance.config.general.dsForceDrm == FF_DS_FORCE_DRM_TYPE_FALSE) {
2076        ffdsConnectWayland(ds);
2077
2078        if (ds->displays.length == 0) {
2079            ffdsConnectXcbRandr(ds);
2080        }
2081
2082        if (ds->displays.length == 0) {
2083            ffdsConnectXrandr(ds);
2084        }
2085
2086        if (ds->displays.length > 0) {
2087            ffdsDetectWMDE(ds);
2088            return;
2089        }
2090    }
2091
2092    // https://source.android.com/docs/core/graphics/surfaceflinger-windowmanager
2093    ffStrbufSetStatic(&ds->wmProcessName, "system_server");
2094    ffStrbufSetStatic(&ds->wmPrettyName, "WindowManager"); // A system service managed by system_server
2095    ffStrbufSetStatic(&ds->wmProtocolName, FF_WM_PROTOCOL_SURFACEFLINGER);
2096
2097    // Binder comes first: it needs no permission, no child process, and it is the only route that
2098    // answers for an app UID on Android 12 and older, where `cmd display get-displays` does not exist.
2099    // `cmd` is behind it for the case the dex read is what fails, see detectWithCommand.
2100    if (!detectWithBinder(ds)) {
2101        detectWithCmd(ds);
2102    }
2103
2104    detectDE(ds);
2105}