Harden read-only thread handling and refresh Android releases

This commit is contained in:
kris
2026-04-06 13:26:48 +08:00
parent 9d7d2f4d17
commit 3564aeaf2e
18 changed files with 346 additions and 27 deletions

View File

@@ -118,7 +118,7 @@ public class AboutActivity extends BossScreenActivity {
appendContent(BossUi.buildWechatMenuRow(
this,
"当前版本",
user == null ? ota.optString("currentVersion", "-") : user.optString("version", ota.optString("currentVersion", "-")),
resolveInstalledVersionLabel(user, ota, BuildConfig.VERSION_NAME),
"已安装版本",
null,
null
@@ -171,6 +171,23 @@ public class AboutActivity extends BossScreenActivity {
return "发现新版本 " + availableRelease.optString("version", "未知版本");
}
private static String resolveInstalledVersionLabel(
@Nullable JSONObject user,
JSONObject ota,
@Nullable String packageVersionName
) {
if (packageVersionName != null && !packageVersionName.isEmpty()) {
return packageVersionName;
}
if (user != null) {
String userVersion = user.optString("version", "");
if (!userVersion.isEmpty()) {
return userVersion;
}
}
return ota.optString("currentVersion", "-");
}
private static String buildOtaStatusMeta(JSONObject ota) {
JSONObject availableRelease = ota.optJSONObject("availableRelease");
if (availableRelease == null) {

View File

@@ -14,6 +14,8 @@ import org.json.JSONArray;
import org.json.JSONObject;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
@@ -25,6 +27,11 @@ public class GroupCreateActivity extends BossScreenActivity {
private final List<CandidateConversation> candidates = new ArrayList<>();
private final Set<String> selectedProjectIds = new LinkedHashSet<>();
private final Set<String> lastCandidateProjectIds = new LinkedHashSet<>();
private static final Set<String> AUTO_JOIN_GROUP_TITLES = new HashSet<>(Arrays.asList(
"主agent",
"硬件审计协作",
"boss移动控制台"
));
private String sourceProjectId;
private String sourceProjectName;
@@ -193,21 +200,41 @@ public class GroupCreateActivity extends BossScreenActivity {
if (conversations == null) {
return result;
}
boolean hasSourceProject = sourceProjectId != null && !sourceProjectId.isEmpty();
for (int i = 0; i < conversations.length(); i++) {
JSONObject item = conversations.optJSONObject(i);
if (item == null) continue;
String projectId = item.optString("projectId", "");
if (projectId.isEmpty()
|| (hasSourceProject && sourceProjectId.equals(projectId))
|| item.optBoolean("isGroup", false)) {
continue;
if (isEligibleForManualGroupSelection(item, sourceProjectId)) {
result.add(item);
}
result.add(item);
}
return result;
}
static boolean isEligibleForManualGroupSelection(@Nullable JSONObject item, @Nullable String sourceProjectId) {
if (item == null) {
return false;
}
String projectId = item.optString("projectId", "");
if (projectId.isEmpty()) {
return false;
}
if (sourceProjectId != null && !sourceProjectId.isEmpty() && sourceProjectId.equals(projectId)) {
return false;
}
if (item.optBoolean("isGroup", false)) {
return false;
}
if (!"single_device".equals(item.optString("conversationType", "single_device"))) {
return false;
}
return !AUTO_JOIN_GROUP_TITLES.contains(normalizeConversationTitle(item));
}
private static String normalizeConversationTitle(JSONObject item) {
String title = item.optString("projectTitle", item.optString("threadTitle", ""));
return title == null ? "" : title.replaceAll("\\s+", "").toLowerCase();
}
static WechatSurfaceMapper.ConversationRow toCandidateConversationRow(JSONObject item, boolean selected) {
return new WechatSurfaceMapper.ConversationRow(
item.optString("projectTitle", item.optString("threadTitle", "未命名会话")),

View File

@@ -144,17 +144,27 @@ public class GroupCreateActivityUiTest {
JSONArray conversations = new JSONArray()
.put(new JSONObject()
.put("projectId", "thread-2")
.put("projectTitle", "硬件审计协作")
.put("projectTitle", "查询树莓派二代")
.put("folderLabel", "Mac Studio")
.put("lastMessagePreview", "检查摄像头供电链路")
.put("lastMessagePreview", "检查树莓派二代供电链路")
.put("latestReplyLabel", "09:28")
.put("conversationType", "single_device")
.put("isGroup", false))
.put(new JSONObject()
.put("projectId", "thread-3")
.put("projectTitle", "Boss 移动控制台")
.put("projectTitle", "Boss 线程修复")
.put("folderLabel", "Boss")
.put("lastMessagePreview", "统一顶部按钮样式")
.put("latestReplyLabel", "09:31")
.put("conversationType", "single_device")
.put("isGroup", false))
.put(new JSONObject()
.put("projectId", "thread-4")
.put("projectTitle", "主Agent")
.put("folderLabel", "Boss")
.put("lastMessagePreview", "系统自动加入")
.put("latestReplyLabel", "09:32")
.put("conversationType", "single_device")
.put("isGroup", false));
return new JSONObject().put("conversations", conversations);
}

View File

@@ -187,6 +187,24 @@ public class WechatSurfaceMapperTest {
assertEquals("版本 v1.2.8\n1. 优化设备状态刷新\n2. 修复主 Agent 会话排序\n3. 提升 OTA 回收稳定性", content);
}
@Test
public void aboutActivity_prefersInstalledPackageVersionOverServerVersion() throws Exception {
JSONObject ota = new StubJSONObject().withString("currentVersion", "v1.4.1");
JSONObject user = new StubJSONObject().withString("version", "v1.4.1");
java.lang.reflect.Method method = AboutActivity.class.getDeclaredMethod(
"resolveInstalledVersionLabel",
JSONObject.class,
JSONObject.class,
String.class
);
method.setAccessible(true);
String installedVersion = (String) method.invoke(null, user, ota, "2.5.3");
assertEquals("2.5.3", installedVersion);
}
@Test
public void aboutActivity_rejectsStaleDownloadedApkWhenAvailableReleaseChanged() throws Exception {
JSONObject availableRelease = new StubJSONObject()