mirror of
https://github.com/fastfetch-cli/fastfetch.git
synced 2026-10-28 07:44:06 +01:00
Android: improves performance of dex parsing
This commit is contained in:
@@ -2,6 +2,7 @@
|
||||
#include "battery.h"
|
||||
#include "common/android/api.h"
|
||||
#include "common/android/binder.h"
|
||||
#include "common/android/dex.h"
|
||||
#include "common/debug.h"
|
||||
#include "common/processing.h"
|
||||
#include "common/properties.h"
|
||||
@@ -18,19 +19,31 @@
|
||||
// everything the module would otherwise like to show. Manufacturer, model name, technology, serial
|
||||
// number, manufacture date, temperature and cycle count all sit behind that permission.
|
||||
|
||||
// The transaction code and the reply layout are both positional, and neither is negotiated with the
|
||||
// service, so each only stays correct as long as AOSP keeps the order it already has. The code has
|
||||
// not: `IBatteryPropertiesRegistrar` still declared registerListener / unregisterListener /
|
||||
// getProperty up to Android 9, and Android 10 dropped the two listener methods, which moved
|
||||
// `getProperty` from the third position to the first. The layout has, because `BatteryProperty` has
|
||||
// only ever grown at the end -- API 35 appended [string8 mValueString] to it -- so the field read
|
||||
// below is still the first one written.
|
||||
// The reply layout is positional and is not negotiated with the service, so it only stays correct as
|
||||
// long as AOSP keeps the order it already has. It has, because `BatteryProperty` has only ever grown
|
||||
// at the end -- API 35 appended [string8 mValueString] to it -- so the field read below is still the
|
||||
// first one written.
|
||||
//
|
||||
// The transaction code is positional for the same reason, and it has *not* stayed: the interface
|
||||
// still declared registerListener / unregisterListener / getProperty up to Android 9, and Android 10
|
||||
// dropped the two listener methods, which moved `getProperty` from the third position to the first.
|
||||
// The number is therefore read out of the device's own jar rather than guessed from the API level,
|
||||
// because a vendor fork is free to insert a method ahead of it -- and a wrong code reaches a
|
||||
// different method, or none. See common/android/dex.h.
|
||||
//
|
||||
// Getting either wrong is quiet rather than loud: `ffBinderReadU64` returns 0 for a read past the
|
||||
// end of the reply, so a drift shows up as a wrong capacity, not as a failed call.
|
||||
#define FF_BATTERY_ANDROID_SERVICE "batteryproperties"
|
||||
#define FF_BATTERY_ANDROID_DESCRIPTOR "android.os.IBatteryPropertiesRegistrar"
|
||||
|
||||
// IBatteryPropertiesRegistrar has always been part of framework.jar -- it is not one of the
|
||||
// frameworks that moved into an APEX -- so there is one path and no fallback. The method this module
|
||||
// calls is named by the constant the dex carries for it, which is what the lookup turns into whatever
|
||||
// number this build uses.
|
||||
#define FF_BATTERY_ANDROID_JAR "/system/framework/framework.jar"
|
||||
#define FF_BATTERY_ANDROID_STUB "Landroid/os/IBatteryPropertiesRegistrar$Stub;"
|
||||
#define FF_BATTERY_ANDROID_GET_PROPERTY "TRANSACTION_getProperty"
|
||||
|
||||
// BatteryManager.BATTERY_PROPERTY_*
|
||||
typedef enum FFBatteryAndroidProperty : uint32_t {
|
||||
FF_BATTERY_ANDROID_PROPERTY_CAPACITY = 4,
|
||||
@@ -103,15 +116,8 @@ static void debugDumpReply(const uint8_t* data, size_t size) {
|
||||
#define FF_BATTERY_ANDROID_DEBUG_DUMP(data, size) ((void) 0)
|
||||
#endif
|
||||
|
||||
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_ANDROID_API_AT_LEAST expands to -- in any other position.
|
||||
uint32_t transaction = 3u;
|
||||
if (FF_ANDROID_API_AT_LEAST(29)) {
|
||||
transaction = 1u;
|
||||
}
|
||||
FF_DEBUG("Property %u goes out as transaction %u (3 up to Android 9, 1 from Android 10)", property, transaction);
|
||||
static const char* getProperty(FFBinder* binder, uint32_t handle, uint32_t transaction, uint32_t property, uint64_t* value) {
|
||||
FF_DEBUG("Property %u goes out as transaction %u", property, transaction);
|
||||
|
||||
uint8_t parcelBuffer[128];
|
||||
FFBinderParcel parcel = ffBinderParcelCreate(parcelBuffer, sizeof(parcelBuffer));
|
||||
@@ -165,8 +171,23 @@ static const char* getProperty(FFBinder* binder, uint32_t handle, uint32_t prope
|
||||
}
|
||||
|
||||
static const char* parseBinder(FFlist* results) {
|
||||
// Resolved before the binder is opened, so that a jar which does not declare the method says so
|
||||
// rather than the module failing later with a message about the transport. The reader reports a
|
||||
// code it cannot find by leaving the sentinel in the result and says nothing more -- what a
|
||||
// missing code means for this module is decided here, not there.
|
||||
int32_t transaction = FF_DEX_STATIC_INT_UNRESOLVED;
|
||||
const FFDexStaticIntRequest request = { FF_BATTERY_ANDROID_STUB, FF_BATTERY_ANDROID_GET_PROPERTY, &transaction };
|
||||
const char* error = ffDexStaticInts(FF_BATTERY_ANDROID_JAR, &request, 1);
|
||||
if (error != nullptr) {
|
||||
FF_DEBUG("Reading \"%s\" from \"%s\" failed: %s", FF_BATTERY_ANDROID_GET_PROPERTY, FF_BATTERY_ANDROID_JAR, error);
|
||||
return error;
|
||||
}
|
||||
if (transaction == FF_DEX_STATIC_INT_UNRESOLVED) {
|
||||
return "The battery service does not declare getProperty";
|
||||
}
|
||||
|
||||
[[gnu::cleanup(ffBinderClose)]] FFBinder binder = { .fd = -1 };
|
||||
const char* error = ffBinderOpen(&binder);
|
||||
error = ffBinderOpen(&binder);
|
||||
if (error != nullptr) {
|
||||
FF_DEBUG("Opening /dev/binder failed: %s", error);
|
||||
return error;
|
||||
@@ -179,17 +200,17 @@ static const char* parseBinder(FFlist* results) {
|
||||
FF_DEBUG("Looking up the \"%s\" service failed: %s", FF_BATTERY_ANDROID_SERVICE, error);
|
||||
return error;
|
||||
}
|
||||
FF_DEBUG("The \"%s\" service is handle %u", FF_BATTERY_ANDROID_SERVICE, service.handle);
|
||||
FF_DEBUG("The \"%s\" service is handle %u, getProperty is transaction %d", FF_BATTERY_ANDROID_SERVICE, service.handle, transaction);
|
||||
|
||||
uint64_t capacity = 0;
|
||||
error = getProperty(&binder, service.handle, FF_BATTERY_ANDROID_PROPERTY_CAPACITY, &capacity);
|
||||
error = getProperty(&binder, service.handle, (uint32_t) transaction, FF_BATTERY_ANDROID_PROPERTY_CAPACITY, &capacity);
|
||||
if (error != nullptr) {
|
||||
FF_DEBUG("The capacity is what failed, so no battery is reported at all");
|
||||
return error;
|
||||
}
|
||||
|
||||
uint64_t status = 0;
|
||||
error = getProperty(&binder, service.handle, FF_BATTERY_ANDROID_PROPERTY_STATUS, &status);
|
||||
error = getProperty(&binder, service.handle, (uint32_t) transaction, FF_BATTERY_ANDROID_PROPERTY_STATUS, &status);
|
||||
if (error != nullptr) {
|
||||
FF_DEBUG("The status is what failed, so no battery is reported at all");
|
||||
return error;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include "media.h"
|
||||
#include "common/android/api.h"
|
||||
#include "common/android/binder.h"
|
||||
#include "common/android/dex.h"
|
||||
#include "common/debug.h"
|
||||
#include "common/io.h"
|
||||
#include "common/mallocHelper.h"
|
||||
@@ -70,15 +71,23 @@
|
||||
#define FF_MEDIA_ANDROID_SESSIONS_DESCRIPTOR "android.media.session.ISessionManager"
|
||||
#define FF_MEDIA_ANDROID_CONTROLLER_DESCRIPTOR "android.media.session.ISessionController"
|
||||
|
||||
// Transaction codes as this device's own framework.jar declares them, read out of its dex rather than
|
||||
// transcribed from AOSP -- a vendor ROM adds and drops methods as it likes, and a wrong code reaches
|
||||
// a different method or none at all. getSessions is the second declared method of ISessionManager;
|
||||
// the numbers on ISessionController are sparse because most of that interface is setters this module
|
||||
// never calls.
|
||||
#define FF_MEDIA_ANDROID_GET_SESSIONS 2u
|
||||
#define FF_MEDIA_ANDROID_GET_PACKAGE_NAME 5u
|
||||
#define FF_MEDIA_ANDROID_GET_METADATA 32u
|
||||
#define FF_MEDIA_ANDROID_GET_PLAYBACK_STATE 33u
|
||||
// The jar and the stub classes the transaction codes are read out of. Both interfaces have always
|
||||
// been part of framework.jar -- neither is one of the frameworks that moved into an APEX -- so there
|
||||
// is one path and no fallback.
|
||||
#define FF_MEDIA_ANDROID_JAR "/system/framework/framework.jar"
|
||||
#define FF_MEDIA_ANDROID_SESSIONS_STUB "Landroid/media/session/ISessionManager$Stub;"
|
||||
#define FF_MEDIA_ANDROID_CONTROLLER_STUB "Landroid/media/session/ISessionController$Stub;"
|
||||
|
||||
// The methods this module calls, by the name of the constant the dex carries for each. Spelling the
|
||||
// names out rather than the numbers is the whole point: the numbering is a property of the `.aidl`
|
||||
// the ROM was built from, and a vendor that inserts a method ahead of one shifts it, after which a
|
||||
// transcribed code reaches a different method or none. `getSessions` is 2 from Android 12 on but 3 on
|
||||
// Android 11, where `notifySession2Created` holds the second slot -- which is exactly the drift a
|
||||
// hardcoded number cannot survive. See common/android/dex.h.
|
||||
#define FF_MEDIA_ANDROID_GET_SESSIONS "TRANSACTION_getSessions"
|
||||
#define FF_MEDIA_ANDROID_GET_PACKAGE_NAME "TRANSACTION_getPackageName"
|
||||
#define FF_MEDIA_ANDROID_GET_METADATA "TRANSACTION_getMetadata"
|
||||
#define FF_MEDIA_ANDROID_GET_PLAYBACK_STATE "TRANSACTION_getPlaybackState"
|
||||
|
||||
// The largest reply any of these getters produced is a metadata Bundle at ~1.6 KB, and the artwork
|
||||
// never lands here -- it arrives as a descriptor. 16 KB leaves room for a Bundle of unusually long
|
||||
@@ -447,6 +456,43 @@ static bool androidCompressCover(FFNativeFD fd, const void* pixels, uint32_t wid
|
||||
// The session
|
||||
// ---------------------------------------------------------------------------------------------
|
||||
|
||||
// Every transaction code this module sends, resolved from the device's own jar. They are resolved
|
||||
// together, in one walk, because that is the cheap way to get them: the walk is dominated by the
|
||||
// dex's type table, and the two stub classes sit in the same entry of every framework.jar seen so far
|
||||
// (classes2.dex on Android 11 and 13, classes3.dex on Android 16), so one pass answers all four.
|
||||
// Asking one at a time would repeat that pass four times -- see common/android/dex.h.
|
||||
typedef struct FFMediaAndroidCodes {
|
||||
int32_t getSessions;
|
||||
int32_t getPackageName;
|
||||
int32_t getMetadata;
|
||||
int32_t getPlaybackState;
|
||||
} FFMediaAndroidCodes;
|
||||
|
||||
static const char* androidResolveCodes(FFMediaAndroidCodes* codes) {
|
||||
const FFDexStaticIntRequest requests[] = {
|
||||
{ FF_MEDIA_ANDROID_SESSIONS_STUB, FF_MEDIA_ANDROID_GET_SESSIONS, &codes->getSessions },
|
||||
{ FF_MEDIA_ANDROID_CONTROLLER_STUB, FF_MEDIA_ANDROID_GET_PACKAGE_NAME, &codes->getPackageName },
|
||||
{ FF_MEDIA_ANDROID_CONTROLLER_STUB, FF_MEDIA_ANDROID_GET_METADATA, &codes->getMetadata },
|
||||
{ FF_MEDIA_ANDROID_CONTROLLER_STUB, FF_MEDIA_ANDROID_GET_PLAYBACK_STATE, &codes->getPlaybackState },
|
||||
};
|
||||
|
||||
const char* error = ffDexStaticInts(FF_MEDIA_ANDROID_JAR, requests, ARRAY_SIZE(requests));
|
||||
if (error != nullptr) {
|
||||
FF_DEBUG("Reading the transaction codes from \"%s\" failed: %s", FF_MEDIA_ANDROID_JAR, error);
|
||||
return error;
|
||||
}
|
||||
|
||||
// All four are codes the module cannot answer without. The reader leaves a code it could not find
|
||||
// at the sentinel instead of judging what that costs the caller, so the judgement is made here.
|
||||
if (codes->getSessions == FF_DEX_STATIC_INT_UNRESOLVED
|
||||
|| codes->getPackageName == FF_DEX_STATIC_INT_UNRESOLVED
|
||||
|| codes->getMetadata == FF_DEX_STATIC_INT_UNRESOLVED
|
||||
|| codes->getPlaybackState == FF_DEX_STATIC_INT_UNRESOLVED) {
|
||||
return "The session service does not declare every method this module calls";
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Every getter on ISessionController takes no argument, so a call is the interface token and nothing
|
||||
// else; `flags` is passed through because one of them needs TF_ACCEPT_FDS.
|
||||
[[gnu::nonnull(1, 5)]] static const char* androidCall(FFBinder* binder, uint32_t handle, uint32_t code, uint32_t flags, FFBinderReply* reply) {
|
||||
@@ -730,9 +776,9 @@ static int64_t androidElapsedRealtime(void) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static void androidReadPackageName(FFBinder* binder, uint32_t token, FFMediaResult* result) {
|
||||
static void androidReadPackageName(FFBinder* binder, uint32_t token, uint32_t transaction, FFMediaResult* result) {
|
||||
FFBinderReply reply = ffBinderReplyCreate(s_reply, sizeof(s_reply));
|
||||
const char* error = androidCall(binder, token, FF_MEDIA_ANDROID_GET_PACKAGE_NAME, 0, &reply);
|
||||
const char* error = androidCall(binder, token, transaction, 0, &reply);
|
||||
if (error != nullptr) {
|
||||
FF_DEBUG("getPackageName failed: %s", error);
|
||||
return;
|
||||
@@ -751,14 +797,14 @@ static void androidReadPackageName(FFBinder* binder, uint32_t token, FFMediaResu
|
||||
ffStrbufSet(&result->player, &result->playerId);
|
||||
}
|
||||
|
||||
static void androidReadMetadata(FFBinder* binder, uint32_t token, FFMediaResult* result, bool saveCover) {
|
||||
static void androidReadMetadata(FFBinder* binder, uint32_t token, uint32_t transaction, FFMediaResult* result, bool saveCover) {
|
||||
FFBinderReply reply = ffBinderReplyCreate(s_reply, sizeof(s_reply));
|
||||
|
||||
// TF_ACCEPT_FDS is not a hint and not optional here: the artwork arrives as a descriptor, and a
|
||||
// request that did not ask for one is answered with BR_FAILED_REPLY -- which reads like a service
|
||||
// that is not running rather than like a missing flag. It is passed unconditionally, because the
|
||||
// alternative is a failure that depends on whether the player happened to publish artwork.
|
||||
const char* error = androidCall(binder, token, FF_MEDIA_ANDROID_GET_METADATA, TF_ACCEPT_FDS, &reply);
|
||||
const char* error = androidCall(binder, token, transaction, TF_ACCEPT_FDS, &reply);
|
||||
|
||||
FFMediaAndroidCover cover = { .fd = -1 };
|
||||
if (error != nullptr) {
|
||||
@@ -782,14 +828,14 @@ static void androidReadMetadata(FFBinder* binder, uint32_t token, FFMediaResult*
|
||||
}
|
||||
}
|
||||
|
||||
static void androidReadSession(FFBinder* binder, uint32_t token, FFMediaResult* result, bool saveCover) {
|
||||
androidReadPackageName(binder, token, result);
|
||||
static void androidReadSession(FFBinder* binder, uint32_t token, const FFMediaAndroidCodes* codes, FFMediaResult* result, bool saveCover) {
|
||||
androidReadPackageName(binder, token, (uint32_t) codes->getPackageName, result);
|
||||
|
||||
// The length has to be known before the position is placed, so the metadata comes first.
|
||||
androidReadMetadata(binder, token, result, saveCover);
|
||||
androidReadMetadata(binder, token, (uint32_t) codes->getMetadata, result, saveCover);
|
||||
|
||||
FFBinderReply reply = ffBinderReplyCreate(s_reply, sizeof(s_reply));
|
||||
const char* error = androidCall(binder, token, FF_MEDIA_ANDROID_GET_PLAYBACK_STATE, 0, &reply);
|
||||
const char* error = androidCall(binder, token, (uint32_t) codes->getPlaybackState, 0, &reply);
|
||||
if (error == nullptr && ffBinderReadI32(reply.data, reply.size, 4) != 0) {
|
||||
FFMediaAndroidReader reader = { .data = reply.data, .size = reply.size, .position = 8 };
|
||||
error = androidReadPlaybackState(&reader, result);
|
||||
@@ -810,8 +856,18 @@ static void androidReadSession(FFBinder* binder, uint32_t token, FFMediaResult*
|
||||
// answer. That costs one transaction, and it keeps the module working wherever the check happens to
|
||||
// pass, instead of hard-coding today's policy into the client.
|
||||
static const char* androidDetectMediaSession(FFMediaResult* media, bool saveCover) {
|
||||
// Resolved before the binder is opened, so that a jar which does not declare the methods says so
|
||||
// rather than the module failing later with a message about the transport. The reader's own
|
||||
// reason is what is returned: a jar that cannot be read, a class it does not define and a field
|
||||
// the class does not declare are three different things.
|
||||
FFMediaAndroidCodes codes = {};
|
||||
const char* error = androidResolveCodes(&codes);
|
||||
if (error != nullptr) {
|
||||
return error;
|
||||
}
|
||||
|
||||
[[gnu::cleanup(ffBinderClose)]] FFBinder binder = { .fd = -1 };
|
||||
const char* error = ffBinderOpen(&binder);
|
||||
error = ffBinderOpen(&binder);
|
||||
if (error != nullptr) {
|
||||
return error;
|
||||
}
|
||||
@@ -822,6 +878,7 @@ static const char* androidDetectMediaSession(FFMediaResult* media, bool saveCove
|
||||
FF_DEBUG("Looking up \"%s\" failed: %s", FF_MEDIA_ANDROID_SERVICE, error);
|
||||
return error;
|
||||
}
|
||||
FF_DEBUG("The \"%s\" service is handle %u, getSessions is transaction %d", FF_MEDIA_ANDROID_SERVICE, service.handle, codes.getSessions);
|
||||
|
||||
// A phone can have several sessions at once -- a music player and a video one, say -- and only one
|
||||
// of them is the answer. Which one is decided first, by state alone, so that the metadata and the
|
||||
@@ -846,7 +903,7 @@ static const char* androidDetectMediaSession(FFMediaResult* media, bool saveCove
|
||||
ffBinderParcelPutI32(&parcel, 0); // userId: the single user a phone has
|
||||
|
||||
FFBinderReply reply = ffBinderReplyCreate(s_reply, sizeof(s_reply));
|
||||
error = ffBinderTransact(&binder, service.handle, FF_MEDIA_ANDROID_GET_SESSIONS, 0, &parcel, &reply);
|
||||
error = ffBinderTransact(&binder, service.handle, (uint32_t) codes.getSessions, 0, &parcel, &reply);
|
||||
replied = true;
|
||||
if (error != nullptr) {
|
||||
return error;
|
||||
@@ -892,7 +949,7 @@ static const char* androidDetectMediaSession(FFMediaResult* media, bool saveCove
|
||||
uint32_t bestScore = 0;
|
||||
for (uint32_t i = 0; i < tokenCount; i++) {
|
||||
FFBinderReply reply = ffBinderReplyCreate(s_reply, sizeof(s_reply));
|
||||
if (androidCall(&binder, tokens[i], FF_MEDIA_ANDROID_GET_PLAYBACK_STATE, 0, &reply) != nullptr) {
|
||||
if (androidCall(&binder, tokens[i], (uint32_t) codes.getPlaybackState, 0, &reply) != nullptr) {
|
||||
continue;
|
||||
}
|
||||
// The presence marker first: a session with no state yet answers a null parcelable, and its
|
||||
@@ -905,7 +962,7 @@ static const char* androidDetectMediaSession(FFMediaResult* media, bool saveCove
|
||||
}
|
||||
}
|
||||
|
||||
androidReadSession(&binder, tokens[best], media, saveCover);
|
||||
androidReadSession(&binder, tokens[best], &codes, media, saveCover);
|
||||
|
||||
// The tokens were acquired by ffBinderTransact() and are given back by the cleanup attribute above.
|
||||
return nullptr;
|
||||
|
||||
@@ -8,11 +8,11 @@
|
||||
|
||||
#include <string.h>
|
||||
|
||||
// Android has no desktop sound stack. PulseAudio is not part of the platform -- a Termux user can
|
||||
// start one, and then the module reads that, but its sink is a virtual device of Termux's own that
|
||||
// feeds Android's OpenSL ES output with a volume that is always at full scale, so the number says
|
||||
// nothing about the phone. ALSA and OSS are not there either: /dev/snd, /proc/asound and
|
||||
// /sys/class/sound are all EACCES from an app's SELinux domain, `dumpsys audio` needs
|
||||
// Android has no desktop sound stack. PulseAudio is not part of the platform, but a Termux user can
|
||||
// run one, and that is what a session sounds through whenever the display server is a real desktop
|
||||
// rather than SurfaceFlinger -- so this file answers for SurfaceFlinger alone and hands the rest to
|
||||
// sound_linux.c, which speaks PulseAudio. ALSA and OSS are not there either: /dev/snd, /proc/asound
|
||||
// and /sys/class/sound are all EACCES from an app's SELinux domain, `dumpsys audio` needs
|
||||
// android.permission.DUMP, and `settings get system volume_music` needs INTERACT_ACROSS_USERS.
|
||||
// `cmd audio` is worse than useless: `cmd` hands the arguments to the service over binder and the
|
||||
// command body runs inside system_server, so from an app it hangs or answers ENOSYS.
|
||||
@@ -214,15 +214,65 @@ static bool soundAndroidIsBluetooth(int32_t type) {
|
||||
}
|
||||
}
|
||||
|
||||
// Calls a method that takes no argument or one int, and answers with one int. Every transaction code
|
||||
// is resolved here rather than passed in, so that the dex is the only place a number lives.
|
||||
static const char* soundAndroidCallInt(FFBinder* binder, uint32_t handle, const char* transactionField, bool hasArgument, int32_t argument, int32_t* result) {
|
||||
int32_t transaction = 0;
|
||||
const char* error = ffDexStaticInt(FF_SOUND_ANDROID_JAR, FF_SOUND_ANDROID_STUB, transactionField, &transaction);
|
||||
// Every transaction code this module sends, resolved from the device's own jar in one walk. They are
|
||||
// asked for together because the walk is dominated by the dex's type table and all seven sit in the
|
||||
// same class of the same entry -- see common/android/dex.h.
|
||||
typedef struct FFSoundAndroidCodes {
|
||||
int32_t getStreamVolume;
|
||||
int32_t getStreamMinVolume;
|
||||
int32_t getStreamMaxVolume;
|
||||
int32_t isStreamMute;
|
||||
int32_t getDevicesForAttributes;
|
||||
int32_t isMusicActive;
|
||||
int32_t getBtActiveDeviceName;
|
||||
} FFSoundAndroidCodes;
|
||||
|
||||
// The five codes the module cannot answer without, against the two that only refine the answer. A
|
||||
// build that does not declare one of these five has nothing to report, which is what the module did
|
||||
// when each code was resolved on its own.
|
||||
static bool soundAndroidCodesIncomplete(const FFSoundAndroidCodes* codes) {
|
||||
return codes->getStreamVolume == FF_DEX_STATIC_INT_UNRESOLVED
|
||||
|| codes->getStreamMinVolume == FF_DEX_STATIC_INT_UNRESOLVED
|
||||
|| codes->getStreamMaxVolume == FF_DEX_STATIC_INT_UNRESOLVED
|
||||
|| codes->isStreamMute == FF_DEX_STATIC_INT_UNRESOLVED
|
||||
|| codes->getDevicesForAttributes == FF_DEX_STATIC_INT_UNRESOLVED;
|
||||
}
|
||||
|
||||
// Resolves all seven in one call, and then makes the judgement the reader leaves to its caller. The
|
||||
// two that only refine the answer are allowed to come back as the sentinel, because a build may
|
||||
// declare one of them and not the other -- Mi 10 has `getDevicesForAttributesUnprotected` and no
|
||||
// `getBtActiveDeviceName` -- and a code the module can live without must not take it down with it.
|
||||
static const char* soundAndroidResolveCodes(FFSoundAndroidCodes* codes) {
|
||||
const FFDexStaticIntRequest requests[] = {
|
||||
{ FF_SOUND_ANDROID_STUB, FF_SOUND_ANDROID_GET_STREAM_VOLUME, &codes->getStreamVolume },
|
||||
{ FF_SOUND_ANDROID_STUB, FF_SOUND_ANDROID_GET_STREAM_MIN_VOLUME, &codes->getStreamMinVolume },
|
||||
{ FF_SOUND_ANDROID_STUB, FF_SOUND_ANDROID_GET_STREAM_MAX_VOLUME, &codes->getStreamMaxVolume },
|
||||
{ FF_SOUND_ANDROID_STUB, FF_SOUND_ANDROID_IS_STREAM_MUTE, &codes->isStreamMute },
|
||||
{ FF_SOUND_ANDROID_STUB, FF_SOUND_ANDROID_GET_DEVICES_FOR_ATTRIBUTES, &codes->getDevicesForAttributes },
|
||||
{ FF_SOUND_ANDROID_STUB, FF_SOUND_ANDROID_IS_MUSIC_ACTIVE, &codes->isMusicActive },
|
||||
{ FF_SOUND_ANDROID_STUB, FF_SOUND_ANDROID_GET_BT_ACTIVE_DEVICE_NAME, &codes->getBtActiveDeviceName },
|
||||
};
|
||||
|
||||
const char* error = ffDexStaticInts(FF_SOUND_ANDROID_JAR, requests, ARRAY_SIZE(requests));
|
||||
if (error != nullptr) {
|
||||
FF_DEBUG("Reading \"%s\" from \"%s\" failed: %s", transactionField, FF_SOUND_ANDROID_JAR, error);
|
||||
FF_DEBUG("Reading the transaction codes from \"%s\" failed: %s", FF_SOUND_ANDROID_JAR, error);
|
||||
return error;
|
||||
}
|
||||
if (soundAndroidCodesIncomplete(codes)) {
|
||||
return "The audio service does not declare every method this module calls";
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Calls a method that takes no argument or one int, and answers with one int. `transaction` comes out
|
||||
// of the jar, and the sentinel the reader leaves for a code it could not find is caught here, so that
|
||||
// a build which does not declare the method is a message rather than a transaction that means
|
||||
// something else. `transactionField` is carried only to name the method in the debug output, which a
|
||||
// release build compiles out -- hence the attribute.
|
||||
static const char* soundAndroidCallInt(FFBinder* binder, uint32_t handle, [[maybe_unused]] const char* transactionField, int32_t transaction, bool hasArgument, int32_t argument, int32_t* result) {
|
||||
if (transaction == FF_DEX_STATIC_INT_UNRESOLVED) {
|
||||
return "The audio service does not declare that method";
|
||||
}
|
||||
|
||||
uint8_t parcelBuffer[FF_SOUND_ANDROID_SCALAR_PARCEL_SIZE];
|
||||
FFBinderParcel parcel = ffBinderParcelCreate(parcelBuffer, sizeof(parcelBuffer));
|
||||
@@ -233,7 +283,7 @@ static const char* soundAndroidCallInt(FFBinder* binder, uint32_t handle, const
|
||||
|
||||
uint8_t replyBuffer[FF_SOUND_ANDROID_SCALAR_REPLY_SIZE];
|
||||
FFBinderReply reply = ffBinderReplyCreate(replyBuffer, sizeof(replyBuffer));
|
||||
error = ffBinderTransact(binder, handle, (uint32_t) transaction, 0, &parcel, &reply);
|
||||
const char* error = ffBinderTransact(binder, handle, (uint32_t) transaction, 0, &parcel, &reply);
|
||||
if (error != nullptr) {
|
||||
return error;
|
||||
}
|
||||
@@ -322,12 +372,9 @@ static bool soundAndroidReadString16(const uint8_t* data, size_t size, size_t of
|
||||
// Bluetooth route as a whole and reports it even when the policy has selected the speaker, so it is
|
||||
// only ever consulted after the device list has already picked a Bluetooth sink, and only its name
|
||||
// is taken from it.
|
||||
static const char* soundAndroidCallBtName(FFBinder* binder, uint32_t handle, FFstrbuf* result) {
|
||||
int32_t transaction = 0;
|
||||
const char* error = ffDexStaticInt(FF_SOUND_ANDROID_JAR, FF_SOUND_ANDROID_STUB, FF_SOUND_ANDROID_GET_BT_ACTIVE_DEVICE_NAME, &transaction);
|
||||
if (error != nullptr) {
|
||||
FF_DEBUG("Reading \"%s\" from \"%s\" failed: %s", FF_SOUND_ANDROID_GET_BT_ACTIVE_DEVICE_NAME, FF_SOUND_ANDROID_JAR, error);
|
||||
return error;
|
||||
static const char* soundAndroidCallBtName(FFBinder* binder, uint32_t handle, int32_t transaction, FFstrbuf* result) {
|
||||
if (transaction == FF_DEX_STATIC_INT_UNRESOLVED) {
|
||||
return "The audio service does not declare getBtActiveDeviceName";
|
||||
}
|
||||
|
||||
uint8_t parcelBuffer[FF_SOUND_ANDROID_SCALAR_PARCEL_SIZE];
|
||||
@@ -336,7 +383,7 @@ static const char* soundAndroidCallBtName(FFBinder* binder, uint32_t handle, FFs
|
||||
|
||||
uint8_t replyBuffer[FF_SOUND_ANDROID_NAME_REPLY_SIZE];
|
||||
FFBinderReply reply = ffBinderReplyCreate(replyBuffer, sizeof(replyBuffer));
|
||||
error = ffBinderTransact(binder, handle, (uint32_t) transaction, 0, &parcel, &reply);
|
||||
const char* error = ffBinderTransact(binder, handle, (uint32_t) transaction, 0, &parcel, &reply);
|
||||
if (error != nullptr) {
|
||||
return error;
|
||||
}
|
||||
@@ -425,13 +472,15 @@ static uint8_t soundAndroidPercent(int32_t volume, int32_t minimum, int32_t maxi
|
||||
}
|
||||
|
||||
static const char* detectNative(FFSoundOptions* options, FFlist* devices) {
|
||||
// Resolved before anything else so that a build without the method says so, rather than opening
|
||||
// binder and failing later with a message about the transport.
|
||||
int32_t transaction = 0;
|
||||
const char* error = ffDexStaticInt(FF_SOUND_ANDROID_JAR, FF_SOUND_ANDROID_STUB, FF_SOUND_ANDROID_GET_DEVICES_FOR_ATTRIBUTES, &transaction);
|
||||
// Resolved before anything else so that a build without the methods says so, rather than opening
|
||||
// binder and failing later with a message about the transport. All seven codes are asked for in
|
||||
// one call and judged here: the five the module cannot answer without decide whether it runs at
|
||||
// all, and the two it only uses when the build declares them are left as sentinels for their
|
||||
// call sites to cope with.
|
||||
FFSoundAndroidCodes codes = {};
|
||||
const char* error = soundAndroidResolveCodes(&codes);
|
||||
if (error != nullptr) {
|
||||
FF_DEBUG("Reading \"%s\" from \"%s\" failed: %s", FF_SOUND_ANDROID_GET_DEVICES_FOR_ATTRIBUTES, FF_SOUND_ANDROID_JAR, error);
|
||||
return "The audio service does not declare getDevicesForAttributesUnprotected";
|
||||
return error;
|
||||
}
|
||||
|
||||
[[gnu::cleanup(ffBinderClose)]] FFBinder binder = { .fd = -1 };
|
||||
@@ -447,7 +496,7 @@ static const char* detectNative(FFSoundOptions* options, FFlist* devices) {
|
||||
return error;
|
||||
}
|
||||
FF_DEBUG("The \"%s\" service is handle %u, getDevicesForAttributesUnprotected is transaction %d",
|
||||
FF_SOUND_ANDROID_SERVICE, service.handle, transaction);
|
||||
FF_SOUND_ANDROID_SERVICE, service.handle, codes.getDevicesForAttributes);
|
||||
|
||||
uint8_t parcelBuffer[FF_SOUND_ANDROID_DEVICE_PARCEL_SIZE];
|
||||
FFBinderParcel parcel = ffBinderParcelCreate(parcelBuffer, sizeof(parcelBuffer));
|
||||
@@ -469,7 +518,7 @@ static const char* detectNative(FFSoundOptions* options, FFlist* devices) {
|
||||
|
||||
uint8_t replyBuffer[FF_SOUND_ANDROID_DEVICE_REPLY_SIZE];
|
||||
FFBinderReply reply = ffBinderReplyCreate(replyBuffer, sizeof(replyBuffer));
|
||||
error = ffBinderTransact(&binder, service.handle, (uint32_t) transaction, 0, &parcel, &reply);
|
||||
error = ffBinderTransact(&binder, service.handle, (uint32_t) codes.getDevicesForAttributes, 0, &parcel, &reply);
|
||||
if (error != nullptr) {
|
||||
return error;
|
||||
}
|
||||
@@ -531,15 +580,15 @@ static const char* detectNative(FFSoundOptions* options, FFlist* devices) {
|
||||
int32_t minimum = 0;
|
||||
int32_t maximum = 0;
|
||||
int32_t mute = 0;
|
||||
error = soundAndroidCallInt(&binder, service.handle, FF_SOUND_ANDROID_GET_STREAM_VOLUME, true, (int32_t) FF_SOUND_ANDROID_STREAM_MUSIC, &volume);
|
||||
error = soundAndroidCallInt(&binder, service.handle, FF_SOUND_ANDROID_GET_STREAM_VOLUME, codes.getStreamVolume, true, (int32_t) FF_SOUND_ANDROID_STREAM_MUSIC, &volume);
|
||||
if (error == nullptr) {
|
||||
error = soundAndroidCallInt(&binder, service.handle, FF_SOUND_ANDROID_GET_STREAM_MIN_VOLUME, true, (int32_t) FF_SOUND_ANDROID_STREAM_MUSIC, &minimum);
|
||||
error = soundAndroidCallInt(&binder, service.handle, FF_SOUND_ANDROID_GET_STREAM_MIN_VOLUME, codes.getStreamMinVolume, true, (int32_t) FF_SOUND_ANDROID_STREAM_MUSIC, &minimum);
|
||||
}
|
||||
if (error == nullptr) {
|
||||
error = soundAndroidCallInt(&binder, service.handle, FF_SOUND_ANDROID_GET_STREAM_MAX_VOLUME, true, (int32_t) FF_SOUND_ANDROID_STREAM_MUSIC, &maximum);
|
||||
error = soundAndroidCallInt(&binder, service.handle, FF_SOUND_ANDROID_GET_STREAM_MAX_VOLUME, codes.getStreamMaxVolume, true, (int32_t) FF_SOUND_ANDROID_STREAM_MUSIC, &maximum);
|
||||
}
|
||||
if (error == nullptr) {
|
||||
error = soundAndroidCallInt(&binder, service.handle, FF_SOUND_ANDROID_IS_STREAM_MUTE, true, (int32_t) FF_SOUND_ANDROID_STREAM_MUSIC, &mute);
|
||||
error = soundAndroidCallInt(&binder, service.handle, FF_SOUND_ANDROID_IS_STREAM_MUTE, codes.isStreamMute, true, (int32_t) FF_SOUND_ANDROID_STREAM_MUSIC, &mute);
|
||||
}
|
||||
if (error != nullptr) {
|
||||
return error;
|
||||
@@ -555,7 +604,7 @@ static const char* detectNative(FFSoundOptions* options, FFlist* devices) {
|
||||
// the ACTIVE bit to the device the policy already selected. A build that does not declare the
|
||||
// method, or a service that refuses it, leaves the bit clear rather than failing the module.
|
||||
int32_t active = 0;
|
||||
const char* activeError = soundAndroidCallInt(&binder, service.handle, FF_SOUND_ANDROID_IS_MUSIC_ACTIVE, true, 0, &active);
|
||||
const char* activeError = soundAndroidCallInt(&binder, service.handle, FF_SOUND_ANDROID_IS_MUSIC_ACTIVE, codes.isMusicActive, true, 0, &active);
|
||||
if (activeError != nullptr) {
|
||||
FF_DEBUG("Whether anything is playing is not known: %s", activeError);
|
||||
active = 0;
|
||||
@@ -575,7 +624,7 @@ static const char* detectNative(FFSoundOptions* options, FFlist* devices) {
|
||||
// in the type name, which is what this module printed before it asked at all.
|
||||
FF_STRBUF_AUTO_DESTROY bluetoothName = ffStrbufCreate();
|
||||
if (soundAndroidIsBluetooth(deviceType)) {
|
||||
const char* nameError = soundAndroidCallBtName(&binder, service.handle, &bluetoothName);
|
||||
const char* nameError = soundAndroidCallBtName(&binder, service.handle, codes.getBtActiveDeviceName, &bluetoothName);
|
||||
if (nameError != nullptr || bluetoothName.length == 0) {
|
||||
FF_DEBUG("The Bluetooth device is not named: %s",
|
||||
nameError != nullptr ? nameError : "the service answered an empty name");
|
||||
@@ -601,8 +650,17 @@ static const char* detectNative(FFSoundOptions* options, FFlist* devices) {
|
||||
}
|
||||
|
||||
const char* ffDetectSound(FFSoundOptions* options, FFlist* devices) {
|
||||
// The binder route below asks the policy about Android's own audio, which is the audio that is
|
||||
// audible only while SurfaceFlinger is the display server. A Termux:X11 or Wayland session runs
|
||||
// a real desktop on top of the device, and the sound there belongs to that desktop, not to
|
||||
// Android -- so that case is handed to the Linux implementation, which speaks PulseAudio. Sound
|
||||
// is only reported for one of the two, never merged.
|
||||
const FFDisplayServerResult* wm = ffConnectDisplayServer();
|
||||
if (!ffStrbufIgnCaseEqualS(&wm->wmProtocolName, FF_WM_PROTOCOL_SURFACEFLINGER)) {
|
||||
return nullptr;
|
||||
FF_DEBUG("The display server is \"%s\", so the Linux implementation answers", wm->wmProtocolName.chars);
|
||||
const char* ffDetectSoundLinux(FFSoundOptions* options, FFlist* devices);
|
||||
return ffDetectSoundLinux(options, devices);
|
||||
}
|
||||
|
||||
return detectNative(options, devices);
|
||||
}
|
||||
|
||||
@@ -1076,7 +1076,17 @@ static const char* fetchSinkList(FFSoundPulseConn* conn, const FFSoundOptions* o
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const char* ffDetectSound(FFSoundOptions* options, FFlist* devices) {
|
||||
// Android compiles this file next to sound_android.c, which owns ffDetectSound and calls this one
|
||||
// only while the display server is not SurfaceFlinger -- a Termux:X11 or Wayland session runs a real
|
||||
// desktop on top of the device, and PulseAudio is the sound of that desktop. So the entry point has
|
||||
// to step aside there. Same arrangement as wallpaper_linux.c and terminalfont_linux.c.
|
||||
const char*
|
||||
#ifdef __ANDROID__
|
||||
ffDetectSoundLinux
|
||||
#else
|
||||
ffDetectSound
|
||||
#endif
|
||||
(FFSoundOptions* options, FFlist* devices) {
|
||||
FF_STRBUF_AUTO_DESTROY defaultSocketPath = ffStrbufCreate();
|
||||
getDefaultSocketPath(&defaultSocketPath);
|
||||
FF_DEBUG("Default pulseaudio socket path: %s", defaultSocketPath.chars);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include "wallpaper.h"
|
||||
#include "common/android/binder.h"
|
||||
#include "common/android/dex.h"
|
||||
#include "common/android/package.h"
|
||||
#include "common/debug.h"
|
||||
#include "common/io.h"
|
||||
@@ -44,10 +45,20 @@ typedef enum FFWallpaperAndroidWhich : int32_t {
|
||||
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 jar and the stub class the transaction code is read out of. framework.jar answered on every
|
||||
// device measured (Android 11, 13 and 16), so that is the one path and there is no fallback; the
|
||||
// stub sits in classes.dex there, which is the entry the lookup tries first.
|
||||
#define FF_WALLPAPER_ANDROID_JAR "/system/framework/framework.jar"
|
||||
#define FF_WALLPAPER_ANDROID_STUB "Landroid/app/IWallpaperManager$Stub;"
|
||||
|
||||
// The method this module calls, by the name of the constant the dex carries for it. `getWallpaper`
|
||||
// is the fourth method of IWallpaperManager$Stub, but the number is resolved from the device's own
|
||||
// jar rather than written down here: the numbering is a property of the `.aidl` the ROM was built
|
||||
// from, and a vendor that inserts a method ahead of this one shifts it, after which a transcribed
|
||||
// code reaches a different method or none. Spelling the name out is the whole point -- the lookup
|
||||
// turns it into whatever this build uses, and a build that does not declare it says so instead of
|
||||
// reaching another method. See common/android/dex.h.
|
||||
#define FF_WALLPAPER_ANDROID_GET_WALLPAPER "TRANSACTION_getWallpaper"
|
||||
|
||||
// `ffAndroidGetOwnPackage` writes at most this many bytes, terminator included.
|
||||
#define FF_WALLPAPER_ANDROID_PACKAGE_SIZE 128
|
||||
@@ -67,7 +78,7 @@ typedef enum FFWallpaperAndroidWhich : int32_t {
|
||||
// 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) {
|
||||
static const char* getWallpaperFile(FFBinder* binder, uint32_t handle, const char* package, FFWallpaperAndroidWhich which, int32_t transaction, FFstrbuf* result) {
|
||||
uint8_t parcelBuffer[FF_WALLPAPER_ANDROID_PARCEL_SIZE];
|
||||
FFBinderParcel parcel = ffBinderParcelCreate(parcelBuffer, sizeof(parcelBuffer));
|
||||
ffBinderParcelPutInterfaceToken(&parcel, FF_WALLPAPER_ANDROID_DESCRIPTOR);
|
||||
@@ -81,7 +92,7 @@ static const char* getWallpaperFile(FFBinder* binder, uint32_t handle, const cha
|
||||
// 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);
|
||||
const char* error = ffBinderTransact(binder, handle, (uint32_t) transaction, 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.
|
||||
@@ -147,8 +158,23 @@ const char* ffDetectWallpaper(FFstrbuf* result) {
|
||||
return "Cannot determine the package name of this process";
|
||||
}
|
||||
|
||||
// Resolved before the binder is opened, so that a jar which does not declare the method says so
|
||||
// rather than the module failing later with a message about the transport. The reader reports a
|
||||
// code it cannot find by leaving the sentinel in the result and says nothing more -- what a
|
||||
// missing code means for this module is decided here, not there.
|
||||
int32_t transaction = FF_DEX_STATIC_INT_UNRESOLVED;
|
||||
const FFDexStaticIntRequest request = { FF_WALLPAPER_ANDROID_STUB, FF_WALLPAPER_ANDROID_GET_WALLPAPER, &transaction };
|
||||
const char* error = ffDexStaticInts(FF_WALLPAPER_ANDROID_JAR, &request, 1);
|
||||
if (error != nullptr) {
|
||||
FF_DEBUG("Reading \"%s\" from \"%s\" failed: %s", FF_WALLPAPER_ANDROID_GET_WALLPAPER, FF_WALLPAPER_ANDROID_JAR, error);
|
||||
return error;
|
||||
}
|
||||
if (transaction == FF_DEX_STATIC_INT_UNRESOLVED) {
|
||||
return "The wallpaper service does not declare getWallpaper";
|
||||
}
|
||||
|
||||
[[gnu::cleanup(ffBinderClose)]] FFBinder binder = { .fd = -1 };
|
||||
const char* error = ffBinderOpen(&binder);
|
||||
error = ffBinderOpen(&binder);
|
||||
if (error != nullptr) {
|
||||
return error;
|
||||
}
|
||||
@@ -160,16 +186,16 @@ const char* ffDetectWallpaper(FFstrbuf* result) {
|
||||
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);
|
||||
FF_DEBUG("The \"%s\" service is handle %u, getWallpaper is transaction %d", FF_WALLPAPER_ANDROID_SERVICE, service.handle, transaction);
|
||||
|
||||
// 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);
|
||||
error = getWallpaperFile(&binder, service.handle, package, FF_WALLPAPER_ANDROID_WHICH_SYSTEM, transaction, 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);
|
||||
return getWallpaperFile(&binder, service.handle, package, FF_WALLPAPER_ANDROID_WHICH_LOCK, transaction, result);
|
||||
}
|
||||
|
||||
@@ -827,15 +827,44 @@ static const char* wifiName(const char* value, bool withheld) {
|
||||
return withheld ? FF_WIFI_ANDROID_REDACTED : "";
|
||||
}
|
||||
|
||||
// Calls a method of the service that takes no arguments and answers with an int. The transaction
|
||||
// code comes out of the jar the same way `getConnectionInfo`'s does, so a release that does not
|
||||
// declare the method answers UNKNOWN_TRANSACTION instead of a number that means something else.
|
||||
static const char* callIntMethod(FFBinder* binder, uint32_t handle, const char* transactionField, int32_t* result) {
|
||||
int32_t transaction = 0;
|
||||
const char* error = ffDexStaticInt(FF_WIFI_ANDROID_JAR, FF_WIFI_ANDROID_STUB, transactionField, &transaction);
|
||||
// Every transaction code this module sends, resolved from the device's own jar in one walk. They are
|
||||
// asked for together because the walk is dominated by the dex's type table and both codes sit in the
|
||||
// same class of the same entry -- see common/android/dex.h.
|
||||
typedef struct FFWifiAndroidCodes {
|
||||
int32_t getConnectionInfo;
|
||||
int32_t getWifiEnabledState;
|
||||
} FFWifiAndroidCodes;
|
||||
|
||||
// Resolves both codes and reports the one outcome the module cannot work around: a jar that does not
|
||||
// declare `getConnectionInfo` has no connection to report. The radio state is a refinement -- the
|
||||
// module already carries on when the service will not answer it -- so it is allowed to come back as
|
||||
// the sentinel, and what that costs is decided where it is used rather than here.
|
||||
static const char* resolveCodes(FFWifiAndroidCodes* codes) {
|
||||
const FFDexStaticIntRequest requests[] = {
|
||||
{ FF_WIFI_ANDROID_STUB, FF_WIFI_ANDROID_GET_CONNECTION_INFO, &codes->getConnectionInfo },
|
||||
{ FF_WIFI_ANDROID_STUB, FF_WIFI_ANDROID_GET_WIFI_ENABLED_STATE, &codes->getWifiEnabledState },
|
||||
};
|
||||
|
||||
const char* error = ffDexStaticInts(FF_WIFI_ANDROID_JAR, requests, ARRAY_SIZE(requests));
|
||||
if (error != nullptr) {
|
||||
FF_DEBUG("Reading the transaction codes from \"%s\" failed: %s", FF_WIFI_ANDROID_JAR, error);
|
||||
return error;
|
||||
}
|
||||
if (codes->getConnectionInfo == FF_DEX_STATIC_INT_UNRESOLVED) {
|
||||
return "The Wifi service does not declare getConnectionInfo";
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Calls a method of the service that takes no arguments and answers with an int. `transaction` comes
|
||||
// out of the jar, and the sentinel the reader leaves for a code it could not find is caught here: a
|
||||
// release that does not declare the method is a message rather than a number that means something
|
||||
// else. `transactionField` is carried only to name the method in the debug output, which a release
|
||||
// build compiles out -- hence the attribute.
|
||||
static const char* callIntMethod(FFBinder* binder, uint32_t handle, [[maybe_unused]] const char* transactionField, int32_t transaction, int32_t* result) {
|
||||
if (transaction == FF_DEX_STATIC_INT_UNRESOLVED) {
|
||||
return "The Wifi service does not declare that method";
|
||||
}
|
||||
|
||||
uint8_t parcelBuffer[256];
|
||||
FFBinderParcel parcel = ffBinderParcelCreate(parcelBuffer, sizeof(parcelBuffer));
|
||||
@@ -843,7 +872,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, 0, &parcel, &reply);
|
||||
const char* error = ffBinderTransact(binder, handle, (uint32_t) transaction, 0, &parcel, &reply);
|
||||
if (error != nullptr) {
|
||||
return error;
|
||||
}
|
||||
@@ -880,10 +909,11 @@ static const char* detectWithBinder(FFlist* result) {
|
||||
return "Cannot determine the package name of this process";
|
||||
}
|
||||
|
||||
int32_t transaction = 0;
|
||||
const char* error = ffDexStaticInt(FF_WIFI_ANDROID_JAR, FF_WIFI_ANDROID_STUB, FF_WIFI_ANDROID_GET_CONNECTION_INFO, &transaction);
|
||||
// Resolved before the binder is opened, so that a jar which does not declare the method says so
|
||||
// rather than the module failing later with a message about the transport.
|
||||
FFWifiAndroidCodes codes = {};
|
||||
const char* error = resolveCodes(&codes);
|
||||
if (error != nullptr) {
|
||||
FF_DEBUG("Reading the transaction code from \"%s\" failed: %s", FF_WIFI_ANDROID_JAR, error);
|
||||
return error;
|
||||
}
|
||||
|
||||
@@ -901,7 +931,7 @@ static const char* detectWithBinder(FFlist* result) {
|
||||
return error;
|
||||
}
|
||||
FF_DEBUG("The \"%s\" service is handle %u, %s is transaction %d",
|
||||
FF_WIFI_ANDROID_SERVICE, service.handle, FF_WIFI_ANDROID_GET_CONNECTION_INFO, transaction);
|
||||
FF_WIFI_ANDROID_SERVICE, service.handle, FF_WIFI_ANDROID_GET_CONNECTION_INFO, codes.getConnectionInfo);
|
||||
|
||||
uint8_t parcelBuffer[FF_WIFI_ANDROID_PARCEL_SIZE];
|
||||
FFBinderParcel parcel = ffBinderParcelCreate(parcelBuffer, sizeof(parcelBuffer));
|
||||
@@ -912,7 +942,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, 0, &parcel, &reply);
|
||||
error = ffBinderTransact(&binder, service.handle, (uint32_t) codes.getConnectionInfo, 0, &parcel, &reply);
|
||||
if (error != nullptr) {
|
||||
return error;
|
||||
}
|
||||
@@ -939,7 +969,7 @@ static const char* detectWithBinder(FFlist* result) {
|
||||
// that is up. WIFI_STATE_DISABLING is a radio that is still up, so only the one value means
|
||||
// down, and WIFI_STATE_UNKNOWN is the service declining to say.
|
||||
int32_t state = 0;
|
||||
const char* stateError = callIntMethod(&binder, service.handle, FF_WIFI_ANDROID_GET_WIFI_ENABLED_STATE, &state);
|
||||
const char* stateError = callIntMethod(&binder, service.handle, FF_WIFI_ANDROID_GET_WIFI_ENABLED_STATE, codes.getWifiEnabledState, &state);
|
||||
if (stateError == nullptr && state != FF_WIFI_ANDROID_WIFI_STATE_UNKNOWN) {
|
||||
connection.up = state != FF_WIFI_ANDROID_WIFI_STATE_DISABLED;
|
||||
connection.upKnown = true;
|
||||
|
||||
Reference in New Issue
Block a user