直接回答
受密码保护 PDF 的流程应是“打开并处理”,不是默认生成无密码副本。须逐个成员固定 Android 版本:PdfRenderer(ParcelFileDescriptor, LoadParams) 构造器标为 API 35;LoadParams 和 LoadParams.Builder.setPassword() 标为 API 35 且也适用于 S Extensions 13;PdfRendererPreV 类及其带密码构造器同样是 API 35 加 S Extensions 13,而该兼容类可处理 Android R–U 的文档。不能把一个成员的版本标签挪给另一个,也不能假设所有 API 30–34 设备都可用。null 表示没有提供密码;未加密文件或空 user password 文件也可能打开,不能据此证明系统接受了必需凭据。先检查准确扩展或兼容路径,再显示密码输入框。该构造器要求可 seek 的 ParcelFileDescriptor。 受保护 PDF 可使用 user password 打开,但后续操作受文档定义权限限制;owner password 则与更广文档权限有关。PDF 标准区分这些角色,但 PdfRenderer 接收一个密码字符串,并未提供接口告诉调用者接受的是哪种角色或有哪些权限位。未加密文件或空 user password 也可能成功打开,所以一般打开成功不证明用户提供的密码被接受;只有已知测试密码对应的必需密码夹具能验证该窄结论。无论如何都不证明用户授权应用导出、传播或修改文档。文档层权限与应用自己的用户同意要分开处理。 失败信号有意保持保守。Android 说明文件需要密码或其安全方案不受支持时可能抛出 SecurityException;错误密码也会触发该异常。因此不要解析异常文字,或把每个 SecurityException 都归类为“密码输错”。除非其他有文档依据的信号能区分原因,否则显示无法打开文件;允许有限重试或取消,并在适当时报告 unsupported/unknown。不要暴力尝试密码、静默回退网络 viewer,或把失败打开转换成空白成功文档。 密码应视为敏感输入。只有用户选中受保护文档且确认 renderer 路径可用后才收集。不要把密码放入日志、分析、崩溃上下文、saved state、WorkManager 参数或持久偏好设置。尝试结束后清空输入控件和引用,但不要声称清掉 UI 字段就将 Java/Kotlin String 从内存安全擦除。不要让 LoadParams/renderer 超过单个文档任务生命周期;用户取消或进程重建后不要自动恢复密码,用户重试时再询问。 明文处理范围要保持窄。成功打开后,renderer 可在当前文档任务内读取页面;这些操作应留在用户请求的本地处理与应用自有临时状态中。Android 也记录了 PdfRenderer.write(destination, removePasswordProtection),这是独立的导出能力。只读分析流程不得调用密码移除写回、持久化生成未保护副本,或暗示打开文件就授权分享。将来若支持无密码导出,应单独说明目的地和保护状态变化,并验证新文件字节。 资源在成功和失败路径上都要有明确责任。每次尝试使用新打开、由应用拥有的 descriptor,绝不把其他任务共享的借用句柄交给构造器。构造成功后 renderer 接管 descriptor,并在关闭时负责关闭;若构造抛出且没有可用 renderer,失败路径必须确定地关闭本次尝试拥有的 descriptor。打开中的 Page 应限制在 renderer 生命周期内:先关闭 Page,再关闭 renderer;成功时不要手动关闭 renderer 的 descriptor。构造可能耗时,应放在 UI 线程之外。Android 建议对不可信 PDF 使用权限最少的独立隔离进程。协程 timeout 或取消信号不代表 native 构造已停止:用当前请求 owner 隔离迟到结果,并等构造实际退出后再关闭输入或删除应用临时副本。来源保持不可变。 准备区分 renderer 与凭据的夹具:未加密文件、必需打开密码且有已知测试密码的文件、错误密码、具有限制权限的文件,以及当前 renderer 不支持的安全方案。按准确类/成员记录系统 API/扩展、renderer 类、打开状态、页面访问与源文件是否保持不变。验证打开失败不会调用 write/removePasswordProtection、不会联网,也不会删除原件。结果仅适用于确切文件、密码、renderer、API/扩展与只读任务,不建立通用 PDF 安全保证、法律授权、Cove 支持或凭据应被保留的结论。
用户为已选 PDF 输入打开密码
Android 应用通过用户选择的文档 URI 收到一份受密码保护的 PDF,目标只是本地查看页面。当前系统是否暴露可用的密码 PdfRenderer surface 尚未确认,而打开失败既可能是密码不对,也可能是安全方案不受支持。
决策
先检查 renderer/API 能力,仅为这次尝试临时询问凭据并使用只读路径。出现含义不明确的错误时,报告当前无法打开,不推测原因,也不自动生成无密码副本。
预期结果
应用可以区分受支持的打开、用户取消和未知/不支持的失败,同时保留原始 PDF,不声称用户授予了更广的处理权限。
需要收集的证据
只有将以下证据与同一模型文件和测试运行关联起来,结论才可复核:
- ANDROID-PDF-PASSWORD-OPEN-2026-10:Android PdfRenderer 的密码参数、API/扩展要求、descriptor 与失败信号。
- ANDROID-PRIVACY-01:应用仍负责数据访问、凭据存储、日志和用户控制边界。
- PRIVACY-01:端侧推理不证明应用没有其他网络或数据路径;PDF 处理仍需单独检查输入、临时数据与导出。
- Android API 及 PDF Reference 1.7 区分 renderer 打开能力、user/owner password 角色与文档权限;Android 接口本身不报告密码角色。
隐私声明需要数据清单和流量证据;“本地模型”标签本身不是证明。
实施流程
- 识别输入并检查 renderer 能力. 记录 source URI/provider、可用时的字节身份、Android API、S extension 和准确类/成员。PdfRenderer(ParcelFileDescriptor, LoadParams) 是 API 35;LoadParams.Builder.setPassword 与 PdfRendererPreV(含其密码构造器)是 API 35 加 S Extensions 13,不可混用版本标签。该 renderer 要求可 seek descriptor。
- 在实际使用时询问凭据. 仅在用户选择受保护文档且 API 路径可用后提示密码。保持临时状态;不要写入日志、分析、saved state、持久存储或后台任务参数。
- 保守分类失败. SecurityException 既可能来自错误密码,也可能来自不支持的安全方案。保留中性错误、允许有限重试或取消;不暴力尝试,也不静默转到网络服务。
- 保持本地只读处理. 只传本次新打开且由应用拥有的 seekable descriptor,不传共享借用句柄。构造失败时确定关闭该 descriptor;成功后先关闭 Page,再关闭 renderer,由 renderer 关闭 descriptor。构造放在 UI 线程之外;不可信 PDF 可按 Android 建议使用隔离且最少权限的进程。
- 验证凭据与数据生命周期. 分别测试未加密文档、必需密码且有已知测试密码的夹具,以及错误密码、受限权限和不支持安全方案。timeout 不证明 native 构造停止:隔离迟到结果,并等实际退出后再关闭输入或删除临时副本。确认秘密未持久化且源字节不变。
让每个阶段的状态都可观察。失败信息应指出阶段、模型、运行时和恢复动作,同时避免记录用户私密内容。
验收标准
- 按成员标注版本:PdfRenderer(ParcelFileDescriptor, LoadParams) 是 API 35;LoadParams.Builder.setPassword 与 PdfRendererPreV/密码构造器要求 API 35 加 S Extensions 13,不将任一成员的标签套给其他成员。
- SecurityException 不被一概标成密码错误,因为不支持的安全方案也可能走同一异常路径。
- 密码不主动持久化、记录日志或塞入后台状态;不声称可安全清零 String 内存。
- 对未加密或空密码文档,成功打开不证明已接受某个输入密码;只用必需密码夹具验证已知测试密码。打开也不证明 owner 权限或导出同意。
- 只把本次新建且由应用拥有的 descriptor 传给构造器;构造失败时关闭它,成功时先关闭 Page 再关闭 renderer 并由 renderer 关闭 descriptor。不可信输入需考虑隔离/最少权限与非 UI 线程构造;取消后的输入清理必须等构造实际退出。
决策对照表
| 维度 | 选择或证据 | 解读方式 |
|---|---|---|
| 未加密 PDF | 在版本支持的 renderer 上不传密码打开。 | 成功打开不证明所有页面功能或后续导出都受支持。 |
| User/open password | 为用户选中的文档临时询问密码。 | PDF 标准将后续操作限制在文档定义权限内;Android 不报告接受的是哪种密码角色。 |
| Owner/permission password | 不能仅因某个密码让 renderer 打开就推断应用拥有 owner 权限。 | 文档权限与应用导出/分享仍是不同边界;不能绕过源文档或用户限制。 |
| 不支持的安全方案 | 报告当前打开路径 unsupported/unknown,并保留原件。 | SecurityException 不能可靠区分错误密码与方案不支持;不应搜索密码或自动联网回退。 |
需要避免的失败模式
- 把 API 35/S Extension 13 的密码参数推广到所有 API 30–34 设备。
- 把 SecurityException 一概解释成用户输错密码,忽略 renderer 不支持加密方案的可能。
- 把成功打开、端侧推理或文件密码解释为用户同意导出、分享或移除保护。
- 将凭据写入日志、saved state、偏好设置或后台任务,或声称清空 String 已安全擦除内存。
还要避免静默回退、版本未固定、缺少取消处理,以及把不同配置的结果混在一起下结论。失败运行也应与成功记录一并保存。
最小复现记录
| 层级 | 记录内容 |
|---|---|
| 设备 | 厂商、型号、芯片、内存档位、操作系统版本 |
| 软件 | 应用版本和 Git 提交号 |
| 模型 | 系列、变体、版本、格式、文件长度、摘要、量化方式 |
| 运行时 | 名称、版本、请求后端及实际后端证据 |
| 工作负载 | 样例版本、输入摘要、提示词摘要、输出策略 |
| 结果 | 完成、失败、取消、回退及保护隐私的诊断信息 |
发布检查表
- ☐ 主要搜索问题已回答,且没有未经支持的数字。
- ☐ 所有模型文件和运行时版本均已固定。
- ☐ 代表性任务和失败处理策略均有说明。
- ☐ 生命周期、取消、清理和回退均已测试。
- ☐ 用户内容与网络边界均有说明。
- ☐ 结论只适用于被记录的配置。
- ☐ 新增测试结果时链接到原始方法或证据。
来源与相关证据
- Android privacy and security
- Android API — LoadParams.Builder.setPassword(API 35;S Extensions 13)
- Android API — PdfRenderer 构造器(API 21/35)、worker-thread 与不可信文件隔离指引
- Android API — PdfRendererPreV 与密码构造器(API 35;S Extensions 13)
- Android PDF viewer — API 35 与兼容 surface
- Adobe — PDF Reference 1.7 安全处理器与密码角色
中文部署与排错专题见。奇连 AI 端侧专题。
常见问题
Android 哪个 API 支持打开受密码保护的 PDF?
PdfRenderer(ParcelFileDescriptor, LoadParams) 构造器是 API 35。LoadParams.Builder.setPassword 和 PdfRendererPreV(包括其密码构造器)是 API 35 加 S Extensions 13。要检查准确成员和设备扩展,不要把一个成员的版本标签套给其他成员。
SecurityException 能证明用户输错密码吗?
不能。Android 对错误密码和不支持的安全方案都记录了 SecurityException。如果没有其他有文档依据的信号可区分,就报告 renderer 无法打开文件。
成功打开能证明已接受输入的密码吗?
不能一概而论:未加密 PDF 或空 user password 也可能打开。用必需密码且有已知测试密码的夹具验证这个窄结论;成功打开仍不报告密码角色,也不授权导出。
为了后续处理,应默认保存解密副本吗?
不应。标准流程应保持只读和临时。PdfRenderer.write 可以移除密码保护;任何无保护副本都需要独立用户动作、目的地确认和输出验证。