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:
yuanyuanxiangandClaude Sonnet 4.6 committed 2026-06-25 17:38:01 +02:00
1 parent 218ee4f43d
commit 5a1430e904
3 files changed
+64 -4

No files matched your search

+61 -2
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 设置)
// 初始化为 0;ScreenSpyThread 等到非零后再读,避免在 nativeSetScreenSize 之前 // 初始化为 0;ScreenSpyThread 等到非零后再读,避免在 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,
@@ -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()
@@ -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()