Android 15 蓝牙OPP文件接收功能修改说明文档
文章目录
- [Android 15 蓝牙OPP文件接收功能修改说明文档](#Android 15 蓝牙OPP文件接收功能修改说明文档)
-
- 一、修改概述
- 二、修改文件清单
-
- [2.1 新增文件](#2.1 新增文件)
- [2.2 修改文件](#2.2 修改文件)
- 三、功能实现说明
-
- [3.1 需求1:拦截文件接收,弹出确认对话框](#3.1 需求1:拦截文件接收,弹出确认对话框)
- [3.2 需求2:传输进度对话框](#3.2 需求2:传输进度对话框)
- [3.3 需求3:接收完成 Toast + 关闭对话框](#3.3 需求3:接收完成 Toast + 关闭对话框)
- [3.4 需求4:缩小为全局悬浮窗](#3.4 需求4:缩小为全局悬浮窗)
- [3.5 需求5:接收失败显示 ✕](#3.5 需求5:接收失败显示 ✕)
- [3.6 中止接收功能](#3.6 中止接收功能)
- 四、效果和数据流图
- 五、移植到其他方案的说明
-
- [5.1 文件复制清单](#5.1 文件复制清单)
- [5.2 AndroidManifest.xml 修改](#5.2 AndroidManifest.xml 修改)
- [5.3 字符串资源](#5.3 字符串资源)
- [5.4 原有文件修改点](#5.4 原有文件修改点)
- [5.5 悬浮窗权限授予](#5.5 悬浮窗权限授予)
- 六、问题排查与解决
-
- [6.1 调试日志](#6.1 调试日志)
- [6.2 常见问题](#6.2 常见问题)
- 七、版本记录
- [Android + Kiro AI软件开发实战教程](#Android + Kiro AI软件开发实战教程)
一、修改概述
本次修改针对 Android AOSP 蓝牙应用(com.android.bluetooth)的 OPP文件接收功能,
适配无状态栏/通知栏的设备(如 RK3588 Android 15 开发板),实现完整的蓝牙文件接收交互流程。
需求列表
- 拦截蓝牙文件接收,弹出确认对话框(替代通知栏确认)
- 确认接收后显示传输进度对话框,实时显示进度和已接收文件列表
- 接收完成后 Toast 提示并关闭对话框
- 支持缩小为全局悬浮窗(TYPE_APPLICATION_OVERLAY),显示接收计数 N/M
- 接收失败或中断时显示红色 ✕ 标记
- 支持中止接收功能
源码路径
release/packages/modules/Bluetooth/android/app/
二、修改文件清单
2.1 新增文件
| 文件路径 | 说明 |
|---|---|
src/.../opp/BluetoothOppTransferDialogActivity.java |
文件传输进度对话框 Activity,显示当前传输进度、已接收文件列表,支持缩小/中止 |
src/.../opp/BluetoothOppFloatService.java |
全局悬浮窗 Service,使用 TYPE_APPLICATION_OVERLAY 实现,显示蓝色图标+接收计数 |
res/layout/bt_file_transfer_dialog.xml |
传输对话框布局:标题栏(含缩小按钮)+ 文件信息 + 进度条(含百分比和中止按钮)+ 已接收文件列表 |
res/layout/bt_file_transfer_item.xml |
已接收文件列表项布局:文件名 + 时间 + 大小 + 状态标记 |
res/layout/bt_file_transfer_float.xml |
悬浮窗布局:透明背景 + 蓝色文件图标 + 计数文字 |
2.2 修改文件
| 文件路径 | 修改内容 |
|---|---|
AndroidManifest.xml |
添加 SYSTEM_ALERT_WINDOW 权限;注册 BluetoothOppTransferDialogActivity(singleTask)和 BluetoothOppFloatService |
src/.../opp/BluetoothOppNotification.java |
updateIncomingFileConfirmNotification() 中添加直接启动确认 Activity 的逻辑(绕过通知栏),添加 mConfirmActivityLaunched 防重复启动标志 |
src/.../opp/BluetoothOppIncomingFileConfirmActivity.java |
onIncomingFileConfirmOk() 中确认后启动 BluetoothOppTransferDialogActivity,传递设备名、文件名、文件大小、share URI |
src/.../opp/BluetoothOppObexServerSession.java |
receiveFile() 中发送 ACTION_PROGRESS_UPDATE 广播(含进度百分比、文件名、文件大小、当前文件索引);onPut() 中发送 ACTION_FILE_COMPLETE / ACTION_TRANSFER_FAIL 广播;onDisconnect() 中发送 ACTION_TRANSFER_DONE 广播 |
src/.../opp/BluetoothOppReceiver.java |
添加 lwzbt 调试日志 |
res/values/strings.xml |
新增 15+ 个英文字符串资源(传输对话框、悬浮窗、中止、权限提示等) |
res/values-zh-rCN/strings.xml |
新增对应简体中文字符串 |
res/values-zh-rTW/strings.xml |
新增对应繁体中文字符串 |
res/values-zh-rHK/strings.xml |
新增对应香港繁体中文字符串 |
res/values/styles.xml |
新增 FloatWindowTheme 样式(透明背景浮窗主题) |
三、功能实现说明
3.1 需求1:拦截文件接收,弹出确认对话框
修改文件 : BluetoothOppNotification.java
实现方式:
- 在
updateIncomingFileConfirmNotification()方法中,检测到待确认的传输记录时,
直接调用startActivity()启动BluetoothOppIncomingFileConfirmActivity - 添加
mConfirmActivityLaunched标志防止重复启动 - 当没有待确认记录时重置标志
原理 : 原生流程通过通知栏 Notification 让用户点击确认,无状态栏设备无法操作。
修改后绕过通知栏,直接弹出确认对话框(AlertActivity)。
3.2 需求2:传输进度对话框
新增文件 : BluetoothOppTransferDialogActivity.java
实现方式:
- 用户在确认对话框点击"接受"后,
BluetoothOppIncomingFileConfirmActivity.onIncomingFileConfirmOk()
启动BluetoothOppTransferDialogActivity,传递设备名、文件名、文件大小、share URI - 对话框通过 BroadcastReceiver 监听 4 种广播:
ACTION_PROGRESS_UPDATE: 更新进度条和百分比ACTION_FILE_COMPLETE: 添加已接收文件到列表ACTION_TRANSFER_DONE: Toast 提示完成并关闭ACTION_TRANSFER_FAIL: 在文件列表显示红色 ✕
广播发送方 : BluetoothOppObexServerSession
receiveFile()循环中每次进度变化发送ACTION_PROGRESS_UPDATE- 文件传输成功后发送
ACTION_FILE_COMPLETE - 文件传输失败后发送
ACTION_TRANSFER_FAIL onDisconnect()中发送ACTION_TRANSFER_DONE
3.3 需求3:接收完成 Toast + 关闭对话框
实现方式:
BluetoothOppObexServerSession.onDisconnect()发送ACTION_TRANSFER_DONE广播BluetoothOppTransferDialogActivity收到后显示 Toast 并调用dismissAndFinish()
3.4 需求4:缩小为全局悬浮窗
新增文件 : BluetoothOppFloatService.java
实现方式:
- 点击对话框右上角缩小按钮时:
- 先检查
Settings.canDrawOverlays()悬浮窗权限 - 无权限:Toast 提示 + 跳转
ACTION_MANAGE_OVERLAY_PERMISSION设置页 - 有权限:启动
BluetoothOppFloatService+finish()关闭 Activity
- 先检查
- FloatService 使用
WindowManager+TYPE_APPLICATION_OVERLAY创建全局悬浮窗 - 悬浮窗默认显示在左下角,可拖动
- 悬浮窗直接监听 ObexServerSession 广播实时更新 N/M 计数
- 点击悬浮窗:启动新的 TransferDialogActivity +
stopSelf()
权限说明 : Android 13+ 的 android.uid.bluetooth 不再自动获得 SYSTEM_ALERT_WINDOW 权限,
需要通过系统应用(如自定义 Settings 模块)为 com.android.bluetooth 授予悬浮窗权限。
3.5 需求5:接收失败显示 ✕
实现方式:
BluetoothOppObexServerSession.onPut()中传输失败/被拒绝时发送ACTION_TRANSFER_FAIL广播- 对话框收到后在已接收文件列表中添加红色 "✕" 标记
- Toast 显示"文件接收异常"
3.6 中止接收功能
实现方式:
- 进度条右侧添加红色"中止接收"按钮
- 点击后调用
contentResolverDelete(mShareUri)删除 share 记录(与原生取消机制一致) - Toast 提示"文件接收已中止",3 秒后关闭对话框
四、效果和数据流图
1、效果:
确认蓝牙文件接收:

第一个蓝牙文件:

第二个文件接收过程:

如果是很多文件,可以点击右上角把接收任务缩小,只显示缩略图在左下角。
缩略图:

缩略图可以上下左右移动,点击缩略图可以重新显示蓝牙文件接收过程。
蓝牙文件接收完成会自动关闭界面。
2、流程图,如下所示:

五、移植到其他方案的说明
5.1 文件复制清单
将以下文件复制到目标方案的对应路径:
# 新增 Java 文件
src/com/android/bluetooth/opp/BluetoothOppTransferDialogActivity.java
src/com/android/bluetooth/opp/BluetoothOppFloatService.java
# 新增布局文件
res/layout/bt_file_transfer_dialog.xml
res/layout/bt_file_transfer_item.xml
res/layout/bt_file_transfer_float.xml
5.2 AndroidManifest.xml 修改
在 <manifest> 中添加权限:
xml
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
在 <application> 中注册组件:
xml
<activity android:name="com.android.bluetooth.opp.BluetoothOppTransferDialogActivity"
android:process="@string/process"
android:excludeFromRecents="true"
android:theme="@android:style/Theme.Translucent.NoTitleBar"
android:launchMode="singleTask"
android:enabled="true">
</activity>
<service android:name="com.android.bluetooth.opp.BluetoothOppFloatService"
android:process="@string/process"
android:enabled="true"
android:exported="false">
</service>
5.3 字符串资源
在 res/values/strings.xml 中添加以 bt_file_transfer_ 开头的字符串资源。
如需中文支持,同步添加到 res/values-zh-rCN/strings.xml 等。
5.4 原有文件修改点
需要修改的原有文件及关键修改点:
BluetoothOppNotification.java:
- 搜索
updateIncomingFileConfirmNotification()方法 - 在遍历待确认记录的循环中,添加直接
startActivity(BluetoothOppIncomingFileConfirmActivity)的逻辑 - 添加
mConfirmActivityLaunched标志防重复
BluetoothOppIncomingFileConfirmActivity.java:
- 搜索
onIncomingFileConfirmOk()方法 - 在确认数据库更新后,添加启动
BluetoothOppTransferDialogActivity的代码 - 通过
intent.setData(mUri)传递 share URI
BluetoothOppObexServerSession.java:
- 搜索
receiveFile()方法,在进度更新处添加ACTION_PROGRESS_UPDATE广播 - 搜索
onPut()方法,在文件完成/失败处添加ACTION_FILE_COMPLETE/ACTION_TRANSFER_FAIL广播 - 搜索
onDisconnect()方法,添加ACTION_TRANSFER_DONE广播 - 所有广播需要
setPackage(mContext.getPackageName())并携带EXTRA_CURRENT_INDEX和EXTRA_TOTAL_COUNT
5.5 悬浮窗权限授予
Android 13+ 的蓝牙应用不再自动获得 SYSTEM_ALERT_WINDOW 权限。需要通过以下方式之一授予:
- adb 命令(调试用):
bash
adb shell appops set com.android.bluetooth SYSTEM_ALERT_WINDOW allow
- 系统应用预置:在系统 Settings 或自定义系统应用/服务中,通过 AppOpsManager 为蓝牙应用授权:
java
private void initOppPermission(Context context) {
try {
setPackageAppOpsPermission(context, "com.android.bluetooth", AppOpsManager.OPSTR_SYSTEM_ALERT_WINDOW);
} catch (Exception e) {
LogUtil.error("error = " + e.getMessage());
}
}
//设置特殊权限通过
private void setPackageAppOpsPermission(Context context, String packageName, String opsString) {
boolean isNeedSetPermission = SystemProperties.getBoolean("persist.sys.debug.need_ops_permission", true);
LogUtil.debug("isNeedSetPermission = " + isNeedSetPermission);
if (!isNeedSetPermission) {
return;
}
AppOpsManager mAppOps = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
PackageManager manager = context.getPackageManager();
int uid = 1;
try {
ApplicationInfo packageInfo = manager.getApplicationInfo(packageName, 0);
uid = packageInfo.uid;
} catch (Exception e) {
e.printStackTrace();
LogUtil.error("error = " + e.getMessage());
return;
}
LogUtil.debug("uid = " + uid);
mAppOps.setUidMode(opsString, uid, AppOpsManager.MODE_ALLOWED);
}
六、问题排查与解决
6.1 调试日志
所有自定义日志包含关键字 lwzbt,使用以下命令过滤:
bash
adb logcat | grep lwzbt
关键日志标签:
| 标签 | 组件 |
|---|---|
lwzbt [Notification] |
通知拦截,直接启动确认 Activity |
lwzbt [ConfirmActivity] |
确认对话框生命周期,用户接受/拒绝 |
lwzbt [ObexServer] |
OBEX 文件接收过程,广播发送 |
lwzbt [TransferDialog] |
传输对话框,进度更新,文件完成 |
lwzbt [FloatService] |
悬浮窗服务,计数更新 |
lwzbt [Receiver] |
广播接收器 |
6.2 常见问题
问题1:确认对话框不弹出
- 检查日志
lwzbt [Notification]是否有 "directly launching confirm activity" - 确认
BluetoothOppNotification.java的修改已编译进固件 - 清理编译:
make clean后重新编译
问题2:确认后传输对话框不显示
- 检查日志
lwzbt [ConfirmActivity]是否有 "user ACCEPTED" - 检查日志
lwzbt [TransferDialog]是否有 "onCreate" - 确认 AndroidManifest.xml 中 TransferDialogActivity 的
android:enabled="true"
问题3:进度不更新
- 检查日志
lwzbt [ObexServer]是否有 "sent ACTION_PROGRESS_UPDATE" - 检查日志
lwzbt [TransferDialog]是否有 "onReceive" - 确认广播接收器使用
RECEIVER_EXPORTED
问题4:悬浮窗崩溃(BadTokenException / permission denied for window type 2003)
- 蓝牙应用缺少 SYSTEM_ALERT_WINDOW 权限
- 执行:
adb shell appops set com.android.bluetooth SYSTEM_ALERT_WINDOW allow - 或通过系统应用授权(见 5.5 节)
问题5:悬浮窗计数不更新(始终显示 0/N)
- 检查日志
lwzbt [FloatService]是否有 "file complete" - 确认 FloatService 的广播接收器使用
RECEIVER_EXPORTED - 确认 ObexServerSession 广播中包含
EXTRA_CURRENT_INDEX和EXTRA_TOTAL_COUNT
问题6:修改后编译报错
JavaUtilDate错误:使用LocalTime.now(ZoneId.systemDefault())替代new Date()JavaTimeDefaultTimeZone警告:LocalTime.now()改为LocalTime.now(ZoneId.systemDefault())UnusedMethod错误:删除未使用的方法- AOSP 使用 errorprone 严格检查,需遵守其规则
问题7:编译后固件中没有新代码
- 确认编译路径正确:
release/packages/modules/Bluetooth/android/app/ - 尝试清理后重新编译:先删除 out 目录下对应的中间产物
- 确认
.java文件在src/com/android/bluetooth/opp/目录下(AOSP 自动编译该目录所有 java 文件)
七、版本记录
| 日期 | 修改内容 |
|---|---|
| 2025-04-13 | 初始实现:确认对话框拦截、传输进度对话框、悬浮窗、失败标记 |
| 2025-04-14 | 修复 TYPE_SYSTEM_ALERT 崩溃,改用 TYPE_APPLICATION_OVERLAY |
| 2025-04-14 | 修复悬浮窗计数不更新,改为直接监听 ObexServerSession 广播 |
| 2025-04-14 | 修复恢复后已接收文件列表丢失,添加 onSaveInstanceState 持久化 |
| 2025-04-14 | 修复对话框恢复时左上角闪烁,改用 moveTaskToBack |
| 2025-04-14 | 添加中止接收功能,进度条右侧红色"中止接收"按钮 |
| 2025-04-14 | 悬浮窗改为全局 Service 方案(TYPE_APPLICATION_OVERLAY),添加权限检查和跳转设置 |
| 2025-04-14 | 缩小时 finish() Activity 释放资源,FloatService 独立运行 |
该需求的代码是我用Kiro工具实现的,虽然调试了很多次,但是总体还是比较满意的;
具体实现的代码和Google写的一样精简。
比我自己写得好,之前没用Kiro前,在不同Android版本的移动代码,差异大,也是会有报错,也是需要多次调试修改。
Android + Kiro AI软件开发实战教程
Kiro 是 AWS 推出的 AI 辅助开发 IDE,基于 VS Code 内核,集成了 AI Agent 能力。
它能理解代码上下文、执行文件操作、运行命令、搜索代码,并通过对话式交互帮助开发者完成复杂的编码任务。
最近我用Kiro解决了不少Android Studio 应用的问题和系统源码应用的问题;
使用Kiro写Android代码确实可以减少 80%的精力,并且生成和修改的代码简洁,功能完善,还有总结文档:
https://blog.csdn.net/wenzhi20102321/article/details/160190206