From 1bab113d25c769dab95d56174d6c37ba478ab2d3 Mon Sep 17 00:00:00 2001 From: Carter Li Date: Mon, 12 Oct 2026 14:20:21 +0800 Subject: [PATCH] Display (Android): be compatible with Android 17 --- .../displayserver/displayserver_android.c | 256 +++++++++++++++--- 1 file changed, 224 insertions(+), 32 deletions(-) diff --git a/src/detection/displayserver/displayserver_android.c b/src/detection/displayserver/displayserver_android.c index 75013d472..b2f9eab14 100644 --- a/src/detection/displayserver/displayserver_android.c +++ b/src/detection/displayserver/displayserver_android.c @@ -26,9 +26,9 @@ // the dex read below, not by the transaction. // // What it costs is the parse. The reply is a Java Parcelable and its field set moves with every -// release and every vendor fork -- `DisplayInfo` writes 34 fields on Android 11, 42 on 13 and 57 on -// 16 -- so neither a fixed offset nor a sequence written against one release survives. The sequence -// is read out of the device's own `framework.jar` instead, from the `iget` order of +// release and every vendor fork -- `DisplayInfo` writes 34 fields on Android 11, 42 on 13, 57 on 16 +// and 57 on 17 -- so neither a fixed offset nor a sequence written against one release survives. The +// sequence is read out of the device's own `framework.jar` instead, from the `iget` order of // `DisplayInfo.writeToParcel`, and the walk below follows it: a field the release does not write is // simply not in the sequence, which is what makes every one of them optional by construction. See // common/android/dex.h for how the sequence is recovered and what it does not say. @@ -40,6 +40,19 @@ // than answering. There is no second route behind this one any more, so a build whose layout is not // one this parser reads reports no display at all, rather than a plausible number out of the middle of // a different field. +// +// Android 17 moved three of the things this parse hangs on, and all three are read the same way -- out +// of the running build's own jar, so nothing here is a table written against a release: +// +// * `address` arrived as a `DisplayAddress$StablePhysical` where every release up to 16 sent a +// `DisplayAddress$Physical`, and the new class writes the port beside the id -- two fields where +// the old one writes one. Both sequences are read and the class name on the wire picks between +// them, so Android 11 to 17 are all read by the walk below. +// * `frameRateVelocityMapping` is new and is a `writeTypedList`, a count and then one body per +// element, each behind the marker the typed writes use and with no class name anywhere. The +// element's own sequence says how long it is. +// * `DeviceProductInfo.mEdidStructureMetadata` is new and is a `writeTypedObject`: the same marker +// and the same absent class name, one body behind it. #define FF_DISPLAY_ANDROID_SERVICE "display" #define FF_DISPLAY_ANDROID_DESCRIPTOR "android.hardware.display.IDisplayManager" @@ -51,17 +64,22 @@ #define FF_DISPLAY_ANDROID_CLASS_INFO "Landroid/view/DisplayInfo;" #define FF_DISPLAY_ANDROID_CLASS_ADDRESS "Landroid/view/DisplayAddress$Physical;" +#define FF_DISPLAY_ANDROID_CLASS_STABLE_ADDRESS "Landroid/view/DisplayAddress$StablePhysical;" #define FF_DISPLAY_ANDROID_CLASS_PRODUCT_INFO "Landroid/hardware/display/DeviceProductInfo;" +#define FF_DISPLAY_ANDROID_CLASS_EDID_STRUCTURE "Landroid/hardware/display/DeviceProductInfo$EdidStructureMetadata;" #define FF_DISPLAY_ANDROID_CLASS_MANUFACTURE_DATE "Landroid/hardware/display/DeviceProductInfo$ManufactureDate;" #define FF_DISPLAY_ANDROID_CLASS_HDR_CAPABILITIES "Landroid/view/Display$HdrCapabilities;" #define FF_DISPLAY_ANDROID_CLASS_MODE "Landroid/view/Display$Mode;" #define FF_DISPLAY_ANDROID_CLASS_FRAME_RATE_CATEGORY_RATE "Landroid/view/FrameRateCategoryRate;" +#define FF_DISPLAY_ANDROID_CLASS_VELOCITY_POINT "Landroid/view/FrameRateVelocityPoint;" #define FF_DISPLAY_ANDROID_CLASS_CUTOUT "Landroid/view/DisplayCutout;" // The Java class names `writeParcelable` writes in front of each nested body, which is the anchor the // walk hangs on. It writes the *runtime* class, so the abstract `DisplayAddress` a `DisplayInfo` -// declares arrives as its `$Physical` subclass. +// declares arrives as whichever subclass the framework put there -- its `$Physical` up to Android 16 +// and its `$StablePhysical` from 17 on, which is why one field needs two names. #define FF_DISPLAY_ANDROID_NAME_ADDRESS "android.view.DisplayAddress$Physical" +#define FF_DISPLAY_ANDROID_NAME_STABLE_ADDRESS "android.view.DisplayAddress$StablePhysical" #define FF_DISPLAY_ANDROID_NAME_PRODUCT_INFO "android.hardware.display.DeviceProductInfo" #define FF_DISPLAY_ANDROID_NAME_MANUFACTURE_DATE "android.hardware.display.DeviceProductInfo$ManufactureDate" #define FF_DISPLAY_ANDROID_NAME_HDR_CAPABILITIES "android.view.Display$HdrCapabilities" @@ -113,8 +131,12 @@ typedef struct FFDisplayAndroidLayout { uint32_t displayInfoCount; FFDexParcelField address[FF_DISPLAY_ANDROID_NESTED_FIELDS]; uint32_t addressCount; + FFDexParcelField stableAddress[FF_DISPLAY_ANDROID_NESTED_FIELDS]; + uint32_t stableAddressCount; FFDexParcelField productInfo[FF_DISPLAY_ANDROID_NESTED_FIELDS]; uint32_t productInfoCount; + FFDexParcelField edidStructureMetadata[FF_DISPLAY_ANDROID_NESTED_FIELDS]; + uint32_t edidStructureMetadataCount; FFDexParcelField manufactureDate[FF_DISPLAY_ANDROID_NESTED_FIELDS]; uint32_t manufactureDateCount; FFDexParcelField hdrCapabilities[FF_DISPLAY_ANDROID_NESTED_FIELDS]; @@ -123,6 +145,8 @@ typedef struct FFDisplayAndroidLayout { uint32_t modeCount; FFDexParcelField frameRateCategoryRate[FF_DISPLAY_ANDROID_NESTED_FIELDS]; uint32_t frameRateCategoryRateCount; + FFDexParcelField velocityPoint[FF_DISPLAY_ANDROID_NESTED_FIELDS]; + uint32_t velocityPointCount; // `DisplayCutout` is the one class whose body is not field-driven -- `writeCutoutToParcel` reads // everything through compiler-synthesised accessors, so there is no `iget` to walk. What says how @@ -135,10 +159,13 @@ typedef struct FFDisplayAndroidLayout { // `jarPath` -- in one lookup, because the walk behind it is what a lookup costs and the class data it // reads along the way already holds everything asked for here. // -// Every sequence but `FrameRateCategoryRate` is required: a class the jar does not declare leaves an -// empty sequence, and a walk without one has nothing to follow. `FrameRateCategoryRate` arrived in -// Android 15, so a jar that does not declare it is answering rather than failing -- the field is -// simply not in the sequence behind it, and the walk never asks. +// A class the jar does not declare leaves an empty sequence, and that is an answer rather than a +// failure for every class a release added part way through: `FrameRateCategoryRate` arrived in +// Android 15 and `StablePhysical`, `EdidStructureMetadata` and `FrameRateVelocityPoint` in 17, and a +// jar that does not declare one of them does not write the field either, so the walk never asks. +// What *is* required is the sequences a record cannot be walked without: `DisplayInfo` itself, a +// `DisplayAddress` -- either of the two shapes, since which one a build sends is what the name on the +// wire says -- a `DeviceProductInfo` and its date, the HDR capabilities and a mode. // // A jar that cannot be read at all fails too, and with nothing behind this route that leaves the // module with no display to report. Which is the point of the dex being the source: a build this @@ -160,6 +187,15 @@ static bool displayLayoutLoad(const char* jarPath, FFDisplayAndroidLayout* layou .capacity = FF_DISPLAY_ANDROID_NESTED_FIELDS, .count = &layout->addressCount, }, + { + // Android 17's shape of the same field. A build that does not declare the class leaves the + // sequence empty, which is how the walk knows the field can only be the older one. + .classDescriptor = FF_DISPLAY_ANDROID_CLASS_STABLE_ADDRESS, + .methodName = "writeToParcel", + .fields = layout->stableAddress, + .capacity = FF_DISPLAY_ANDROID_NESTED_FIELDS, + .count = &layout->stableAddressCount, + }, { .classDescriptor = FF_DISPLAY_ANDROID_CLASS_PRODUCT_INFO, .methodName = "writeToParcel", @@ -167,6 +203,15 @@ static bool displayLayoutLoad(const char* jarPath, FFDisplayAndroidLayout* layou .capacity = FF_DISPLAY_ANDROID_NESTED_FIELDS, .count = &layout->productInfoCount, }, + { + // Android 17's addition to `DeviceProductInfo`. Same as above: an empty sequence means the + // release does not carry the field, and it is not in the product info walk either. + .classDescriptor = FF_DISPLAY_ANDROID_CLASS_EDID_STRUCTURE, + .methodName = "writeToParcel", + .fields = layout->edidStructureMetadata, + .capacity = FF_DISPLAY_ANDROID_NESTED_FIELDS, + .count = &layout->edidStructureMetadataCount, + }, { .classDescriptor = FF_DISPLAY_ANDROID_CLASS_MANUFACTURE_DATE, .methodName = "writeToParcel", @@ -195,6 +240,15 @@ static bool displayLayoutLoad(const char* jarPath, FFDisplayAndroidLayout* layou .capacity = FF_DISPLAY_ANDROID_NESTED_FIELDS, .count = &layout->frameRateCategoryRateCount, }, + { + // Android 17's `frameRateVelocityMapping` is a list of these, and this is the sequence that + // says how long one element is. + .classDescriptor = FF_DISPLAY_ANDROID_CLASS_VELOCITY_POINT, + .methodName = "writeToParcel", + .fields = layout->velocityPoint, + .capacity = FF_DISPLAY_ANDROID_NESTED_FIELDS, + .count = &layout->velocityPointCount, + }, { // The one class whose body is not field-driven. `writeCutoutToParcel` reads everything // through compiler-synthesised accessors, so there is no `iget` to walk and no class name @@ -214,15 +268,18 @@ static bool displayLayoutLoad(const char* jarPath, FFDisplayAndroidLayout* layou FF_DEBUG("Reading the display layout out of %s failed: %s", jarPath, error); return false; } - if (layout->displayInfoCount == 0 || layout->addressCount == 0 || layout->productInfoCount == 0 - || layout->manufactureDateCount == 0 || layout->hdrCapabilitiesCount == 0 || layout->modeCount == 0) { + if (layout->displayInfoCount == 0 || (layout->addressCount == 0 && layout->stableAddressCount == 0) + || layout->productInfoCount == 0 || layout->manufactureDateCount == 0 || layout->hdrCapabilitiesCount == 0 + || layout->modeCount == 0) { FF_DEBUG("The classes a display is made of are not all declared in %s", jarPath); return false; } FF_DEBUG( - "Display layout: %u fields in DisplayInfo, %u in a mode, cutout path parser info %s, side overrides %s", + "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", layout->displayInfoCount, layout->modeCount, + layout->addressCount > 0 ? (layout->stableAddressCount > 0 ? "physical and stable" : "physical") : "stable", + layout->productInfoCount, layout->edidStructureMetadataCount, layout->velocityPointCount, layout->cutoutFields[FF_DISPLAY_ANDROID_CUTOUT_PATH_PARSER_INFO] ? "yes" : "no", layout->cutoutFields[FF_DISPLAY_ANDROID_CUTOUT_SIDE_OVERRIDES] ? "yes" : "no" ); @@ -495,11 +552,14 @@ static bool readerScalar(FFDisplayAndroidReader* reader, const char* type, FFDis return false; } -// The class name in front of a nested body, checked against the class the field is expected to carry. -// `writeParcelable` writes the runtime class, so the abstract `DisplayAddress` a `DisplayInfo` -// declares arrives as its `$Physical` subclass; a null field is a single -1 where the class name -// would be, which is a value and not a failure. -static bool readerParcelableHead(FFDisplayAndroidReader* reader, const char* expected, bool* present) { +// The class name in front of a nested body, when which class it is decides how long the body is. +// `writeParcelable` writes the runtime class, so a field declared an abstract type arrives as +// whichever subclass the framework put there: `DisplayInfo.address` is a `DisplayAddress`, whose body +// is one field on `$Physical` and two on the `$StablePhysical` Android 17 added. The name is read +// first and the index of the one it matched is reported, so the caller walks the sequence that +// belongs to it -- and a name that matches none of them fails, because there is nothing else that +// says where the body ends. +static bool readerParcelableHeadAny(FFDisplayAndroidReader* reader, const char* const* names, uint32_t nameCount, uint32_t* index, bool* present) { const int32_t length = readerI32(reader); if (reader->failed) { return false; @@ -510,7 +570,21 @@ static bool readerParcelableHead(FFDisplayAndroidReader* reader, const char* exp } reader->offset -= sizeof(int32_t); *present = true; - return readerClassName(reader, expected); + for (uint32_t i = 0; i < nameCount; ++i) { + if (readerClassNameAt(reader, reader->offset, names[i])) { + *index = i; + return readerClassName(reader, names[i]); + } + } + reader->failed = true; + return false; +} + +// The same head for the fields that carry one class only. A null field is a single -1 where the class +// name would be, which is a value and not a failure. +static bool readerParcelableHead(FFDisplayAndroidReader* reader, const char* expected, bool* present) { + uint32_t index = 0; + return readerParcelableHeadAny(reader, &expected, 1, &index, present); } // A `Rect` written by `writeTypedObject` or by `writeTypedArray`: the non-null marker the typed write @@ -523,6 +597,58 @@ static bool readerRect(FFDisplayAndroidReader* reader) { return marker < 0 || readerSkip(reader, 4 * sizeof(int32_t)); } +// A nested Parcelable written by `writeTypedObject`, walked out of the sequence the jar gives for its +// own class. What is on the wire is a marker and the body, with no class name in front of it -- the +// reader is handed the `Creator` to build the value with, so the parcel does not have to carry one, +// and that is the whole difference between this and `writeParcelable`. The device's own +// `Parcel.writeTypedObject` writes a zero marker for a null and a one before every body, so anything +// else is a field read at the wrong place rather than a value. +static bool readerTypedObject(FFDisplayAndroidReader* reader, const FFDexParcelField* fields, uint32_t count) { + const int32_t marker = readerI32(reader); + if (reader->failed) { + return false; + } + if (marker == 0) { + return true; + } + if (marker != 1) { + reader->failed = true; + return false; + } + for (uint32_t i = 0; i < count; ++i) { + FFDisplayAndroidScalar value = {}; + if (!readerScalar(reader, fields[i].type, &value)) { + return false; + } + } + return true; +} + +// A `writeTypedList`: a count and then one `writeTypedObject` per element, all of them the same class, +// which is why one sequence answers the whole list. A count of -1 is a null list and carries none. +static bool readerTypedObjectList(FFDisplayAndroidReader* reader, const FFDexParcelField* fields, uint32_t count) { + const int32_t size = readerI32(reader); + if (reader->failed) { + return false; + } + if (size < 0) { + return true; + } + // A non-empty list whose elements this parser has no sequence for is a failure rather than an empty + // walk: the elements are still on the wire, and stepping over none of them would put every field + // behind the list at the wrong place, which is worse than reporting nothing. + if (size > 64 || (size > 0 && count == 0)) { + reader->failed = true; + return false; + } + for (int32_t i = 0; i < size; ++i) { + if (!readerTypedObject(reader, fields, count)) { + return false; + } + } + return true; +} + // `writeValue(mManufactureDate)`, the only Parcelable a `writeValue` carries here, and the one place // the parcel does something the AOSP sources do not describe: on Android 13 and later the tag is // followed by the size of everything behind it -- the class name and the body -- before the class @@ -599,11 +725,13 @@ static bool readerManufactureDate(FFDisplayAndroidReader* reader, const FFDispla // used, but it sits in front of `name`, the density and the rotation, so it has to be walked whether // or not its values are wanted -- and it is null on some displays. // -// Three of its six fields are handed to `writeValue`, which frames them by their runtime type rather -// than by their declared one: `mProductId` is declared a `String` and goes through `writeValue` where +// Three of its fields are handed to `writeValue`, which frames them by their runtime type rather than +// by their declared one: `mProductId` is declared a `String` and goes through `writeValue` where // `mName` next to it goes through `writeString`, so those are named rather than dispatched by type. -// The sixth is `mRelativeAddress`, an int array, on Android 11 and `mConnectionToSinkType`, an int, -// on 13 and later; the descriptor settles that one, which is what makes the two read the same code. +// The connection type is `mRelativeAddress`, an int array, on Android 11 and `mConnectionToSinkType`, +// an int, on 13 and later; the descriptor settles that one, which is what makes the two read the same +// code. Android 17 added `mVideoInputType`, an int, which the descriptor settles the same way, and +// `mEdidStructureMetadata`, a `writeTypedObject` -- six fields up to 16, eight from 17 on. static bool readerDeviceProductInfo(FFDisplayAndroidReader* reader, const FFDisplayAndroidLayout* layout, uint16_t* year, uint16_t* week) { bool present = false; if (!readerParcelableHead(reader, FF_DISPLAY_ANDROID_NAME_PRODUCT_INFO, &present)) { @@ -627,6 +755,13 @@ static bool readerDeviceProductInfo(FFDisplayAndroidReader* reader, const FFDisp if (!readerManufactureDate(reader, layout, year, week)) { return false; } + } else if (ffStrEquals(field->name, "mEdidStructureMetadata")) { + // Android 17. Named rather than left to the descriptor for the same reason the fields above + // are: `writeTypedObject` and `writeParcelable` are both a `L...;` on the wire and are not + // the same length, and this one is the typed write -- a marker, the body, no class name. + if (!readerTypedObject(reader, layout->edidStructureMetadata, layout->edidStructureMetadataCount)) { + return false; + } } else { FFDisplayAndroidScalar value = {}; if (!readerScalar(reader, field->type, &value)) { @@ -637,21 +772,30 @@ static bool readerDeviceProductInfo(FFDisplayAndroidReader* reader, const FFDisp return true; } -// `address`, which is null on some displays and a `DisplayAddress$Physical` on others. Its body is -// one `long` on every release seen -- the older sources describe a `long` plus an `int`, and that -// shape has not turned up on any of the three jars -- and it is walked out of the jar's own sequence -// rather than assumed, so a release that changes it is read rather than misread. +// `address`, which is null on some displays and a `DisplayAddress` on others: its `$Physical` up to +// Android 16, whose body is one `long`, and its `$StablePhysical` from 17 on, whose body is that +// `long` and the port beside it. Which one is on the wire is what the class name says, and either +// sequence comes out of the jar rather than being assumed, so a release that changes either body is +// read rather than misread. static bool readerDisplayAddress(FFDisplayAndroidReader* reader, const FFDisplayAndroidLayout* layout) { + static const char* const names[] = { + FF_DISPLAY_ANDROID_NAME_ADDRESS, + FF_DISPLAY_ANDROID_NAME_STABLE_ADDRESS, + }; bool present = false; - if (!readerParcelableHead(reader, FF_DISPLAY_ANDROID_NAME_ADDRESS, &present)) { + uint32_t index = 0; + if (!readerParcelableHeadAny(reader, names, ARRAY_SIZE(names), &index, &present)) { return false; } if (!present) { return true; } - for (uint32_t i = 0; i < layout->addressCount; ++i) { + const bool stable = index != 0; + const FFDexParcelField* fields = stable ? layout->stableAddress : layout->address; + const uint32_t count = stable ? layout->stableAddressCount : layout->addressCount; + for (uint32_t i = 0; i < count; ++i) { FFDisplayAndroidScalar value = {}; - if (!readerScalar(reader, layout->address[i].type, &value)) { + if (!readerScalar(reader, fields[i].type, &value)) { return false; } } @@ -873,12 +1017,16 @@ static uint64_t displayUniqueId(const char* uniqueId) { // One field of the `DisplayInfo` write sequence. The name says which value the field carries; the // type descriptor says how it is framed, and settles that for every primitive and both array shapes. // -// Four things the descriptor does not settle, and each is named: +// Six things the descriptor does not settle, and each is named: // +// * `address`, whose runtime class decides how long its body is. See readerDisplayAddress. // * `displayCutout`, which has no write sequence to follow at all. See readerDisplayCutout. +// * `deviceProductInfo`, whose `mEdidStructureMetadata` is a typed write inside a named write. // * `frameRateCategoryRate`, a Parcelable whose values this module does not use. -// * `supportedModes` and `appsSupportedModes`, arrays of Parcelables written by a loop rather than -// by a `write*Array`, so the shape behind them is `Display$Mode`'s own. +// * `frameRateVelocityMapping`, a `writeTypedList` -- a count and then a body per element, all of +// them the class the jar's own sequence was read for. Android 17 added it in place of the +// `appsSupportedModes` that 16 wrote, which was an array of Parcelables written by a loop rather +// than by a `write*Array`, and which `supportedModes` is still the shape of. // * The `String` fields. `DisplayInfo` writes its own through `writeString8` -- a byte count and // UTF-8 -- while `DeviceProductInfo` writes its through `writeString`, which is a code-unit count // and UTF-16; the descriptor is `Ljava/lang/String;` either way. Within one class the framing is @@ -909,6 +1057,9 @@ static bool displayInfoField(FFDisplayAndroidReader* reader, const FFDexParcelFi if (ffStrEquals(name, "frameRateCategoryRate")) { return readerSkippedParcelable(reader, FF_DISPLAY_ANDROID_NAME_FRAME_RATE_CATEGORY_RATE, layout->frameRateCategoryRate, layout->frameRateCategoryRateCount); } + if (ffStrEquals(name, "frameRateVelocityMapping")) { + return readerTypedObjectList(reader, layout->velocityPoint, layout->velocityPointCount); + } if (ffStrEquals(name, "supportedModes") || ffStrEquals(name, "appsSupportedModes")) { // The two ids the list is resolved against are written in front of it on every release seen. // One that moved behind it would leave the list resolved against nothing, so the walk says so @@ -967,6 +1118,35 @@ static bool displayInfoField(FFDisplayAndroidReader* reader, const FFDexParcelFi return true; } +// How much of a reply a failure prints. A record is 2404 bytes on the test device and most of it is +// the two mode lists, so this covers one whole reply -- and the cap is there only so that a reply +// which is not one cannot fill the log. +#define FF_DISPLAY_ANDROID_DEBUG_DUMP_SIZE 4096 + +#ifndef NDEBUG +// The reply is read by shape, so a reply that fails leaves nothing behind to diagnose it with: the +// bytes are the only evidence. Each line carries the text beside the bytes, because what the walk +// hangs on is the class name in front of a nested body -- UTF-16, and the one thing that a length +// read out of the middle of a different field cannot be told from without looking at it. +static void debugDumpReply(const uint8_t* data, size_t size) { + const size_t limit = size < FF_DISPLAY_ANDROID_DEBUG_DUMP_SIZE ? size : FF_DISPLAY_ANDROID_DEBUG_DUMP_SIZE; + for (size_t offset = 0; offset < limit; offset += 16) { + char hex[16 * 3 + 1]; + char text[16 + 1]; + size_t used = 0; + for (size_t i = 0; i < 16 && offset + i < limit; ++i) { + used += (size_t) snprintf(hex + used, sizeof(hex) - used, "%02x ", data[offset + i]); + text[i] = data[offset + i] >= 32 && data[offset + i] < 127 ? (char) data[offset + i] : '.'; + text[i + 1] = '\0'; + } + FF_DEBUG(" +0x%04zx %-47s | %s", offset, hex, text); + } +} + #define FF_DISPLAY_ANDROID_DEBUG_DUMP(data, size) debugDumpReply(data, size) +#else + #define FF_DISPLAY_ANDROID_DEBUG_DUMP(data, size) ((void) 0) +#endif + // One `getDisplayInfo` reply: the exception code the stub writes, the non-null marker // `writeTypedObject` writes in front of the Parcelable, and the record itself -- walked in the order // the device's own `DisplayInfo.writeToParcel` writes it, out of the sequence `displayLayoutLoad` @@ -981,6 +1161,7 @@ static bool displayInfoParse(const uint8_t* data, size_t size, const FFDisplayAn } if (readerI32(&reader) != 1) { FF_DEBUG("getDisplayInfo answered no display"); + FF_DISPLAY_ANDROID_DEBUG_DUMP(data, size); return false; } @@ -1012,7 +1193,14 @@ static bool displayInfoParse(const uint8_t* data, size_t size, const FFDisplayAn for (uint32_t i = 0; i < layout->displayInfoCount; ++i) { const FFDexParcelField* field = &layout->displayInfo[i]; if (!displayInfoField(&reader, field, layout, &walk)) { - FF_DEBUG("getDisplayInfo(%u) answered a %s this parser does not recognise", info->displayId, field->name); + // Which field, out of how many, what the jar says it is and how far the walk got before it + // gave up -- followed by the reply itself, which is what says whether the layout moved or + // this parser is reading a shape it was not written for. + FF_DEBUG( + "getDisplayInfo(%u) answered a %s (%s) this parser does not recognise -- field %u of %u, cursor at %zu of %zu", + info->displayId, field->name, field->type, i + 1, layout->displayInfoCount, reader.offset, reader.size + ); + FF_DISPLAY_ANDROID_DEBUG_DUMP(data, size); return false; } if (ffStrEquals(field->name, "uniqueId")) { @@ -1022,6 +1210,7 @@ static bool displayInfoParse(const uint8_t* data, size_t size, const FFDisplayAn } if (!complete) { FF_DEBUG("getDisplayInfo(%u) answered a record with no uniqueId", info->displayId); + FF_DISPLAY_ANDROID_DEBUG_DUMP(data, size); return false; } @@ -1042,6 +1231,7 @@ static bool displayInfoParse(const uint8_t* data, size_t size, const FFDisplayAn info->displayId, info->type, info->width, info->height, info->rotation, info->modeId, info->density, info->physicalXDpi, info->physicalYDpi ); + FF_DISPLAY_ANDROID_DEBUG_DUMP(data, size); return false; } @@ -1146,6 +1336,7 @@ static bool detectWithBinder(FFDisplayServerResult* ds) { const int32_t count = ffBinderReadI32(reply.data, reply.size, sizeof(int32_t)); if (count <= 0 || count > FF_DISPLAY_ANDROID_MAX_DISPLAYS) { FF_DEBUG("getDisplayIds answered %d displays", count); + FF_DISPLAY_ANDROID_DEBUG_DUMP(reply.data, reply.size); return false; } @@ -1154,6 +1345,7 @@ static bool detectWithBinder(FFDisplayServerResult* ds) { displayIds[i] = ffBinderReadI32(reply.data, reply.size, (size_t) (2 + i) * sizeof(int32_t)); if (displayIds[i] < 0) { FF_DEBUG("getDisplayIds answered display id %d", displayIds[i]); + FF_DISPLAY_ANDROID_DEBUG_DUMP(reply.data, reply.size); return false; } }