mirror of
https://github.com/fastfetch-cli/fastfetch.git
synced 2026-10-28 02:31:25 +01:00
Display (Android): detects via binder
This commit is contained in:
@@ -12,6 +12,9 @@ Changes:
|
||||
|
||||
Features:
|
||||
* Added Sound detection on Android. (Sound, Android)
|
||||
* Added display detection on Android over binder, which needs neither a child process nor a permission.
|
||||
* The module took 16.0-26.5 ms on the test device and now takes 1.3-1.5 ms. The `cmd` and `dumpsys` routes are unchanged and remain the fallback.
|
||||
* It is also the only route that answers for an app UID on Android 12 and older, where `cmd display get-displays` does not exist and `dumpsys` is behind `android.permission.DUMP`. (Display, Android)
|
||||
* Added battery temperature, remaining time and charger type (AC / USB / wireless) detection on Android. (Battery, Android)
|
||||
* Performance improvements & internal cleanups
|
||||
* Added Finnish language support. (General)
|
||||
|
||||
+777
-156
File diff suppressed because it is too large
Load Diff
@@ -62,3 +62,102 @@ typedef struct FFDexStaticIntRequest {
|
||||
// caller cannot read out of the results: the jar itself, which is reported when it cannot be opened
|
||||
// or does not hold a dex that can be read. nullptr means the lookup ran.
|
||||
[[gnu::nonnull(1, 2), nodiscard]] const char* ffDexStaticInts(const char* jarPath, const FFDexStaticIntRequest* requests, uint32_t count);
|
||||
|
||||
// ---------------------------------------------------------------------------------------------
|
||||
// Reading a Parcelable's wire layout
|
||||
// ---------------------------------------------------------------------------------------------
|
||||
//
|
||||
// A Parcelable's `writeToParcel` puts its fields on the wire in the order the method reads them, and
|
||||
// that order is the only thing that says where each one is. It is not in the class's declaration: a
|
||||
// dex stores a class's instance fields sorted by field index, which is sorted by *name*, so the
|
||||
// declaration order a reader would want is not recorded anywhere but the method body.
|
||||
//
|
||||
// Recovering it therefore means walking the bytecode. The compiler emits
|
||||
//
|
||||
// iget v0, p0, LDisplayInfo;->logicalWidth:I
|
||||
// invoke-virtual {p1, v0}, Landroid/os/Parcel;->writeInt(I)V
|
||||
//
|
||||
// once per field, in write order, so the `iget` opcodes in a linear pass over the instruction grid
|
||||
// are the sequence. Two things have to be got right for that pass to stay on the grid, and both are
|
||||
// easy to get wrong in a way that still produces a plausible-looking list:
|
||||
//
|
||||
// * Instructions are 1, 2, 3, 4 or 5 code units wide, and the width is a property of the opcode.
|
||||
// The table below is ART's `Instruction::SizeInCodeUnits`.
|
||||
// * The payload pseudo-instructions a `switch` or a `fill-array-data` leaves in the stream have a
|
||||
// low byte of zero and a high byte of 1, 2 or 3. They are not instructions, they sit behind every
|
||||
// reachable instruction, and stepping into one desynchronises the grid -- so the walk stops at the
|
||||
// first of them rather than trying to step over it.
|
||||
//
|
||||
// An array field is read twice: once for its `length`, which is the count that goes on the wire, and
|
||||
// again inside the loop that writes the elements. A class cannot declare two instance fields under
|
||||
// one name, so a repeat is always that reload, and the first occurrence is where the field is
|
||||
// written. The reader de-duplicates by name for that reason.
|
||||
//
|
||||
// What is *not* recovered is how each field is framed -- `writeString` against `writeString8`,
|
||||
// `writeParcelable` against `writeTypedObject`. That is a property of the method the field is handed
|
||||
// to, not of the field, and two `String` fields of the same class can differ: `DisplayInfo.name` goes
|
||||
// through `writeString8` while `DeviceProductInfo.mName` goes through `writeString`. Telling them
|
||||
// apart would mean resolving every invoke target, so the type descriptor is reported instead and the
|
||||
// caller frames the fields its descriptor settles -- an `I`, a `J`, an `[F` -- and names the few it
|
||||
// does not. Measured over three releases' own `framework.jar`, that is four names per class.
|
||||
//
|
||||
// The point of asking the device rather than carrying a table is that the field set moves: `DisplayInfo`
|
||||
// has 34 instance fields on Android 11, 42 on 13 and 59 on 16, and the fields that arrived in between
|
||||
// (`displayGroupId`, `renderFrameRate`, `appsSupportedModes`, `committedState`, ...) are exactly the
|
||||
// ones a parser written against one release reads out of the middle of another. Reading the sequence
|
||||
// from the build that is running makes every one of them optional by construction.
|
||||
|
||||
// A field name and a type descriptor are ASCII and short -- the longest name in `DisplayInfo` is 28
|
||||
// bytes and the longest descriptor `Landroid/hardware/display/DeviceProductInfo$ManufactureDate;` is
|
||||
// 60. A longer one is a class this reader was not written for, and is reported as unresolved rather
|
||||
// than truncated: half a name would silently match nothing, which is indistinguishable from the field
|
||||
// not being there.
|
||||
#define FF_DEX_PARCEL_FIELD_NAME_MAX 40
|
||||
#define FF_DEX_PARCEL_FIELD_TYPE_MAX 72
|
||||
|
||||
// One field of the write sequence. `type` is the dex type descriptor, e.g. "I", "[F",
|
||||
// "Ljava/lang/String;" or "Landroid/view/DisplayAddress;".
|
||||
typedef struct FFDexParcelField {
|
||||
char name[FF_DEX_PARCEL_FIELD_NAME_MAX];
|
||||
char type[FF_DEX_PARCEL_FIELD_TYPE_MAX];
|
||||
} FFDexParcelField;
|
||||
|
||||
// One `<classDescriptor>.<methodName>` write sequence, and where it is written.
|
||||
typedef struct FFDexParcelRequest {
|
||||
const char* classDescriptor;
|
||||
const char* methodName;
|
||||
FFDexParcelField* fields;
|
||||
uint32_t capacity;
|
||||
uint32_t* count; // how many fields the sequence holds, or 0 when the jar cannot answer
|
||||
} 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.
|
||||
//
|
||||
// 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.
|
||||
[[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);
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user