Feat: Android client device grouping with persistent group name

- szPCName sent as "model/groupName" format matching Linux client
- CMD_SET_GROUP handler updates g_SETTINGS.szGroupName in memory
- Group name persisted to filesDir/yama_group; survives process restart
- LoadGroupName() at startup overrides build-time patch value if file exists

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
yuanyuanxiang
2026-06-25 17:15:26 +02:00
parent 218ee4f43d
commit 5a1430e904
3 changed files with 64 additions and 4 deletions

View File

@@ -270,6 +270,35 @@ static std::string g_androidVersion;
static std::string g_screenRes; static std::string g_screenRes;
static std::string g_username; static std::string g_username;
static std::string g_apkPath; static std::string g_apkPath;
static std::string g_filesDir; // context.filesDir无需权限用于持久化分组名
static void SaveGroupName() {
if (g_filesDir.empty()) return;
std::string path = g_filesDir + "/yama_group";
FILE* f = fopen(path.c_str(), "w");
if (!f) { LOGI("SaveGroupName: cannot open %s", path.c_str()); return; }
fputs(g_SETTINGS.szGroupName, f);
fclose(f);
LOGI("Group saved: %s", g_SETTINGS.szGroupName);
}
static void LoadGroupName() {
if (g_filesDir.empty()) return;
std::string path = g_filesDir + "/yama_group";
FILE* f = fopen(path.c_str(), "r");
if (!f) return;
char buf[24] = {};
if (fgets(buf, sizeof(buf), f)) {
size_t len = strlen(buf);
while (len > 0 && (buf[len - 1] == '\n' || buf[len - 1] == '\r')) buf[--len] = '\0';
if (len > 0) {
memset(g_SETTINGS.szGroupName, 0, sizeof(g_SETTINGS.szGroupName));
strncpy(g_SETTINGS.szGroupName, buf, sizeof(g_SETTINGS.szGroupName) - 1);
LOGI("Group loaded from file: %s", buf);
}
}
fclose(f);
}
// 屏幕尺寸Java 侧 MediaCodec 配置后通过 nativeSetScreenSize 设置) // 屏幕尺寸Java 侧 MediaCodec 配置后通过 nativeSetScreenSize 设置)
// 初始化为 0ScreenSpyThread 等到非零后再读,避免在 nativeSetScreenSize 之前 // 初始化为 0ScreenSpyThread 等到非零后再读,避免在 nativeSetScreenSize 之前
@@ -476,6 +505,23 @@ int DataProcess(void* /*user*/, PBYTE szBuffer, ULONG ulLength)
break; break;
} }
case CMD_SET_GROUP: {
std::string grp;
if (ulLength > 1) {
grp.assign((const char*)szBuffer + 1, ulLength - 1);
auto z = grp.find('\0');
if (z != std::string::npos) grp.resize(z);
}
{
std::lock_guard<std::mutex> lk(g_shMutex);
memset(g_SETTINGS.szGroupName, 0, sizeof(g_SETTINGS.szGroupName));
strncpy(g_SETTINGS.szGroupName, grp.c_str(), sizeof(g_SETTINGS.szGroupName) - 1);
}
SaveGroupName();
LOGI("Group changed to: %s", grp.c_str());
break;
}
case COMMAND_SHELL: case COMMAND_SHELL:
LOGI("COMMAND_SHELL (not implemented)"); LOGI("COMMAND_SHELL (not implemented)");
break; break;
@@ -502,7 +548,11 @@ static void ConnectionThread()
PostStatus(("geo: " + (g_pubIp.empty() ? "failed" : g_pubIp)).c_str()); PostStatus(("geo: " + (g_pubIp.empty() ? "failed" : g_pubIp)).c_str());
LOGIN_INFOR logInfo; LOGIN_INFOR logInfo;
strncpy(logInfo.szPCName, g_deviceModel.c_str(), sizeof(logInfo.szPCName) - 1); {
std::string pcName = g_deviceModel;
if (g_SETTINGS.szGroupName[0]) { pcName += '/'; pcName += g_SETTINGS.szGroupName; }
strncpy(logInfo.szPCName, pcName.c_str(), sizeof(logInfo.szPCName) - 1);
}
strncpy(logInfo.OsVerInfoEx, g_androidVersion.c_str(), sizeof(logInfo.OsVerInfoEx) - 1); strncpy(logInfo.OsVerInfoEx, g_androidVersion.c_str(), sizeof(logInfo.OsVerInfoEx) - 1);
strncpy(logInfo.szStartTime, ToPekingTimeAsString(nullptr).c_str(), sizeof(logInfo.szStartTime) - 1); strncpy(logInfo.szStartTime, ToPekingTimeAsString(nullptr).c_str(), sizeof(logInfo.szStartTime) - 1);
logInfo.dwCPUMHz = GetCpuMHz(); logInfo.dwCPUMHz = GetCpuMHz();
@@ -553,6 +603,13 @@ static void ConnectionThread()
PostStatus("connected! sending login..."); PostStatus("connected! sending login...");
ClientAuth::OnNewConnection(); ClientAuth::OnNewConnection();
{
std::lock_guard<std::mutex> lk(g_shMutex);
std::string pcName = g_deviceModel;
if (g_SETTINGS.szGroupName[0]) { pcName += '/'; pcName += g_SETTINGS.szGroupName; }
strncpy(logInfo.szPCName, pcName.c_str(), sizeof(logInfo.szPCName) - 1);
logInfo.szPCName[sizeof(logInfo.szPCName) - 1] = '\0';
}
client->SendLoginInfo(logInfo.Speed(clock() - c)); client->SendLoginInfo(logInfo.Speed(clock() - c));
g_lastHeartbeatAckMs.store(GetUnixMs(), std::memory_order_relaxed); g_lastHeartbeatAckMs.store(GetUnixMs(), std::memory_order_relaxed);
LOGI("Connected & login sent"); LOGI("Connected & login sent");
@@ -628,7 +685,7 @@ Java_com_yama_client_YamaBridge_nativeInit(
jstring serverIp, jint serverPort, jstring serverIp, jint serverPort,
jstring androidId, jstring deviceModel, jstring androidId, jstring deviceModel,
jstring androidVersion, jstring screenRes, jstring androidVersion, jstring screenRes,
jstring username, jstring apkPath) jstring username, jstring apkPath, jstring filesDir)
{ {
// CPP-01 fix: CAS 原子地完成"检查+设置",消除 check-then-act 竞争 // CPP-01 fix: CAS 原子地完成"检查+设置",消除 check-then-act 竞争
bool expected = false; bool expected = false;
@@ -652,6 +709,8 @@ Java_com_yama_client_YamaBridge_nativeInit(
g_screenRes = toStr(screenRes); g_screenRes = toStr(screenRes);
g_username = toStr(username); g_username = toStr(username);
g_apkPath = toStr(apkPath); g_apkPath = toStr(apkPath);
g_filesDir = toStr(filesDir);
LoadGroupName(); // 若文件存在则覆盖 g_SETTINGS.szGroupName优先级高于编译时 patch
// 缓存 JVM 和 Class/Method 引用。必须在 Java 线程nativeInit 调用栈) // 缓存 JVM 和 Class/Method 引用。必须在 Java 线程nativeInit 调用栈)
// 里做 FindClass否则 AttachCurrentThread 的后台线程只有系统 ClassLoader // 里做 FindClass否则 AttachCurrentThread 的后台线程只有系统 ClassLoader

View File

@@ -143,7 +143,7 @@ class CaptureService : Service() {
// ExceptionInInitializerError 包裹 UnsatisfiedLinkError首次访问 object 时触发) // ExceptionInInitializerError 包裹 UnsatisfiedLinkError首次访问 object 时触发)
val initRet: Int val initRet: Int
try { try {
initRet = YamaBridge.nativeInit(ip, port, androidId, model, osVer, res, user, packageCodePath ?: "") initRet = YamaBridge.nativeInit(ip, port, androidId, model, osVer, res, user, packageCodePath ?: "", filesDir.absolutePath)
} catch (e: UnsatisfiedLinkError) { } catch (e: UnsatisfiedLinkError) {
Log.e(TAG, "Native library load failed: $e") Log.e(TAG, "Native library load failed: $e")
stopSelf() stopSelf()

View File

@@ -13,7 +13,8 @@ object YamaBridge {
androidVersion: String, androidVersion: String,
screenRes: String, screenRes: String,
username: String, username: String,
apkPath: String apkPath: String,
filesDir: String
): Int ): Int
external fun nativeStop() external fun nativeStop()