From 5faedd53af50989922339064e663f45d4859dfaa Mon Sep 17 00:00:00 2001 From: sakuradairong Date: Sat, 6 Jun 2026 13:09:23 +0800 Subject: [PATCH] release: v1.13 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - CRITICAL: 配置文件权限加固, 无障碍修复 - HIGH: CancellationException 透传 ×8, SMB/WebDAV Failure 修复, supervisorScope - 构建: bind 127.0.0.1, allowBackup=false, CI test - 安全: 签名密码加固, ResticRestBridge auth - 死代码: 删除 MD4Provider, 3 个死方法, DataSizes, isFileNotFound, getAppLabel - 修复: ResticCommandRunner NPE, MissingAlgoProvider 全局注册 - 网络: SMB/WebDAV 重试+退避, WebDAV Range 断点续传 - 稳定性: onDestroyView null-safety, isArchiveSafe symlink 误杀修复, WebDAV 超时配置 --- .github/workflows/ci.yml | 2 + accessibility-review-report.md | 303 ++++++++++ app/build.gradle | 10 +- app/proguard-rules.pro | 2 +- app/src/main/AndroidManifest.xml | 2 +- .../androidbackupgui/backup/AppScanner.kt | 20 - .../androidbackupgui/backup/BackupConfig.kt | 2 + .../backup/BackupOperation.kt | 2 +- .../androidbackupgui/backup/MD4Provider.kt | 131 ---- .../backup/MissingAlgoProvider.kt | 23 + .../backup/RemoteTransport.kt | 3 - .../backup/RestBridgeRunner.kt | 21 +- .../androidbackupgui/backup/ResticBackup.kt | 17 +- .../backup/ResticCommandRunner.kt | 10 +- .../backup/ResticEnvResolver.kt | 7 +- .../backup/ResticMaintenance.kt | 12 +- .../androidbackupgui/backup/ResticRepoInit.kt | 4 +- .../backup/ResticRestBridge.kt | 21 +- .../androidbackupgui/backup/ResticRestore.kt | 13 +- .../backup/ResticSnapshotOps.kt | 8 +- .../backup/RestoreOperation.kt | 10 +- .../androidbackupgui/backup/RetryUtils.kt | 44 ++ .../androidbackupgui/backup/SmbTransport.kt | 83 ++- .../backup/WebdavTransport.kt | 191 ++++-- .../androidbackupgui/root/RootShell.kt | 7 +- .../androidbackupgui/ui/BackupFragment.kt | 127 +--- .../androidbackupgui/ui/PackageListAdapter.kt | 3 + .../androidbackupgui/ui/RestoreFragment.kt | 6 +- app/src/main/res/values/strings.xml | 1 + docs/plans/roadmap.md | 88 +++ docs/reviews/full-project-review-report.md | 247 ++++++++ docs/reviews/refactor-cleaner-review.md | 266 +++++++++ .../plans/security-review-report.md | 561 ++++++++++++++++++ security-review-report.md | 333 +++++++++++ silent-failure-review.md | 484 +++++++++++++++ 35 files changed, 2629 insertions(+), 435 deletions(-) create mode 100644 accessibility-review-report.md delete mode 100644 app/src/main/java/com/example/androidbackupgui/backup/MD4Provider.kt create mode 100644 app/src/main/java/com/example/androidbackupgui/backup/RetryUtils.kt create mode 100644 docs/plans/roadmap.md create mode 100644 docs/reviews/full-project-review-report.md create mode 100644 docs/reviews/refactor-cleaner-review.md create mode 100644 docs/superpowers/plans/security-review-report.md create mode 100644 security-review-report.md create mode 100644 silent-failure-review.md diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e2b770d..fd9ea60 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -24,6 +24,8 @@ jobs: - name: Lint run: ./gradlew lint + - name: Test + run: ./gradlew test - name: Build release APK run: ./gradlew assembleRelease diff --git a/accessibility-review-report.md b/accessibility-review-report.md new file mode 100644 index 0000000..56b0e48 --- /dev/null +++ b/accessibility-review-report.md @@ -0,0 +1,303 @@ +# 无障碍审查报告 — Android Backup GUI + +**审查阶段**:第三层 — 可维护性与用户体验 +**审查技能**:accessibility (WCAG 2.2) +**审查日期**:2026-06-06 +**项目规模**:37 个源文件 +**审查范围**:UI 相关 4 个 Fragment/Activity、4 个布局 XML、菜单、资源文件 + +--- + +## 严重程度说明 + +| 级别 | 定义 | +|------|------| +| **严重** | 用户无法完成核心操作,或屏幕阅读器完全无法识别交互元素 | +| **高** | 严重阻碍无障碍使用,有合理的替代方案但未实现 | +| **中** | 影响使用体验,但用户可通过变通方式完成任务 | +| **低** | 体验可改进点,非阻塞性 | + +--- + +## 发现汇总 + +| # | 文件 | 行号 | 问题 | 严重程度 | +|---|------|------|------|----------| +| 1 | PackageListAdapter.kt | 61-69, 116 | `TextView` 模拟按钮(排除数据切换)缺少无障碍角色 | **严重** | +| 2 | PackageListAdapter.kt | 76-88 | `MaterialCardView` 点击区域未合并无障碍语义 | **高** | +| 3 | PackageListAdapter.kt | 52-53 | `CheckBox` 缺少对应应用的 `contentDescription` | **高** | +| 4 | PackageListAdapter.kt | 109-115 | 排除数据切换状态未以文字方式通知 | **中** | +| 5 | fragment_backup.xml | 168-169 | statusText 缺少无障碍实时区域 | **高** | +| 6 | fragment_restore.xml | 90-91 | statusText 缺少无障碍实时区域 | **高** | +| 7 | fragment_config.xml | 384-390 | configStatusText 缺少无障碍实时区域 | **高** | +| 8 | fragment_backup.xml | 152-160 | progressBar 开始/结束状态无无障碍通知 | **中** | +| 9 | fragment_restore.xml | 73-81 | progressBar 开始/结束状态无无障碍通知 | **中** | +| 10 | fragment_backup.xml | 100-133 | 排序/全选按钮文本仅 11sp,不符合可缩放要求 | **中** | +| 11 | fragment_backup.xml | 39-43 | "用户:" 标签与 Spinner 无程序化关联 | **中** | +| 12 | fragment_backup.xml | 59-65 | "输出目录:" 标签与目录显示无程序化关联 | **低** | +| 13 | fragment_restore.xml | 49-53 | "用户:" 标签与 Spinner 无程序化关联 | **中** | +| 14 | PackageListAdapter.kt | 61-69 | "数据"文本排除切换触摸目标可能不足 48dp | **中** | +| 15 | MainActivity.kt | 93-100 | BottomNavigation 缺少 `contentDescription` 仅凭图标导航 | **低** | + +--- + +## 详细发现 + +### 发现 1:`TextView` 模拟按钮 — 排除数据切换(严重) + +**文件**:`PackageListAdapter.kt` +**行号**:61-69(创建),116(点击监听器) +**问题**:使用 `TextView`(非标准按钮控件)实现点击交互。`TextView` 默认不向 TalkBack 宣告其可点击角色。屏幕阅读器用户听到"数据"但不知道可以点击切换。 + +```kotlin +// 第 61-69 行:创建 +val et = TextView(ctx).apply { + id = R.id.excludeToggle + // ... +} + +// 第 116 行:添加点击 +toggle.setOnClickListener { + dataToggleCb(pkg, !excluded) +} +``` + +**修复建议**: +- 方案A(最优):改用 `MaterialButton` 或 `ImageButton` 替代 `TextView`,并设置 `contentDescription`。 +- 方案B(最小改动):在 `TextView` 上显式设置 `focusable = true`、`clickable = true`,并设置 `contentDescription` 说明其作用和状态。 +- 添加 `stateDescription` 或更新 `contentDescription` 反映当前切换状态("点击排除数据备份" / "点击包含数据备份")。 + +--- + +### 发现 2:卡片点击区域无障碍语义缺失(高) + +**文件**:`PackageListAdapter.kt` +**行号**:76-88 +**问题**:`MaterialCardView` 上设置了 `setOnClickListener` 用于切换 CheckBox,但 TalkBack 视卡片为一个独立可点击元素,未与内部 CheckBox 的角色合并。用户无法明确知道点击卡片的效果是切换选中状态。 + +```kotlin +// 第 76 行 +card.setOnClickListener { + val pos = holder.adapterPosition + // ... 切换 checkbox +} +``` + +**修复建议**: +- 为卡片设置 `contentDescription` 关联应用名和选中状态。 +- 或为卡片添加 `role = Role.Button` 的语义,合并内部子元素的无障碍信息。 +- 最佳实践是在 `onBindViewHolder` 中更新卡片的 `contentDescription` 为"勾选 ${app.label}"或"取消勾选 ${app.label}"。 + +--- + +### 发现 3:CheckBox 缺少对应描述(高) + +**文件**:`PackageListAdapter.kt` +**行号**:52-53(创建),92-102(绑定) +**问题**:CheckBox 在代码中创建且未设置 `contentDescription`。虽然旁边有 `TextView` 显示应用名,但程序化关联不完善。 + +```kotlin +// 第 52-53 行 +val cb = CheckBox(ctx).apply { + id = R.id.checkbox + // 没有 contentDescription +} +``` + +**修复建议**:在 `onBindViewHolder`(第 96 行设置 `textView.text` 之后)为 checkbox 设置 `contentDescription`: + +```kotlin +holder.checkbox.contentDescription = "选择 ${app.label.ifEmpty { pkg }}" +``` + +当选中/取消时同步更新描述。 + +--- + +### 发现 4:排除数据切换状态缺少文字通知(中) + +**文件**:`PackageListAdapter.kt` +**行号**:109-115 +**问题**:排除数据开关通过 `paintFlags`(删除线)和 `isSelected` 来表示状态,这些仅视觉变化不会被 TalkBack 识别。用户无法知道当前是否已排除数据。 + +```kotlin +// 第 109-115 行 +toggle.text = "数据" +toggle.paintFlags = if (excluded) { + toggle.paintFlags or android.graphics.Paint.STRIKE_THRU_TEXT_FLAG +} else { + toggle.paintFlags and android.graphics.Paint.STRIKE_THRU_TEXT_FLAG.inv() +} +toggle.isSelected = excluded +``` + +**修复建议**:显式更新 `contentDescription`: + +```kotlin +toggle.contentDescription = if (excluded) { + "排除 ${app.label.ifEmpty { pkg }} 的用户数据备份" +} else { + "包含 ${app.label.ifEmpty { pkg }} 的用户数据备份" +} +``` + +--- + +### 发现 5/6/7:状态文字缺少无障碍实时区域(高) + +**文件**: +- `fragment_backup.xml` 第 168-169 行(statusText) +- `fragment_restore.xml` 第 90-91 行(statusText) +- `fragment_config.xml` 第 384-390 行(configStatusText) + +**问题**:三个状态文字 View 均未设置 `accessibilityLiveRegion`。当代码调用 `updateStatus()` 或 `applyState()` 更新文本内容时,TalkBack 不会自动朗读变化。 + +**修复建议**:在布局 XML 中添加: +```xml +android:accessibilityLiveRegion="polite" +``` + +同时,建议在 `BackupFragment.kt:406` 的 `updateStatus` 方法和 `ConfigFragment.kt:136` 设置状态文本后手动触发无障碍事件,确保 TalkBack 播报: +```kotlin +binding.statusText.sendAccessibilityEvent( + AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED +) +``` + +--- + +### 发现 8/9:进度指示器状态无通知(中) + +**文件**: +- `fragment_backup.xml` 第 152-160 行(progressBar) +- `fragment_restore.xml` 第 73-81 行(progressBar) + +**问题**:`LinearProgressIndicator` 的 `visibility` 在 `VISIBLE`/`GONE` 之间切换(`BackupFragment.kt:402`、`RestoreFragment.kt:395`),但 TalkBack 不会主动通知用户加载开始或结束。 + +**修复建议**:在 `setRunning()` 方法中切换进度条可见性时,同步更新 `statusText` 并确保其 `accessibilityLiveRegion` 生效。例如在显示进度条时更新 statusText 为"正在加载…",隐藏时更新为"加载完成"。 + +--- + +### 发现 10:排序/全选按钮文本过小(中) + +**文件**:`fragment_backup.xml` +**行号**:100-133 +**问题**:排序和全选按钮的 `android:textSize` 设置为 `11sp`。 + +```xml +