mirror of
https://github.com/fastfetch-cli/fastfetch.git
synced 2026-10-27 23:30:47 +01:00
Display (Android): be compatible with Android 17
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user