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_username;
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 设置)
// 初始化为 0ScreenSpyThread 等到非零后再读,避免在 nativeSetScreenSize 之前
@@ -476,6 +505,23 @@ int DataProcess(void* /*user*/, PBYTE szBuffer, ULONG ulLength)
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:
LOGI("COMMAND_SHELL (not implemented)");
break;
@@ -502,7 +548,11 @@ static void ConnectionThread()
PostStatus(("geo: " + (g_pubIp.empty() ? "failed" : g_pubIp)).c_str());
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.szStartTime, ToPekingTimeAsString(nullptr).c_str(), sizeof(logInfo.szStartTime) - 1);
logInfo.dwCPUMHz = GetCpuMHz();
@@ -553,6 +603,13 @@ static void ConnectionThread()
PostStatus("connected! sending login...");
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));
g_lastHeartbeatAckMs.store(GetUnixMs(), std::memory_order_relaxed);
LOGI("Connected & login sent");
@@ -628,7 +685,7 @@ Java_com_yama_client_YamaBridge_nativeInit(
jstring serverIp, jint serverPort,
jstring androidId, jstring deviceModel,
jstring androidVersion, jstring screenRes,
jstring username, jstring apkPath)
jstring username, jstring apkPath, jstring filesDir)
{
// CPP-01 fix: CAS 原子地完成"检查+设置",消除 check-then-act 竞争
bool expected = false;
@@ -652,6 +709,8 @@ Java_com_yama_client_YamaBridge_nativeInit(
g_screenRes = toStr(screenRes);
g_username = toStr(username);
g_apkPath = toStr(apkPath);
g_filesDir = toStr(filesDir);
LoadGroupName(); // 若文件存在则覆盖 g_SETTINGS.szGroupName优先级高于编译时 patch
// 缓存 JVM 和 Class/Method 引用。必须在 Java 线程nativeInit 调用栈)
// 里做 FindClass否则 AttachCurrentThread 的后台线程只有系统 ClassLoader

View File

@@ -143,7 +143,7 @@ class CaptureService : Service() {
// ExceptionInInitializerError 包裹 UnsatisfiedLinkError首次访问 object 时触发)
val initRet: Int
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) {
Log.e(TAG, "Native library load failed: $e")
stopSelf()

View File

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