Repository navigation
Releases
v2.3.1
防御性小版本:把“打开方式”的 content scheme 显式写进 manifest,不改变框架语义,只是不再依赖框架的隐式推断。
Changed
- VIEW filter 显式化:新增
<data android:mimeType="*/*" android:scheme="content"/>的 filter,与原有的“只写 MIME”“只写contentscheme 兜底 null type”两条并存。框架本来就把“只声明 MIME”的 filter 视为支持content:/file:(Data test 规则 4),显式写出来只是把“OEM 解析器没实现这条隐式规则”的可能性降到最低——HyperOS/MIUI 的MiuiResolverActivity会自己再算一遍候选列表,第三方模块(HyperSelect 等)也是围绕它做排序/隐藏。 - SEND 保持不带 scheme:
ACTION_SEND/SEND_MULTIPLE仍然只写 MIME。分享请求一般没有 data URI,一旦带上contentscheme,scheme 为 null 的 Intent 会直接NO_MATCH_DATA;新增用例守住这一点。 - 版本号:
versionName2.3.1,versionCode最低 19(覆盖 v2.3.0 的 18)。
Testing
- 新增
sendIntentFilters_resolveTypedIntentsWithoutData(SEND/SEND_MULTIPLE在无 data URI 时仍解析到本应用),API 36 instrumentation 共 20 条;JVM 仍 88 条。 qa_check.py新增“VIEW 显式声明contentscheme +*/*”断言。- README「已知限制」补充:HyperOS/MIUI 的“打开方式”由小米自己实现,候选会分成“推荐选项 / 其他选项”并按使用频率排序;看不到本应用时往下翻或设为默认即可,这不是 manifest 少声明了什么。
本包已签名,证书 SHA-256: 3682cce6d8fbf859b9b0a90ef8818acc747c82bb5f9d97418a4e6749cb6ccaca
校验安装包完整性: sha256sum -c SHA256SUMS.txt
v2.3.0
分享之外再开一个入口:文件管理器、邮件附件、浏览器下载完成后的“打开”也能直接调起本应用。进入后和单文件分享走同一条“复制 → 改名 → 发送/保存”,源文件依旧不动,依旧零权限。
Added
- “打开方式”入口(
ACTION_VIEW):<data android:mimeType="*/*"/>覆盖任意格式的content:/file:请求;另加一条只声明contentscheme 的 filter,接住来源没setType()、provider 也拿不到 MIME 的请求(框架的*/*filter 匹配不上 type 为 null 的 Intent)。只声明 MIME 的 filter 不会匹配http(s),浏览器链接不会被接管。 - 共用加载路径:VIEW 直接复用
loadSource,文件名 / MIME / 扩展名锁定 / 校验值 / 保存重名规则与分享完全一致;目录沿用 2.1.0 的策略按纯文本处理、不展开。文本兜底的“发送”原样转发来源 VIEW 请求,并给data里的content://补FLAG_GRANT_READ_URI_PERMISSION。 - instrumentation:新增 4 条 API 36 用例(
viewIntent_copiesFileLikeShare、viewIntent_withoutType_stillOpens、viewIntent_ofFolder_fallsBackToPlainText、viewIntentFilter_resolvesContentUris_andIgnoresWebLinks),模拟器门禁共 19 条。 qa_check.py:新增VIEWaction 断言,防止后续改动把入口删掉。
Changed
- 版本号:
versionName2.3.0,versionCode最低 18(覆盖 v2.2.0 的 17)。 - 已知限制:
file://裸路径能进来但零权限读不了,界面会提示“源文件不可用”,这类来源请改用文件管理器里的分享。
本包已签名,证书 SHA-256: 3682cce6d8fbf859b9b0a90ef8818acc747c82bb5f9d97418a4e6749cb6ccaca
校验安装包完整性: sha256sum -c SHA256SUMS.txt
v2.2.0
标题栏右上角加了一个垃圾桶:手动强制清空缓存,只留下当前正在用的那一份。Windows/Android 设置里的“清除缓存”语义,只是这个应用自己实现,不依赖系统。
Added
- 清空缓存按钮:
MainActivity顶栏右侧 48dp 图标按钮,二次确认后删除cacheDir/shared/下所有非活会话目录,以及shared-state/里对应的交付标记、孤儿标记和原子写临时文件;当前会话(CacheState登记 +sessionDir/currentFile/savePendingFile兜底)绝不删除。 - 结果提示:清理在 IO 线程执行,完成后 Toast 报告释放的字节数(B/KB/MB/GB),没有可清理内容时提示“没有可清理的缓存”。
- 单测:新增 3 条 JVM 用例(保留活会话并删除其它、清孤儿标记与临时文件、目录不可读时不动标记),JVM 共 88 条。
- instrumentation:新增
clearCacheButton_keepsCurrentSession_andDeletesOtherSessions(造一个旧会话 + 交付标记,点按钮并确认后断言旧会话/标记被删、当前会话仍在、发送仍可用),API 36 门禁共 15 条。
Changed
- 版本号:
versionName2.2.0,versionCode最低 17(覆盖 v2.1.0 的 16)。
本包已签名,证书 SHA-256: 3682cce6d8fbf859b9b0a90ef8818acc747c82bb5f9d97418a4e6749cb6ccaca
校验安装包完整性: sha256sum -c SHA256SUMS.txt
v2.1.0
撤回 2.0.0 的文件夹递归。跨应用分享只授予目录这一个 URI,列子项需要 FLAG_GRANT_PREFIX_URI_PERMISSION 或 SAF 树授权,普通 ACTION_SEND 不会给;HyperOS 3 等来源上文件夹分享必然失败。与其留一个假入口,不如把路径/链接当纯文本交出去。
Changed
- 文件夹分享改为纯文本:分享的是文件夹时,直接把它的 URI/路径放进文本模式——可改名、可保存为 UTF-8 无 BOM 的
.txt、也可点“发送”原样转发来源请求;不再尝试读取文件夹内容。 - 多文件:只处理文件;同时选中文件夹时跳过文件夹,状态栏提示跳过了几个;全部是文件夹时整单降级为纯文本。
- 多文件保存路径:统一写
Download/根(Android 7–9 写用户选中目录的根),不再创建/复用来源的目录层级。 - 版本号:
versionName2.1.0,versionCode最低 16(覆盖 v2.0.0 的 15)。 - 测试:
folderShare_preservesNestedStructure换成folderShare_isTreatedAsPlainText——断言预览是文件夹 URI、可改名/保存/发送、重建后仍在,并校验保存的.txt内容就是该 URI。instrumentation 仍为 14 条。
Removed
- 文件夹递归展开(
DocumentsContract子项遍历)、RELATIVE_PATH子目录与 SAF 逐级建目录的代码及文案。 status_folder_unreadable(“无法读取分享的文件夹”)——这条失败路径已经不存在。
Fixed
- 文件夹分享在真机上必然失败:来源只授予目录本身,列子项会抛
SecurityException,界面停在“无法读取分享的文件夹”。现在不再把文件夹当成可保存的目录树。 - 文本模式的状态/提示能区分“普通文本”“未知请求转储”“文件夹路径”三种来源,不再是笼统的文本提示。
本包已签名,证书 SHA-256: 3682cce6d8fbf859b9b0a90ef8818acc747c82bb5f9d97418a4e6749cb6ccaca
校验安装包完整性: sha256sum -c SHA256SUMS.txt
v2.0.0
分享入口的又一次扩展:多文件分享可以带上整棵目录树;纯文本和无法识别的调用不再只是“落个 .txt”,还能原样转发给别的应用;顺手修掉 .ini 被存成 .ini.txt 的问题。因为新增了文件夹分享和转发能力,按语义化版本升到 2.0.0。
Added
- 文件夹分享:
ACTION_SEND/ACTION_SEND_MULTIPLE中的目录/树 URI 会递归展开;Android 10+ 用MediaStore的RELATIVE_PATH保留Download/<根目录>/<子目录>/...,Android 7–9 在 SAF 目标目录里逐级createDocument(..., MIME_TYPE_DIR, ...)。不新增 manifest 权限,依赖来源 Provider 的目录/子项授权;空文件夹不复制。 - 纯文本/未知请求的“发送”:文本模式和多文件以外的未知兜底现在启用发送按钮,直接把来源
Intent复制到 chooser,不在应用里重新编码内容;仅在有ClipData/EXTRA_STREAM时补FLAG_GRANT_READ_URI_PERMISSION。 - 未知请求兜底:没有可读文件/文本的分享或非
ACTION_MAIN调用,生成Intent.toUri(URI_INTENT_SCHEME)原始转储并保存为.txt,同时保留原 Intent 供转发。
Changed
- 纯文本模式:保存为 UTF-8 无 BOM
.txt,不规范化换行(CRLF/CR/LF 原样);默认名仍是本地时区YYYYMMDD_hhmmss。发送不再禁用。 - 多文件 UI:列表显示相对路径(如
share-root/sub/b.txt),保存时按multiPaths创建/复用子目录。 - 版本号:
versionName2.0.0,versionCode最低 15(覆盖 v1.6.0 的 14)。 - 测试:新增 debug-only
TestFolderProvider与folderShare_preservesNestedStructure,覆盖文件夹递归展开和 MediaStore 相对路径;未知分享/畸形EXTRA_STREAM用例同步到新兜底行为。JVM 单测 85 条,API 36 instrumentation 14 条。
Fixed
- 保存
.ini等非标准扩展名时被追加.txt:当分享方把文件报成text/plain、而 Android 的 MIME 表里text/plain只对应txt时,MediaStore 会把desktop.ini保存成desktop.ini.txt。保存时若源 MIME 的规范扩展名与当前扩展名冲突,改用扩展名推断的 MIME(推断不到则application/octet-stream),单文件和多文件共用该规则,保留原扩展名。
本包已签名,证书 SHA-256: 3682cce6d8fbf859b9b0a90ef8818acc747c82bb5f9d97418a4e6749cb6ccaca
校验安装包完整性: sha256sum -c SHA256SUMS.txt
v1.5.0
复制完成后显示文件校验值,方便用户自行和源文件对比,验证传输/写盘后内容是否一致。
Added
- MD5 / SHA-1 / SHA-256 展示:文件复制完成后,在输入框/按钮下方显示三个小写十六进制校验值;支持长按选择,以及“Copy all”一键复制三行。
- 复制后回读计算:校验值是对
cacheDir/shared/<会话>/里的最终缓存文件做一次只读回读得到的,而不是边复制边算。这样显示的是真正会被分享/保存的那份文件,写盘阶段若发生损坏也能通过和源文件对比发现。 - 计算进度:回读期间复用现有进度条,显示“Calculating checksums… %”;期间禁用发送/保存,避免在拿到校验值前交付。
- 状态恢复:MD5/SHA-1/SHA-256 进入
onSaveInstanceState,深浅色/字体/语言重建后保留;新分享/重置/缓存失效时清空。 - 失败可重试:回读失败显示“Could not calculate checksums. Tap Send to retry.”,不展示半套值,也不会把未完成校验的文件当成 ready。
Changed
- 版本号:
versionName1.5.0,versionCode最低 13(可覆盖 v1.4.0 的 12)。 - 测试:新增
FileDigestUtilsTest(已知向量、空文件、二进制、进度、取消、十六进制填充),JVM 单测增加到 78 条;MainActivityFlowTest增加“校验值展示 + 重建后保留”用例,API 36 模拟器门禁共 8 条 instrumentation。
Notes
- 回读是一次额外的完整读盘;大文件期间界面仍可响应,但发送/保存要等校验值完成。
- MD5/SHA-1 仅用于兼容旧工具和快速对比,完整性/安全性优先看 SHA-256。
本包已签名,证书 SHA-256: 3682cce6d8fbf859b9b0a90ef8818acc747c82bb5f9d97418a4e6749cb6ccaca
校验安装包完整性: sha256sum -c SHA256SUMS.txt
v1.4.0
构建工具链升级到 AGP 9 / Gradle 9 / Kotlin 2.4,并把 compileSdk / targetSdk 升到 36(Android 16)。这是一个 minor 版本:targetSdk 36 带来强制 edge-to-edge,界面适配是本次的主要功能性改动。
Changed(工具链)
- AGP 8.5.2 → 9.4.1,Gradle 8.9 → 9.6.0,Kotlin 2.0.21 → 2.4.21。
- 按 AGP 9 的 built-in Kotlin 迁移:移除
org.jetbrains.kotlin.android插件;在根build.gradle.kts的buildscriptclasspath 中显式指定 KGP 2.4.21,避免沿用 AGP 内置的旧 KGP。 - release 构建改用 AGP 9.3+ 的
optimization { enable = false }DSL,保持当前“不混淆、不 shrink”的行为。 - AndroidX 依赖升级到当前 stable:core-ktx 1.18.0、appcompat 1.8.0、activity 1.13.0、lifecycle 2.11.0、material 1.14.0、coroutines 1.11.0;AndroidX Test 升到 runner 1.7.0 / ext-junit 1.3.0 / espresso 3.7.0。
compileSdk = 36、targetSdk = 36,minSdk 24不变;Build Tools 使用 AGP 9.4 默认的 36.0.0。
Changed(Android 16 行为)
- 强制 edge-to-edge:targetSdk 36 在 Android 16 上不能再通过
windowOptOutEdgeToEdgeEnforcement退出。MainActivity对 API 35+ 调用WindowCompat.setDecorFitsSystemWindows(window, false),用WindowInsets给根布局加系统栏/刘海/IME padding;Toolbar 自身仍保持 56dp,状态栏区域由根布局让出。 - 状态栏/导航栏图标明暗改用
WindowInsetsControllerCompat跟随深浅色;API 34 及以下保留原有statusBarColor+ 系统栏行为。 - 没有重写返回键,因此 Android 16 的 predictive back 默认行为不需要额外迁移;布局没有锁定方向/宽高比,大屏忽略
screenOrientation/resizableActivity限制不会造成额外问题(已有的状态恢复继续覆盖重建)。 - 未使用 native 库,不涉及 16 KB page size;未使用健康、蓝牙或
ScheduledExecutorService固定频率任务。
Changed(验证)
- Emulator 门禁从 API 34 升到 API 36,只保留 API 36 主门禁;
ThemeUiTest改为 edge-to-edge aware(Toolbar top = 根布局状态栏 padding,而不是 0)。 tools/qa_check.py的 targetSdk 期望改为 36。- release
versionCode最低值提高到 12,确保能覆盖 v1.3.2 的 11。
本包已签名,证书 SHA-256: 3682cce6d8fbf859b9b0a90ef8818acc747c82bb5f9d97418a4e6749cb6ccaca
校验安装包完整性: sha256sum -c SHA256SUMS.txt
v1.3.2
底层逻辑集中修复:生命周期/状态恢复、保存竞态、缓存交付标记、主线程 Provider 查询、MediaStore/SAF 异常路径、文件名与 Intent 健壮性。全部修复都有对应的 JVM 单测或 API 34 模拟器 instrumentation 覆盖。
Fixed(生命周期与状态)
- H1 深浅色/字体/语言切换后丢状态:Activity 重建时通过
onSaveInstanceState保存并恢复源 URI、MIME、原文件名、缓存会话/当前文件、复制状态、输入框内容、扩展名锁定、已交付路径和 SAF 待保存文件。复制未完成会自动重试;已完成的文件不会重新复制。 - H11 SAF 待保存文件在重建后丢失:
savePendingFile进入保存状态,重建后仍能接住ACTION_CREATE_DOCUMENT的回调,不再把保存静默变成“已取消”。 - L6/L7 MAIN/纯文本 Intent 与后台启动:已有文件时收到
ACTION_MAIN或纯文本分享不再清空界面;prepare 完成时如果界面已经onStop,不会从后台启动分享面板/SAF,而是提示回到应用重试。 - H8
showEmptyState不失效旧任务:重置/新入口统一递增操作代际并取消复制、prepare、MediaStore 保存和 SAF 写入;旧任务回到主线程后不再改 UI、不再 finish。
Fixed(保存竞态与错误处理)
- H2 保存中收到新分享会关掉新界面:MediaStore/SAF 保存都捕获自己的操作代际,新分享后旧任务只清理自己的半成品,不再
finish新界面、不再弹旧 Toast。 - H5 MediaStore 异常/孤儿条目:
insert、openOutputStream、写入、update(IS_PENDING=0)全部捕获;update返回<= 0视为失败,删除 pending 条目;整个保存流程不会因 Provider 异常崩溃。 - H6 SAF
wt部分写入后回退w:只有一个字节都没写进去时才回退"w";已经写了部分数据时不再盲目重写,失败后尝试deleteDocument/"wt"截断,并明确提示目标文件可能不完整;成功时校验写入字节数。
Fixed(缓存与交付标记)
- H7 交付标记在分享之后写:改为先写标记再启动分享面板;
startActivity失败会回滚标记;标记写不进去时不启动分享,避免把死链交给目标 App。 - H3 清理误删新会话标记:
cleanup只 prune 本次真正列到过、且确实已经删除的会话标记;清理开始后新建的会话标记不会被误删。listFiles()返回 null 时直接跳过 prune,不再把“读不出来”当成空目录。清理加进程内互斥,避免两个强制清理并发 prune。 - H12 会话目录撞名:会话名改为“时间戳 + 会话号 + UUID”,时钟回拨/进程重启/同毫秒都不会复用旧目录;目录已存在时重新生成,不再复用。
- H9 后台未交付缓存永远不清理:
onStop后给 30 分钟宽限,期间返回仍受保护;30 分钟后且没有复制/保存在跑时撤掉活会话登记,让 10 分钟未交付 TTL 生效。返回时如果缓存文件已被清理,会自动重试复制。 - L9 移除无用的持久化 URI 授权:不再对每次 SAF 选择的文件调用
takePersistableUriPermission,避免长期累积授权。
Fixed(复制与空间)
- H4 主线程查询 ContentProvider:
getType/query移到 IO 线程,界面先显示“正在读取文件”;mkdirs也移出主线程。慢/恶意 Provider 不会再直接 ANR。 - H10 32MB 固定预留拒绝小文件:预留量改为
min(32MB, 剩余空间/4),只剩 20MB 的设备也能复制 1KB 文件。 - L4 未知大小流无上限:未知大小(或报 0)的流最多复制
min(2GB, 可写空间 - 动态预留);超限时删除半成品并明确提示,不会无限填满磁盘。 - 复制路径健壮性:
statSize == 0不再走“空文件成功”快路径;statSize < 0复用已经打开的 PFD;读写0字节时让出而不是忙等;同目录copyFile改用copyTo并校验字节数,避免transferTo瞬时 0 导致改名失败。
Fixed(文件名 / Intent / 其它)
- L1 文件名劈开 emoji 代理对:先按 code point 再按 UTF-8 字节截断,ASCII 前缀 + emoji 不再产生
?;Toast 文件名也按 code point 省略。 - L2 控制字符 / 双向控制符:C0/C1 控制字符替换为
_,bidi 控制符(U+061C、U+200E/200F、U+202A–202E、U+2066–2069)直接移除,避免文件名伪装。 - L5
EXTRA_STREAM类型不安全:支持Uri、ArrayList<Uri>、Parcelable[],其他 Parcelable/坏数据按“没有文件”处理,不再ClassCastException/BadParcelableException崩溃;clipData解析也做了兜底。 - L3 分享面板包含自己:chooser 通过
EXTRA_EXCLUDE_COMPONENTS排除 HelloRename,底部警示语保留作为双保险。 - L8 空间不足文案识别:补充
not enough space、disk full、quota、insufficient storage、storage full。 - 时钟回拨导致清理永久节流:
CacheState.shouldCleanup检测到now < lastCleanupAt时重置基准并放行一次。
Tests
- JVM 单测增加到 71 条:新增
StreamCopierTest、OperationRegistryTest,并补充CacheCleanerTest(prune 竞态、listFiles为 null、原子标记、UUID、动态预留、未知流上限、时钟回拨、isNoSpace)与FileNameUtilsTest(代理对、控制符、bidi、Toast 省略)。 - API 34 模拟器新增
MainActivityFlowTest:分享复制、重建后恢复输入/旧文件名、ACTION_MAIN不清空、异常EXTRA_STREAM不崩溃、Parcelable[]形式仍可用;与ThemeUiTest一起作为 QA/Release 的模拟器门禁。
本包已签名,证书 SHA-256: 3682cce6d8fbf859b9b0a90ef8818acc747c82bb5f9d97418a4e6749cb6ccaca
校验安装包完整性: sha256sum -c SHA256SUMS.txt
v1.3.1
修复 v1.3.0 的重复标题,并把云端模拟器 UI 验证设为发布前的硬门槛。
Fixed
- 界面出现两个标题:v1.3.0 给 Activity 加了自绘 56dp Toolbar,但主题仍继承
Theme.Material3.DayNight(自带系统 ActionBar),于是系统标题栏和自绘顶栏各显示一次应用名。现在改为Theme.Material3.DayNight.NoActionBar,只保留与页面同色的 56dp 顶栏。
Added
- 云端模拟器 UI 门禁:新增
.github/workflows/emulator-ui.yml,由 QA 与 Release 复用。在 GitHub Actions 的 API 34 模拟器上运行 instrumentation 测试,断言:supportActionBar必须为 null,且全屏只有一个应用名标题;- 顶栏紧贴内容区顶部,高度恰好 56dp;
- 浅色模式白底黑字、深色模式黑底白字。
- 验证通过后额外截取浅色 / 深色两张截图和 UI 层级,作为 Actions artifact 留存。
- Release workflow 以模拟器验证为前置依赖:
releasejob 通过needs: emulator-verify等待模拟器门禁;门禁失败时不会构建、更不会发布。
本包已签名,证书 SHA-256: 3682cce6d8fbf859b9b0a90ef8818acc747c82bb5f9d97418a4e6749cb6ccaca
校验安装包完整性: sha256sum -c SHA256SUMS.txt
v1.3.0
⚠️ 此版本存在重复标题问题(系统 ActionBar + 自绘顶栏),请改用 v1.3.1。
界面与命名更新:英文应用名改为 HelloRename;应用内跟随系统深色 / 浅色模式自动切换,只保留黑白两套默认配色。
Added
- 56dp 顶部应用栏:标题显示当前语系的应用名,顶栏与页面同色,不额外加分割线或阴影(与 hellowidget 首页的默认设计一致)。
- 状态栏颜色与顶栏一致,状态栏图标(时间 / 电量)在浅色模式下用深色、深色模式下用浅色。
Changed
- 英文应用名从
hellorename改为首字母大写的 HelloRename(中文名仍为「改名助手」)。 - 深色 / 浅色模式自动切换:页面、顶栏、按钮、输入框、勾选框统一使用黑白配色 —— 浅色白底黑字,深色黑底白字;不提供用户自定义颜色的入口,只保留这两套默认配色。
- 移除 Material 3 默认的品牌蓝强调色:主要按钮、输入框描边、勾选框等一并改为黑 / 白,避免深浅色模式下残留彩色元素。
Fixed
- 深色模式切换后界面不跟着变色:
MainActivity原来在configChanges里声明了uiMode,系统切换深浅色时 Activity 不会重建,已加载的界面继续使用旧配色。现在移除该声明,由系统正常重建 Activity 并应用values/values-night中对应的颜色资源。
本包已签名,证书 SHA-256: 3682cce6d8fbf859b9b0a90ef8818acc747c82bb5f9d97418a4e6749cb6ccaca
校验安装包完整性: sha256sum -c SHA256SUMS.txt