mirror of
https://github.com/fastfetch-cli/fastfetch.git
synced 2026-10-28 07:44:06 +01:00
Display (Android): detects via binder
This commit is contained in:
@@ -12,6 +12,9 @@ Changes:
|
|||||||
|
|
||||||
Features:
|
Features:
|
||||||
* Added Sound detection on Android. (Sound, Android)
|
* 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)
|
* Added battery temperature, remaining time and charger type (AC / USB / wireless) detection on Android. (Battery, Android)
|
||||||
* Performance improvements & internal cleanups
|
* Performance improvements & internal cleanups
|
||||||
* Added Finnish language support. (General)
|
* 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
|
// 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.
|
// 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);
|
[[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