Global (Android): code cleanup

This commit is contained in:
李通洲
2026-09-20 08:23:43 +08:00
parent f9f368c1fb
commit 15e4bc998a
7 changed files with 166 additions and 96 deletions
+33 -4
View File
@@ -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;
}
+31 -26
View File
@@ -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;
+3 -3
View File
@@ -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);
}
+68 -34
View File
@@ -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;
+5 -5
View File
@@ -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");