mirror of
https://github.com/fastfetch-cli/fastfetch.git
synced 2026-10-28 02:31:25 +01:00
Common (Android): unifies dex parsers
This commit is contained in:
+76
-142
@@ -698,6 +698,46 @@ static const char* dexWalkParcelFields(const FFDexTables* tables, const uint8_t*
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Walks the instance field list of one `class_data_item` and writes `true` for every name it holds.
|
||||
// The list is the caller's, so this answers one request's worth of names or several.
|
||||
//
|
||||
// This is the question a Parcelable whose layout is not field-driven needs: `DisplayCutout` reads
|
||||
// everything through compiler-synthesised accessors, so the set of fields its class declares is the
|
||||
// only thing that says how long its body is. It is asked as part of a write-sequence request rather
|
||||
// than by a call of its own -- the list is already positioned by the `class_data_item` the sequence
|
||||
// comes out of, and a separate call would repeat the whole walk to reach it.
|
||||
static const char* dexMatchInstanceFields(const FFDexTables* tables, const uint8_t* fields, uint32_t fieldCount, const char* const* names, bool* results, uint32_t nameCount) {
|
||||
const uint8_t* dex = tables->dex;
|
||||
const uint8_t* end = tables->end;
|
||||
|
||||
uint32_t fieldIndex = 0;
|
||||
for (uint32_t j = 0; j < fieldCount; ++j) {
|
||||
if (fields >= end) {
|
||||
return "The dex class data is truncated";
|
||||
}
|
||||
const uint32_t fieldIndexDiff = dexUleb128(&fields, end); // field_idx_diff
|
||||
if (fieldIndexDiff > UINT32_MAX - fieldIndex) {
|
||||
return "The dex field index is out of range";
|
||||
}
|
||||
fieldIndex += fieldIndexDiff;
|
||||
(void) dexUleb128(&fields, end); // access_flags
|
||||
|
||||
if (fieldIndex >= tables->fieldIdCount) {
|
||||
return "The dex field table is out of range";
|
||||
}
|
||||
const char* name = dexString(dex, end, dexU32(tables->fieldIds + (size_t) fieldIndex * FF_DEX_FIELD_ID_SIZE + FF_DEX_OFF_FIELD_ID_NAME));
|
||||
if (name == nullptr) {
|
||||
continue;
|
||||
}
|
||||
for (uint32_t i = 0; i < nameCount; ++i) {
|
||||
if (!results[i] && strcmp(name, names[i]) == 0) {
|
||||
results[i] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Walks the method list of the class that `typeIndex` names, and for every request whose method name
|
||||
// it holds walks that method's instructions. `defined` is what `dexClassStaticInts` reports, for the
|
||||
// same reason.
|
||||
@@ -721,6 +761,22 @@ static const char* dexClassParcelFields(const FFDexTables* tables, uint32_t type
|
||||
return error;
|
||||
}
|
||||
|
||||
// The instance fields first, out of the list the `class_data_item` has already positioned. A
|
||||
// request that asked for them is answered here whether or not it also asked for a sequence, and
|
||||
// one that asked for neither is settled by the loop at the end like any other.
|
||||
for (uint32_t r = 0; r < count; ++r) {
|
||||
if (settled[r] || typeIndexes[r] != typeIndex || requests[r].instanceFieldCount == 0) {
|
||||
continue;
|
||||
}
|
||||
error = dexMatchInstanceFields(
|
||||
tables, classData.instanceFields, classData.instanceFieldCount,
|
||||
requests[r].instanceFieldNames, requests[r].instanceFieldResults, requests[r].instanceFieldCount
|
||||
);
|
||||
if (error != nullptr) {
|
||||
return error;
|
||||
}
|
||||
}
|
||||
|
||||
const uint8_t* methods = classData.methods;
|
||||
// Two runs, each delta-encoded from zero on its own -- so the index is reset per run rather than
|
||||
// carried across, and a direct method and a virtual one under the same index both resolve.
|
||||
@@ -748,7 +804,10 @@ static const char* dexClassParcelFields(const FFDexTables* tables, uint32_t type
|
||||
}
|
||||
|
||||
for (uint32_t r = 0; r < count; ++r) {
|
||||
if (settled[r] || typeIndexes[r] != typeIndex || strcmp(name, requests[r].methodName) != 0) {
|
||||
// A request that left the sequence out is one that only wanted the instance fields,
|
||||
// which are already written; it has no method name to match against.
|
||||
if (settled[r] || typeIndexes[r] != typeIndex || requests[r].methodName == nullptr
|
||||
|| strcmp(name, requests[r].methodName) != 0) {
|
||||
continue;
|
||||
}
|
||||
// A method with no body -- abstract, or native -- writes nothing, and the count stays 0.
|
||||
@@ -777,66 +836,6 @@ static const char* dexClassParcelFields(const FFDexTables* tables, uint32_t type
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Walks the instance field list of the class that `typeIndex` names and answers every request whose
|
||||
// field name it holds. `defined` as above.
|
||||
//
|
||||
// This is the question a Parcelable whose layout is not field-driven needs: `DisplayCutout` reads
|
||||
// everything through compiler-synthesised accessors, so the set of fields its class declares is the
|
||||
// only thing that says how long its body is.
|
||||
static const char* dexClassInstanceFields(const FFDexTables* tables, uint32_t typeIndex, const FFDexInstanceFieldRequest* requests, const uint32_t* typeIndexes, uint8_t* settled, uint32_t count, bool* defined) {
|
||||
const uint8_t* classDef = dexFindClassDef(tables, typeIndex);
|
||||
*defined = classDef != nullptr;
|
||||
if (classDef == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const uint8_t* dex = tables->dex;
|
||||
const uint8_t* end = tables->end;
|
||||
|
||||
FFDexClassData classData;
|
||||
const char* error = dexReadClassData(tables, classDef, &classData);
|
||||
if (error != nullptr) {
|
||||
return error;
|
||||
}
|
||||
|
||||
const uint8_t* fields = classData.instanceFields;
|
||||
uint32_t fieldIndex = 0;
|
||||
for (uint32_t j = 0; j < classData.instanceFieldCount; ++j) {
|
||||
if (fields >= end) {
|
||||
return "The dex class data is truncated";
|
||||
}
|
||||
const uint32_t fieldIndexDiff = dexUleb128(&fields, end); // field_idx_diff
|
||||
if (fieldIndexDiff > UINT32_MAX - fieldIndex) {
|
||||
return "The dex field index is out of range";
|
||||
}
|
||||
fieldIndex += fieldIndexDiff;
|
||||
(void) dexUleb128(&fields, end); // access_flags
|
||||
|
||||
if (fieldIndex >= tables->fieldIdCount) {
|
||||
return "The dex field table is out of range";
|
||||
}
|
||||
const char* name = dexString(dex, end, dexU32(tables->fieldIds + (size_t) fieldIndex * FF_DEX_FIELD_ID_SIZE + FF_DEX_OFF_FIELD_ID_NAME));
|
||||
if (name == nullptr) {
|
||||
continue;
|
||||
}
|
||||
for (uint32_t r = 0; r < count; ++r) {
|
||||
if (settled[r] || typeIndexes[r] != typeIndex || strcmp(name, requests[r].fieldName) != 0) {
|
||||
continue;
|
||||
}
|
||||
*requests[r].result = true;
|
||||
settled[r] = 1;
|
||||
}
|
||||
}
|
||||
|
||||
// A field the class does not declare is a `false`, which the results already hold.
|
||||
for (uint32_t r = 0; r < count; ++r) {
|
||||
if (!settled[r] && typeIndexes[r] == typeIndex) {
|
||||
settled[r] = 1;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------------------------
|
||||
// Locating classes.dex
|
||||
// ---------------------------------------------------------------------------------------------
|
||||
@@ -1215,10 +1214,17 @@ const char* ffDexStaticInts(const char* jarPath, const FFDexStaticIntRequest* re
|
||||
}
|
||||
|
||||
const char* ffDexParcelFields(const char* jarPath, const FFDexParcelRequest* requests, uint32_t count) {
|
||||
// Every request is written a count of 0 before anything can fail, so that a caller reads an
|
||||
// answer rather than whatever its own stack held there, however this returns.
|
||||
// Every request is written its answers before anything can fail, so that a caller reads an answer
|
||||
// rather than whatever its own stack held there, however this returns. The instance fields go
|
||||
// first: `false` is what both "the class does not declare it" and "the jar cannot be read" mean,
|
||||
// and the walk below only ever turns one of them true.
|
||||
for (uint32_t r = 0; r < count; ++r) {
|
||||
*requests[r].count = 0;
|
||||
if (requests[r].methodName != nullptr) {
|
||||
*requests[r].count = 0;
|
||||
}
|
||||
for (uint32_t i = 0; i < requests[r].instanceFieldCount; ++i) {
|
||||
requests[r].instanceFieldResults[i] = false;
|
||||
}
|
||||
}
|
||||
if (count == 0) {
|
||||
return nullptr;
|
||||
@@ -1287,88 +1293,16 @@ const char* ffDexParcelFields(const char* jarPath, const FFDexParcelRequest* req
|
||||
// A request still holding 0 is one whose class no entry of the jar defines, or whose class does
|
||||
// not declare the method. Both are the same answer -- this build does not write that class -- and
|
||||
// for a debug build it is worth saying which of the two it was, since the count on its own does
|
||||
// not tell "this build has no such method" apart from "the reader went wrong".
|
||||
// not tell "this build has no such method" apart from "the reader went wrong". The same goes for
|
||||
// an instance field left `false`.
|
||||
for (uint32_t r = 0; r < count; ++r) {
|
||||
if (*requests[r].count == 0) {
|
||||
if (requests[r].methodName != nullptr && *requests[r].count == 0) {
|
||||
FF_DEBUG("The write sequence of \"%s.%s\" is not in the jar", requests[r].classDescriptor, requests[r].methodName);
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const char* ffDexInstanceFields(const char* jarPath, const FFDexInstanceFieldRequest* requests, uint32_t count) {
|
||||
// Every request is written `false` before anything can fail, so that a caller reads an answer
|
||||
// rather than whatever its own stack held there, however this returns.
|
||||
for (uint32_t r = 0; r < count; ++r) {
|
||||
*requests[r].result = false;
|
||||
}
|
||||
if (count == 0) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
[[gnu::cleanup(wrapDexMapping)]] FFDexMapping mapping = {};
|
||||
const char* error = dexMapJar(jarPath, &mapping);
|
||||
if (error != nullptr) {
|
||||
return error;
|
||||
}
|
||||
|
||||
FF_AUTO_FREE uint8_t* settled = calloc(count, 1);
|
||||
FF_AUTO_FREE uint32_t* typeIndexes = malloc((size_t) count * sizeof(uint32_t));
|
||||
FF_AUTO_FREE const char** descriptors = malloc((size_t) count * sizeof(const char*));
|
||||
if (settled == nullptr || typeIndexes == nullptr || descriptors == nullptr) {
|
||||
FF_DEBUG("Allocating the tables for %u instance field requests failed", count);
|
||||
return "malloc failed";
|
||||
}
|
||||
for (uint32_t r = 0; r < count; ++r) {
|
||||
descriptors[r] = requests[r].classDescriptor;
|
||||
}
|
||||
|
||||
uint32_t pending = count;
|
||||
for (uint32_t index = 1; index <= FF_DEX_MAX_ENTRIES && pending > 0; ++index) {
|
||||
FF_AUTO_FREE uint8_t* inflated = nullptr;
|
||||
FFDexEntry entry = {};
|
||||
bool present = false;
|
||||
error = dexOpenEntry(&mapping, index, &present, &inflated, &entry);
|
||||
if (error != nullptr) {
|
||||
// The dex cannot be read at all, which no later entry can fix.
|
||||
return error;
|
||||
}
|
||||
if (!present) {
|
||||
break;
|
||||
}
|
||||
|
||||
dexFindTypes(&entry.tables, descriptors, settled, typeIndexes, count);
|
||||
|
||||
for (uint32_t r = 0; r < count; ++r) {
|
||||
if (settled[r] || typeIndexes[r] == UINT32_MAX) {
|
||||
continue;
|
||||
for (uint32_t i = 0; i < requests[r].instanceFieldCount; ++i) {
|
||||
if (!requests[r].instanceFieldResults[i]) {
|
||||
FF_DEBUG("\"%s\" is not an instance field of \"%s\"", requests[r].instanceFieldNames[i], requests[r].classDescriptor);
|
||||
}
|
||||
const uint32_t typeIndex = typeIndexes[r];
|
||||
bool defined = false;
|
||||
error = dexClassInstanceFields(&entry.tables, typeIndex, requests, typeIndexes, settled, count, &defined);
|
||||
if (error != nullptr) {
|
||||
return error;
|
||||
}
|
||||
if (!defined) {
|
||||
for (uint32_t s = r; s < count; ++s) {
|
||||
if (typeIndexes[s] == typeIndex) {
|
||||
typeIndexes[s] = UINT32_MAX;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pending = 0;
|
||||
for (uint32_t r = 0; r < count; ++r) {
|
||||
if (!settled[r]) {
|
||||
++pending;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (uint32_t r = 0; r < count; ++r) {
|
||||
if (!*requests[r].result) {
|
||||
FF_DEBUG("\"%s\" is not an instance field of \"%s\"", requests[r].fieldName, requests[r].classDescriptor);
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
|
||||
+33
-29
@@ -122,42 +122,46 @@ typedef struct FFDexParcelField {
|
||||
char type[FF_DEX_PARCEL_FIELD_TYPE_MAX];
|
||||
} FFDexParcelField;
|
||||
|
||||
// One `<classDescriptor>.<methodName>` write sequence, and where it is written.
|
||||
// One class, and the two things that can be asked about it: the write sequence of one of its methods,
|
||||
// and which instance fields it declares. Either half may be left out, and both are answered out of
|
||||
// the same `class_data_item` -- which is why they are one request rather than two calls.
|
||||
//
|
||||
// Asking for them together is not a convenience. Both halves are read from a walk that is dominated
|
||||
// by the type table -- 8428 descriptors in `framework.jar`'s `classes.dex`, each read from a
|
||||
// different part of a 51 MB mapping -- and the fields a class declares are listed in the very
|
||||
// `class_data_item` the sequence is read out of. A second call would map the jar again, open every
|
||||
// entry again and make that pass again to reach a list the first call already had in hand. Measured
|
||||
// on a Redmi 9A, that second call was 7.0 ms of a 23.5 ms lookup.
|
||||
typedef struct FFDexParcelRequest {
|
||||
const char* classDescriptor;
|
||||
|
||||
// The write sequence. Left out by a request that only wants the instance fields, in which case
|
||||
// `fields`, `capacity` and `count` are not read.
|
||||
const char* methodName;
|
||||
FFDexParcelField* fields;
|
||||
uint32_t capacity;
|
||||
uint32_t* count; // how many fields the sequence holds, or 0 when the jar cannot answer
|
||||
|
||||
// The instance fields. This is the question a Parcelable whose layout is not field-driven needs.
|
||||
// `DisplayCutout` is one: its `writeCutoutToParcel` reads everything through `-$$Nest$` accessors
|
||||
// the compiler synthesised, so there is no `iget` to walk and no class name on the wire either.
|
||||
// What does say how long its body is, is which fields the class carries -- Android 11 declares
|
||||
// three, Android 13 adds `mCutoutPathParserInfo` and Android 16 adds `mSideOverrides`, and each
|
||||
// one adds a known run of writes behind the three both have. A declaration is a field set rather
|
||||
// than an order, which is exactly what this answers.
|
||||
const char* const* instanceFieldNames;
|
||||
bool* instanceFieldResults;
|
||||
uint32_t instanceFieldCount;
|
||||
} FFDexParcelRequest;
|
||||
|
||||
// Resolves the write sequence of every request out of the dex entries of `jarPath`, walking
|
||||
// `classes.dex`, `classes2.dex`, ... in order until one of them defines the class -- the same walk,
|
||||
// and the same one pass over the type table, that ffDexStaticInts makes.
|
||||
// Resolves every request out of the dex entries of `jarPath`, walking `classes.dex`, `classes2.dex`,
|
||||
// ... in order until one of them defines the class -- the same walk, and the same one pass over the
|
||||
// type table, that ffDexStaticInts makes.
|
||||
//
|
||||
// Every request is written a count or 0. A class the jar does not declare, a method it does not
|
||||
// declare, and a sequence longer than the caller's `capacity` all leave 0 behind, and none of them
|
||||
// holds back the others: they are independent questions about the same pass. A count of 0 is not a
|
||||
// failure but an answer -- this build does not write that class, so a caller reading it has nothing
|
||||
// to walk. The return value is reserved for the jar itself, as in ffDexStaticInts.
|
||||
// Every request is written its answers before anything can fail: a count of 0 for the sequence, and
|
||||
// `false` for every instance field. A class the jar does not declare, a method it does not declare,
|
||||
// and a sequence longer than the caller's `capacity` all leave the count at 0, and none of them holds
|
||||
// back the others: they are independent questions about the same pass. A count of 0 is not a failure
|
||||
// but an answer -- this build does not write that class, so a caller reading it has nothing to walk.
|
||||
// The return value is reserved for the jar itself, as in ffDexStaticInts.
|
||||
[[gnu::nonnull(1, 2), nodiscard]] const char* ffDexParcelFields(const char* jarPath, const FFDexParcelRequest* requests, uint32_t count);
|
||||
|
||||
// Whether `<classDescriptor>` declares an instance field named `<fieldName>`.
|
||||
//
|
||||
// This is the question a Parcelable whose layout is not field-driven needs. `DisplayCutout` is one:
|
||||
// its `writeCutoutToParcel` reads everything through `-$$Nest$` accessors the compiler synthesised,
|
||||
// so there is no `iget` to walk and no class name on the wire either. What does say how long its body
|
||||
// is, is which fields the class carries -- Android 11 declares three, Android 13 adds
|
||||
// `mCutoutPathParserInfo` and Android 16 adds `mSideOverrides`, and each one adds a known run of
|
||||
// writes behind the three both have. The declaration is a field set rather than an order, which is
|
||||
// exactly what this answers.
|
||||
typedef struct FFDexInstanceFieldRequest {
|
||||
const char* classDescriptor;
|
||||
const char* fieldName;
|
||||
bool* result;
|
||||
} FFDexInstanceFieldRequest;
|
||||
|
||||
// Writes `false` for every request the jar cannot answer, on every path out -- a class the jar does
|
||||
// not declare and a field the class does not declare are the same answer here, because both mean the
|
||||
// field is not in the parcel. The return value is reserved for the jar itself.
|
||||
[[gnu::nonnull(1, 2), nodiscard]] const char* ffDexInstanceFields(const char* jarPath, const FFDexInstanceFieldRequest* requests, uint32_t count);
|
||||
|
||||
@@ -411,6 +411,20 @@ static bool detectWithDumpsys(FFDisplayServerResult* ds) {
|
||||
#define FF_DISPLAY_ANDROID_NESTED_FIELDS 8
|
||||
#define FF_DISPLAY_ANDROID_MODE_FIELDS 12
|
||||
|
||||
// The two instance fields of `DisplayCutout` that lengthen its body, in the order the request writes
|
||||
// their results. `mCutoutPathParserInfo` arrived in Android 13 and adds nine writes, `mSideOverrides`
|
||||
// in 16 and adds one; Android 11 declares neither.
|
||||
typedef enum FFDisplayAndroidCutoutField : uint32_t {
|
||||
FF_DISPLAY_ANDROID_CUTOUT_PATH_PARSER_INFO,
|
||||
FF_DISPLAY_ANDROID_CUTOUT_SIDE_OVERRIDES,
|
||||
FF_DISPLAY_ANDROID_CUTOUT_FIELD_COUNT,
|
||||
} FFDisplayAndroidCutoutField;
|
||||
|
||||
static const char* const ffDisplayAndroidCutoutFields[FF_DISPLAY_ANDROID_CUTOUT_FIELD_COUNT] = {
|
||||
"mCutoutPathParserInfo",
|
||||
"mSideOverrides",
|
||||
};
|
||||
|
||||
// The write sequence of every class the reply is made of, read out of the device's own jar. It is
|
||||
// what turns the parse from a sequence written against one release into a walk of the release that
|
||||
// is running.
|
||||
@@ -432,13 +446,14 @@ typedef struct FFDisplayAndroidLayout {
|
||||
|
||||
// `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
|
||||
// long its body is, is which fields the class declares: `mCutoutPathParserInfo` arrived in
|
||||
// Android 13 and adds nine writes, `mSideOverrides` in 16 and adds one.
|
||||
bool cutoutPathParserInfo;
|
||||
bool cutoutSideOverrides;
|
||||
// long its body is, is which fields the class declares, and that is what is asked for here. They
|
||||
// are one array rather than two members because the request writes them as a run.
|
||||
bool cutoutFields[FF_DISPLAY_ANDROID_CUTOUT_FIELD_COUNT];
|
||||
} FFDisplayAndroidLayout;
|
||||
|
||||
// Reads every sequence above out of the dex entries of `jarPath`, in one lookup.
|
||||
// Reads every sequence above, and `DisplayCutout`'s declared fields, out of the dex entries of
|
||||
// `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
|
||||
@@ -499,6 +514,18 @@ static bool displayLayoutLoad(const char* jarPath, FFDisplayAndroidLayout* layou
|
||||
.capacity = FF_DISPLAY_ANDROID_NESTED_FIELDS,
|
||||
.count = &layout->frameRateCategoryRateCount,
|
||||
},
|
||||
{
|
||||
// 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
|
||||
// on the wire either -- what says how long its body is, is which fields the class
|
||||
// declares. Asking here rather than by a call of its own is the point: the walk that
|
||||
// answers the seven sequences above is already holding this class's field list, and a
|
||||
// second call would repeat it from the mapping down for 7.0 ms of a 23.5 ms lookup.
|
||||
.classDescriptor = FF_DISPLAY_ANDROID_CLASS_CUTOUT,
|
||||
.instanceFieldNames = ffDisplayAndroidCutoutFields,
|
||||
.instanceFieldResults = layout->cutoutFields,
|
||||
.instanceFieldCount = ARRAY_SIZE(ffDisplayAndroidCutoutFields),
|
||||
},
|
||||
};
|
||||
|
||||
const char* error = ffDexParcelFields(jarPath, requests, ARRAY_SIZE(requests));
|
||||
@@ -512,28 +539,11 @@ static bool displayLayoutLoad(const char* jarPath, FFDisplayAndroidLayout* layou
|
||||
return false;
|
||||
}
|
||||
|
||||
const FFDexInstanceFieldRequest cutoutRequests[] = {
|
||||
{
|
||||
.classDescriptor = FF_DISPLAY_ANDROID_CLASS_CUTOUT,
|
||||
.fieldName = "mCutoutPathParserInfo",
|
||||
.result = &layout->cutoutPathParserInfo,
|
||||
},
|
||||
{
|
||||
.classDescriptor = FF_DISPLAY_ANDROID_CLASS_CUTOUT,
|
||||
.fieldName = "mSideOverrides",
|
||||
.result = &layout->cutoutSideOverrides,
|
||||
},
|
||||
};
|
||||
error = ffDexInstanceFields(jarPath, cutoutRequests, ARRAY_SIZE(cutoutRequests));
|
||||
if (error != nullptr) {
|
||||
FF_DEBUG("Reading the cutout layout out of %s failed: %s", jarPath, error);
|
||||
return false;
|
||||
}
|
||||
|
||||
FF_DEBUG(
|
||||
"Display layout: %u fields in DisplayInfo, %u in a mode, cutout path parser info %s, side overrides %s",
|
||||
layout->displayInfoCount, layout->modeCount,
|
||||
layout->cutoutPathParserInfo ? "yes" : "no", layout->cutoutSideOverrides ? "yes" : "no"
|
||||
layout->cutoutFields[FF_DISPLAY_ANDROID_CUTOUT_PATH_PARSER_INFO] ? "yes" : "no",
|
||||
layout->cutoutFields[FF_DISPLAY_ANDROID_CUTOUT_SIDE_OVERRIDES] ? "yes" : "no"
|
||||
);
|
||||
return true;
|
||||
}
|
||||
@@ -1044,7 +1054,8 @@ static bool readerDisplayCutout(FFDisplayAndroidReader* reader, const FFDisplayA
|
||||
return false;
|
||||
}
|
||||
|
||||
if (layout->cutoutPathParserInfo) {
|
||||
const bool* cutout = layout->cutoutFields;
|
||||
if (cutout[FF_DISPLAY_ANDROID_CUTOUT_PATH_PARSER_INFO]) {
|
||||
// the display size the cutout was built for and then the physical one
|
||||
if (!readerSkip(reader, 4 * sizeof(int32_t))) {
|
||||
return false;
|
||||
@@ -1060,7 +1071,7 @@ static bool readerDisplayCutout(FFDisplayAndroidReader* reader, const FFDisplayA
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (layout->cutoutSideOverrides) {
|
||||
if (cutout[FF_DISPLAY_ANDROID_CUTOUT_SIDE_OVERRIDES]) {
|
||||
return readerSkipArray(reader, sizeof(int32_t)); // mSideOverrides
|
||||
}
|
||||
return true;
|
||||
|
||||
Reference in New Issue
Block a user