Android 15 蓝牙OPP文件接收功能修改说明文档

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 开发板),实现完整的蓝牙文件接收交互流程。

需求列表

  1. 拦截蓝牙文件接收,弹出确认对话框(替代通知栏确认)
  2. 确认接收后显示传输进度对话框,实时显示进度和已接收文件列表
  3. 接收完成后 Toast 提示并关闭对话框
  4. 支持缩小为全局悬浮窗(TYPE_APPLICATION_OVERLAY),显示接收计数 N/M
  5. 接收失败或中断时显示红色 ✕ 标记
  6. 支持中止接收功能

源码路径

复制代码
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

实现方式:

  • 点击对话框右上角缩小按钮时:
    1. 先检查 Settings.canDrawOverlays() 悬浮窗权限
    2. 无权限:Toast 提示 + 跳转 ACTION_MANAGE_OVERLAY_PERMISSION 设置页
    3. 有权限:启动 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_INDEXEXTRA_TOTAL_COUNT

5.5 悬浮窗权限授予

Android 13+ 的蓝牙应用不再自动获得 SYSTEM_ALERT_WINDOW 权限。需要通过以下方式之一授予:

  1. adb 命令(调试用):
bash 复制代码
adb shell appops set com.android.bluetooth SYSTEM_ALERT_WINDOW allow
  1. 系统应用预置:在系统 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_INDEXEXTRA_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

相关推荐
楼田莉子4 分钟前
仿Muduo的高并发服务器:LoopThread模块及其ThreadPool模块
linux·服务器·c++·后端·学习
荣月灵的小梅花11 分钟前
Android 给广播接收器增加权限(permission)或signature签名权限
android
菜鸟的日志39 分钟前
【嵌入系统】嵌入式学习笔记(一)
windows·笔记·嵌入式硬件·学习·ubuntu·操作系统
沐言人生1 小时前
ReactNative 源码分析4——ReactActivity之加载JSBundle
android·react native
暗夜猎手-大魔王1 小时前
OpenCode提示词工程学习
学习
Slow菜鸟1 小时前
Docker 学习篇(七)| 实战 — 用 Docker 构建 SpringBoot + Vue 全栈项目
spring boot·学习·docker
南境十里·墨染春水1 小时前
linux 学习进展 网络编程 ——TCP 协议 TIME_WAIT 状态详解
linux·网络·学习
薛定e的猫咪1 小时前
(AAMAS 2023)基于广义策略改进优先级的高效多目标学习 GPI - LS/PD
人工智能·学习·机器学习
@杰克成2 小时前
Java学习22
java·python·学习·idea
Hello_Embed2 小时前
串口硬件结构与三种编程方式
笔记·stm32·学习·ai编程