mirror of
https://github.com/fastfetch-cli/fastfetch.git
synced 2026-10-27 23:30:47 +01:00
Global (Android): code cleanup
This commit is contained in:
+12
-22
@@ -3,17 +3,11 @@
|
||||
Changes:
|
||||
* ImageMagick is no longer used for image logos on Windows, macOS and Android, and has been replaced by the platform image frameworks (WIC on Windows, ImageIO on macOS, AImageDecoder on Android). (Logo)
|
||||
* An image logo needs Android 11 (API 30), the release that introduced the platform image decoder, and is reported as an error on Android 10 and older.
|
||||
* ImageMagick 6 support is deprecated. It is kept only for old Debian and Ubuntu releases that don't have ImageMagick 7 available.
|
||||
* It is intended to be removed in a future release. Users are encouraged to upgrade to ImageMagick 7 when possible.
|
||||
|
||||
* ImageMagick 6 support is deprecated. It is kept only for old Debian and Ubuntu releases that don't have ImageMagick 7 available, and is intended to be removed in a future release. Users are encouraged to upgrade to ImageMagick 7 when possible.
|
||||
* The `--logo-recache` option has been replaced by `--logo-cache <bool|regen>`, and the `logo.recache` JSON property has been renamed to `logo.cache`. (Logo)
|
||||
* `--logo-cache true` (the default) reuses a cached rendering when it is valid, and writes it back on a cache miss.
|
||||
* `--logo-cache false` ignores the image logo cache completely: nothing is read from it and nothing is written to it.
|
||||
* `--logo-cache regen` does what `--logo-recache true` used to do.
|
||||
* `logo.cache` accepts a boolean, or the string `"regen"`.
|
||||
* `--logo-cache true` (the default) reuses a cached rendering when it is valid, and writes it back on a cache miss; `false` ignores the image logo cache completely, reading nothing from it and writing nothing to it; `regen` does what `--logo-recache true` used to do. `logo.cache` accepts a boolean, or the string `"regen"`.
|
||||
* The `waitTime` option of `DiskIO` and `NetIO` now defaults to `250` ms instead of `500`. (DiskIO / NetIO)
|
||||
* The byte counters are maintained by the kernel as I/O happens, so a shorter sampling window still yields an accurate rate, and both modules now finish about 250 ms sooner.
|
||||
* Note that the two modules wait concurrently, so enabling both does not cost twice the wait time.
|
||||
* The byte counters are maintained by the kernel as I/O happens, so a shorter sampling window still yields an accurate rate, and both modules now finish about 250 ms sooner. The two modules wait concurrently, so enabling both does not cost twice the wait time.
|
||||
|
||||
Features:
|
||||
* Improved image logo support
|
||||
@@ -22,21 +16,18 @@ Features:
|
||||
* Added an embedded libsixel encoder, used to produce sixel output on Windows, macOS and Android. It is reported by `fastfetch --list-features` as "Embedded sixel".
|
||||
* Enabled chafa image output on Windows, macOS and Android independently of ImageMagick.
|
||||
* As a result, `fastfetch --sixel X:\path\to\image` now works out of the box on Windows Terminal.
|
||||
* Image logo cache entries are now validated against the modification time of the source image. (Logo)
|
||||
* Editing an image logo in place now invalidates its cached rendering.
|
||||
* Image logo cache entries are now validated against the modification time of the source image, so editing an image logo in place invalidates its cached rendering. (Logo)
|
||||
* Cache entries written by older versions are not reused, as they carry no modification time.
|
||||
* Image logos can now be animated when the terminal and the image protocol support it. (Logo)
|
||||
* `--logo-animation-frame <0>` (`logo.animationFrame: 0` in the JSON config) plays a GIF or APNG. Only the `kitty` image protocol can play an animation; the frames are decoded and composed by fastfetch, so no external program is involved.
|
||||
* `--logo-animation-frame <N>` renders the Nth frame as a still image, and negative values count back from the end, so `-1` is the last frame. This works for the `sixel`, `kitty` and `chafa` logo types. Note that negative values can only be given in the JSON config, as the command line parser reads a leading `-` as another option.
|
||||
* The default is `1`, which renders a still image, so nothing changes for anyone who does not opt in.
|
||||
* The frames come from the platform image framework (WIC on Windows, ImageIO on macOS, AImageDecoder on Android, ImageMagick 7 on Linux). A single-frame GIF falls back to a still image.
|
||||
* On Android an animation needs Android 12 (API 31). The repeat count is not available there, so an animation is reported as looping forever. A build with no image decoder, or one built with ImageMagick 6, reports an error instead of silently showing a still image.
|
||||
* The default is `1`, which renders a still image, so nothing changes for anyone who does not opt in. The frames come from the platform image framework (WIC on Windows, ImageIO on macOS, AImageDecoder on Android, ImageMagick 7 on Linux); a single-frame GIF falls back to a still image.
|
||||
* On Android an animation needs Android 12 (API 31), and the repeat count is not available there, so an animation is reported as looping forever. A build with no image decoder, or one built with ImageMagick 6, reports an error instead of silently showing a still image.
|
||||
* A terminal that supports the kitty graphics protocol but not its animation frames, such as Konsole, shows the first frame.
|
||||
* Added the CMake option `ENABLE_IMAGE_LOGO`, which defaults to `ON`. Configure with `-DENABLE_IMAGE_LOGO=OFF` to build fastfetch without any image logo support. (Logo)
|
||||
* Image logos are the only consumer of ImageMagick, chafa, and the embedded libsixel encoder, so none of the three is searched for at configure time, and no image decoding sources are compiled in.
|
||||
* Image logos are the only consumer of ImageMagick, chafa, and the embedded libsixel encoder, so none of the three is searched for at configure time, and no image decoding sources are compiled in. This is intended only to reduce binary size on embedded systems (such as OpenWrt).
|
||||
* The `sixel`, `kitty`, `kitty-direct`, `kitty-icat`, `iterm` and `chafa` logo types are rejected with an error, both on the command line and in the JSON config, and the `auto` logo type never tries an image.
|
||||
* `--logo-type raw` keeps working: it passes a pre-rendered byte stream through unchanged and needs no decoder, so a logo can still be displayed by converting the image externally.
|
||||
* This is intended only to reduce binary size on embedded systems (such as OpenWrt).
|
||||
* Added CPU name and frequency detection support on SPARC. (CPU, Linux)
|
||||
* Added package detection support for CRUX. (Packages, Linux)
|
||||
* Exposed in custom format as `{crux}`.
|
||||
@@ -44,8 +35,7 @@ Features:
|
||||
* Added support for HarmonyOS, HarmonyOS NEXT, Flyme, JOYUI, SmartisanOS, realme UI, HydrogenOS, ZUI, ZUXOS, MyOS, NebulaAIOS, ObricUI, MiFavor, LineageOS, PixelExperience, EUI and 360 UI.
|
||||
* Added support for MagicUI 3.x, which stores a bare version number instead of a `MagicUI_x.y.z` string.
|
||||
* Added Samsung OneUI support (#2541)
|
||||
* It is read from system properties, so no particular Android version is required.
|
||||
* This is mostly untested due to lack of available devices running these ROMs. Please report any issues you encounter.
|
||||
* It is read from system properties, so no particular Android version is required. This is mostly untested due to lack of available devices running these ROMs. Please report any issues you encounter.
|
||||
* Improved Camera detection on Android (Camera, Android)
|
||||
* The camera list is now read from the camera2 NDK instead of `termux-api CameraInfo`, so the Termux:API app is no longer required and no subprocess is spawned.
|
||||
* This needs Android 7.0 (API 24), which is where the camera2 NDK was introduced.
|
||||
@@ -53,13 +43,14 @@ Features:
|
||||
* The charge level and charging state are now read from the battery properties service over `/dev/binder` instead of `termux-api BatteryStatus`, so the Termux:API app is no longer required and no subprocess is spawned.
|
||||
* The service interface changed in Android 10, and the right request is picked at run time, so every Android release is covered.
|
||||
* Battery temperature, cycle count, manufacturer, model name, serial number and manufacture date need the `BATTERY_STATS` permission, which an app cannot obtain, so an app no longer reports them.
|
||||
* Running as `adb shell` or as root, `dumpsys battery` is used. The battery temperature and the battery technology are reported as well, and a battery that has reached the critical level is reported as such. An app can read none of the three.
|
||||
* Running as `adb shell` or as root, `dumpsys battery` is used, which also reports the battery temperature and technology, and reports a battery that has reached the critical level as such. An app can read none of the three.
|
||||
* Improved Display detection on Android (Display, Android)
|
||||
* The displays are now read from the display service instead of a vendor property that only some Xiaomi devices set. The preferred mode, the physical size, the rotation, the manufacture date and the display id are now reported as well.
|
||||
* This needs Android 13 (API 33). On Android 12 and older only that vendor property is available to an app, and a device that does not set it reports no display.
|
||||
* Running as `adb shell` or as root, `dumpsys display` is used as well, which is what covers Android 12 and older.
|
||||
* The refresh rate is now the rate of the active display mode, and the HDR capability is read from the display itself, for every display rather than only for the built-in one.
|
||||
* Improved WiFi detection on Android (Wifi, Android)
|
||||
* The connection details are now read from the WiFi service over `/dev/binder` instead of `termux-api WifiConnectionInfo`, so the Termux:API app is no longer required and no subprocess is spawned.
|
||||
* The connection details are now read from the WiFi service over `/dev/binder` instead of `termux-api WifiConnectionInfo`, so no subprocess is spawned. The Termux:API app is still required, though: it is what carries the WiFi permission that the Termux app does not request itself, and uninstalling it turns this module off.
|
||||
* The interface name and its state, the connection state and the Wi-Fi standard are now reported as well.
|
||||
* This needs Android 11 (API 30), the release that moved the Wi-Fi framework into an APEX. There is no fallback, so Android 10 and older report an error instead.
|
||||
* Improved COSMIC detection (DE / WM, Linux)
|
||||
@@ -72,8 +63,7 @@ Features:
|
||||
* Removed the `kvm` dependency on OpenBSD by using `sysctl` directly. (General, OpenBSD)
|
||||
* Modules that were selected on the command line via `--structure` / `-s` now honor module options configured in the JSON config. (CommandOption)
|
||||
* Improved reliability of fastfetch's built-in HTTP client. (PublicIP, Weather)
|
||||
* It now supports custom ports and can properly handle chunked transfer encoding.
|
||||
* It is designed for minimal resource usage and fast performance. It does not support full HTTP features like HTTPS. Users can always use the `Command` module with `curl` to achieve similar functionality.
|
||||
* It now supports custom ports and can properly handle chunked transfer encoding. It is designed for minimal resource usage and fast performance, and does not support full HTTP features like HTTPS; the `Command` module with `curl` can be used for those.
|
||||
* Added Umbriel wayland compositor version detection (WM, Linux)
|
||||
* Improved the player name detection on Windows to show the name Windows shows for it. (Player, Windows)
|
||||
* An unpackaged player such as Chrome is now reported as `Google Chrome` instead of `Chrome`.
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
// The NDK marks an API unavailable whenever it is newer than the API level the build targets, and
|
||||
// fastfetch keeps running on devices below the levels it uses: AImageDecoder is 30, decoding past
|
||||
// the first frame is 31, and AMediaCodec_getName is 28. CMake compiles the files that reach for
|
||||
@@ -7,10 +10,36 @@
|
||||
// those entry points into weak references and makes every unguarded use an error. Two things follow:
|
||||
//
|
||||
// * A weak reference resolves to null on an older device, so every use needs a run-time check --
|
||||
// that is FF_API_AT_LEAST. Unguarded, the call would jump to a null pointer.
|
||||
// * Annotating a helper with FF_REQUIRES_API keeps the check in one place: the compiler rejects
|
||||
// that is FF_ANDROID_API_AT_LEAST. Unguarded, the call would jump to a null pointer.
|
||||
// * Annotating a helper with FF_ANDROID_REQUIRES_API keeps the check in one place: the compiler rejects
|
||||
// any call of that helper which is not itself inside a guard.
|
||||
//
|
||||
// https://developer.android.com/ndk/guides/using-newer-apis
|
||||
#define FF_REQUIRES_API(x) [[clang::availability(android, introduced = x)]]
|
||||
#define FF_API_AT_LEAST(x) __builtin_available(android x, *)
|
||||
#define FF_ANDROID_REQUIRES_API(x) [[clang::availability(android, introduced = x)]]
|
||||
#define FF_ANDROID_API_AT_LEAST(x) __builtin_available(android x, *)
|
||||
|
||||
// The UIDs that hold android.permission.DUMP, which is what `dumpsys` checks before it prints
|
||||
// anything: every other UID is refused on stdout and the command still exits 0, which a caller cannot
|
||||
// tell apart from a command that printed nothing useful. Forking a `dumpsys` from anywhere else is
|
||||
// therefore pure waste, and for some fields `dumpsys` is the only route there is.
|
||||
//
|
||||
// The refusal is not always the permission, and the text differs per service and per release. On
|
||||
// an Android 16 device an app UID gets `Permission Denial: can't dump DisplayManagerService ... due to
|
||||
// missing android.permission.DUMP permission` for `display`, but `Can't find service: battery` for
|
||||
// `battery` -- that service is not in the list `dumpsys -l` shows an app at all, while `display` is.
|
||||
// The UID is what decides, so one check covers both.
|
||||
//
|
||||
// android.os.Process.ROOT_UID and SHELL_UID. The shell UID has a second use: it owns exactly one
|
||||
// package name, which the system services accept, so a process that cannot derive its own package
|
||||
// can answer with that one instead.
|
||||
typedef enum FFAndroidPrivilegedUid : uint32_t {
|
||||
FF_ANDROID_PRIVILEGED_UID_ROOT = 0,
|
||||
FF_ANDROID_PRIVILEGED_UID_SHELL = 2000,
|
||||
} FFAndroidPrivilegedUid;
|
||||
|
||||
// Whether the given UID is allowed to run `dumpsys`. The UID is passed in rather than read here, so
|
||||
// that the caller can take it from `instance.state.platform.uid` -- which is what makes the
|
||||
// privileged path reachable from a test that does not run as root or as the shell.
|
||||
static inline bool ffAndroidIsRootOrShell(uint32_t uid) {
|
||||
return uid == FF_ANDROID_PRIVILEGED_UID_ROOT || uid == FF_ANDROID_PRIVILEGED_UID_SHELL;
|
||||
}
|
||||
|
||||
@@ -29,24 +29,29 @@
|
||||
#define FF_BATTERY_ANDROID_SERVICE "batteryproperties"
|
||||
#define FF_BATTERY_ANDROID_DESCRIPTOR "android.os.IBatteryPropertiesRegistrar"
|
||||
|
||||
// android.os.Process.ROOT_UID and SHELL_UID. These two are the only UIDs that hold
|
||||
// android.permission.DUMP, which `dumpsys` checks before it prints anything. See ffDetectBattery.
|
||||
#define FF_BATTERY_ANDROID_ROOT_UID 0
|
||||
#define FF_BATTERY_ANDROID_SHELL_UID 2000
|
||||
|
||||
// BatteryManager.BATTERY_PROPERTY_*
|
||||
#define FF_BATTERY_ANDROID_PROPERTY_CAPACITY 4u
|
||||
#define FF_BATTERY_ANDROID_PROPERTY_STATUS 6u
|
||||
typedef enum FFBatteryAndroidProperty : uint32_t {
|
||||
FF_BATTERY_ANDROID_PROPERTY_CAPACITY = 4,
|
||||
FF_BATTERY_ANDROID_PROPERTY_STATUS = 6,
|
||||
} FFBatteryAndroidProperty;
|
||||
|
||||
// BatteryManager.BATTERY_STATUS_*, which the health HAL's `BatteryStatus` mirrors value for value.
|
||||
#define FF_BATTERY_ANDROID_STATUS_UNKNOWN 1u
|
||||
#define FF_BATTERY_ANDROID_STATUS_CHARGING 2u
|
||||
#define FF_BATTERY_ANDROID_STATUS_DISCHARGING 3u
|
||||
// Only the three the module has something to say about are carried: NOT_CHARGING (4) and FULL (5)
|
||||
// both mean a charger is attached, which the powered bits already say.
|
||||
typedef enum FFBatteryAndroidStatus : uint32_t {
|
||||
FF_BATTERY_ANDROID_STATUS_UNKNOWN = 1,
|
||||
FF_BATTERY_ANDROID_STATUS_CHARGING = 2,
|
||||
FF_BATTERY_ANDROID_STATUS_DISCHARGING = 3,
|
||||
} FFBatteryAndroidStatus;
|
||||
|
||||
// BatteryCapacityLevel.BATTERY_CAPACITY_LEVEL_CRITICAL, the level the framework itself shuts the
|
||||
// device down on. This is the health HAL's numbering, not the one the older
|
||||
// `BatteryManager.BATTERY_CAPACITY_LEVEL_*` constants used, which had CRITICAL at 4.
|
||||
#define FF_BATTERY_ANDROID_CAPACITY_LEVEL_CRITICAL 1
|
||||
// `BatteryManager.BATTERY_CAPACITY_LEVEL_*` constants used, which had CRITICAL at 4. The rest of the
|
||||
// enum -- -1 UNSUPPORTED, 0 UNKNOWN, 2 LOW, 3 NORMAL, 4 HIGH, 5 FULL -- only restates the
|
||||
// percentage, so only this value is carried.
|
||||
typedef enum FFBatteryAndroidCapacityLevel : int32_t {
|
||||
FF_BATTERY_ANDROID_CAPACITY_LEVEL_CRITICAL = 1,
|
||||
} FFBatteryAndroidCapacityLevel;
|
||||
|
||||
// BatteryProperty starts with [int64 mValueLong], behind the usual [exception code][return value]
|
||||
// [out-param non-null marker] prefix of an AIDL reply. API 35 appended [string8 mValueString] after
|
||||
@@ -71,9 +76,9 @@ static void initResult(FFBatteryResult* battery) {
|
||||
static const char* getProperty(FFBinder* binder, uint32_t handle, uint32_t property, uint64_t* value) {
|
||||
// `getProperty` is the third method declared in `IBatteryPropertiesRegistrar` up to Android 9 and
|
||||
// the first one from Android 10 on. Written as an `if` because clang rejects `__builtin_available`
|
||||
// -- which is what FF_API_AT_LEAST expands to -- in any other position.
|
||||
// -- which is what FF_ANDROID_API_AT_LEAST expands to -- in any other position.
|
||||
uint32_t transaction = 3u;
|
||||
if (FF_API_AT_LEAST(29)) {
|
||||
if (FF_ANDROID_API_AT_LEAST(29)) {
|
||||
transaction = 1u;
|
||||
}
|
||||
|
||||
@@ -146,7 +151,7 @@ static const char* parseBinder(FFlist* results) {
|
||||
// `dumpsys battery` prints what `BatteryService.dumpInternal()` prints: the whole of the health HAL's
|
||||
// `HealthInfo`, plus the timestamps the service keeps for its own broadcast rate limiter. It is a flat
|
||||
// `key: value` list read by name, which is what makes it the sturdier of the two routes -- a vendor
|
||||
// that prints extra keys (vivo does: `engine`, `soc decimal`, `adapter power`, `board temp status`,
|
||||
// that prints extra keys (some vendors do: `engine`, `soc decimal`, `adapter power`, `board temp status`,
|
||||
// `low bat status`, `reverse wl chg status`, `reverse wl chg exception`, `chg shut vbat`,
|
||||
// `last mode flag` and `last mode keep time`, in the middle of the list) only adds keys nobody asks
|
||||
// for, and a release that appends a field costs nothing. The binder reply is positional and breaks
|
||||
@@ -206,8 +211,9 @@ static const char* parseDumpsys(FFBatteryOptions* options, FFlist* results) {
|
||||
}
|
||||
|
||||
if (!ffStrbufStartsWithS(&buf, "Current Battery Service state:\n")) {
|
||||
// The only other thing the command prints is `Permission Denial: can't dump Battery service`,
|
||||
// which ffDetectBattery has already ruled out by looking at the UID.
|
||||
// A refusal, which ffDetectBattery has already ruled out by looking at the UID: an app UID gets
|
||||
// `Can't find service: battery` on Android 16, and the permission denial some other
|
||||
// release would print lands here too. Both exit 0, so this is the only place that notices.
|
||||
return "Invalid `/system/bin/dumpsys battery` result";
|
||||
}
|
||||
|
||||
@@ -299,15 +305,14 @@ static const char* parseDumpsys(FFBatteryOptions* options, FFlist* results) {
|
||||
}
|
||||
|
||||
const char* ffDetectBattery(FFBatteryOptions* options, FFlist* results) {
|
||||
// `dumpsys battery` is gated behind android.permission.DUMP, which only the shell UID and root
|
||||
// hold: any other UID is answered with `Permission Denial: can't dump Battery service` on stdout
|
||||
// and a zero exit status, so forking it there costs a child process and cannot succeed. It is also
|
||||
// the richer of the two routes -- the registrar answers neither temperature nor technology, and
|
||||
// the dump is what spells out the status enum -- so it is tried first wherever it is allowed to
|
||||
// run, and the binder route covers everything else at ~0.3 ms. termux-api was dropped: it returns
|
||||
// nothing on this device and can hang for minutes, the same reason its camera path was removed.
|
||||
const uint32_t uid = instance.state.platform.uid;
|
||||
if (uid == FF_BATTERY_ANDROID_ROOT_UID || uid == FF_BATTERY_ANDROID_SHELL_UID) {
|
||||
// `dumpsys battery` needs android.permission.DUMP, which only the shell UID and root hold: an app
|
||||
// UID is answered with `Can't find service: battery` on stdout and a zero exit status, so forking
|
||||
// it there costs a child process and cannot succeed. It is also the richer of the two routes --
|
||||
// the registrar answers neither temperature nor technology, and the dump is what spells out the
|
||||
// status enum -- so it is tried first wherever it is allowed to run, and the binder route covers
|
||||
// everything else at ~0.3 ms. termux-api was dropped: it returns nothing on this device and can
|
||||
// hang for minutes, the same reason its camera path was removed.
|
||||
if (ffAndroidIsRootOrShell(instance.state.platform.uid)) {
|
||||
const char* error = parseDumpsys(options, results);
|
||||
if (error == nullptr) {
|
||||
return nullptr;
|
||||
|
||||
@@ -35,7 +35,7 @@ static bool ffCodecIsLikelySoftware(const char* codecName) {
|
||||
|
||||
// Only the name query is newer than the API level this build targets: AMediaCodec_getName and
|
||||
// AMediaCodec_releaseName are API 28, while creating and deleting a codec is API 21.
|
||||
FF_REQUIRES_API(28) static bool ffCodecIsHardwareAccelerated(AMediaCodec* codec) {
|
||||
FF_ANDROID_REQUIRES_API(28) static bool ffCodecIsHardwareAccelerated(AMediaCodec* codec) {
|
||||
if (!codec) {
|
||||
return false;
|
||||
}
|
||||
@@ -51,7 +51,7 @@ FF_REQUIRES_API(28) static bool ffCodecIsHardwareAccelerated(AMediaCodec* codec)
|
||||
return isHardware;
|
||||
}
|
||||
|
||||
FF_REQUIRES_API(28) static const char* ffDetectCodecNativeImpl(FFCodecOptions* options, FFlist* result /*list of FFCodecResult*/) {
|
||||
FF_ANDROID_REQUIRES_API(28) static const char* ffDetectCodecNativeImpl(FFCodecOptions* options, FFlist* result /*list of FFCodecResult*/) {
|
||||
FFCodecType decoders = FF_CODEC_TYPE_NONE;
|
||||
FFCodecType encoders = FF_CODEC_TYPE_NONE;
|
||||
|
||||
@@ -92,7 +92,7 @@ FF_REQUIRES_API(28) static const char* ffDetectCodecNativeImpl(FFCodecOptions* o
|
||||
}
|
||||
|
||||
const char* ffDetectCodecNative(FFCodecOptions* options, FFlist* result /*list of FFCodecResult*/) {
|
||||
if (FF_API_AT_LEAST(28)) {
|
||||
if (FF_ANDROID_API_AT_LEAST(28)) {
|
||||
return ffDetectCodecNativeImpl(options, result);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#include "displayserver.h"
|
||||
#include "common/android/api.h"
|
||||
#include "common/arrutil.h"
|
||||
#include "common/settings.h"
|
||||
#include "common/strutil.h"
|
||||
@@ -314,6 +315,16 @@ static bool detectWithCmd(FFDisplayServerResult* ds) {
|
||||
}
|
||||
|
||||
static bool detectWithDumpsys(FFDisplayServerResult* ds) {
|
||||
// `dumpsys` needs android.permission.DUMP, which only the shell UID and root hold. Every other UID
|
||||
// is answered with `Permission Denial: can't dump DisplayManagerService from from pid=..., uid=...
|
||||
// due to missing android.permission.DUMP permission` on stdout and a zero exit status, so the fork
|
||||
// buys a child process and the record loop then finds no `DisplayInfo` -- the same "no display" the
|
||||
// caller reads as "try the next route". Not forking is the only difference this makes, but it is
|
||||
// the difference between the fallback chain describing what is available and it guessing.
|
||||
if (!ffAndroidIsRootOrShell(instance.state.platform.uid)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return detectWithCommand(ds,
|
||||
(char*[]) { "/system/bin/dumpsys", "display", nullptr },
|
||||
"mBaseDisplayInfo=",
|
||||
@@ -356,7 +367,7 @@ static bool detectDE(FFDisplayServerResult* ds) {
|
||||
|
||||
// vivo reports the marketing name and version in `ro.vivo.os.build.display.id`,
|
||||
// separated by an underscore (`Funtouch OS_10`, `OriginOS 5`), and the build number
|
||||
// in `ro.vivo.product.version` (`PD2505D_A_9.16.42`).
|
||||
// in `ro.vivo.product.version`.
|
||||
if (ffSettingsGetAndroidProperty("ro.vivo.os.build.display.id", &ds->dePrettyName)) {
|
||||
ffStrbufReplaceAllC(&ds->dePrettyName, '_', ' ');
|
||||
if (ffSettingsGetAndroidProperty("ro.vivo.product.version", &buffer)) {
|
||||
@@ -663,7 +674,8 @@ void ffConnectDisplayServerImpl(FFDisplayServerResult* ds) {
|
||||
|
||||
// `cmd` comes first because it needs no permission and therefore also works for an app UID.
|
||||
// `dumpsys` is the only route that answers on Android 12 and older, and only for `adb shell` and
|
||||
// root, and `getprop` is MiUI specific and the last resort.
|
||||
// root -- it is skipped without a fork for every other UID, see detectWithDumpsys -- and `getprop`
|
||||
// is MiUI specific and the last resort.
|
||||
if (!detectWithCmd(ds) && !detectWithDumpsys(ds)) {
|
||||
detectWithGetprop(ds);
|
||||
}
|
||||
|
||||
@@ -16,9 +16,40 @@
|
||||
// it -- the call comes back as a silent exit status 255 with nothing on either stream, which is
|
||||
// what makes it look like it worked. `dumpsys wifi` needs android.permission.DUMP, and
|
||||
// /proc/net/wireless, /proc/net/dev and /sys/class/net/wlan0 are all EACCES. That leaves
|
||||
// `IWifiManager.getConnectionInfo`, which needs only ACCESS_WIFI_STATE -- a normal permission,
|
||||
// granted without asking. There is no Termux:API fallback: it returned less than this does, and
|
||||
// needed a separate app installed.
|
||||
// `IWifiManager.getConnectionInfo`, which needs ACCESS_WIFI_STATE.
|
||||
//
|
||||
// ACCESS_WIFI_STATE is a normal permission, so the user is never asked, but it is granted to a UID
|
||||
// rather than to a package: the service checks the caller's UID, and a UID holds the permission as
|
||||
// soon as any one of the packages sharing it requests it. The question is therefore not whether the
|
||||
// host app declares it, but whether its UID ends up with it.
|
||||
//
|
||||
// Under Termux it does, and only because of a second app. `com.termux` requests no Wi-Fi permission at
|
||||
// all, while `com.termux.api` -- Termux:API -- requests ACCESS_WIFI_STATE and shares `sharedUserId
|
||||
// com.termux`, so it lands on the app's own UID. Uninstalling Termux:API takes the permission off
|
||||
// the UID with it, and this module then reports "The Wifi service raised an exception" where it had
|
||||
// reported the connection; reinstalling it brings the connection back. Both states were measured on
|
||||
// the test device with Android 16 and the same binary. So Termux:API is still needed for this module -- as
|
||||
// the thing that carries the permission, not as the `termux-api` command that used to be spawned. That
|
||||
// command is gone from here because it could not do better than this does: whenever it was available,
|
||||
// so is the permission, and whenever the permission is missing the command is missing too.
|
||||
//
|
||||
// What a UID that does not hold the permission is told, for both calls below. The service names the
|
||||
// reason itself, and the message is 93 characters:
|
||||
//
|
||||
// WifiService: Neither user <uid> nor current process has android.permission.ACCESS_WIFI_STATE.
|
||||
// at com.android.server.wifi.WifiServiceImpl.enforceAccessPermission(WifiServiceImpl.java:1541)
|
||||
// at com.android.server.wifi.WifiServiceImpl.getConnectionInfo(WifiServiceImpl.java:5537)
|
||||
//
|
||||
// It is the calling UID that is checked, not the package name in the request: the same call made as
|
||||
// the shell, asking about a package it does not own, is refused with `Package com.termux does not
|
||||
// belong to <shell uid>` instead. Nothing in this file can work around the permission, since the calling UID
|
||||
// is the process itself.
|
||||
//
|
||||
// The second call sits behind the same check, which is what makes the permission a gate on the module
|
||||
// rather than on one field: `getWifiEnabledState` from a UID that lacks it comes back with the
|
||||
// identical exception and the same message, where a UID that holds it reads the state. Such a host has
|
||||
// no route left in this file at all, and the error the caller sees is raised by the first call, before
|
||||
// the second one could have answered anything.
|
||||
//
|
||||
// The transaction code is read out of the device's own jar rather than carried as a table, so the
|
||||
// module is not tied to a list of releases. It does need the jar to exist, and Android 11 is the
|
||||
@@ -41,24 +72,24 @@
|
||||
// more accurate -- pays nothing for it.
|
||||
//
|
||||
// Three things about the call are positional rather than negotiated, and all three were measured
|
||||
// on a vivo V2505A (Android 16) and a Redmi 9A (Android 11):
|
||||
// on an Android 16 device and an Android 11 device:
|
||||
//
|
||||
// * The transaction code is a build-time constant of the `.aidl` (`getConnectionInfo` is 29 on
|
||||
// Android 11 and 41 on Android 16) -- see common/android/dex.h.
|
||||
// * The reply layout drifts, and not only by release. Android 12 dropped the duplicate length word
|
||||
// that Android 11 and 10 wrote in front of the SSID octets, and the vivo's Android 16 build
|
||||
// fills an int between the transmit and the receive speed that the Redmi's Android 11 build
|
||||
// leaves out -- while AOSP's own Android 16 writes the head exactly the Android 11 way, so that
|
||||
// one int is a vendor addition rather than a release difference. Frequency is the only channel
|
||||
// centre in the head, so it is located by value, with the whole result validated before it is
|
||||
// used. A wrong guess shows up as a rejected layout, not as a plausible wrong number.
|
||||
// that Android 11 and 10 wrote in front of the SSID octets, and the Android 16 device fills an
|
||||
// int between the transmit and the receive speed that the Android 11 device leaves out -- while
|
||||
// AOSP's own Android 16 writes the head exactly the Android 11 way, so that one int is a vendor
|
||||
// addition rather than a release difference. Frequency is the only channel centre in the head, so
|
||||
// it is located by value, with the whole result validated before it is used. A wrong guess shows
|
||||
// up as a rejected layout, not as a plausible wrong number.
|
||||
// * The tail of the parcel, which holds the Wi-Fi standard, is not laid out the same way by every
|
||||
// vendor: the Android 11 build on the Redmi carries three words there that AOSP's own Android 11
|
||||
// vendor: the Android 11 device carries three words there that AOSP's own Android 11
|
||||
// does not. It is located by shape for that reason.
|
||||
//
|
||||
// BSSID and MAC are the one place the binder route beats `cmd wifi status`: MAC addresses are
|
||||
// redacted for a caller targeting a recent SDK, and the shell targets the current one, so `cmd wifi
|
||||
// status` prints 24:**:**:**:70:2c where this reads 24:a4:87:3c:70:2c.
|
||||
// status` prints an address with its middle octets redacted where this reads the whole one.
|
||||
//
|
||||
// What is deliberately left empty:
|
||||
//
|
||||
@@ -91,8 +122,8 @@
|
||||
#define FF_WIFI_ANDROID_WIFI_STATE_UNKNOWN 4
|
||||
|
||||
// The other argument is the caller's package name. The shell UID owns exactly one package and the
|
||||
// service accepts that name, which is what `cmd` and `dumpsys` pass. See getOwnPackage().
|
||||
#define FF_WIFI_ANDROID_SHELL_UID 2000
|
||||
// service accepts that name, which is what `cmd` and `dumpsys` pass. The UID itself is
|
||||
// FF_ANDROID_PRIVILEGED_UID_SHELL, see common/android/api.h. See getOwnPackage().
|
||||
#define FF_WIFI_ANDROID_SHELL_PACKAGE "com.android.shell"
|
||||
|
||||
// An AIDL reply opens with the exception code and, for a Parcelable return, a non-null marker;
|
||||
@@ -106,7 +137,7 @@
|
||||
|
||||
// A `WifiInfo` with no connection to describe carries the sentinels for the two fields that are
|
||||
// always at a fixed offset: -1 for a network id it does not have and -127 for a signal it cannot
|
||||
// measure. Both were measured on the vivo's Android 16 reply, which is what the service answers
|
||||
// measure. Both were measured on the Android 16 device's reply, which is what the service answers
|
||||
// while Wi-Fi is on with nothing associated -- the head holds no frequency and no SSID at all, only
|
||||
// the "02:00:00:00:00:00" placeholder that `WifiInfo` writes in place of a BSSID.
|
||||
#define FF_WIFI_ANDROID_NET_ID_NONE (-1)
|
||||
@@ -132,10 +163,10 @@
|
||||
#define FF_WIFI_ANDROID_SSID_LENGTH 0x04
|
||||
|
||||
// The receive speed is the word in front of Frequency; the transmit speed is the word after the link
|
||||
// speed. The vivo writes one extra word between those two that AOSP does not -- it repeats the
|
||||
// speed. The Android 16 device writes one extra word between those two that AOSP does not -- it repeats the
|
||||
// transmit speed -- so the receive speed can only be reached from Frequency, and Frequency cannot be
|
||||
// reached by stepping over the two link speeds. Both were checked against `cmd wifi status` at the
|
||||
// same moment: 131/219 on the vivo, 86/-1 on the Redmi.
|
||||
// same moment: 131/219 on the Android 16 device, 86/-1 on the Android 11 device.
|
||||
#define FF_WIFI_ANDROID_FREQ_RX_LINK_SPEED 0x04
|
||||
#define FF_WIFI_ANDROID_SSID 0x08 // Android 12 and later
|
||||
#define FF_WIFI_ANDROID_SSID_LEGACY 0x0c // Android 11 and older
|
||||
@@ -170,15 +201,16 @@
|
||||
// ---------------------------------------------------------------------------------------------
|
||||
|
||||
// `getConnectionInfo` takes the caller's package name, and whether it is checked depends on the
|
||||
// release: the vivo answered "Package com.termux does not belong to 2000" for a name the shell does
|
||||
// not own, while the Redmi accepted the same name. Passing the real one is what works on both. There
|
||||
// is no way for a process to ask for its own package name -- it is not in /proc/self/status, and an
|
||||
// app cannot list /data/data -- but the executable path carries it: an app's binaries live under
|
||||
// /data/data/<package>/ or /data/user/<user>/<package>/, and /proc/self/exe resolves there.
|
||||
// release: the Android 16 device answered "Package com.termux does not belong to <shell uid>" for a name
|
||||
// the shell does not own, while the Android 11 device accepted the same name. Passing the real one is
|
||||
// what works on both. There is no way for a process to ask for its own package name -- it is not in
|
||||
// /proc/self/status, and an app cannot list /data/data -- but the executable path carries it: an app's
|
||||
// binaries live under /data/data/<package>/ or /data/user/<user>/<package>/, and /proc/self/exe
|
||||
// resolves there.
|
||||
//
|
||||
// A binary outside those directories has no package of its own, and that is not an edge case: a
|
||||
// static build pushed to /data/local/tmp is how this runs on a device without Termux, and the Redmi
|
||||
// reported "Cannot determine the package name of this process" for exactly that. The shell UID can
|
||||
// static build pushed to /data/local/tmp is how this runs on a device without Termux, and the Android
|
||||
// 11 device reported "Cannot determine the package name of this process" for exactly that. The shell UID can
|
||||
// be answered instead, because it has one name the service accepts. No other UID reaches here: an
|
||||
// app's own binaries are always under its data directory, so a failure there is a real failure.
|
||||
static bool getOwnPackage(char* buffer, size_t capacity) {
|
||||
@@ -202,8 +234,9 @@ static bool getOwnPackage(char* buffer, size_t capacity) {
|
||||
}
|
||||
}
|
||||
if (rest == nullptr) {
|
||||
FF_DEBUG("The executable is not in an app data directory (\"%s\"), uid %u", path, (unsigned) getuid());
|
||||
if (getuid() != FF_WIFI_ANDROID_SHELL_UID) {
|
||||
const uint32_t uid = instance.state.platform.uid;
|
||||
FF_DEBUG("The executable is not in an app data directory (\"%s\"), uid %u", path, uid);
|
||||
if (uid != FF_ANDROID_PRIVILEGED_UID_SHELL) {
|
||||
return false;
|
||||
}
|
||||
static const char shellPackage[] = FF_WIFI_ANDROID_SHELL_PACKAGE;
|
||||
@@ -281,7 +314,7 @@ typedef struct FFWifiAndroidConnection {
|
||||
//
|
||||
// What bionic's `getifaddrs` will not do is list an interface that has no address: the list is
|
||||
// built out of the addresses, so `wlan0` leaves it as soon as nothing is associated -- measured on
|
||||
// the vivo, and the Redmi behaves the same. That is exactly the state its flags are wanted in, so
|
||||
// the Android 16 device, and the Android 11 device behaves the same. That is exactly the state its flags are wanted in, so
|
||||
// when the name is missing the radio state stands in for them. See detectWithBinder().
|
||||
static void detectInterface(FFWifiAndroidConnection* connection) {
|
||||
struct ifaddrs* addrs = nullptr;
|
||||
@@ -374,8 +407,8 @@ static void findStrings(const uint8_t* data, size_t size, size_t from, FFWifiAnd
|
||||
}
|
||||
|
||||
// AOSP writes the Wi-Fi standard between three nullable strings and the two maximum-supported link
|
||||
// speeds, all of them ints. Vendors insert fields around that run -- the Android 11 build on the
|
||||
// Redmi carries three words before the standard that AOSP's own Android 11 does not -- so a fixed
|
||||
// speeds, all of them ints. Vendors insert fields around that run -- the Android 11 device carries
|
||||
// three words before the standard that AOSP's own Android 11 does not -- so a fixed
|
||||
// offset would be a guess. The run is recognised by shape instead, searching forward from the end of
|
||||
// the supplicant state: an int naming a standard, followed by two rates, in a band the standard can
|
||||
// live in.
|
||||
@@ -452,7 +485,7 @@ static const char* parseConnectionInfo(const uint8_t* data, size_t size, FFWifiA
|
||||
// older, not at all on Android 12 and later, which leaves the octets one word earlier. The one
|
||||
// that matches this release is tried first.
|
||||
uint32_t ssidOffsets[2] = { FF_WIFI_ANDROID_SSID_LEGACY, FF_WIFI_ANDROID_SSID };
|
||||
if (FF_API_AT_LEAST(31)) {
|
||||
if (FF_ANDROID_API_AT_LEAST(31)) {
|
||||
ssidOffsets[0] = FF_WIFI_ANDROID_SSID;
|
||||
ssidOffsets[1] = FF_WIFI_ANDROID_SSID_LEGACY;
|
||||
}
|
||||
@@ -540,10 +573,11 @@ static const char* parseConnectionInfo(const uint8_t* data, size_t size, FFWifiA
|
||||
}
|
||||
|
||||
// Which of the speeds the service wrote becomes a rate. It writes a negative one when it does not
|
||||
// know, which is what the Redmi does for the receive side, and a link speed of 0 when the link is
|
||||
// down. `generic` is the fallback for a direction the service left unset: the transmit side has one
|
||||
// -- the generic link speed is a real negotiated rate, just not direction-specific -- the way the
|
||||
// Linux backend falls back to SIOCGIWRATE when the station info carries no bitrate. There is no
|
||||
// know, which is what the Android 11 device does for the receive side, and a link speed of 0 when
|
||||
// the link is down. `generic` is the fallback for a direction the service left unset: the transmit
|
||||
// side has one -- the generic link speed is a real negotiated rate, just not direction-specific --
|
||||
// the way the Linux backend falls back to SIOCGIWRATE when the station info carries no bitrate.
|
||||
// There is no
|
||||
// generic receive speed, so 0 is passed there.
|
||||
static double wifiAndroidRate(int32_t specific, int32_t generic) {
|
||||
const int32_t rate = specific > 0 ? specific : generic;
|
||||
|
||||
@@ -66,7 +66,7 @@ static uint8_t* androidPackFrame(const uint8_t* src, size_t stride, uint32_t wid
|
||||
|
||||
// Everything the still and the animation path share: read the source size, resolve the requested
|
||||
// pixel size, ask for RGBA8888 and let the decoder do the scaling.
|
||||
FF_REQUIRES_API(30) static bool androidResolveDecoder(AImageDecoder* decoder, FFLogoRequestData* requestData, size_t* outStride, bool* outPremultiplied, const char** error) {
|
||||
FF_ANDROID_REQUIRES_API(30) static bool androidResolveDecoder(AImageDecoder* decoder, FFLogoRequestData* requestData, size_t* outStride, bool* outPremultiplied, const char** error) {
|
||||
const AImageDecoderHeaderInfo* header = AImageDecoder_getHeaderInfo(decoder);
|
||||
// Both are int32_t, and anything <= 0 is not a usable source
|
||||
const int32_t sourceWidth = AImageDecoderHeaderInfo_getWidth(header);
|
||||
@@ -123,7 +123,7 @@ FF_REQUIRES_API(30) static bool androidResolveDecoder(AImageDecoder* decoder, FF
|
||||
bool ffImageCreateAID(FFLogoRequestData* requestData, FFImageBuffer* out, const char** error) {
|
||||
// AImageDecoder is API 30, and fastfetch still runs on devices below that, so this is a real
|
||||
// run-time check and not a compile-time constant. See common/android/api.h for why.
|
||||
if (FF_API_AT_LEAST(30)) {
|
||||
if (FF_ANDROID_API_AT_LEAST(30)) {
|
||||
FF_AUTO_CLOSE_FD int fd = open(instance.config.logo.source.chars, O_RDONLY | O_CLOEXEC);
|
||||
if (fd < 0) {
|
||||
return androidImageDecoderError(error, "failed to open the image source");
|
||||
@@ -217,7 +217,7 @@ typedef struct FFAndroidAnimation {
|
||||
int32_t* delaysCs; // one per frame, in centiseconds
|
||||
} FFAndroidAnimation;
|
||||
|
||||
FF_REQUIRES_API(31) static bool androidAnimationGetFrame(FFImageAnimation* animation, uint32_t index, FFImageFrame* out, const char** error) {
|
||||
FF_ANDROID_REQUIRES_API(31) static bool androidAnimationGetFrame(FFImageAnimation* animation, uint32_t index, FFImageFrame* out, const char** error) {
|
||||
FFAndroidAnimation* session = (FFAndroidAnimation*) ffImageAnimationGetImpl(animation);
|
||||
|
||||
// Frames come out of the decoder in order, so anything before the current one means starting
|
||||
@@ -265,7 +265,7 @@ FF_REQUIRES_API(31) static bool androidAnimationGetFrame(FFImageAnimation* anima
|
||||
return true;
|
||||
}
|
||||
|
||||
FF_REQUIRES_API(31) static void androidAnimationDestroy(FFImageAnimation* animation) {
|
||||
FF_ANDROID_REQUIRES_API(31) static void androidAnimationDestroy(FFImageAnimation* animation) {
|
||||
FFAndroidAnimation* session = (FFAndroidAnimation*) ffImageAnimationGetImpl(animation);
|
||||
if (session == nullptr) {
|
||||
return;
|
||||
@@ -281,7 +281,7 @@ bool ffImageAnimationOpenAID(FFLogoRequestData* requestData, FFImageAnimation**
|
||||
// Decoding past the first frame needs API 31: AImageDecoder_advanceFrame and
|
||||
// AImageDecoderFrameInfo are both introduced there, and AImageDecoder_decodeImage only
|
||||
// documents decoding "all of the frames" from that level on.
|
||||
if (FF_API_AT_LEAST(31)) {
|
||||
if (FF_ANDROID_API_AT_LEAST(31)) {
|
||||
FF_AUTO_CLOSE_FD int fd = open(instance.config.logo.source.chars, O_RDONLY | O_CLOEXEC);
|
||||
if (fd < 0) {
|
||||
return androidImageDecoderError(error, "failed to open the image source");
|
||||
|
||||
Reference in New Issue
Block a user