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:
@@ -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()
|
||||||
|
|||||||
Reference in New Issue
Block a user