ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

cw-omnibus 示例解读:在 Android 编辑器中阻止 ACTION_PROCESS_TEXT 文本处理入口的 ProcessTextBlocker 安全方案

cw-omnibus 示例解读:在 Android 编辑器中阻止 ACTION_PROCESS_TEXT 文本处理入口的 ProcessTextBlocker 安全方案
  • 示例工程
  • 教程

【免费下载链接】cw-omnibus

Source code to omnibus edition of _The Busy Coder's Guide to Android Development_

项目地址:https://gitcode.com/gh_mirrors/cw/cw-omnibus
点击查看免费下载

本文围绕 cw-omnibus 仓库中 Introspection/ProcessTextBlocker 示例展开,讲解如何在一个 Android 文本编辑器应用中屏蔽ACTION_PROCESS_TEXT(“处理文本”)入口,防止编辑中的内容被其他应用读取。文章以该示例的源码实现为证据,从协议背景、核心技巧、工程配置到对照实验逐层拆解,读完你可以掌握一套不侵入 Intent 体系、仅在控件层面完成防护的可复用方案。

一、为什么要“阻止” ACTION_PROCESS_TEXT

ACTION_PROCESS_TEXT是 Android 6.0(API 23)引入的文本处理协议。当用户在可编辑文本上长按并选中内容时,系统会弹出一个“Process text / 处理文本”菜单,列出所有声明了该 action 的第三方应用;用户点击其中某一项后,被选中的文本会作为Intent.EXTRA_PROCESS_TEXT附带数据发送给目标应用。

从安全角度看,这意味着一个普通的文本编辑器存在隐患:用户选中的内容——可能是邮件正文、配置文件、密码笔记等敏感数据——会被“顺理成章”地投递给翻译、搜索、分享之类的任意第三方应用,而用户未必意识到文本被完整传出去了。

本示例的 README.markdown 对此给出了明确动机:

This sample Android app demonstrates blockingACTION_PROCESS_TEXTfrom your Android app, for security reasons.

即:出于安全原因,主动屏蔽该入口。仓库中与其成对存在的 Introspection/ProcessText 示例则演示了“接收端”如何实现ACTION_PROCESS_TEXT处理,两者对照阅读可以完整理解这套协议的攻防两面。

二、示例工程结构一览

ProcessTextBlocker 是典型的单模块 Android Studio 工程,源码位于 app/src/main 下:

Introspection/ProcessTextBlocker/ ├── app/ │ ├── build.gradle │ └── src/main/ │ ├── AndroidManifest.xml │ ├── java/com/commonsware/android/fileseditor/ │ │ ├── AbstractPermissionActivity.java // 运行时权限模板 │ │ ├── EditorFragment.java // 编辑器 + 阻止 PROCESS_TEXT 的核心 │ │ ├── MainActivity.java // 入口 Activity │ │ └── SampleAdapter.java // 三个存储位置的 Tab 适配器 │ └── res/ │ ├── layout/ // main.xml、editor.xml │ └── values/ // strings.xml、styles.xml、colors.xml ├── build.gradle // 顶层构建脚本 └── settings.gradle

值得注意的第一点:这个工程的 Java 包名是com.commonsware.android.fileseditor,类结构与仓库中 Files/FilesEditor 示例几乎一致——它本质上是“文件编辑器”示例加上了阻止文本处理入口的能力,为验证防护效果提供了一个真实的文本编辑场景。

值得注意的第二点:AndroidManifest.xml 中只有MAIN/LAUNCHER的 intent-filter,没有任何与ACTION_PROCESS_TEXT相关的声明。这一点很关键,它说明“阻止”发生在 UI 控件层面,而不是 Intent 过滤层面,下文会展开解释。

三、核心实现:用 PasswordTransformationMethod 子类屏蔽“处理文本”菜单

3.1 关键代码位置

整个方案的落点在 EditorFragment.java 的onCreateView()中:

@Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View result=inflater.inflate(R.layout.editor, container, false); editor=result.findViewById(R.id.editor); editor.setTransformationMethod(new DummyTransformationMethod()); return(result); }

第 67 行的setTransformationMethod(new DummyTransformationMethod())就是全部“机关”。编辑器布局 editor.xml 本身只是一个普通的EditText(android:inputType="textMultiLine"),并没有设置密码输入类型。

3.2 DummyTransformationMethod 源码解读

同文件末尾定义了DummyTransformationMethod(第 178~210 行):

private static class DummyTransformationMethod extends PasswordTransformationMethod { @Override public CharSequence getTransformation(CharSequence source, View view) { return(source); } @Override public void onTextChanged(CharSequence s, int start, int before, int count) { // no-op } @Override public void onFocusChanged(View view, CharSequence sourceText, boolean focused, int direction, Rect previouslyFocusedRect) { // no-op } @Override public void afterTextChanged(Editable s) { // no-op } @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) { // no-op } }

它做了两件事:

  1. 继承PasswordTransformationMethod:让系统把该EditText当作“密码输入”处理。Android 框架的公开行为是:密码输入框不会向用户提供ACTION_PROCESS_TEXT菜单入口,从而切断了选中文本被投递给第三方应用的通道。
  2. 覆写getTransformation()原样返回 source:PasswordTransformationMethod的默认行为是把输入字符全部替换成圆点(密码掩码)。这里重写为直接返回原文,因此界面仍然以明文显示,用户看起来就是一个普通多行文本框,既保留了编辑体验,又拿到了“无 Process text 菜单”的副作用。

其余四个回调全部写成 no-op,是为了避免密码变换逻辑在文本变化、焦点变化时产生任何额外副作用。

需要说明的是:这一技巧依赖的是 Android 框架“密码输入不提供文本处理菜单”的既定行为,并非官方提供的显式开关 API;本示例正是利用该行为实现“阻止”的目的。若未来框架行为发生变化,方案也需要随之调整。

四、为什么“阻止”不在 Intent 层做

对比接收端示例 Introspection/ProcessText/app/src/main/AndroidManifest.xml 可见,要实现ACTION_PROCESS_TEXT的应用是这样声明的:

<intent-filter> <action android:name="android.intent.action.PROCESS_TEXT"/> <category android:name="android.intent.category.DEFAULT" /> <data android:mimeType="text/plain" /> </intent-filter>

而 ProcessTextBlocker 的 Manifest 完全没有这样的过滤器。原因在于:PROCESS_TEXT 菜单是系统按“当前编辑控件”动态生成的,即使你的应用自身不声明任何 intent-filter,只要界面上存在可编辑文本框,系统就会在长按菜单中列出其他应用注册的文本处理服务。因此,单纯不在自己应用里注册过滤器并不能阻止用户选中内容被发往第三方,必须在控件层面让系统认为该输入框“不可处理文本”——这正是PasswordTransformationMethod子类方案的价值所在。

五、工程配置与运行验证

5.1 Gradle 配置

app/build.gradle 中与本主题相关的关键配置:

apply plugin: 'com.android.application' dependencies { implementation 'com.android.support:support-fragment:27.1.1' } android { compileSdkVersion 27 buildToolsVersion '27.0.3' defaultConfig { minSdkVersion 15 targetSdkVersion 27 applicationId "com.commonsware.android.fileseditor.ptb" } }
  • minSdkVersion 15:ACTION_PROCESS_TEXT自 API 23 才有,但工程仍可运行在更老设备上(老版本没有该菜单,天然不受影响);
  • applicationId "com.commonsware.android.fileseditor.ptb":与 Manifest 中的package属性分离,便于与原始 FilesEditor 示例共存安装;
  • 依赖support-fragment提供FragmentActivity、FragmentPagerAdapter等兼容层组件。

仓库根目录 README.markdown 同时提醒:所有示例项目均可直接导入 Android Studio 构建,但导入前务必检查gradle-wrapper.properties;若用命令行构建,本仓库未附带gradlew,需要自行准备本地 Gradle 3.3 以上环境。

5.2 界面与编辑场景

MainActivity.java 继承自AbstractPermissionActivity,在onReady()中加载 main.xml(ViewPager+PagerTabStrip),并由 SampleAdapter.java 提供三个编辑 Tab(字符串资源见 strings.xml):

Tab文件位置
InternalgetFilesDir()/test.txt
ExternalgetExternalFilesDir(null)/test.txt
PublicEnvironment.getExternalStoragePublicDirectory(DIRECTORY_DOCUMENTS)/test.txt

由于涉及外部存储写入,工程在 AndroidManifest.xml 声明了WRITE_EXTERNAL_STORAGE,并通过 AbstractPermissionActivity.java 走标准的运行时权限流程:onCreate中检查权限齐全则直接onReady(),否则requestPermissions()申请,回调中再次校验,不通过则触发onPermissionDenied()提示并finish()。这个权限模板与主题无直接关系,仅作为示例在 Android 6.0+ 上顺利运行的支撑。

编辑器本身(EditorFragment.java)是完整的文件读写实现:LoadTextTask在后台线程用BufferedReader读取文件,SaveThread在onPause()时把文本写回并用fos.getFD().sync()落盘,setRetainInstance(true)保证旋转后保留编辑状态。

5.3 运行与验证步骤

  1. 用 Android Studio 导入Introspection/ProcessTextBlocker目录(或命令行gradle assembleDebug构建);
  2. 在设备/模拟器上安装运行(Android 6.0 及以上系统);
  3. 进入任一 Tab,输入或加载一段文本;
  4. 长按并选中文本,观察弹出的上下文菜单:不会出现“Process text / 处理文本”条目;
  5. 对照安装仓库中未加防护的文件编辑器示例,相同操作下菜单会列出第三方文本处理应用,即可直观确认防护生效。

六、对照:如何正确“实现” ACTION_PROCESS_TEXT

为了理解协议全貌,再看接收端示例 Introspection/ProcessText。其 MainActivity.java 展示了接收与回传的标准写法:

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { if (Intent.ACTION_PROCESS_TEXT.equals(getIntent().getAction())) { search = getIntent().getStringExtra(Intent.EXTRA_PROCESS_TEXT); if (search == null) { search = getIntent() .getStringExtra(Intent.EXTRA_PROCESS_TEXT_READONLY); } } }
  • 先判断Build.VERSION.SDK_INT >= M,因为该协议只在 Android 6.0+ 存在;
  • 用getStringExtra(Intent.EXTRA_PROCESS_TEXT)取到被选中的文本;若为 null,回退读取EXTRA_PROCESS_TEXT_READONLY(只读文本场景);
  • 处理完成后,通过setResult(Activity.RESULT_OK, new Intent().putExtra(Intent.EXTRA_PROCESS_TEXT, ...))把结果文本回传并finish(),即可让原输入框原地替换为处理结果。

把两个示例放在一起:ProcessText 展示了“如果不加防护,选中文本会以EXTRA_PROCESS_TEXT被交给谁、怎么被消费”;ProcessTextBlocker 则展示了“如何让这类菜单根本不出现”。两者的 Manifest 差异(是否有PROCESS_TEXTintent-filter)与控件层差异(是否设置PasswordTransformationMethod子类)正是理解整套机制的两条主线。

七、总结与参考文件

在 cw-omnibus 的 ProcessTextBlocker 示例中,阻止ACTION_PROCESS_TEXT的核心是一条轻量技巧:让编辑器EditText使用继承自PasswordTransformationMethod的“假密码”变换器,原样返回明文的同时,利用框架对密码输入框不提供文本处理菜单的行为,从控件层面彻底切断选中文本外发的通道。它不依赖 Intent 过滤、不修改 Manifest、不影响明文编辑体验,适用于文件编辑器、笔记类、凭证类等对内容外泄敏感的输入场景。

  • 示例说明文档:Introspection/ProcessTextBlocker/README.markdown
  • 核心实现:EditorFragment.java(setTransformationMethod与DummyTransformationMethod)
  • 工程配置:app/build.gradle、AndroidManifest.xml
  • 编辑场景支撑:SampleAdapter.java、AbstractPermissionActivity.java
  • 协议接收端对照:Introspection/ProcessText/app/src/main/java/com/commonsware/android/processtext/MainActivity.java
  • 示例工程
  • 教程

【免费下载链接】cw-omnibus

Source code to omnibus edition of _The Busy Coder's Guide to Android Development_

项目地址:https://gitcode.com/gh_mirrors/cw/cw-omnibus
点击查看免费下载
上一篇:如何3秒破解百度网盘提取码:免费智能工具完整使用指南
下一篇:百度网盘提取码终极破解指南:3秒快速获取资源密码的完整教程

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进