mirror of
https://github.com/fastfetch-cli/fastfetch.git
synced 2026-10-28 07:44:06 +01:00
Wallpaper (Android): adds support
This commit is contained in:
+19
-10
@@ -47,11 +47,12 @@ void ffBinderClose(FFBinder* binder) {
|
||||
binder->sharedSize = 0;
|
||||
}
|
||||
|
||||
const char* ffBinderTransact(FFBinder* binder, uint32_t handle, uint32_t code, const FFBinderParcel* parcel, FFBinderReply* reply) {
|
||||
const char* ffBinderTransact(FFBinder* binder, uint32_t handle, uint32_t code, uint32_t flags, const FFBinderParcel* parcel, FFBinderReply* reply) {
|
||||
reply->size = 0;
|
||||
reply->code = 0;
|
||||
reply->flags = 0;
|
||||
reply->handleCount = 0;
|
||||
reply->fdCount = 0;
|
||||
|
||||
if (parcel->truncated) {
|
||||
return "Binder parcel does not fit the caller buffer";
|
||||
@@ -62,7 +63,7 @@ const char* ffBinderTransact(FFBinder* binder, uint32_t handle, uint32_t code, c
|
||||
.target = { .handle = handle },
|
||||
.cookie = 0,
|
||||
.code = code,
|
||||
.flags = 0, // a synchronous call; TF_ONE_WAY is the only flag the kernel acts on here
|
||||
.flags = flags,
|
||||
.sender_pid = 0,
|
||||
.sender_euid = 0,
|
||||
.data_size = parcel->size,
|
||||
@@ -146,8 +147,10 @@ const char* ffBinderTransact(FFBinder* binder, uint32_t handle, uint32_t code, c
|
||||
}
|
||||
}
|
||||
|
||||
// Walk the offset table for the flat objects the service sent us and keep the
|
||||
// strong handles: those are the ones a later transaction can address.
|
||||
// Walk the offset table for the flat objects the service sent us. A reply may carry
|
||||
// both kinds at once and they are not interchangeable: a handle is a reference that
|
||||
// has to be acquired (see below) to outlive the reply buffer, while a file
|
||||
// descriptor was installed in our own fd table by the kernel and is simply ours.
|
||||
for (size_t offset = 0; offset + sizeof(binder_size_t) <= offsetsSize; offset += sizeof(binder_size_t)) {
|
||||
binder_size_t objectOffset = 0;
|
||||
memcpy(&objectOffset, offsets + offset, sizeof(objectOffset));
|
||||
@@ -156,11 +159,17 @@ const char* ffBinderTransact(FFBinder* binder, uint32_t handle, uint32_t code, c
|
||||
}
|
||||
struct flat_binder_object object;
|
||||
memcpy(&object, payload + objectOffset, sizeof(object));
|
||||
if (object.hdr.type != BINDER_TYPE_HANDLE) {
|
||||
continue;
|
||||
}
|
||||
if (reply->handleCount < FF_BINDER_MAX_HANDLES) {
|
||||
reply->handles[reply->handleCount++] = object.handle;
|
||||
if (object.hdr.type == BINDER_TYPE_HANDLE) {
|
||||
if (reply->handleCount < FF_BINDER_MAX_HANDLES) {
|
||||
reply->handles[reply->handleCount++] = object.handle;
|
||||
}
|
||||
} else if (object.hdr.type == BINDER_TYPE_FD) {
|
||||
if (reply->fdCount < FF_BINDER_MAX_FDS) {
|
||||
reply->fds[reply->fdCount++] = (int32_t) object.handle;
|
||||
} else {
|
||||
// Nothing is going to hold this one and nothing is going to close it.
|
||||
close((int) object.handle);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -224,7 +233,7 @@ const char* ffBinderLookupService(FFBinder* binder, const char* name, uint32_t t
|
||||
|
||||
uint8_t replyBuffer[256];
|
||||
FFBinderReply reply = ffBinderReplyCreate(replyBuffer, sizeof(replyBuffer));
|
||||
const char* error = ffBinderTransact(binder, FF_BINDER_SERVICE_MANAGER_HANDLE, transactionCode, &parcel, &reply);
|
||||
const char* error = ffBinderTransact(binder, FF_BINDER_SERVICE_MANAGER_HANDLE, transactionCode, 0, &parcel, &reply);
|
||||
if (error != nullptr) {
|
||||
return error;
|
||||
}
|
||||
|
||||
@@ -51,6 +51,12 @@
|
||||
// the surplus handles are left unacquired and will fail on first use.
|
||||
#define FF_BINDER_MAX_HANDLES 4
|
||||
|
||||
// How many file descriptors a reply may hand over. A descriptor is not a handle: the kernel installs
|
||||
// it in the caller's own fd table as it delivers the reply, so it is owned rather than borrowed and
|
||||
// the caller closes it. A reply carrying more than this still succeeds, and the surplus is closed
|
||||
// again by ffBinderTransact() because nothing would be left holding it.
|
||||
#define FF_BINDER_MAX_FDS 4
|
||||
|
||||
// The three int32 values Parcel::writeInterfaceToken() puts in front of the descriptor, and that
|
||||
// Parcel::enforceInterface() checks the last of. These come from libbinder rather than from the
|
||||
// kernel, which is why they are spelled out here.
|
||||
@@ -159,10 +165,12 @@ typedef struct FFBinderReply {
|
||||
uint32_t flags; // TF_* of the reply
|
||||
uint32_t handleCount;
|
||||
uint32_t handles[FF_BINDER_MAX_HANDLES];
|
||||
uint32_t fdCount;
|
||||
int32_t fds[FF_BINDER_MAX_FDS]; // owned by the caller, which closes them
|
||||
} FFBinderReply;
|
||||
|
||||
[[nodiscard]] static inline FFBinderReply ffBinderReplyCreate(uint8_t* data, size_t capacity) {
|
||||
return (FFBinderReply) { .data = data, .capacity = capacity, .size = 0, .code = 0, .flags = 0, .handleCount = 0, .handles = {} };
|
||||
return (FFBinderReply) { .data = data, .capacity = capacity, .size = 0, .code = 0, .flags = 0, .handleCount = 0, .handles = {}, .fdCount = 0, .fds = {} };
|
||||
}
|
||||
|
||||
// A reply carrying TF_STATUS_CODE is not an AIDL parcel at all: the first four bytes are the raw
|
||||
@@ -196,13 +204,21 @@ typedef struct FFBinderReply {
|
||||
return value;
|
||||
}
|
||||
|
||||
// Synchronous transaction. Returns nullptr on success, a static message otherwise. Handles the
|
||||
// reply carries are acquired and added to `reply->handles` before the reply buffer is freed.
|
||||
// Synchronous transaction. Returns nullptr on success, a static message otherwise.
|
||||
//
|
||||
// `flags` carries the TF_* values for the request, and one of them is not cosmetic: a reply that
|
||||
// carries a file descriptor is only delivered at all when the request asked with TF_ACCEPT_FDS. A
|
||||
// request that did not gets BR_FAILED_REPLY instead, which reads like a service that is not running
|
||||
// rather than like a missing flag. TF_ONE_WAY is the other one the kernel acts on, and it is what
|
||||
// makes a call fire-and-forget -- there is then no reply to fill `reply` from.
|
||||
//
|
||||
// Handles the reply carries are acquired and added to `reply->handles` before the reply buffer is
|
||||
// freed; descriptors are added to `reply->fds`, which the caller owns and closes.
|
||||
//
|
||||
// `handle` 0 is the service manager; every other handle comes from ffBinderLookupService(). A
|
||||
// service that never replies blocks this call forever -- none of the services fastfetch reads does,
|
||||
// but there is no timeout here to fall back on.
|
||||
[[gnu::nonnull(1, 4, 5), nodiscard]] const char* ffBinderTransact(FFBinder* binder, uint32_t handle, uint32_t code, const FFBinderParcel* parcel, FFBinderReply* reply);
|
||||
[[gnu::nonnull(1, 5, 6), nodiscard]] const char* ffBinderTransact(FFBinder* binder, uint32_t handle, uint32_t code, uint32_t flags, const FFBinderParcel* parcel, FFBinderReply* reply);
|
||||
|
||||
// ---------------------------------------------------------------------------------------------
|
||||
// Service manager
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
#include "common/android/package.h"
|
||||
#include "common/android/api.h"
|
||||
#include "common/debug.h"
|
||||
#include "common/strutil.h"
|
||||
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
// 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. The executable path carries it instead -- an app's binaries live
|
||||
// under /data/data/<package>/ or /data/user/<user>/<package>/, and /proc/self/exe resolves there.
|
||||
// Under Termux that is /data/data/com.termux/files/usr/bin/<binary>, whose third component is the
|
||||
// package.
|
||||
//
|
||||
// The memory cgroup carries the same information (4:memory:/apps/com.termux on the device this was
|
||||
// verified on), but the unified hierarchy spells it out as /apps/uid_10478/pid_26943 with no name
|
||||
// left in it, so the executable is the portable half of the two.
|
||||
bool ffAndroidGetOwnPackage(char* buffer, size_t capacity) {
|
||||
char path[4096];
|
||||
const ssize_t length = readlink("/proc/self/exe", path, sizeof(path) - 1);
|
||||
if (length <= 0) {
|
||||
FF_DEBUG("Cannot read /proc/self/exe, so the calling package cannot be derived");
|
||||
return false;
|
||||
}
|
||||
path[length] = '\0';
|
||||
|
||||
const char* rest = nullptr;
|
||||
const char* const dataPrefix = "/data/data/";
|
||||
const char* const userPrefix = "/data/user/";
|
||||
if (ffStrStartsWith(path, dataPrefix)) {
|
||||
rest = path + strlen(dataPrefix);
|
||||
} else if (ffStrStartsWith(path, userPrefix)) {
|
||||
// /data/user/<user>/<package>/... -- the user number has to be stepped over before the
|
||||
// package name starts, and only strchr() answers where that is.
|
||||
rest = strchr(path + strlen(userPrefix), '/');
|
||||
if (rest != nullptr) {
|
||||
rest += 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (rest == nullptr) {
|
||||
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_ANDROID_SHELL_PACKAGE;
|
||||
if (sizeof(shellPackage) > capacity) {
|
||||
return false;
|
||||
}
|
||||
memcpy(buffer, shellPackage, sizeof(shellPackage));
|
||||
FF_DEBUG("The calling package is \"%s\" (the shell owns it)", buffer);
|
||||
return true;
|
||||
}
|
||||
|
||||
const char* end = strchr(rest, '/');
|
||||
const size_t nameLength = end != nullptr ? (size_t) (end - rest) : strlen(rest);
|
||||
if (nameLength == 0 || nameLength >= capacity) {
|
||||
return false;
|
||||
}
|
||||
memcpy(buffer, rest, nameLength);
|
||||
buffer[nameLength] = '\0';
|
||||
FF_DEBUG("The calling package is \"%s\"", buffer);
|
||||
return true;
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
|
||||
// The one package name the shell UID owns. android.os.Process.SHELL_UID is a single app UID that
|
||||
// owns exactly one package, so a process that cannot derive a package of its own -- a static build
|
||||
// pushed to /data/local/tmp is the case that showed up -- can answer with this name and have the
|
||||
// system services accept it. See FF_ANDROID_PRIVILEGED_UID_SHELL in common/android/api.h.
|
||||
#define FF_ANDROID_SHELL_PACKAGE "com.android.shell"
|
||||
|
||||
// Some of the system services take the caller's package name as an argument and check it against the
|
||||
// calling UID rather than against the name being real: a name nobody owns, and a real name belonging
|
||||
// to another app, are refused exactly like a null one. So it has to be the caller's own name.
|
||||
//
|
||||
// Whether the name is checked at all depends on the service and the release -- `getConnectionInfo`
|
||||
// answered "Package com.termux does not belong to <shell uid>" on one Android 16 device while the
|
||||
// same name was accepted on an Android 11 one, and `getWallpaper` refuses a null, empty or foreign
|
||||
// name on that Android 16 device -- so passing the real one is what works everywhere.
|
||||
//
|
||||
// Writes the name into `buffer`, terminator included, and returns whether one was found. `capacity`
|
||||
// is the caller's, because the parcel the name ends up in is sized from it.
|
||||
[[gnu::nonnull(1), nodiscard]] bool ffAndroidGetOwnPackage(char* buffer, size_t capacity);
|
||||
@@ -120,7 +120,7 @@ static const char* getProperty(FFBinder* binder, uint32_t handle, uint32_t prope
|
||||
|
||||
uint8_t replyBuffer[128];
|
||||
FFBinderReply reply = ffBinderReplyCreate(replyBuffer, sizeof(replyBuffer));
|
||||
const char* error = ffBinderTransact(binder, handle, transaction, &parcel, &reply);
|
||||
const char* error = ffBinderTransact(binder, handle, transaction, 0, &parcel, &reply);
|
||||
if (error != nullptr) {
|
||||
FF_DEBUG("Property %u could not be transacted: %s", property, error);
|
||||
return error;
|
||||
|
||||
@@ -0,0 +1,167 @@
|
||||
#include "wallpaper.h"
|
||||
#include "common/android/binder.h"
|
||||
#include "common/android/package.h"
|
||||
#include "common/debug.h"
|
||||
#include "common/io.h"
|
||||
#include "detection/displayserver/displayserver.h"
|
||||
|
||||
#include <unistd.h>
|
||||
|
||||
// Android keeps the wallpaper as a file per user, in a directory no app can enter:
|
||||
//
|
||||
// /data/system/users/<userId>/wallpaper the image the launcher draws
|
||||
// /data/system/users/<userId>/wallpaper_orig the picture as it was uploaded, before cropping
|
||||
//
|
||||
// /data/system/users/<userId> is mode 0700 and owned by system:system, so `ls`, `stat` and `open` all
|
||||
// answer EACCES to an app UID: the path is not readable even though it is not secret. What is
|
||||
// reachable is a descriptor to the file, because android.app.IWallpaperManager opens it inside
|
||||
// system_server and passes one back, and a descriptor is an ordinary file from then on. readlink(2)
|
||||
// on it names the file, which is the string this module is asked for -- the image itself is never
|
||||
// read or decoded.
|
||||
//
|
||||
// The gate is the calling package. `getWallpaper(String callingPackage, int which, int userId)`
|
||||
// answers only when that name is the caller's own: the two-argument overload, which is the null name,
|
||||
// comes back as a SecurityException raised by StorageManager.checkPermissionReadImages(), and an
|
||||
// empty string, an unknown package and a real package belonging to a different uid all leave with no
|
||||
// descriptor. That READ_MEDIA_IMAGES denial is not something a Termux-style app can lift -- the
|
||||
// permission is not in its manifest and it targets SDK 28 -- so naming the caller is the only route.
|
||||
//
|
||||
// Measured on an Android 16 device as an app UID: the descriptor for `which` 1 is
|
||||
// /data/system/users/0/wallpaper, a 2833949-byte JPEG whose SOF0 reads 2159x3168, while `which` 2
|
||||
// answers with no descriptor at all because that lock screen is a live wallpaper.
|
||||
//
|
||||
// Only one of the two display servers is ever asked. The binder route is Android's, and it answers
|
||||
// while SurfaceFlinger is the display server; a Termux:X11 or Wayland session puts a real compositor
|
||||
// on top, and there the wallpaper belongs to that desktop -- so that case goes to wallpaper_linux.c,
|
||||
// which is compiled alongside this file for exactly this reason.
|
||||
|
||||
#define FF_WALLPAPER_ANDROID_SERVICE "wallpaper"
|
||||
#define FF_WALLPAPER_ANDROID_DESCRIPTOR "android.app.IWallpaperManager"
|
||||
|
||||
// WallpaperManager.FLAG_SYSTEM and FLAG_LOCK.
|
||||
typedef enum FFWallpaperAndroidWhich : int32_t {
|
||||
FF_WALLPAPER_ANDROID_WHICH_SYSTEM = 1,
|
||||
FF_WALLPAPER_ANDROID_WHICH_LOCK = 2,
|
||||
} FFWallpaperAndroidWhich;
|
||||
|
||||
// `getWallpaper` is the fourth method of IWallpaperManager$Stub, and the number is read from the
|
||||
// device's own framework.jar rather than transcribed from AOSP, because a vendor ROM adds and drops
|
||||
// methods as it likes -- this one dropped `getName`, whose code answers UNKNOWN_TRANSACTION here.
|
||||
#define FF_WALLPAPER_ANDROID_TRANSACTION_GET_WALLPAPER 4u
|
||||
|
||||
// The request is the interface token, the package name, `which` and `userId`. The package name is the
|
||||
// longest thing in it, and the helper will not write more than 128 bytes of it.
|
||||
#define FF_WALLPAPER_ANDROID_PARCEL_SIZE 256
|
||||
|
||||
// The reply carries the descriptor object plus, for this method, a Bundle of two ints: 108 bytes on
|
||||
// the device it was measured on, 80 when the wallpaper is a live one and the Bundle comes alone. Only
|
||||
// the descriptor table is read out of it, so this is sized for headroom rather than for a field --
|
||||
// being wrong here would fail the detection, not misread it.
|
||||
#define FF_WALLPAPER_ANDROID_REPLY_SIZE 512
|
||||
|
||||
static const char* getWallpaperFile(FFBinder* binder, uint32_t handle, const char* package, FFWallpaperAndroidWhich which, FFstrbuf* result) {
|
||||
uint8_t parcelBuffer[FF_WALLPAPER_ANDROID_PARCEL_SIZE];
|
||||
FFBinderParcel parcel = ffBinderParcelCreate(parcelBuffer, sizeof(parcelBuffer));
|
||||
ffBinderParcelPutInterfaceToken(&parcel, FF_WALLPAPER_ANDROID_DESCRIPTOR);
|
||||
ffBinderParcelPutString16(&parcel, package);
|
||||
ffBinderParcelPutI32(&parcel, (int32_t) which);
|
||||
ffBinderParcelPutI32(&parcel, 0); // userId, which is the one user a phone has
|
||||
|
||||
uint8_t replyBuffer[FF_WALLPAPER_ANDROID_REPLY_SIZE];
|
||||
FFBinderReply reply = ffBinderReplyCreate(replyBuffer, sizeof(replyBuffer));
|
||||
|
||||
// TF_ACCEPT_FDS is neither optional nor a hint: a reply carrying a descriptor is only delivered
|
||||
// when the request asked for one, and a request that did not is answered with BR_FAILED_REPLY --
|
||||
// which reads like a service that is not running rather than like a missing flag.
|
||||
const char* error = ffBinderTransact(binder, handle, FF_WALLPAPER_ANDROID_TRANSACTION_GET_WALLPAPER, TF_ACCEPT_FDS, &parcel, &reply);
|
||||
|
||||
// Whatever else happened, a descriptor that did arrive is ours: the kernel installed it in our fd
|
||||
// table, and on the paths below that return early nothing else would close it.
|
||||
FF_AUTO_CLOSE_FD FFNativeFD fd = reply.fdCount > 0 ? reply.fds[0] : -1;
|
||||
|
||||
if (error != nullptr) {
|
||||
FF_DEBUG("getWallpaper(which %d) could not be transacted: %s", (int) which, error);
|
||||
return error;
|
||||
}
|
||||
if (ffBinderReplyIsStatus(&reply)) {
|
||||
FF_DEBUG("getWallpaper(which %d) came back as a status reply of %d instead of a parcel",
|
||||
(int) which, (int) ffBinderReadI32(reply.data, reply.size, 0));
|
||||
return "Wallpaper service rejected the request";
|
||||
}
|
||||
|
||||
const int32_t exception = ffBinderReadI32(reply.data, reply.size, 0);
|
||||
if (exception != 0) {
|
||||
// -1 is a SecurityException, and for this method it is what the package name check raises.
|
||||
FF_DEBUG("getWallpaper(which %d) raised exception %d over %zu bytes of reply", (int) which, exception, reply.size);
|
||||
return "Wallpaper service refused the request";
|
||||
}
|
||||
if (!ffIsValidNativeFD(fd)) {
|
||||
FF_DEBUG("getWallpaper(which %d) carried no descriptor, which is how a live wallpaper answers: "
|
||||
"%zu bytes of reply",
|
||||
(int) which, reply.size);
|
||||
return "The wallpaper is not a file";
|
||||
}
|
||||
|
||||
char path[64];
|
||||
const int pathLength = snprintf(path, sizeof(path), "/proc/self/fd/%d", (int) fd);
|
||||
if (pathLength <= 0 || (size_t) pathLength >= sizeof(path)) {
|
||||
return "Failed to build the /proc path of the wallpaper descriptor";
|
||||
}
|
||||
|
||||
char target[512];
|
||||
const ssize_t length = readlink(path, target, sizeof(target) - 1);
|
||||
if (length <= 0) {
|
||||
FF_DEBUG("readlink(%s) failed", path);
|
||||
return "Failed to resolve the wallpaper path";
|
||||
}
|
||||
target[length] = '\0';
|
||||
|
||||
ffStrbufSetNS(result, (uint32_t) length, target);
|
||||
FF_DEBUG("The wallpaper for which %d is \"%s\"", (int) which, result->chars);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const char* ffDetectWallpaper(FFstrbuf* result) {
|
||||
// The binder route below reads Android's own wallpaper, which is the one behind the launcher
|
||||
// only while SurfaceFlinger is the display server. A Termux:X11 or Wayland session runs a real
|
||||
// compositor on top of the device, and the wallpaper visible there belongs to that desktop, not
|
||||
// to Android -- so that case is handed to the Linux implementation, which knows GTK and Qt
|
||||
// settings. Wallpaper is only reported for one of the two, never merged.
|
||||
const FFDisplayServerResult* wm = ffConnectDisplayServer();
|
||||
if (!ffStrbufIgnCaseEqualS(&wm->wmProtocolName, FF_WM_PROTOCOL_SURFACEFLINGER)) {
|
||||
FF_DEBUG("The display server is \"%s\", so the Linux implementation answers", wm->wmProtocolName.chars);
|
||||
const char* ffDetectWallpaperLinux(FFstrbuf* result);
|
||||
return ffDetectWallpaperLinux(result);
|
||||
}
|
||||
|
||||
char package[128];
|
||||
if (!ffAndroidGetOwnPackage(package, sizeof(package))) {
|
||||
return "Cannot determine the package name of this process";
|
||||
}
|
||||
|
||||
[[gnu::cleanup(ffBinderClose)]] FFBinder binder = { .fd = -1 };
|
||||
const char* error = ffBinderOpen(&binder);
|
||||
if (error != nullptr) {
|
||||
return error;
|
||||
}
|
||||
|
||||
// Released on every path out of this function, including the ones below that return early.
|
||||
[[gnu::cleanup(ffBinderServiceHandleRelease)]] FFBinderServiceHandle service = { .binder = &binder };
|
||||
error = ffBinderLookupService(&binder, FF_WALLPAPER_ANDROID_SERVICE, FF_BINDER_SM_GET_SERVICE, &service.handle);
|
||||
if (error != nullptr) {
|
||||
FF_DEBUG("Looking up the \"%s\" service failed: %s", FF_WALLPAPER_ANDROID_SERVICE, error);
|
||||
return error;
|
||||
}
|
||||
FF_DEBUG("The \"%s\" service is handle %u", FF_WALLPAPER_ANDROID_SERVICE, service.handle);
|
||||
|
||||
// The system wallpaper is what the module means by "the current wallpaper"; the lock screen is a
|
||||
// second, separate picture, and is only reported when the system one is not a file -- which is
|
||||
// the live-wallpaper case, where a component draws it and there is no image behind it.
|
||||
error = getWallpaperFile(&binder, service.handle, package, FF_WALLPAPER_ANDROID_WHICH_SYSTEM, result);
|
||||
if (error == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
FF_DEBUG("The system wallpaper is not a file (%s), so the lock screen is asked for instead", error);
|
||||
return getWallpaperFile(&binder, service.handle, package, FF_WALLPAPER_ANDROID_WHICH_LOCK, result);
|
||||
}
|
||||
@@ -72,7 +72,16 @@ static const char* detectCosmicComp(FFstrbuf* result) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const char* ffDetectWallpaper(FFstrbuf* result) {
|
||||
// Android compiles this file next to wallpaper_android.c, which owns ffDetectWallpaper and calls
|
||||
// this one only while the display server is not SurfaceFlinger -- so the entry point has to step
|
||||
// aside there. Same arrangement as terminalfont_linux.c and terminalfont_android.c.
|
||||
const char*
|
||||
#ifdef __ANDROID__
|
||||
ffDetectWallpaperLinux
|
||||
#else
|
||||
ffDetectWallpaper
|
||||
#endif
|
||||
(FFstrbuf* result) {
|
||||
const FFDisplayServerResult* wm = ffConnectDisplayServer();
|
||||
if (ffStrbufIgnCaseEqualS(&wm->wmPrettyName, FF_WM_PRETTY_COSMIC_COMP)) {
|
||||
return detectCosmicComp(result);
|
||||
|
||||
@@ -3,7 +3,9 @@
|
||||
#include "common/android/api.h"
|
||||
#include "common/android/binder.h"
|
||||
#include "common/android/dex.h"
|
||||
#include "common/android/package.h"
|
||||
#include "common/debug.h"
|
||||
#include "common/strutil.h"
|
||||
|
||||
#include <ifaddrs.h>
|
||||
#include <net/if.h>
|
||||
@@ -153,11 +155,6 @@ typedef enum FFWifiAndroidRadioState : int32_t
|
||||
FF_WIFI_ANDROID_WIFI_STATE_UNKNOWN = 4,
|
||||
} FFWifiAndroidRadioState;
|
||||
|
||||
// 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. 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;
|
||||
// WifiInfo itself then starts at 8. The network id, the RSSI and the link speed have held those
|
||||
// offsets from Android 9 to 16, and the transmit speed joined them in Android 10 without moving
|
||||
@@ -290,64 +287,9 @@ typedef enum FFWifiAndroidStandard : int32_t
|
||||
// Binder
|
||||
// ---------------------------------------------------------------------------------------------
|
||||
|
||||
// `getConnectionInfo` takes the caller's package name, and whether it is checked depends on the
|
||||
// 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 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) {
|
||||
char path[4096];
|
||||
const ssize_t length = readlink("/proc/self/exe", path, sizeof(path) - 1);
|
||||
if (length <= 0) {
|
||||
FF_DEBUG("Cannot read /proc/self/exe, so the calling package cannot be derived");
|
||||
return false;
|
||||
}
|
||||
path[length] = '\0';
|
||||
|
||||
const char* rest = nullptr;
|
||||
const char* const dataPrefix = "/data/data/";
|
||||
const char* const userPrefix = "/data/user/";
|
||||
if (strncmp(path, dataPrefix, strlen(dataPrefix)) == 0) {
|
||||
rest = path + strlen(dataPrefix);
|
||||
} else if (strncmp(path, userPrefix, strlen(userPrefix)) == 0) {
|
||||
rest = strchr(path + strlen(userPrefix), '/');
|
||||
if (rest != nullptr) {
|
||||
rest += 1;
|
||||
}
|
||||
}
|
||||
if (rest == nullptr) {
|
||||
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;
|
||||
if (sizeof(shellPackage) > capacity) {
|
||||
return false;
|
||||
}
|
||||
memcpy(buffer, shellPackage, sizeof(shellPackage));
|
||||
FF_DEBUG("The calling package is \"%s\" (the shell owns it)", buffer);
|
||||
return true;
|
||||
}
|
||||
|
||||
const char* end = strchr(rest, '/');
|
||||
const size_t nameLength = end != nullptr ? (size_t) (end - rest) : strlen(rest);
|
||||
if (nameLength == 0 || nameLength >= capacity) {
|
||||
return false;
|
||||
}
|
||||
memcpy(buffer, rest, nameLength);
|
||||
buffer[nameLength] = '\0';
|
||||
FF_DEBUG("The calling package is \"%s\"", buffer);
|
||||
return true;
|
||||
}
|
||||
// The caller's own package name, which `getConnectionInfo` takes as its second argument: see
|
||||
// ffAndroidGetOwnPackage() in common/android/package.h for why it has to be that one and how it is
|
||||
// derived. `getWallpaper` in the Wallpaper module asks for the same thing.
|
||||
|
||||
static bool isMacAddress(const char* value, uint32_t length) {
|
||||
if (length != FF_WIFI_ANDROID_MAC_STRING_LENGTH) {
|
||||
@@ -418,7 +360,7 @@ static void detectInterface(FFWifiAndroidConnection* connection) {
|
||||
}
|
||||
|
||||
for (const struct ifaddrs* ifa = addrs; ifa != nullptr; ifa = ifa->ifa_next) {
|
||||
if (ifa->ifa_name == nullptr || strncmp(ifa->ifa_name, "wlan", 4) != 0) {
|
||||
if (ifa->ifa_name == nullptr || !ffStrStartsWith(ifa->ifa_name, "wlan")) {
|
||||
continue;
|
||||
}
|
||||
snprintf(connection->interface, sizeof(connection->interface), "%s", ifa->ifa_name);
|
||||
@@ -493,7 +435,7 @@ static void findStrings(const uint8_t* data, size_t size, size_t from, FFWifiAnd
|
||||
connection->bssidSeen = true;
|
||||
// The placeholder means there is no address to report: it is what the service writes
|
||||
// both for a caller it may not tell and for a connection that has none.
|
||||
if (strcmp(buffer, FF_WIFI_ANDROID_DEFAULT_MAC_ADDRESS) != 0) {
|
||||
if (!ffStrEquals(buffer, FF_WIFI_ANDROID_DEFAULT_MAC_ADDRESS)) {
|
||||
memcpy(connection->bssid, buffer, length + 1);
|
||||
}
|
||||
} else if (connection->state[0] == '\0' && isSupplicantState(buffer, length)) {
|
||||
@@ -804,7 +746,7 @@ static const char* parseConnectionInfo(const uint8_t* data, size_t size, FFWifiA
|
||||
// connection has no frequency to write, so one that is here and passed the checks above is a
|
||||
// connection.
|
||||
if (connection->state[0] != '\0') {
|
||||
connection->connected = strcmp(connection->state, "COMPLETED") == 0;
|
||||
connection->connected = ffStrEquals(connection->state, "COMPLETED");
|
||||
} else {
|
||||
connection->connected = connection->ssidAbsent
|
||||
|| ffBinderReadI32(data, size, FF_WIFI_ANDROID_OFF_NET_ID) >= 0;
|
||||
@@ -876,7 +818,7 @@ static const char* callIntMethod(FFBinder* binder, uint32_t handle, const char*
|
||||
|
||||
uint8_t replyBuffer[64];
|
||||
FFBinderReply reply = ffBinderReplyCreate(replyBuffer, sizeof(replyBuffer));
|
||||
error = ffBinderTransact(binder, handle, (uint32_t) transaction, &parcel, &reply);
|
||||
error = ffBinderTransact(binder, handle, (uint32_t) transaction, 0, &parcel, &reply);
|
||||
if (error != nullptr) {
|
||||
return error;
|
||||
}
|
||||
@@ -894,22 +836,22 @@ static const char* callIntMethod(FFBinder* binder, uint32_t handle, const char*
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// `getOwnPackage` writes at most this many bytes, terminator included.
|
||||
// `ffAndroidGetOwnPackage` writes at most this many bytes, terminator included.
|
||||
#define FF_WIFI_ANDROID_PACKAGE_SIZE 128
|
||||
|
||||
// The parcel for `getConnectionInfo` is the interface token, the package name and the -1 that
|
||||
// stands for a null `callingFeatureId`. A string16 costs 4 + 2 * (length + 1) bytes padded to four,
|
||||
// and the token puts three int32 in front of the descriptor. Deriving the size from the two names
|
||||
// rather than rounding it up is what keeps the longest package name `getOwnPackage` can produce
|
||||
// from marking the parcel truncated -- that fails the whole detection, rather than degrading to a
|
||||
// call without a name.
|
||||
// rather than rounding it up is what keeps the longest package name `ffAndroidGetOwnPackage` can
|
||||
// produce from marking the parcel truncated -- that fails the whole detection, rather than degrading
|
||||
// to a call without a name.
|
||||
#define FF_WIFI_ANDROID_PARCEL_SIZE \
|
||||
(12 + ((4 + 2 * sizeof(FF_WIFI_ANDROID_DESCRIPTOR) + 3) & ~3u) \
|
||||
+ ((4 + 2 * FF_WIFI_ANDROID_PACKAGE_SIZE + 3) & ~3u) + 4)
|
||||
|
||||
static const char* detectWithBinder(FFlist* result) {
|
||||
char package[FF_WIFI_ANDROID_PACKAGE_SIZE];
|
||||
if (!getOwnPackage(package, sizeof(package))) {
|
||||
if (!ffAndroidGetOwnPackage(package, sizeof(package))) {
|
||||
return "Cannot determine the package name of this process";
|
||||
}
|
||||
|
||||
@@ -945,7 +887,7 @@ static const char* detectWithBinder(FFlist* result) {
|
||||
|
||||
uint8_t replyBuffer[FF_WIFI_ANDROID_REPLY_SIZE];
|
||||
FFBinderReply reply = ffBinderReplyCreate(replyBuffer, sizeof(replyBuffer));
|
||||
error = ffBinderTransact(&binder, service.handle, (uint32_t) transaction, &parcel, &reply);
|
||||
error = ffBinderTransact(&binder, service.handle, (uint32_t) transaction, 0, &parcel, &reply);
|
||||
if (error != nullptr) {
|
||||
return error;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user