Compare commits

..

544 Commits

Author SHA1 Message Date
9d90b35fc2 Merge remote-tracking branch 'origin/powerbell' into winboll 2026-05-13 15:38:40 +08:00
f6e2504c50 Merge remote-tracking branch 'origin/positions' into winboll 2026-05-13 15:38:35 +08:00
6400e6ba6c 使用最新WinBoLL基础项目源码分支 2026-05-13 15:37:55 +08:00
76a9233b10 git checkout origin/originmaster .gitignore and copy local to update the commit point. 2026-05-13 15:33:02 +08:00
4dac53254e Merge remote-tracking branch 'origin/mymessagemanager' into winboll 2026-05-13 15:22:46 +08:00
585e67f1ad 恢复失误删除的appbase模块文件 2026-05-13 15:21:35 +08:00
c6e9d3ff7e 恢复winboll项目文件为当前origin/winboll分支最新提交版 2026-05-13 15:00:58 +08:00
b017f84449 恢复appbase项目文件为当前origin/appbase分支最新提交版 2026-05-13 14:56:15 +08:00
23997857e4 Merge remote-tracking branch 'origin/gpsrelaysentinelv15.11-zhangsken-mi15' into winboll 2026-05-13 14:17:20 +08:00
2511753af0 添加GPSRelaySentinel应用 2026-05-13 14:16:40 +08:00
b70663fd81 添加相册应用分支 2026-05-13 14:14:02 +08:00
a5fdffba29 添加Git临时调试模板分支 2026-05-13 14:09:45 +08:00
fb336412fb Merge remote-tracking branch 'origin/contacts' into winboll 2026-05-13 14:04:44 +08:00
937d35d35a Merge remote-tracking branch 'origin/autonfc' into winboll 2026-05-13 14:04:37 +08:00
4c2002a7ce WinBoLL 私有分支开发规范初始化
新增内容:
- LICENSE-Private-Demo 主规范文档
- LICENSE-Private-Demo_docs/ 完整文档套件
  - Word 格式规范说明书
  - PDF 格式规范说明书
  - TXT 格式规范说明书
  - 说明长图

编译更新:
- winboll/build.properties: buildCount 1→2
- libwinboll/build.properties: buildCount 1→2
2026-05-13 14:02:11 +08:00
bbb0e22198 feat: MyTermuxActivity 重构为 TermuxButton 列表管理界面
- models/TermuxButtonManager: 新增数据管理层,支持 CRUD 及 JSON 文件持久化
- MyTermuxActivity: 替换硬编码按钮为 ListView,支持点击执行、长按编辑/删除
- layout/activity_my_termux: 布局重构,替换 ScrollView 为 ListView + 添加按钮
- strings.xml: 新增 17 条中文字符串资源
2026-05-13 11:28:09 +08:00
ee75f95c79 <winboll>APK 15.20.1 release Publish. 2026-05-13 10:28:36 +08:00
c40e301c90 fix: 移除重复依赖和权限声明,统一 Java 7 编译配置
- winboll/build.gradle: 移除重复的 okhttp:3.14.9 和 gson:2.8.5 依赖
- libwinboll/build.gradle: 新增 compileOptions 强制 Java 7 编译
- AndroidManifest.xml: 移除重复的 GET_PACKAGE_SIZE 权限声明
2026-05-13 10:21:10 +08:00
5f2170a7a1 feat(winboll): 重构应用主题系统,继承libaes主题元素
主要修改:
- 新增 MyAppTheme 系列主题样式,继承自 AESTheme
  - MyAppTheme (默认)
  - MyDepthAppTheme, MySkyAppTheme, MyGoldenAppTheme
  - MyBearingAppTheme, MyMemorAppTheme, MyTaoAppTheme
- 新增 MyAESAToolbar/MyAESASupportToolbar 样式继承
- 新增 WinBoLLThemeBean/WinBoLLThemeUtil 主题工具类
- MainActivity 使用自定义主题 ID 替换 AES 原始样式
- BaseWinBoLLActivity 主题设置改用 WinBoLLThemeUtil
- 修复 MainActivity Fragment 初始化逻辑
- 集成 AESThemeUtil 主题切换功能

文件变更:
- winboll/src/main/res/values/styles.xml
- winboll/src/main/java/.../winboll/theme/*.java (新增)
- winboll/src/main/java/.../MainActivity.java
- winboll/src/main/java/.../BaseWinBoLLActivity.java

注意:应用正在测试中
2026-05-12 16:08:48 +08:00
6308df8f36 Merge remote-tracking branch 'origin/aes' into winboll 2026-05-12 15:38:27 +08:00
5f24c2d351 更新类库版本 2026-05-12 15:35:42 +08:00
882016454f <libaes>Library Release 15.20.3 2026-05-12 13:11:30 +08:00
c6c290dcd2 <aes>APK 15.20.3 release Publish. 2026-05-12 13:11:09 +08:00
e64f3a3e97 恢复 DrawerFragmentActivity 与 ASupportToolbar 显示正常的版本 2026-05-12 13:08:12 +08:00
998cb3d193 <winboll>APK 15.20.0 release Publish. 2026-05-12 12:16:45 +08:00
9ba3374b16 基线版本号对齐 2026-05-12 12:11:17 +08:00
4ded8537e8 升级 WinBoLL 库依赖版本并完善应用主题配置
- 升级 WinBoLL 库依赖版本至 15.20.x 系列:
  - libaes: 15.15.10 → 15.20.2
  - libappbase: 15.15.23 → 15.20.9
- 完善 winboll 模块主题配置:
  - MyAppTheme 改用 Theme.AppCompat.Light.NoActionBar 解决 AppCompat 兼容问题
  - 添加完整的应用主题属性定义
  - 添加 MyDebugActivityTheme 调试主题
- 添加主窗口、工具栏、调试文字颜色定义
2026-05-12 12:05:28 +08:00
59e51da991 Merge remote-tracking branch 'origin/aes' into winboll 2026-05-12 11:42:13 +08:00
070e2fb4f0 Merge remote-tracking branch 'origin/appbase' into winboll 2026-05-12 11:42:07 +08:00
632ecc51d8 重构ASupportToolbar自定义控件,支持AES主题切换
- 优化ASupportToolbar控件的绘图流程
- 移除atoolbar_frame.xml依赖,改用Java代码内部绘图
- 添加notifyColorChange()方法绘制三层渐变背景
- 在onAttachedToWindow()时自动调用refreshFromTheme()刷新背景
- 主题切换时通过DrawerFragmentActivity调用refreshFromTheme()
- 添加attrs.xml属性:android:background用于自定义style
- 添加详细调试日志追踪整个绘制流程
- TestASupportToolbarActivity添加日志便于测试验证

修改文件:
- libaes/src/main/java/.../views/ASupportToolbar.java:重构绘图逻辑,添加调试日志
- libaes/src/main/java/.../activitys/DrawerFragmentActivity.java:主题切换时刷新Toolbar
- libaes/src/main/java/.../unittests/TestASupportToolbarActivity.java:添加调试日志
- libaes/src/main/res/values/attrs.xml:添加android:background属性
- aes/build.properties:版本号更新
- libaes/build.properties:版本号更新
2026-05-12 10:53:18 +08:00
58b2cace16 重构AES主题配置,继承APPBaseTheme属性支持
- AESTheme改为继承Theme.AppCompat并支持APPBaseTheme属性
- 修复运行时inflate错误:将?attr/xxx引用改为@color/xxx直接引用
- 添加DebugActivityTheme调试样式支持
- 添加必需的颜色定义(toolbarBackgroundColor、mainWindowBackgroundColor等)
- 新增夜间模式主题配置(values-night目录)
- 清理冗余注释和无用的调试配色方案
- aes模块的MyAESTheme添加themeDebug属性

修改文件:
- aes/src/main/res/values/styles.xml:添加MyDebugActivityTheme
- aes/src/main/res/values-night/:新增夜间模式样式
- libaes/src/main/res/values/colors.xml:添加主题必需颜色
- libaes/src/main/res/values/styles.xml:重构使用AppCompat主题
- libaes/src/main/res/values-night/:新增夜间模式颜色和样式
2026-05-12 09:52:57 +08:00
3648fa3361 更新基础类库版本 2026-05-12 09:26:33 +08:00
122122ef80 Merge remote-tracking branch 'origin/appbase' into aes 2026-05-12 09:22:37 +08:00
4caeb83e5e <libappbase>Library Release 15.20.9 2026-05-12 09:17:18 +08:00
f36ee0d9b1 <appbase>APK 15.20.9 release Publish. 2026-05-12 09:16:45 +08:00
fe0b2f97ea 重构日志窗口UI主题颜色系统,统一使用toolbarTextColor和toolbarBackgroundColor
【主要变更】

1. 新增主题颜色属性
   - 新增 toolbarTextColor 属性定义(普通模式:黑色, 深色模式:#E0E0E0)
   - 新增 toolbarBackgroundColor 属性定义(普通模式:绿色, 深色模式:深蓝色)

2. 移除废弃颜色属性
   - 移除 buttonTextColor 属性定义
   - 移除 buttonBackgroundColor 属性定义

3. 更新布局文件(普通模式+深色模式)
   - view_log.xml: TextView(LV:)、CheckBox(Selectable/ALL) 使用 toolbarTextColor
   - view_log.xml: Button(Clean/Copy) 使用 toolbarBackgroundColor
   - item_logtag.xml: CheckBox 使用 toolbarTextColor
   - activity_main.xml: 所有Button使用 toolbarBackgroundColor
   - activity_crash_test.xml: 所有Button使用 toolbarBackgroundColor
   - activity_nfc_rsa_operate.xml: Button使用 toolbarBackgroundColor
   - dialog_winboll_host.xml: Button使用 toolbarBackgroundColor

4. 更新Java代码
   - LogView.java: CheckBox和TextView使用主题属性获取 toolbarTextColor
   - LogTagSpinner.java: 下拉框文本颜色使用 toolbarTextColor

5. 更新样式定义
   - styles.xml: 主题属性引用新的 toolbarTextColor/toolbarBackgroundColor
   - attrs.xml: 移除废弃属性,保留 toolbarTextColor/toolbarBackgroundColor
   - colors.xml: 添加新的颜色定义

【适配说明】
- 统一普通模式和深色模式的颜色管理
- 确保日志窗口所有控件颜色与主窗口保持一致
2026-05-12 09:13:16 +08:00
43da2bc03a 统一日志窗口Clean/Copy按钮样式与主窗口保持一致 2026-05-12 00:33:14 +08:00
d26ef7bf7f Merge remote-tracking branch 'origin/appbase' into aes 2026-05-11 21:18:47 +08:00
6baca2779d 同步build.gradle至origin/winboll分支最新配置 2026-05-11 21:13:50 +08:00
83da84371c Merge remote-tracking branch 'origin/aes' into winboll 2026-05-11 20:59:23 +08:00
f0fbdaf121 <libaes>Library Release 15.20.2 2026-05-11 20:41:44 +08:00
a20f29728e <aes>APK 15.20.2 release Publish. 2026-05-11 20:41:30 +08:00
9669fe2a8d 更新应用基础类库,改进应用调试接口。 2026-05-11 20:38:17 +08:00
ff8cb2fb42 <libappbase>Library Release 15.20.8 2026-05-11 20:30:07 +08:00
e42219ac5a <appbase>APK 15.20.8 release Publish. 2026-05-11 20:29:55 +08:00
42112eb677 LibAppBase: 添加崩溃通知分享日志功能
- 在崩溃通知中添加"分享日志"按钮,点击可分享崩溃日志
- 新增 ShareLogActivity 窗口类处理分享逻辑
- 崩溃日志先保存到缓存文件,再读取分享
- 移除广播接收器方案,简化实现
- 更新 AndroidManifest 注册新 Activity

修改文件:
- libappbase/src/main/AndroidManifest.xml
- libappbase/src/main/java/.../utils/CrashHandleNotifyUtils.java
- libappbase/src/main/java/.../utils/ShareLogActivity.java (新增)

影响范围: 崩溃通知功能
2026-05-11 20:27:40 +08:00
f3114a8121 <libappbase>Library Release 15.20.7 2026-05-11 19:10:54 +08:00
b5b29b8a77 <appbase>APK 15.20.7 release Publish. 2026-05-11 19:10:34 +08:00
01b0a7736d feat(CrashHandleNotifyUtils): 增强崩溃通知摘要提取与展示逻辑
- 新增正则表达式解析崩溃日志,提取异常类型、消息、原因及堆栈信息
- 实现 extractBriefInfo() 方法,生成结构化的崩溃摘要
- 优化通知内容展示:异常类型 + 错误信息 + 关键堆栈(最多3行)
- 添加摘要最大长度控制(200字符),防止内容过长
- 新增展开通知布局文件支持
2026-05-11 19:02:51 +08:00
ed5ab24bd3 <libaes>Library Release 15.20.1 2026-05-11 17:17:23 +08:00
b5c989b5f9 <aes>APK 15.20.1 release Publish. 2026-05-11 17:17:05 +08:00
725b336501 更新基础类库版本,提高应用调试能力。 2026-05-11 17:14:00 +08:00
d20923eaee <libappbase>Library Release 15.20.6 2026-05-11 16:56:33 +08:00
9db3b3b703 <appbase>APK 15.20.6 release Publish. 2026-05-11 16:56:19 +08:00
744fb23291 完成应用崩溃事务处理逻辑。 2026-05-11 16:53:51 +08:00
bd01220892 20260511_164013_233 2026-05-11 16:40:16 +08:00
4b2b5acc99 20260511_152720_122 2026-05-11 15:27:25 +08:00
57e4f8770b <libaes>Library Release 15.20.0 2026-05-11 15:02:44 +08:00
3b313e2362 <aes>APK 15.20.0 release Publish. 2026-05-11 15:02:27 +08:00
1e96cd02bc 对齐基础类库基准型号版本。 2026-05-11 14:58:21 +08:00
1274bc7c05 <libappbase>Library Release 15.20.5 2026-05-11 14:46:01 +08:00
f67c57108a <appbase>APK 15.20.5 release Publish. 2026-05-11 14:45:48 +08:00
6d694992b7 <appbase>APK 15.20.4 release Publish. 2026-05-11 14:45:14 +08:00
6f5aa807c0 refactor: 重构崩溃处理机制与日志工具
- 优化 CrashHandler 崩溃处理逻辑,统一通知发送逻辑
- 新增 GlobalAppCrashSafetyWire 独立崩溃安全防护类
- 增强 LogUtils 日志工具,补齐异常日志重载方法
- 重构 GlobalCrashActivity 页面,提升稳定性与可读性
- 修复 activity_globalcrash.xml 布局多余符号
- 新增 CrashTestActivity 崩溃测试页面
- 更新 buildCount 编译计数
2026-05-11 14:43:18 +08:00
f897f6e9ab <libappbase>Library Release 15.20.3 2026-05-11 10:33:43 +08:00
e2c73fdec0 <appbase>APK 15.20.3 release Publish. 2026-05-11 10:32:47 +08:00
4fcc5f9689 提高应用崩溃时的基础处理能力 2026-05-11 10:31:20 +08:00
4208cda32f <libappbase>Library Release 15.20.2 2026-05-11 10:04:19 +08:00
7c83b903f3 <appbase>APK 15.20.2 release Publish. 2026-05-11 10:04:02 +08:00
54b040285c refactor: 主题颜色值统一引用 colors.xml 定义的命名颜色
1. 新增 colors.xml 颜色定义
   - 普通模式: mainWindowBackgroundColor, mainWindowTextColor, buttonBackgroundColor, debugTextColor
   - 深色模式: 同上,颜色值适配深色主题

2. 重构 styles.xml 颜色引用
   - APPBaseTheme 和 DebugActivityTheme 中的颜色值改为 @color/xxx 引用
   - 统一使用命名颜色属性

3. 重构 MyDebugActivityTheme
   - 继承父主题的颜色属性定义
2026-05-11 10:01:48 +08:00
e8667bb26f <libappbase>Library Release 15.20.1 2026-05-11 09:40:47 +08:00
05259e5ca9 <appbase>APK 15.20.1 release Publish. 2026-05-11 09:40:26 +08:00
a5a5b37121 refactor: 重构调试主题,统一定义应用调试文字颜色
1. 重命名调试主题属性
   - themeGlobalCrashActivity → themeDebug
   - GlobalCrashActivityTheme → DebugActivityTheme

2. 新增 debugTextColor 属性
   - 定义 debugTextColor 属性,用于统一应用调试文字颜色
   - 普通模式: 灰色 #808080
   - 深色模式: 绿色 #FF00FF00

3. 重构视图控件
   - view_globalcrashreport.xml 和 view_log.xml 使用 themeDebug 主题
   - 控件颜色统一引用主题属性
   - 日志显示文本使用 debugTextColor 属性

4. GlobalCrashReportView Java 代码
   - 新增 obtainDebugTextColor() 方法获取主题中的 debugTextColor
   - 崩溃视图文字颜色通过主题属性获取,与日志视图一致
2026-05-11 09:37:25 +08:00
29726828b0 refactor: 重构主题颜色系统,统一使用命名的颜色属性
1. 新增主窗口颜色属性定义
   - mainWindowBackgroundColor - 普通模式主窗口背景色
   - mainWindowTextColor - 普通模式主窗口文字色
   - mainWindowDarkBackgroundColor - 深色模式主窗口背景色
   - mainWindowDarkTextColor - 深色模式主窗口文字色

2. 重构 view_globalcrashreport.xml 布局
   - 添加 themeGlobalCrashActivity 主题
   - 控件颜色属性改为引用主题属性

3. 统一应用内颜色配置
   - APPBaseTheme 所有颜色属性统一引用命名颜色值
   - GlobalCrashActivityTheme 文字颜色引用主窗口文字色

4. 修复崩溃循环问题
   - 避免属性自引用导致的循环解析
2026-05-11 08:59:34 +08:00
436e92702f 把应用统一资源配置属性放到类库,方便其他应用统一调用。 2026-05-11 01:29:32 +08:00
3669a96768 chore: 移除文档质量不佳的docs目录 2026-05-10 15:44:36 +08:00
37c3d1563c refactor(build): 精简项目模块配置,统一Java编译版本
- 简化 settings.gradle,仅保留 appbase 和 libappbase 模块
- 更新根目录 build.gradle 编译语言为 Java 7
- 移除其他模块(aes、libaes、winboll、libwinboll)引用
- 添加技术文档:基于 sharedUserId + 自有签名 + LocalBroadcastManager 跨应用通信
- 确保 Java 源文件语法符合 API 26-30 适配范围
2026-05-10 15:39:12 +08:00
6741c41c83 <libappbase>Library Release 15.20.0 2026-05-10 13:31:38 +08:00
4708dd4426 <appbase>APK 15.20.0 release Publish. 2026-05-10 13:24:08 +08:00
36e2ed0b48 配置统一应用资源配置版本基础产品线型号 2026-05-10 13:20:21 +08:00
3cfee1c4a8 添加应用共享用户ID配置
- AndroidManifest.xml: 添加 android:sharedUserId 和 android:sharedUserLabel
- strings.xml: 新增 shared_user_label 字符串资源用于 sharedUserLabel 引用
2026-05-10 13:13:26 +08:00
24af31d51d feat(dark mode): 统一深色模式适配,所有窗体使用attrs.xml主题属性
## 核心变更
- 所有布局文件文本颜色统一使用 ?attr/* 主题属性引用
- 普通模式文本颜色: #000000 (黑色)
- 暗黑模式文本颜色: #E0E0E0 (灰色)

## attrs.xml 属性统一 (libappbase)
- 新增 AboutView 样式属性 (AboutView、AboutViewStyle)
- 新增 ButtonStyle 样式属性 (buttonBackgroundColor、buttonTextColor)
- 新增 DialogStyle 样式属性 (dialogBackgroundColor、dialogTextColor)
- 新增通用窗体属性 (activityBackgroundColor、activityTextColor、toolbarBackgroundColor 等)
- 移除 appbase/src/main/res/values/attrs.xml,合并到 libappbase

## styles.xml 主题配置
- 普通模式:背景色 #F5F5F5,文本色 #000000,工具栏/按钮背景色 #00B322
- 暗黑模式 (values-night):背景色 #0D1B2A,文本色 #E0E0E0,工具栏/按钮背景色 #1E3A5F

## 布局适配
- 所有窗体使用 ?attr/activityBackgroundColor / ?attr/activityTextColor
- 所有工具栏使用 ?attr/toolbarBackgroundColor
- 所有按钮使用 ?attr/buttonBackgroundColor / ?attr/buttonTextColor
- 所有对话框使用 ?attr/dialogBackgroundColor / ?attr/dialogTextColor
- AboutView 使用 ?attr/aboutViewBackgroundColor 等

## Java代码适配
- GlobalCrashReportView.java: 默认颜色改为黑色 (Color.BLACK)
- CrashHandler.java: 添加 isNightMode 判断,动态设置文本颜色
- AboutView.java: 深色模式标题颜色调整为 gray_500
2026-05-10 07:22:06 +08:00
54f77a8d87 feat(AboutView): 适配深色模式
- 优化布局颜色:应用名改为colorPrimaryDark,分割线改为gray_400
- 功能项背景根据深色/浅色模式动态设置背景色(gray_800/white)
- 功能项标题文字根据深色/浅色模式动态设置颜色(gray_100/gray_900)
2026-05-10 05:28:06 +08:00
69b18343c9 fix(libappbase): 强制Java 7语法兼容性
- 为libappbase模块添加compileOptions配置,确保源码和目标字节码均为Java 7
- 修复BackupUtils.java中HashMap<>泛型简写语法,改为完整类型声明new HashMap<String, String>(),兼容Java 7编译器
2026-05-10 05:20:54 +08:00
e1bd959842 主流版本阶段性调整 2026-05-10 05:15:39 +08:00
8f869e19cc 更新基础类库 2026-05-10 05:12:19 +08:00
270e21ed23 <libaes>Library Release 15.15.10 2026-05-10 05:04:34 +08:00
6412554096 <aes>APK 15.15.10 release Publish. 2026-05-10 05:04:15 +08:00
286f8513d4 refactor: 升级编译配置并调整最低API版本
- 升级 Gradle 编译版本为 Java 11
  根目录 build.gradle 中 JavaCompile 配置从 VERSION_1_7 改为 VERSION_11

- 调整 minSdkVersion 从 21 升级至 26
  适配 Android 8.0 (API 26) 及以上系统
  修改范围: aes/build.gradle, libaes/build.gradle

- 移除未使用的 XXPermissions 依赖
  该库使用 Java 8 字节码与 minSdkVersion 21 不兼容,项目中未引用此库
2026-05-10 04:48:29 +08:00
bad38e37ae <libappbase>Library Release 15.15.23 2026-05-10 04:17:16 +08:00
08eb360dbd <appbase>APK 15.15.23 release Publish. 2026-05-10 04:07:17 +08:00
819018b149 fix: appbase 和 libappbase 模块 minSdkVersion 从 21 升级至 26
- appbase/build.gradle: minSdkVersion 21 → 26
- libappbase/build.gradle: minSdkVersion 21 → 26
2026-05-10 04:01:44 +08:00
6e6b262e86 feat(build): 升级 Gradle 构建脚本 Java 版本为 11
将 root build.gradle 中 JavaCompile 任务的 sourceCompatibility 和 targetCompatibility
从 VERSION_1_7 升级至 VERSION_11,与项目需求保持一致。
2026-05-10 03:54:30 +08:00
9665856b1b 简化应用启动窗口配置:移除分屏测试功能,MainActivity 设为唯一启动器
- 删除 MainActivityAlias 分屏窗口类及相关文件
- 移除 AndroidManifest 中的 MainActivityAlias activity 声明
- 从 activity_main.xml 移除分屏测试按钮
- 删除 MainActivity 中的 onSplitScreenMode() 方法
- 为 MainActivity 添加 LAUNCHER intent-filter,作为唯一启动主窗口
2026-05-10 03:50:12 +08:00
052bbce839 Merge branch 'winboll' into appbase 2026-05-10 03:15:46 +08:00
90102f4eea refactor(winboll): 重命名菜单项为 WinBoLLLibraryActivity 2026-05-10 03:06:43 +08:00
759a08cec9 更换类库窗口标识名称 2026-05-10 03:03:00 +08:00
42cc7a2822 feat(winboll): 添加 UnitTest 菜单项跳转到 LibraryActivity
- 在 UnitTest 菜单组添加 LibraryActivity 菜单项
- 实现 MainActivity 中菜单项点击事件处理
- 更新 library.xml 布局显示类全名标识
- buildCount 更新为 25
2026-05-10 02:55:37 +08:00
c9c95d6ab0 refactor(gradle): 强制项目使用Java 7语法并统一API适配范围
- build.gradle: subprojects Java版本从11降级为1.7
- libwinboll/build.gradle: minSdkVersion从21统一为26
- build.properties: 编译计数器自动递增(buildCount: 23→24)
2026-05-09 22:02:59 +08:00
bc9bd47daa 添加类库模块,便于调试类库编译配置。 2026-05-09 21:17:23 +08:00
4bec8c3e9e 忽略编译问题,暂缓新功能叠加。 2026-05-09 21:16:44 +08:00
e726c9d435 <libappbase>Library Release 15.15.22 2026-05-09 20:50:14 +08:00
5277913606 <appbase>APK 15.15.22 release Publish. 2026-05-09 20:49:55 +08:00
c1bd31df2f refactor(libappbase): 完全重构 LogUtils 日志工具类
- 重构目录结构,按功能模块化拆分初始化、私有工具、日志级别等方法
- 补全所有日志重载方法(Error/Warn/Info/Debug/Verbose)
- 优化日志文件裁剪逻辑,保留最新3MB内容
- 新增TAG自动扫描管理机制
- 替换所有内部 LogUtils 调用为 android.util.Log,避免递归嵌套
- 严格遵循 Java 7 语法规范(final 参数、传统 try-catch)
- 优化异常/堆栈格式化输出
2026-05-09 20:43:17 +08:00
899673cec4 <mymessagemanager>APK 15.12.11 release Publish. 2026-05-09 14:21:58 +08:00
f544ecb283 fix: AboutActivity引入错误的AboutView/APPInfo类导致崩溃
将 import 从 cc.winboll.studio.libaes 修正为
cc.winboll.studio.libappbase,与布局文件保持一致,
修复打开应用介绍窗口时的 ClassCastException。
2026-05-09 14:16:54 +08:00
862157309b 更新应用介绍窗口资源 2026-05-09 14:03:46 +08:00
dc97f43a72 <mymessagemanager>APK 15.12.10 release Publish. 2026-05-09 13:46:04 +08:00
222538d259 fix: SMSView/TTSRuleView 圆角统一为全局属性,修复 CardView 裁剪边框
SMSView/TTSRuleView 的 cardCornerRadius 统一为全局配置:
  - 5 个布局文件 app:cardCornerRadius="12dp" → "?attr/borderCornerRadius"
    (listview_sms / listview_smsrecycle / listview_ttsplayrule
     listview_ttsplayrule_simple / view_smssend)

修复 CardView 裁剪内部 bg_container_border 圆角描边:
  - view_smssend.xml:SMSView(CardView)添加 android:padding="2dp"
    使内部 LinearLayout 的 bg_container_border 描边不被裁剪
  - view_smssend_part1.xml:android:padding 10dp→6dp 与圆角半径对齐
2026-05-09 13:42:39 +08:00
14f0b7c935 <mymessagemanager>APK 15.12.9 release Publish. 2026-05-09 11:16:04 +08:00
4e4673a93b feat: 容器统一加灰色边框,borderCornerRadius 属性统一管理圆角
创建通用灰色边框 drawable(5 模块各一份):
  - bg_container_border.xml:透明填充 + 1dp #B0B0B0 描边
  - 自动应用到 80 个无背景容器的布局文件

深色模式 bg_frame 加边框:
  - drawable-night/bg_frame.xml(mymessagemanager + aes):
    背景 shape 添加 1dp #666666 描边,保留渐变蒙版

统一圆角为 6dp(borderCornerRadius 属性化):
  - 5 模块 attrs.xml 声明 borderCornerRadius(dimension)
  - 12 个主题(6 普通 + 6 深色)设置 borderCornerRadius=6dp
  - 12 个 drawable 文件约 40 处 corners 改用 ?attr/borderCornerRadius
    (bg_frame / bg_frame_black/white / bg_shadow / bg_border_round
     bg_toolbar_log / acard_frame_main / atoolbar_frame / ohpcts_frame
     bg_container_border 等)
  - 3 个 Java 自定义控件(AToolbar / ASupportToolbar / AboutView)
    从硬编码 px 改为读取 ?attr/borderCornerRadius

受益布局模块:libaes / libappbase / appbase / mymessagemanager / aes / winboll
2026-05-09 10:59:55 +08:00
b385aa7030 feat: 各主题独立文本颜色,深色模式 bg_frame 去白底加渐变蒙版
主题文本颜色独立化(mymessagemanager):
  - values/colors.xml:text_color_primary 拆分为 6 个主题独立色
    default=#FF212121(暗灰)、depth=#FF1A237E(深靛蓝)
    sky=#FF01579B(深蓝)、golden=#FF3E2723(深棕)
    memor=#FF4A148C(深紫)、tao=#FF424242(深灰)
  - values/styles.xml:6 个主题分别引用各自的 text_color_primary_*

深色模式文本统一中灰色:
  - values-night/colors.xml:6 个主题统一使用 #FF999999
    与各主题 SMS 气泡色(黄/绿/蓝/紫/金/灰)明显区分
  - values-night/styles.xml:6 个主题引用各自的 text_color_primary_* 资源

深色模式 bg_frame 背景去白改蒙版:
  - drawable-night/bg_frame.xml(mymessagemanager + aes):
    第二层渐变从 #FFFFFFFF→#0FFFFFFF 改为 #1AFFFFFF→#00000000
    去除白底,保留 10% 白渐变过渡蒙版提供边框定义感
2026-05-09 10:29:56 +08:00
55c7f7d327 feat: 统一应用文本颜色由主题控制,支持深色/普通模式自适应
主题改造(mymessagemanager):
  - values/styles.xml + values-night/styles.xml:6种风格x2模式共12个主题
    统一设置 android:textColorPrimary / textColorPrimary / android:textColor / colorTextColor
    指向 @color/text_color_primary
  - values/colors.xml:text_color_primary=#FF000000(普通模式黑色)
  - values-night/colors.xml:text_color_primary=#FF00FF00(深色模式绿色)
  - attrs.xml:声明 textColorPrimary 属性解决 AAPT2 编译错误

清除硬编码文本颜色(跨模块):
  - 移除 13 个布局文件中的 android:textColor 属性(libappbase/libaes/winboll/appbase)
  - 移除 3 个 Java 文件中的 setTextColor(Color.BLACK/white/gray) 调用
  - 移除 ComposeSMSActivity / CharsetRefuseEditDialog 中的自定义文本颜色
  - 移除 toast_custom_view.xml / view_tts_back.xml 中的 android:textColor

CompoundButton 文本颜色适配:
  - ConfirmSwitchView:initView 中读取主题 textColorPrimary 的 ColorStateList 并显式设置
  - activity_appsettings.xml:平台 Switch 替换为 ConfirmSwitchView
  - fragment_main.xml:Switch 添加 textColor=?android:attr/textColorPrimary
  - 10个 RadioButton + 4个 CheckBox 添加 textColor=?android:attr/textColorPrimary
2026-05-09 10:02:44 +08:00
99de6c05ba <mymessagemanager>APK 15.12.8 release Publish. 2026-05-08 20:59:57 +08:00
4c856367f5 Merge branch 'winboll' into mymessagemanager 2026-05-08 20:50:27 +08:00
63580b111c feat: 新增SMSRecycle2Activity自由模式回收站与刻度全局同步
- 新增SMSRecycle2Activity窗口,每项使用ProtectModeTextView显示短信内容
- 顶部添加示例ProtectModeTextView,刻度值通过SP全局同步到列表所有项
- AppSettingsActivity新增回收站模式RadioGroup:简洁模式/自由模式
- MainActivity回收站菜单根据配置路由到对应Activity
- AppConfigBean新增recycleBinClass字段持久化模式选择
- ProtectModeTextView新增OnScaleChangedListener与setContentTextWithScale
2026-05-08 20:46:27 +08:00
3b60a3b713 feat: 新增ProtectModeTextView自定义控件
1. 继承LinearLayout,内置TextView与0~12刻度SeekBar
2. 刻度0保持原始文本不打乱,1~12为每组相邻字符个数
3. 按刻度固定长度从头至尾字符分组,分组列表随机打乱后拼接输出
4. 支持含空格/标点完整字符解析,对外提供setContentText设置文本接口
2026-05-08 19:35:39 +08:00
660e2908f5 fix: 移除根build.gradle中-parameters编译选项与Java 7的冲突 2026-05-07 19:20:28 +08:00
a3855b4375 docs: 重构 gpsrelaysentinel/README.md 中英文双语文档
- 中文文档新增核心功能清单(双模式运行、前台服务、订阅者管理、模拟面板、日志输出、崩溃处理、关于页面)
- 技术栈改用表格展示,模块说明更新为实际编译模块(:gpsrelaysentinel 与 :libgpsrelaysentinel)
- 核心依赖库分类更清晰(网络、终端模拟、功能组件、UI 组件)
- 项目结构树精确到具体 Java 源文件及其功能说明
- 权限说明改为具体 Android 权限声明
- 新增完整英文版本文档(Project Introduction、Core Features、Tech Stack、Module Structure、Core Dependencies、Build Instructions、Permissions、Project Structure、Contributing、License)
- 删除失效的参考文档链接
2026-05-07 16:25:40 +08:00
d5100a8aa4 feat: MainActivity工具栏添加About按钮跳转AboutActivity窗口
- 新增菜单资源文件 res/menu/menu_main.xml
- MainActivity 添加 onCreateOptionsMenu() 加载菜单
- MainActivity 添加 onOptionsItemSelected() 处理 About 按钮点击事件
- 点击 About 按钮后通过 Intent 启动 AboutActivity
2026-05-07 16:17:37 +08:00
e17929c09b 添加应用介绍窗口编译资源。 2026-05-07 15:57:54 +08:00
332c7ee21c 双剑合璧。
Merge remote-tracking branch 'origin/gpsrelaysentinel' into gpsrelaysentinel
2026-05-07 15:20:33 +08:00
20cb50ff29 feat(gpsrelaysentinel): 模拟GPS发送面板与订阅系统重构
[主应用]
- MainActivity: 新增模拟移动GPS发送面板(方向/距离/目标坐标预览/静态坐标同步)
- MainService: 代码模块化重构,方法拆分,实时同步最新GPS到MainActivity
- 新增3个子服务 GpsReceiverChildService1/2/3
- activity_main.xml: 深色主题改版,新增模拟面板、订阅面板容器、日志容器
- 新增资源: border_gray.xml、spinner_item_gray.xml、arrays.xml(8方向)

[类库]
- SubscribeLocationManager: 新增精准推送计数统计,公开配置查询方法
- GpsSubscribeReceiverService: 改为抽象父类,统一 onReceiveGpsData 入口
- GpsSubscribeControlView: 移除广播/倒计时,改用Manager直调+Handler自动刷新
- view_gps_subscribe_control.xml: 深色主题,新增SID标识与订阅数据记录表
2026-05-07 15:18:38 +08:00
498372c914 fix(libgpsrelaysentinel): 对齐 minSdk 与 Java 编译配置
- minSdkVersion 21 -> 26,与 gpsrelaysentinel 主模块及 API 26~30 要求一致
- 新增 compileOptions 设置 Java 7 编译,与项目 Java 语法规范统一
2026-05-07 15:10:09 +08:00
e147d46921 添加示例服务类注册 2026-05-07 14:41:12 +08:00
42d135068c 改进应用主窗口与调试接口UI 2026-05-07 14:39:49 +08:00
ceeacb5022 改进应用主要服务启动类 2026-05-07 14:38:56 +08:00
e24c9bdce3 改进GPS订阅服务发送框架 2026-05-07 14:37:07 +08:00
9c16685c1f 添加应用GPS订阅示例服务类 2026-05-07 14:35:58 +08:00
6ffcbbc4f4 添加模拟方位下拉列表项的视图资源 2026-05-07 14:34:33 +08:00
3c39225087 添加灰色边框资源,用于辅助深色视图渲染。 2026-05-07 14:32:58 +08:00
39b4761e49 添加定向方位数组 2026-05-07 14:30:50 +08:00
534ec28637 更新Maven库基础类库 2026-05-07 14:08:54 +08:00
89f96a7b99 预备调试框架 2026-05-07 11:11:24 +08:00
429db23050 fix(libgpsrelaysentinel): 修复LocationPoint无法通过Intent传递的编译错误
LocationPoint类实现Serializable接口,解决
GpsSubscribeReceiverService中使用putExtra()传递对象时的类型不匹配问题。
2026-05-07 11:00:48 +08:00
3e4a64f31e 添加libgpsrelaysentinel类库初始源码 2026-05-07 10:54:13 +08:00
2927303a88 GPSRelaySentinel项目添加类库模块libgpsrelaysentinel。 2026-05-07 10:33:56 +08:00
2c4fc218b0 fix(gpsrelaysentinel): 修复MainActivity访问MainService常量的权限问题
- 将PREF_NAME和KEY_SERVICE_ENABLED字段从private改为包内可见
- 允许MainActivity访问SP相关常量以设置服务状态标记
- 修复编译错误:KEY_SERVICE_ENABLED has private access
2026-05-07 03:05:47 +08:00
b065a20c4d feat(gpsrelaysentinel): 前台服务通知添加GPS数据计数值
- 添加mGpsCount计数器统计GPS数据接收次数
- 每次onLocationChanged时计数器自增
- 通知栏实时显示GPS数据计数值(Count: x)
- 计数包含在通知内容中:经纬度 | Count: x
2026-05-07 02:56:15 +08:00
b3df8c7770 feat(gpsrelaysentinel): 使用SP标记管理服务状态并支持自启动
- onStartCommand返回START_STICKY实现服务自启动
- onStartCommand直接设置SP标记为启用,不检查现有标记
- onCreate时检查SP标记,已启用则自动启动GPS
- onDestroy不再改变SP标记
- MainActivity stopService前先设置SP标记为不启用
2026-05-07 02:45:56 +08:00
dae269ff77 feat(gpsrelaysentinel): 升级为始终允许GPS监听权限申请
- 添加ACCESS_BACKGROUND_LOCATION权限声明
- 在Android Q及以上版本申请后台位置权限
- 权限检查包含后台位置权限验证
- 权限申请时根据系统版本动态添加后台位置权限
2026-05-07 02:26:55 +08:00
cb8c3448f5 feat(gpsrelaysentinel): 添加Switch打开时的GPS权限检查与申请
- Switch打开时检查是否有定位权限
- 无权限时自动申请ACCESS_FINE_LOCATION和ACCESS_COARSE_LOCATION
- 权限申请成功后自动启动MainService
- 权限申请失败时提示用户并关闭switch
- 根据当前权限状态初始化switch显示状态
2026-05-07 02:23:14 +08:00
0e90f40f0f feat(gpsrelaysentinel): 实现前台服务通知并通过Switch控制服务启停
- 添加FOREGROUND_SERVICE权限支持前台服务
- 使用startForegroundService替代startService启动服务
- 实现前台服务通知,实时显示GPS经纬度数据
- 在MainActivity添加Switch开关控制服务启停
- GPS位置更新时通过updateNotification实时更新通知内容
- 创建通知渠道适配Android O及以上版本
2026-05-07 02:15:19 +08:00
11aee7e373 refactor(gpsrelaysentinel): 重构MainService添加run函数管理GPS监听
- 将GPS定位申请逻辑从onCreate()转移到新增的run()函数
- onStartCommand()调用run()启动GPS监听
- 添加mIsRunning标志防止重复启动
- onCreate()不再直接初始化定位功能
- onDestroy()中重置mIsRunning标志
2026-05-07 02:02:50 +08:00
58a93a6746 feat(gpsrelaysentinel): 新增MainService服务用于接收GPS定位消息
- 添加MainService服务类,监听GPS定位更新(1秒间隔,1米距离)
- 在AndroidManifest.xml注册MainService服务
- 添加定位权限ACCESS_FINE_LOCATION和ACCESS_COARSE_LOCATION
- 使用LogUtils替代android.util.Log进行日志记录
- TAG属性改为public static final
2026-05-07 01:50:25 +08:00
38eacb9a57 docs(gpsrelaysentinel): 重新整理README.md项目说明书
- 基于项目实际情况重新组织文档结构
- 使用Markdown语法完善项目说明
- 补充技术栈、模块说明、依赖库等详细信息
- 添加项目结构、使用说明和参与贡献指南
- 更新项目名称为GPSRelaySentinel
2026-05-06 21:03:51 +08:00
377d084aad chore(gpsrelaysentinel): 配置Java 7编译选项适配项目技术栈
- 为gpsrelaysentinel模块添加compileOptions配置
- 设置sourceCompatibility和targetCompatibility为Java 7
- 满足项目要求:Java文件使用Java 7语法
- 保持Gradle编译使用Java 11(根目录subprojects配置)
- 保持安卓API适配范围26-30,compileSdkVersion 30
- 保持Gradle插件7.2.1版本
2026-05-06 21:01:38 +08:00
a16d98cad0 添加GPSRelaySentinel项目 2026-05-06 20:51:01 +08:00
c6591e83a5 chore(winboll): 改造winboll模块适配API 26-30并兼容Java 7
- 调整minSdkVersion从23到26,符合API 26-30适配范围要求
- 修复PatternLockActivity.java中3处lambda表达式,
  改为Java 7兼容的匿名内部类形式
- 保持Gradle插件7.2.1、compileSdkVersion 30、
  targetSdkVersion 30及Java 11编译配置不变

Modified files:
- winboll/build.gradle
- winboll/build.properties
- winboll/src/main/java/cc/winboll/studio/winboll/activities/PatternLockActivity.java
2026-05-06 13:43:06 +08:00
7119b3b7a5 feat(modules): 新增 appbase 和 libappbase 基础库模块
- 添加 appbase 应用基础模块 (Activities, Resources, Build config)
- 添加 libappbase 通用基础库模块 (Utils, Views, Dialogs, Resources)
- 更新 .gitignore 忽略规则
- 包含日志查看、NFC RSA 操作、全局崩溃处理等基础功能
2026-05-06 12:44:30 +08:00
48d36c6d96 更新.gitignore配置并移除项目特定配置文件
将项目特定的配置文件移至忽略列表,避免上传至版本库:
- 添加 /settings.gradle 和 /gradle.properties 到 .gitignore
- 从版本库中删除 settings.gradle 和 gradle.properties
- 保持项目配置本地化,便于多项目切换管理
2026-05-06 12:40:48 +08:00
2850d3ca3b chore(config): 移除 gradle.properties 配置文件
- 删除项目根目录下的 gradle.properties
2026-05-06 12:34:19 +08:00
74443950c4 chore(config): 调整项目构建配置,取消忽略 settings.gradle
- 修改 .gitignore 取消注释 settings.gradle 和 gradle.properties
- 删除 settings.gradle 文件
2026-05-06 12:25:05 +08:00
0607af429b Merge branch 'winboll' of https://gitea.winboll.cc/Studio/WinBoLL into winboll 2026-05-06 12:21:06 +08:00
55baf0afac Merge branch 'aes' into winboll 2026-05-06 12:06:40 +08:00
1d0dec8de5 降级Java版本从11到7 2026-05-06 12:04:19 +08:00
39e825f03e fix(browser): 外部调用时直接打开传入的网页链接,不再默认加载首页
- BrowserFragment 新增 newInstance(initialUrl) 工厂方法
- initWinBoLLView 优先使用外部传入的 URL 参数
- MainActivity 在创建 Fragment 时即传入外部 URL,避免先加载首页再跳转的闪烁
2026-05-06 11:28:14 +08:00
cd0599d639 feat(browser): 支持外部应用调用传入网页地址
- 在 AndroidManifest.xml 为 MainActivity 添加 http/https 的 intent-filter
- 设置 singleTask 启动模式以复用 Activity 实例
- BrowserFragment 新增 MSG_OPEN_URL 消息处理外部 URL 跳转
- MainActivity 实现 handleExternalUrl 方法,在 onCreate/onNewIntent 中捕获并加载网页
2026-05-06 11:17:13 +08:00
aef5a62e47 feat(network): 全局启用 HTTP 明文流量支持
- 修改 network_security_config.xml 允许所有 HTTP 协议访问
- 移除对 HTTP 访问的域名限制
2026-05-06 11:11:39 +08:00
06253feba8 feat(ollama): 添加 Ollama 模型对话功能
- 新增 OllamaWindowActivity 用于模型对话交互
- 添加 Ollama 配置对话框(API地址、模型、温度、token等)
- 在主菜单中增加 Ollama 窗口入口
- 包含发送、停止、清空等对话控制功能
- 更新 buildCount 至 15
2026-05-06 11:08:04 +08:00
1dbca0f290 添加 Gradle 编译调试信息。 2026-05-04 20:16:25 +08:00
14d0227158 编译输出信息调整 2026-05-04 19:57:39 +08:00
3fcdbabcc9 改进APK应用包输出资源配置。 2026-05-04 19:38:52 +08:00
e99d7ebc06 <positions>APK 15.12.21 release Publish. 2026-05-04 11:31:30 +08:00
07a286e7e0 重构主界面菜单与调试模式UI控制逻辑
- 将空转切换菜单项(item_idle_switch)从toolbar_main.xml移至新建的toolbar_main_idle.xml
- 调试模式(App.isDebugging()为真)时加载toolbar_main_idle.xml菜单,非调试模式不加载
- onCreate时根据调试状态控制日志区域(tv_idle_log)的显示/隐藏
- onResume时通过invalidateOptionsMenu()重新加载菜单,确保状态同步
2026-05-04 11:28:10 +08:00
7761c80275 更新基础类库 2026-05-04 10:57:41 +08:00
e1f6c3de05 <positions>APK 15.12.20 release Publish. 2026-05-04 09:52:15 +08:00
84c616cbda feat: 实现应用空转状态持久化与MainService动态数据源切换
1. App.java:
   - 添加SharedPreferences持久化空转状态,App重启后自动恢复
   - setAppIdleRunning()中增加MainService服务重启逻辑,同步空转状态

2. MainService.java:
   - onCreate/onStartCommand: 检测空转状态,自动启动IdleGpsService
   - 重构GPS监听逻辑,新增mIdleGpsListener实例与中央处理方法
   - startGpsLocation/stopGpsLocation: 根据空转状态自动切换系统GPS与IdleGpsService数据源
   - 前台通知栏显示 [IDLE RUNNING] 标识及全精度(%.15f)经纬度数据
   - 通知内容区分"空转GPS"与真实"GPS位置"前缀

3. IdleGpsService.java:
   - 添加服务启动时的Toast提示反馈
   - 修复MainService中IdleGpsService类型不匹配问题

4. MainActivity.java:
   - 简化空转切换逻辑,移除冗余的IdleGpsService手动启停代码
2026-05-04 09:41:42 +08:00
9d868f216c feat(IdleGpsService): 实现圆形轨迹GPS模拟并优化服务启动逻辑
- 重构IdleGpsService,将固定坐标模拟改为圆形轨迹动态生成
- 添加角动量(bearing)递增机制,实现坐标沿圆周持续运动
- 新增calculatePosition()方法,基于锚点坐标和半径计算轨迹位置
- 在MainActivity.onResume中根据空转状态自动启动GPS服务
- 更新build.properties版本号(buildCount 21->23)
2026-05-04 03:41:57 +08:00
79b0320fb9 feat(IdleGpsService): 添加手动控制空转GPS服务的启动/停止逻辑
- 新增 IdleGpsService.start() 和 stop() 方法,支持外部手动控制服务状态
- 在 MainActivity 菜单切换空转状态时,联动启动/停止空转GPS服务
- 优化模拟坐标更新间隔从1秒调整为5秒,降低资源消耗
- 添加空转日志记录,便于追踪GPS服务状态变更
2026-05-03 18:29:32 +08:00
173290333d feat(GPS): 添加空转GPS模拟服务与动态源切换逻辑
1. 新增IdleGpsService,在空转模式下定时发送模拟GPS坐标(默认北京)
2. LocationActivity支持动态切换GPS源:空转时使用IdleGpsService,正常时使用MainService
3. 优化onResume逻辑,确保空转状态变更时GPS监听源实时同步
4. 统一反注册逻辑,避免多服务监听冲突
2026-05-03 16:49:59 +08:00
f9ef5ab16e refactor(LocationActivity): 优化代码结构与新增空转状态提示
1. 新增Toolbar副标题动态显示应用空转运行状态
2. 重构类结构,生命周期方法移至底部,工具方法置顶
3. 规范日志输出格式,统一使用"invoke"等标识
4. 增强变量不可变性声明,多处添加final修饰符
5. 简化冗余逻辑判断与条件检查
6. 清理冗余注释,更新Javadoc描述
2026-05-03 16:02:21 +08:00
748661b984 源码整理 2026-05-03 15:30:13 +08:00
2cfc29845e fix(MainActivity): 优化定位权限校验逻辑与空转状态UI刷新
1. 服务开关开启前增加定位权限前置校验,无权限时阻止开启并提示
2. 空转关闭时自动检测权限,无权限则强制停止GPS服务
3. 权限被拒绝时强制关闭服务开关并禁用管理按钮
4. 新增Toolbar副标题显示空转运行状态提示
5. 新增管理按钮状态刷新,空转中强制可点击并追加提示文字
6. 移除冗余注释,精简接口定义
2026-05-03 15:22:18 +08:00
389300433c 添加主窗口空转状态日志UI切换。 2026-05-03 13:43:04 +08:00
4a65e16427 添加应用空转调试资源 2026-05-03 13:16:46 +08:00
21d7da20d3 添加应用空转标志和属性设置接口。以便应用的节能优化与调试。 2026-05-03 12:08:41 +08:00
66ea626fbd Merge branch 'winboll' into positions 2026-05-03 11:53:00 +08:00
74ab06448f feat: 添加剪裁背景相关组件及版本更新
- 新增BackgroundRadioButton自定义单选按钮组件
- 新增CustomApplicationBackground应用背景管理类
- 更新build.properties版本构建号(0->19)
- 完善封面剪裁背景修改功能相关基础设施
2026-05-02 10:49:36 +08:00
6cf5ac2034 feat: 添加CropBackgroundUtils工具类及封面剪裁背景修改功能
- 创建CropBackgroundUtils工具类,实现与BackgroundUtils类似的背景管理功能
- 在封面剪裁窗口(CropActivity)添加"修改剪裁背景颜色"按钮
- 按钮点击后弹出Toast提示信息
- 使用独立的偏好设置存储(crop_background_prefs)
2026-05-02 10:48:28 +08:00
a2884122aa 实现主窗口背景颜色修改功能。 2026-05-02 10:40:22 +08:00
03ae37dc91 fix: BackgroundUtils初始化时自动保存配置至SP
- initFromResource 和 initFromColor 增加自动调用 saveToPreferences()
- 优化 initFromPreferences 默认逻辑,避免重复调用保存方法
2026-05-02 10:32:58 +08:00
aea9f1d745 feat: 增强BackgroundUtils属性获取功能
- 新增 getAttributeValueType() 函数,返回当前属性值类型
- 新增 getResId() 和 getColor() 公共函数,用于获取具体的属性值
2026-05-02 10:15:03 +08:00
0786d69ad6 refactor: 调整主窗口背景设置逻辑与布局透明度
- MainActivity: 将背景设置逻辑从 onCreate 移至 onPostCreate
- activity_main.xml: RecyclerView 背景颜色修改为透明
2026-05-02 10:05:26 +08:00
1585ff7eed feat: 重构背景管理系统,移除选择背景对话框并添加BackgroundUtils工具类
- 移除 BgSelectorDialog 对话框及 dialog_bg_selector 布局文件
- 移除 ImageViewerActivity 中的背景选择按钮及相关方法(switchBg/applyBg)
- 移除 CropActivity 中的背景颜色预览视图及 showBgDialog 方法
- 移除 ImagePagerAdapter 中的 bgType 参数,背景固定为黑色
- 新增 BackgroundUtils 单例工具类,支持:
  - 通过资源ID或颜色值创建背景Drawable
  - DrawableType枚举记录创建方式
  - SharedPreferences持久化存储背景配置
  - 首次启动默认使用绿色背景并保存
- 在 GlobalWinBoLLApplication 中初始化BackgroundUtils
- 在 MainActivity 中应用BackgroundUtils设置的背景
- 主窗口菜单添加修改背景颜色选项
2026-05-02 09:54:48 +08:00
91b2b1b480 处理AIDE Pro编译提示问题 2026-05-02 02:53:05 +08:00
bef3f3ce81 <gallery>APK 15.0.15 release Publish. 2026-05-01 21:09:33 +08:00
c0da46e0fd 为preview_image添加单击事件,随机改变容器背景色
- 为preview_image的父级LinearLayout添加id标识(preview_image_container)
- 实现单击preview_image时随机生成颜色并改变容器背景
- 添加必要的import语句(LinearLayout, Random)
2026-05-01 21:05:23 +08:00
72ca11a1af 剪裁信息对话框,预览图片时添加一个边框。 2026-05-01 20:52:33 +08:00
5cd9c88cdc <debugtemp>APK 15.0.56 release Publish. 2026-05-01 17:09:11 +08:00
ed96d0ed07 <debugtemp>APK 15.0.55 release Publish. 2026-05-01 17:08:10 +08:00
e69ffa4e66 初始化类库模块编译参数配置。 2026-05-01 16:19:58 +08:00
68ddb22e83 添加类库项目模板libdebugtemp。 2026-05-01 16:07:05 +08:00
5decb2f8d9 <gallery>APK 15.0.14 release Publish. 2026-05-01 10:29:10 +08:00
29e7cfe985 refactor: 抽象背景选择对话框为独立的BgSelectorDialog类
- 新增BgSelectorDialog对话框类,继承Dialog
- 新增dialog_bg_selector.xml布局文件定义对话框视图
- 重构ImageViewerActivity.switchBg()使用新的BgSelectorDialog
- 重构CropActivity.showBgDialog()使用新的BgSelectorDialog
- 统一两个Activity的背景选择交互方式
2026-05-01 10:25:45 +08:00
6d521fefdb <gallery>APK 15.0.13 release Publish. 2026-05-01 09:21:07 +08:00
f7932c134f 编译调试 2026-05-01 09:18:40 +08:00
93c59b0424 feat: 封面剪裁窗口使用与图片浏览窗口一致的背景颜色设置
- CropCanvasView: 将画布背景从固定的 Color.BLUE 改为支持 3 种背景类型
  (灰白棋盘格 / 全白 / 全黑),与 ImageViewerActivity 保持一致
  - 新增 bgType 字段、setBackgroundType() / getBackgroundType() 方法
  - 新增 drawBackground() 统一绘制方法,用于 onDraw() 和 getCanvasBitmap()
  - 棋盘格使用 Drawable.draw() 渲染 Vector 到 Bitmap,再通过 BitmapShader 平铺

- CropActivity: 从 Preferences 读取保存的 bgType 并应用到画布
  - 将颜色拾取按钮替换为背景选择按钮 (ic_bg),弹出单选对话框切换背景
  - 切换时调用 prefs.setBgType() 保存,与图片浏览窗口共享同一数据源
  - 工具栏颜色指示器随背景类型更新
  - 剪裁信息对话框改为显示背景类型名称
2026-05-01 09:15:30 +08:00
fe248349df <gallery>APK 15.0.12 release Publish. 2026-05-01 08:34:01 +08:00
4790238343 更新图片浏览窗口工具栏Gallery按钮图标
- 将ImageViewerActivity工具栏中的btn_gallery图标从ic_cover替换为ic_view_gallery_outline
- 修改ic_view_gallery_outline的fillColor从黑色(#ff000000)改为白色(#FFFFFF),与工具栏其他图标颜色保持一致
2026-05-01 08:31:11 +08:00
f144d91bb6 添加ic_view_gallery_outline图标 2026-05-01 08:22:37 +08:00
9c0e56462e <gallery>APK 15.0.11 release Publish. 2026-05-01 04:09:20 +08:00
4d977646e6 Merge remote-tracking branch 'origin/gallery' into gallery 2026-05-01 03:59:54 +08:00
2d912abf92 feat: 重构主窗口菜单,将系统相册入口移至图片浏览窗口
- MainActivity: 移除 action_gallery 按钮,替换为 action_mi_gallery (小米相册)
- MainActivity: 移除 action_reset_gallery 按钮及响应代码
- ImageViewerActivity: 新增 btn_gallery 按钮,点击当前浏览图片打开相册选择器
- menu_main: 删除 action_gallery 和 action_reset_gallery 菜单项
- drawable: ic_cover 颜色改为白色以适配暗色工具栏
- drawable: 新增 ic_mi_gallery (小米相册图标),删除 ic_cover_reset
- strings: 新增 mi_gallery 字符串资源
2026-05-01 03:56:03 +08:00
f764de4077 <gallery>APK 15.0.10 release Publish. 2026-04-30 23:01:42 +08:00
0788a52652 feat: 添加系统相册入口及重置默认打开方式功能
【功能更新】
1. 系统相册按钮:
   - 工具栏新增“系统相册”菜单项 (ic_cover)。
   - 点击后通过 Intent 调用系统默认相册。

2. 重置按钮:
   - 工具栏新增“重置”菜单项 (ic_cover_reset)。
   - 清除图库 ACTION_VIEW 的默认打开记录,恢复应用选择框。
   - 兼容处理:API <= 30 自动清除,API > 30 提示适配限制说明。

【代码变更】
- MainActivity.java: 新增 action_gallery 与 action_reset_gallery 响应逻辑,包含异常捕获。
- menu_main.xml: 注册两个新菜单项。
- strings.xml: 新增 "system_gallery", "reset_gallery" 字符串资源。

【其他新增】
- 新增 ColorPaletteDialog 类及 dialog_color_palette.xml 布局。
- 新增 styles.xml 样式 ColorPaletteDialog。
- 新增重置图标资源 ic_cover_reset.xml。
- 更新 build.properties 版本配置。
2026-04-30 22:54:44 +08:00
f3b3036591 feat(pattern-lock): 添加图案密码解锁功能
- 创建带图案打开意图过滤器的 PatternLockActivity
- 构建图案锁布局和点背景样式
- 添加图案锁颜色和字符串资源
- 更新构建计数到 11

注意:图案锁 UI 已创建但尚未集成
2026-04-30 15:11:20 +08:00
28ecc605e1 <winboll>APK 15.11.26 release Publish. 2026-04-30 12:07:31 +08:00
523a8e49e0 更新一下属性命名,清理冗余代码。 2026-04-30 12:03:37 +08:00
59a9e0ee45 添加TermuxButton按钮控件类 2026-04-30 11:48:27 +08:00
cbf1341435 添加TermuxButtonModel数据模型 2026-04-30 10:57:34 +08:00
dadf573675 改进Termux应用调用函数,添加TermuxWorkSpaces按钮响应。 2026-04-30 10:42:50 +08:00
7420a5cd48 添加TermuxWorkSpaces按钮视图 2026-04-30 10:14:27 +08:00
dc6a589db4 调整UI布局 2026-04-30 10:09:12 +08:00
e3f47043ef 更新Termux应用打开方法 2026-04-30 09:58:50 +08:00
a825951aad feat: 在 MyTermuxActivity 中添加 Termux 按钮功能
- 在 activity_my_termux.xml 布局中添加 Termux 按钮(底部居中)
- 在 MyTermuxActivity.java 中实现按钮点击事件
- 调用 TermuxCommandExecutor 执行 Termux 命令
- 移除了空 FrameLayout,简化布局结构
2026-04-30 09:42:08 +08:00
79cb841349 feat: 添加 MyTermuxActivity 菜单及工具栏功能
- MainActivity 添加 MyTermuxActivity 菜单项
- 配置 MyTermuxActivity 注册到 AndroidManifest.xml
- 添加 Toolbar 布局并初始化工具栏
- 设置一级标题应用名称、二级标题活动名称
- 添加返回按钮导航逻辑

修改文件:MainActivity.java, MyTermuxActivity.java, activity_my_termux.xml, toolbar_main.xml, strings.xml, AndroidManifest.xml, gradlew
2026-04-30 08:56:49 +08:00
d3c40efffa 添加我的Termux活动类 2026-04-30 08:34:29 +08:00
ffea383a4e feat: 添加封面裁剪窗口标题
- 在关闭按钮后添加标题 TextView
- 标题文本:封面裁剪
- 样式配置:
  * 文字颜色:白色
  * 文字大小:18sp
  * 居中对齐
  * 布局权重:1(占据剩余空间)
  * 结束边距:8dp
- 提升窗口语义化,符合 Android 设计规范
2026-04-29 11:54:29 +08:00
9c1e08a88b style: 统一封面剪裁窗口工具栏按钮尺寸与间距
- 关闭按钮 (btn_close): 由 48dp x 48dp+12dp padding 改为 40dp x 40dp+5dp padding
- 颜色拾取按钮 (btn_color_pick): 由 48dp x 48dp+12dp padding 改为 40dp x 40dp+5dp padding
- 信息按钮 (btn_info): 由 48dp x 48dp+12dp padding 改为 40dp x 40dp+5dp padding
- 完成按钮 (btn_done): 由 48dp x 48dp+12dp padding 改为 40dp x 40dp+5dp padding

统一使用 40dp 标准尺寸,减小 padding 至 5dp 增强点击区域。
2026-04-29 11:11:26 +08:00
abb28b766a 修复AIDE Pro 编辑器的编译提示问题。处理函数固定赋值传递问题。 2026-04-29 01:31:17 +08:00
b6cd91c298 安卓手机端使用AIDE Pro应用的Gradle方式编译成功。 2026-04-29 00:11:09 +08:00
61c2bab83e OpenCode 使用 WINBOT 模型编译调试成功 2026-04-29 00:06:26 +08:00
5a700a0808 <gallery>优化裁剪功能:更新颜色拾取逻辑、同步工具栏图标、调整布局文件 2026-04-28 19:46:23 +08:00
dad179c15f 完善剪裁窗口颜色拾取功能,工具栏图标同步更新 2026-04-28 18:31:16 +08:00
1b62ff288f <gallery>APK 15.0.9 release Publish. 2026-04-28 17:21:55 +08:00
87ad6668a4 更新 gallery 模块 libappbase 依赖到 15.15.21 2026-04-28 17:18:30 +08:00
4baee6f0e1 <libappbase>Library Release 15.15.21 2026-04-28 17:08:33 +08:00
8f6b615949 <appbase>APK 15.15.21 release Publish. 2026-04-28 17:08:05 +08:00
d02d57d4dd 添加LogUtils日志文件自动裁剪功能 2026-04-28 17:05:50 +08:00
139083c22f <gallery>APK 15.0.8 release Publish. 2026-04-28 13:58:56 +08:00
c49e68d7f1 添加裁剪信息窗口功能
- 添加信息按钮和对话框,显示画布、颜色、裁剪信息
- 显示裁剪结果预览图和拾取坐标
- 松开手指时重置拾取按钮状态
2026-04-28 13:56:08 +08:00
b035d461aa 优化剪裁窗口颜色拾取功能
- 按下手指时预览颜色,放开时确认拾取
- 拖动时实时跟随手指位置更新颜色
- 分离颜色预览和确认拾取的回调接口
2026-04-28 13:06:45 +08:00
474ddcbb3b 添加剪裁窗口颜色拾取功能
- 在工具栏添加颜色图标显示当前背景颜色
- 添加背景颜色变化监听器
- 修复画布放大时颜色拾取坐标计算
- 拾取颜色后自动退出拾取模式
2026-04-28 12:35:55 +08:00
a7e2646eca <gallery>APK 15.0.7 release Publish. 2026-04-28 10:57:44 +08:00
a12577a369 限制裁剪框放大时超出画布范围
- 双指缩放裁剪框时添加边界检测
- 裁剪框超出边界时停止放大,保持在画布内
2026-04-28 10:45:42 +08:00
990ca8e27c 修复裁剪框在画布缩放后的触摸操作
- onTouchEvent()中将触摸坐标除以containerScale进行转换
- 边界检测使用转换后的坐标buckets
- 确保单指移动裁剪框在缩放后正常工作
2026-04-28 10:26:58 +08:00
7f6cd8295c 裁剪框跟随画布控件同步缩放
- 添加containerScale属性跟踪缩放比例
- setContainerScale()同步缩放比例到裁剪框
- onDraw()中使用canvas.scale()缩放裁剪框绘制
- 角标半径按比例调整
2026-04-28 10:18:22 +08:00
ae3a0c6f64 使用进度条控制画布控件缩放
- 移除顶部放大/缩小按钮,改用底部SeekBar进度条控制
- 缩放范围0.1-5.0对应进度条0-100
- 添加setScaleFactor()方法
2026-04-28 10:01:10 +08:00
e07931fd3b 修复裁剪窗口画布控件裁剪源问题
- 添加getCanvasBitmap()方法创建包含背景的画布位图
- 裁剪源使用画布位图,与预览显示一致
2026-04-28 09:51:49 +08:00
8d62e7df21 修改裁剪窗口默认背景颜色
- CropCanvasView背景设为蓝色
- ZoomContainerView背景设为黄色
2026-04-28 09:24:57 +08:00
1cca476acd 修复剪裁窗口画布控件缩放和布局问题
- 修复ZoomContainerView缩放按钮无效问题,添加onLayout正确缩放子视图
- 修复CropCanvasView裁剪框显示不一致问题
- 更新libappbase版本到15.15.20
- 优化剪裁窗口布局
2026-04-28 09:15:43 +08:00
e337bb7a04 <libappbase>Library Release 15.15.20 2026-04-27 20:19:13 +08:00
9ae848e4c2 <appbase>APK 15.15.20 release Publish. 2026-04-27 20:18:59 +08:00
9c66f61891 调整MainActivity按钮顺序将关于应用置顶 2026-04-27 20:15:54 +08:00
bfaf3543b9 添加多窗口支持与LogActivity独立任务栈 2026-04-27 20:04:17 +08:00
b44fe3aaf3 添加分屏测试功能支持多窗口MainActivity 2026-04-27 19:27:25 +08:00
d518ac50a9 优化LogActivity分屏模式支持Android 11适配 2026-04-27 18:40:36 +08:00
d532eae971 调整onLogTest调用LogActivity分屏模式 2026-04-27 17:58:53 +08:00
f661acbbbc 添加LogActivity重载函数支持分屏模式切换 2026-04-27 17:33:51 +08:00
31ea5c8fbb 添加主窗口封面刷新机制和剪裁图片调试日志
- 添加剪裁图片保存时的调试日志输出
- 添加广播机制通知主窗口刷新封面图片
- 优化剪裁画布的显示和缩放功能
- 添加缩放按钮和ZoomContainerView支持
- 添加新的图标资源(ic_done、ic_zoom_in、ic_zoom_out)
2026-04-27 15:59:12 +08:00
e62a907378 <gallery>APK 15.0.6 release Publish. 2026-04-26 23:52:51 +08:00
c85ba8324b 添加相册集封面裁剪功能
- AlbumCoverDbHelper添加裁剪封面字段crop_path
- 数据库升级从版本1到版本2
- 添加setCoverWithCrop方法保存裁剪封面
2026-04-26 23:47:30 +08:00
3e49c33bc1 修复主窗口启动时相册集封面不显示问题
- 裁剪封面是本地文件,直接使用Uri.fromFile加载
- 文件不存在时使用MediaStore查询
2026-04-26 21:06:41 +08:00
3aab93cc4d 添加主窗口关于菜单和刷新封面功能
- 主窗口添加工具栏关于应用菜单
- AboutActivity使用AppBarLayout与主窗口一致
- 添加返回按钮到AboutActivity工具栏
- 去掉主窗口刷新菜单
- onResume时刷新相册集封面
2026-04-26 20:41:26 +08:00
90d8330798 添加应用介绍窗口资源 2026-04-26 20:03:17 +08:00
b01482470a 实现画布控件背景颜色拾取和剪裁画布功能
- 画布控件创建canvasBitmap保存背景和图片
- 剪裁时剪裁画布控件而非原图片
- 添加颜色拾取按钮和功能
- 可从图片中拾取颜色设置为背景
2026-04-26 19:54:50 +08:00
57c36b09ac 合并裁剪框到画布控件并添加两指缩放功能
- 将剪裁框从CropOverlayView移到CropCanvasView
- 删除独立的剪裁窗口CropOverlayView
- 添加两指缩放剪裁框大小的功能
2026-04-26 19:23:28 +08:00
b5d68dfd1e 添加画布视图控件类用于相册集封面裁剪
- 将CropCanvas改造成CropCanvasView画布视图控件类
- 画布控件有宽度、高度和画布比例属性
- 初始化函数传入原始图片宽度、高度和图片比例
- 计算扩展高度和扩展宽度属性
- 画布高度和宽度取较大值
- 图片居中平铺到画布控件
2026-04-26 19:12:31 +08:00
262d35fb4e <gallery>APK 15.0.5 release Publish. 2026-04-26 11:32:37 +08:00
5b0bb599bb 添加相册集封面自定义功能
- 新增 AlbumCoverDbHelper 数据库类,存储相册集封面路径
- 相册集浏览窗口长按菜单添加"设置为封面"选项
- 封面图片右下角显示封面图标标识
- 主窗口加载时优先使用设置的封面图片
- 修复 Cursor 在 try-with-resources 中的处理问题
2026-04-26 11:27:41 +08:00
3955de100d 添加相册集浏览窗口排序功能
- 工具栏添加排序按钮组,支持时间倒序/正序、名称倒序/正序
- 排序设置保存到本地,应用重启后恢复默认排序方式
- 排序菜单显示当前选中的排序方法(带勾选标记)
- 修复置顶图片排序,保留原有排序顺序排在前面
2026-04-26 11:06:08 +08:00
7456db1729 <gallery>APK 15.0.4 release Publish. 2026-04-26 10:16:07 +08:00
226cbf43fe 添加滚动到顶部悬浮按钮及置顶图标样式优化
- 主窗口和相册集浏览窗口添加滚动到顶部悬浮按钮
- 当滚动超过一页时自动显示按钮,点击返回顶部
- 相册集浏览窗口置顶图标添加圆形半透明白色背景
- 圆形背景添加1dp黑色边框,图标为黑色
2026-04-26 10:10:09 +08:00
4a267d5606 添加相册集浏览窗口图片置顶功能
- 新增 PinnedImageDbHelper 数据库帮助类,存储图片置顶路径
- 使用独立数据库文件 pinned_image.db 存储图片置顶数据
- 相册集封面试图添加置顶/取消置顶菜单,长按弹出
- 置顶图片显示置顶图标并排在前面显示
- 相册集浏览窗口初始加载时按置顶排序
2026-04-26 09:46:02 +08:00
4d0d8c4d59 添加相册集置顶功能
- 长按相册封面显示菜单,可选择置顶
- 置顶的相册优先显示并按名称排序
- 非置顶的相册也按名称排序
- 置顶图标显示在封面上
2026-04-26 09:06:27 +08:00
9a43267b63 移除相册集窗口的工具栏菜单 2026-04-26 08:21:07 +08:00
c2618672bb <gallery>APK 15.0.3 release Publish. 2026-04-25 20:05:35 +08:00
6d9bd175f6 统一各界面的图片默认背景
- 主界面相册集预览封面使用背景设置
- 相册浏览界面图片预览使用背景设置
- 返回界面时自动刷新背景设置
2026-04-25 19:59:45 +08:00
ffbecaa31d 添加图片默认背景切换功能
- 工具栏添加背景切换按钮,点击弹出对话框
- 支持三种背景模式:灰白相间、全白、全黑
- 背景设置持久化保存,应用重启后保持原设置
- 切换背景时保持当前浏览的图片位置
2026-04-25 19:47:32 +08:00
e26df437c5 添加图片查看器棋盘格背景
- 设置图片显示控件的默认背景为5像素的灰色正方形与5像素的白色正方形间隔组成的棋盘格
- 10排 x 10排 = 100个方块
2026-04-25 19:19:36 +08:00
ecced75a4d 调整AboutActivity工具栏与MainActivity一致 2026-04-25 12:10:17 +08:00
5e5d34c90c 调整BaseFunctionItemView派生控件高度间隔为无间隔 2026-04-25 10:43:18 +08:00
85a0d39498 添加BaseFunctionItemView类视图1像素美化边框 2026-04-25 10:40:50 +08:00
c542d8dca7 源码整理 2026-04-25 10:25:59 +08:00
ccbdb4010e 调整一下libappbase模块中layout_about_view.xml的布局文件。缩小一下布局中控件的高度间隔。 2026-04-25 10:22:20 +08:00
248fd9d8d8 <gallery>APK 15.0.2 release Publish. 2026-04-25 06:42:01 +08:00
5b631710a9 添加图片信息按钮与信息窗口 2026-04-25 06:40:26 +08:00
cda85feddd <gallery>APK 15.0.1 release Publish. 2026-04-25 05:59:24 +08:00
ecad4a7913 调整应用初始化时的默认相册路径 2026-04-25 05:57:46 +08:00
2f14443624 <gallery>APK 15.0.0 release Publish. 2026-04-25 05:49:43 +08:00
21f38be35d 修复相册删除与恢复显示不正常的BUG 2026-04-25 05:43:45 +08:00
ab9ff33b72 更新说明书 2026-04-25 04:55:57 +08:00
5bc930e96d 把项目https://gitea.winboll.cc/Studio/Gallery_Bck20260425_042304_713.git移入WinBoLL项目系列 2026-04-25 04:52:16 +08:00
fe4060f00e <libaes>Library Release 15.15.9 2026-04-25 04:16:44 +08:00
676a3466ef <aes>APK 15.15.9 release Publish. 2026-04-25 04:16:30 +08:00
d6243b052d 更新基础类库 2026-04-25 04:14:59 +08:00
2e7b9173f2 <libappbase>Library Release 15.15.19 2026-04-25 04:12:02 +08:00
4f12a5de4f <appbase>APK 15.15.19 release Publish. 2026-04-25 04:11:46 +08:00
7ab399e520 编译调试 2026-04-25 04:10:29 +08:00
dd2d9f3e55 Merge branch 'appbase' of https://gitea.winboll.cc/Studio/WinBoLL.git into appbase 2026-04-25 04:07:49 +08:00
098516d4d7 Merge branch 'winboll' into appbase 2026-04-25 04:07:03 +08:00
5d72ee1a6a 更新基础类库 2026-04-25 04:05:05 +08:00
b1fab5ce46 <libappbase>Library Release 15.15.18 2026-04-25 03:59:02 +08:00
0ef6141c9d <contacts>APK 15.14.12 release Publish. 2026-04-18 21:14:59 +08:00
fb79b83705 隐藏调试信息 2026-04-18 21:13:45 +08:00
de89a326c0 设置特殊通道控件与倒计时同步更新。 2026-04-18 21:09:27 +08:00
dd95e1ea6c <contacts>APK 15.14.11 release Publish. 2026-04-18 20:58:20 +08:00
0237e72c62 添加倒计时特殊通道控制按钮 2026-04-18 20:56:22 +08:00
9771f5d8e0 <contacts>APK 15.14.10 release Publish. 2026-04-18 16:47:18 +08:00
4d0049f66f 特殊通道初步调试完成 2026-04-18 16:44:31 +08:00
eeb64b00b8 <aes>APK 15.15.8 release Publish. 2026-04-14 22:35:57 +08:00
8bcd803404 编译测试 2026-04-14 22:33:26 +08:00
76d20c32bf Merge branch 'winboll' into aes 2026-04-14 22:30:47 +08:00
876237fa04 <positions>APK 15.12.19 release Publish. 2026-04-11 21:07:32 +08:00
89604946e2 排版位置信息栏,方便编辑任务时对校时间。 2026-04-11 21:03:46 +08:00
c509a0126c <positions>APK 15.12.18 release Publish. 2026-04-11 13:00:42 +08:00
07a7409d10 添加任务编辑框的范围概念助记符号。 2026-04-11 12:58:22 +08:00
f1a7313ac1 <positions>APK 15.12.17 release Publish. 2026-04-11 02:20:27 +08:00
41492a2251 修复 Task 任务备注文本编辑框多行显示问题。 2026-04-11 02:18:47 +08:00
b5431ccac2 <positions>APK 15.12.16 release Publish. 2026-04-11 00:18:11 +08:00
ee3e202ecf 添加距离范围概念理解助记符号。 2026-04-11 00:15:34 +08:00
5e7828cf2b Merge branch 'winboll' into positions 2026-04-11 00:09:03 +08:00
e68098aa10 <appbase>APK 15.15.18 release Publish. 2026-04-10 05:38:18 +08:00
d673ba46a1 更正应用验证时使用的应用包名称配置 2026-04-10 05:36:14 +08:00
3231cd557a Merge branch 'winboll' into mymessagemanager 2026-04-09 13:16:14 +08:00
88431a6688 <contacts>APK 15.14.9 release Publish. 2026-04-09 02:36:26 +08:00
2b32bb91a4 添加云盾测试提示辅助识别字符。 2026-04-09 02:34:54 +08:00
e9c999dd73 <contacts>APK 15.14.8 release Publish. 2026-04-09 02:21:20 +08:00
96c828f27b 添加拨号记录列表的云盾防御测试菜单。 2026-04-09 02:19:02 +08:00
b48217cf91 <contacts>APK 15.14.7 release Publish. 2026-04-09 01:41:37 +08:00
177a9f66d4 Merge branch 'winboll' into contacts 2026-04-09 01:39:49 +08:00
6c8867e15c 更正maven库引用顺序 2026-04-09 01:38:28 +08:00
8a0b98cd4f 移除不能再联编的控件。 2026-04-08 23:55:14 +08:00
0eedbd75bb 编译配置调整为最新策略 2026-04-08 20:43:54 +08:00
32c25f1b0a 拷贝上个版本仓库提交点为d805fe8ebe3fc4157a4b8c7464635a84798106bc的contacts项目模块源码。 2026-04-08 20:38:48 +08:00
5a1342156f <appbase>APK 15.15.17 release Publish. 2026-04-06 20:39:30 +08:00
4e1784d99f 更换App为APP。以适配应用版本检查时使用的APK应用包名称APPBase。 2026-04-06 20:38:50 +08:00
069e5a66ad <appbase>APK 15.15.16 release Publish. 2026-04-06 20:25:21 +08:00
e9a1dca8ca 应用调试功能切换Logo准备完成。 2026-04-06 20:23:37 +08:00
7e3a3d1446 添加调试状态切换Logo控件 2026-04-06 19:42:34 +08:00
4c5df10c54 <positions>APK 15.12.15 release Publish. 2026-03-31 13:51:27 +08:00
c0084cd160 添加任务删除确定对话框,简化对话框提示信息。 2026-03-31 13:49:38 +08:00
f89dbede9c 源码整理 2026-03-31 13:27:10 +08:00
c946d7af3a 添加删除位置记录的确定对话框。 2026-03-31 13:24:09 +08:00
b8ddd87e66 <positions>APK 15.12.14 release Publish. 2026-03-31 13:03:31 +08:00
5ffc049790 在任务编辑窗口里,添加当前时间辅助显示。 2026-03-31 13:00:55 +08:00
0fcf1c5952 Merge remote-tracking branch 'origin/winboll' into positions 2026-03-27 13:10:10 +08:00
7414cd0f33 <appbase>APK 15.15.15 release Publish. 2026-03-25 20:52:43 +08:00
b2b3f949b7 更新Git仓库项目名称 2026-03-25 20:51:24 +08:00
83c1b888b6 <appbase>APK 15.15.14 release Publish. 2026-03-25 20:36:14 +08:00
6afc81939d <appbase>APK 15.15.13 release Publish. 2026-03-25 20:17:45 +08:00
1cf4c67b4f <appbase>APK 15.15.12 release Publish. 2026-03-25 20:17:45 +08:00
89697f8c49 Merge remote-tracking branch 'origin/winboll' into appbase 2026-03-25 20:16:31 +08:00
5419fad1cf 移除通用FTP服务应用数据备份功能 2026-03-25 19:47:06 +08:00
610d3811db 栏目字体与分段调整 2026-03-18 15:49:42 +08:00
2d949eb5a3 分类栏目排版 2026-03-18 15:44:01 +08:00
e6940805d9 明确操作优先级 2026-03-18 15:41:48 +08:00
1641424276 通顺表达句法。 2026-03-18 15:36:58 +08:00
5d1cdff283 使用Markdown语法调整说明书显示格式。 2026-03-18 15:32:49 +08:00
da66cea1e5 详细解析说明 2026-03-18 12:05:42 +08:00
5eb7441dc7 更新说明书 2026-03-18 12:03:32 +08:00
5f3168e17f 更新说明书 2026-03-18 11:49:58 +08:00
e3c4bab6c9 更正项目说明书。 2026-03-18 11:41:23 +08:00
6d907e46cb <positions>APK 15.12.13 release Publish. 2026-03-17 15:21:11 +08:00
5ab16c2387 添加任务列表里启用状态颜色标识。设置实时距离文字的绿色与任务列表的绿色协调一致。 2026-03-17 15:17:33 +08:00
d027c8affc <debugtemp>APK 15.0.54 release Publish. 2026-03-17 06:06:00 +08:00
7bf6007117 <debugtemp>APK 15.0.53 release Publish. 2026-03-17 06:01:21 +08:00
870bf6f8d2 <debugtemp>APK 15.0.52 release Publish. 2026-03-17 06:00:32 +08:00
8d8922f3f1 <debugtemp>APK 15.0.51 release Publish. 2026-03-17 05:59:14 +08:00
ff03270429 <debugtemp>APK 15.0.50 release Publish. 2026-03-17 05:58:36 +08:00
c9ec7e9a63 <debugtemp>APK 15.0.49 release Publish. 2026-03-17 05:57:27 +08:00
d747ab2ea8 <debugtemp>APK 15.0.48 release Publish. 2026-03-17 05:39:19 +08:00
eda55b23c3 <debugtemp>APK 15.0.47 release Publish. 2026-03-17 05:38:18 +08:00
df717290ec <debugtemp>APK 15.0.46 release Publish. 2026-03-17 05:21:21 +08:00
9dae7a01c4 <debugtemp>APK 15.0.45 release Publish. 2026-03-17 05:02:33 +08:00
fa34ef3b75 <debugtemp>APK 15.0.44 release Publish. 2026-03-17 05:00:41 +08:00
25b395b864 <debugtemp>APK 15.0.43 release Publish. 2026-03-17 04:59:54 +08:00
ef837e9b32 <debugtemp>APK 15.0.42 release Publish. 2026-03-17 04:59:06 +08:00
52677bf9cf <debugtemp>APK 15.0.41 release Publish. 2026-03-17 04:58:12 +08:00
209a73943e <debugtemp>APK 15.0.40 release Publish. 2026-03-17 04:57:25 +08:00
490d3c9da6 <debugtemp>APK 15.0.39 release Publish. 2026-03-17 04:54:53 +08:00
01109f9ae9 <debugtemp>APK 15.0.38 release Publish. 2026-03-17 04:53:33 +08:00
35a077d01f <debugtemp>APK 15.0.37 release Publish. 2026-03-17 04:47:35 +08:00
6003bec1a1 <debugtemp>APK 15.0.36 release Publish. 2026-03-17 04:09:24 +08:00
71f4663198 <debugtemp>APK 15.0.35 release Publish. 2026-03-17 04:07:14 +08:00
2af6427ca8 精简nfc action 数量 2026-03-17 04:03:20 +08:00
33e3a7c24a <debugtemp>APK 15.0.34 release Publish. 2026-03-17 04:02:43 +08:00
ed8391ad2c <debugtemp>APK 15.0.33 release Publish. 2026-03-17 03:58:58 +08:00
159d2199ae <debugtemp>APK 15.0.32 release Publish. 2026-03-17 03:53:19 +08:00
1365a1e499 <debugtemp>APK 15.0.31 release Publish. 2026-03-17 03:36:42 +08:00
b3a5407442 <debugtemp>APK 15.0.30 release Publish. 2026-03-17 03:35:06 +08:00
7a48358cbb <debugtemp>APK 15.0.29 release Publish. 2026-03-17 03:33:30 +08:00
04429ab30b <debugtemp>APK 15.0.28 release Publish. 2026-03-17 03:31:24 +08:00
d85b04f02d <debugtemp>APK 15.0.27 release Publish. 2026-03-17 03:26:16 +08:00
e641c66b72 <debugtemp>APK 15.0.26 release Publish. 2026-03-17 03:25:35 +08:00
69a9eb09af <debugtemp>APK 15.0.25 release Publish. 2026-03-17 03:23:44 +08:00
d3351b699b <debugtemp>APK 15.0.24 release Publish. 2026-03-17 03:22:48 +08:00
9971e94347 <debugtemp>APK 15.0.23 release Publish. 2026-03-17 03:20:26 +08:00
6487672697 <debugtemp>APK 15.0.22 release Publish. 2026-03-17 03:16:44 +08:00
86401f958e <debugtemp>APK 15.0.21 release Publish. 2026-03-17 03:13:55 +08:00
5a38f7b37e <debugtemp>APK 15.0.20 release Publish. 2026-03-17 03:12:49 +08:00
b6d767f0ba <debugtemp>APK 15.0.19 release Publish. 2026-03-17 03:04:26 +08:00
84e9bb6830 <debugtemp>APK 15.0.18 release Publish. 2026-03-17 02:55:02 +08:00
bbb9c7704c <debugtemp>APK 15.0.17 release Publish. 2026-03-17 02:35:34 +08:00
e44a3bc66b <debugtemp>APK 15.0.16 release Publish. 2026-03-16 16:45:07 +08:00
90a0eaad74 <debugtemp>APK 15.0.15 release Publish. 2026-03-16 16:44:27 +08:00
63354f21df <debugtemp>APK 15.0.14 release Publish. 2026-03-16 16:43:39 +08:00
b8c70bef98 调整NFC接口窗体根据动作类型指定对应的脚本运行。 2026-03-16 16:42:02 +08:00
828ae091e0 <debugtemp>APK 15.0.13 release Publish. 2026-03-16 16:41:15 +08:00
6cc0d1de00 <debugtemp>APK 15.0.12 release Publish. 2026-03-16 16:07:46 +08:00
a7aa90fb26 <debugtemp>APK 15.0.11 release Publish. 2026-03-16 16:06:01 +08:00
850a754d6c <debugtemp>APK 15.0.10 release Publish. 2026-03-16 16:04:13 +08:00
74c319f01a <debugtemp>APK 15.0.9 release Publish. 2026-03-16 15:16:39 +08:00
ede5ebf50f <debugtemp>APK 15.0.8 release Publish. 2026-03-15 20:29:37 +08:00
9da73f41f0 <debugtemp>APK 15.0.7 release Publish. 2026-03-15 20:28:26 +08:00
7713d6c460 基本实现NFC Build View 模块功能。 2026-03-15 20:25:16 +08:00
7c8e5a26b6 <debugtemp>APK 15.0.6 release Publish. 2026-03-15 20:23:52 +08:00
afb0525d0b <debugtemp>APK 15.0.5 release Publish. 2026-03-15 20:17:04 +08:00
73c69bd665 20260315_193958_991 2026-03-15 19:40:11 +08:00
d2afb716be <debugtemp>APK 15.0.4 release Publish. 2026-03-15 19:02:38 +08:00
41d6d453b2 <debugtemp>APK 15.0.3 release Publish. 2026-03-15 18:01:34 +08:00
a076fe50cd 调整Termux 调用模块 UI显示与环境参数配置。 2026-03-15 13:45:49 +08:00
fd9014ecb5 <debugtemp>APK 15.0.2 release Publish. 2026-03-15 13:43:58 +08:00
368d70f175 <debugtemp>APK 15.0.1 release Publish. 2026-03-15 13:21:13 +08:00
db4f18d077 <debugtemp>APK 15.0.0 release Publish. 2026-03-15 13:16:41 +08:00
bdf428a9fa 创建DebugTemp空项目 2026-03-15 13:03:26 +08:00
1512b76c36 编译参数修复 2026-03-15 11:55:10 +08:00
850b9af6ec 编译参数修复 2026-03-15 11:52:51 +08:00
31c1592086 Termux终端调用接口完成 2026-03-15 11:50:01 +08:00
b3976a8633 <winboll>APK 15.11.25 release Publish. 2026-03-15 11:48:40 +08:00
ea896228d7 <winboll>APK 15.11.24 release Publish. 2026-03-15 11:46:14 +08:00
d49ecb3943 <winboll>APK 15.11.23 release Publish. 2026-03-15 11:09:40 +08:00
ad3aecf867 <winboll>APK 15.11.22 release Publish. 2026-03-15 11:07:05 +08:00
c417d9732a <winboll>APK 15.11.21 release Publish. 2026-03-15 10:52:23 +08:00
7bd1357c8c <winboll>APK 15.11.20 release Publish. 2026-03-15 10:46:25 +08:00
16a2c3c0c8 <winboll>APK 15.11.19 release Publish. 2026-03-15 10:36:01 +08:00
b747d83972 <winboll>APK 15.11.18 release Publish. 2026-03-15 10:26:55 +08:00
f2788dda96 <winboll>APK 15.11.17 release Publish. 2026-03-15 10:09:28 +08:00
ea3a66bebe <winboll>APK 15.11.16 release Publish. 2026-03-15 10:07:00 +08:00
7db4c90e11 <powerbell>APK 15.15.16 release Publish. 2026-03-11 04:24:14 +08:00
8cc466dc3b 编译测试 2026-03-11 04:19:28 +08:00
29c024273d <powerbell>APK 15.15.15 release Publish. 2026-03-10 20:04:26 +08:00
e85fa2481a <powerbell>APK 15.15.14 release Publish. 2026-03-10 20:03:39 +08:00
4e1d72a02b <powerbell>APK 15.15.13 release Publish. 2026-03-10 20:02:12 +08:00
a21a2ba4a5 <powerbell>APK 15.15.12 release Publish. 2026-03-10 20:00:43 +08:00
f4c16dd332 <powerbell>APK 15.15.11 release Publish. 2026-03-02 23:42:54 +08:00
feae9f4e14 修正TTS电量通知语音描述。 2026-03-02 23:40:50 +08:00
c4f5f18089 <powerbell>APK 15.15.10 release Publish. 2026-03-01 00:24:19 +08:00
c030de64b8 隐藏TTS语音调试信息。 2026-03-01 00:22:21 +08:00
59094217da <powerbell>APK 15.15.9 release Publish. 2026-02-28 21:19:52 +08:00
6a17d3d7f9 添加语音播放模块实现部分。 2026-02-28 21:15:22 +08:00
bc730dffc8 设置窗口添加“电量通知TTS语音播放选项”。 2026-02-28 20:17:15 +08:00
65f0515139 <mymessagemanager>APK 15.12.7 release Publish. 2026-02-11 05:29:20 +08:00
5316ac1815 设置窗口UI优化。添加TTS悬浮窗口位置调整功能。 2026-02-11 05:22:36 +08:00
6c2581276e 复制 https://gitea.winboll.cc/Studio/WinBoLL_Bck20260112_122031_590.git mymessagemanager 分支最新源码。 2026-02-11 03:13:39 +08:00
f30a269129 Merge remote-tracking branch 'origin/winboll' into originmaster 2026-02-04 13:15:19 +08:00
17d1c2f321 Merge remote-tracking branch 'origin/winboll' into appbase 2026-02-04 13:14:53 +08:00
9e2affbc4d Merge remote-tracking branch 'origin/winboll' into aes 2026-02-04 13:14:30 +08:00
98b48a5926 Merge remote-tracking branch 'origin/winboll' into originmaster 2026-02-04 12:43:26 +08:00
aed4aa1a86 Merge remote-tracking branch 'origin/winboll' into aes 2026-02-04 12:40:44 +08:00
447b7fa5a8 Merge remote-tracking branch 'origin/winboll' into appbase 2026-02-04 12:39:22 +08:00
4b196acfce <powerbell>APK 15.15.8 release Publish. 2026-02-04 11:07:22 +08:00
cf857c1359 尝试叫豆包修复每天10半左右会无端端调用TTS服务的问题。 2026-02-04 11:04:13 +08:00
dae39b43d6 添加FTP备份目标保存路径设置。 2026-01-31 21:03:39 +08:00
530316b976 添加data与sdcard两种应用数据测试。 2026-01-31 20:07:56 +08:00
3f924b004c 完成应用Data区数据备份测试。 2026-01-31 19:47:12 +08:00
1db94b52e6 完成二次备份点击功能 2026-01-31 18:52:01 +08:00
55c653af09 应用备份打包上传功能完成 2026-01-31 14:18:26 +08:00
9d97d6ed94 正在调试FTP应用备份功能。 2026-01-30 21:38:04 +08:00
160614ce2a <positions>APK 15.12.12 release Publish. 2026-01-24 21:01:09 +08:00
1e0a9d222c 增加对签名证书修改后的证书识别能力。 2026-01-24 20:59:38 +08:00
81b758ddc5 <powerbell>APK 15.15.7 release Publish. 2026-01-24 20:41:05 +08:00
64a53058cc 增加对签名证书修改后的证书识别能力。 2026-01-24 20:38:52 +08:00
e21bb9058d <libappbase>Library Release 15.15.11 2026-01-24 20:32:30 +08:00
ad6175f977 <appbase>APK 15.15.11 release Publish. 2026-01-24 20:32:20 +08:00
8b659f4b24 编译参数修复 2026-01-24 20:31:49 +08:00
13b841f923 <libappbase>Library Release 15.15.10 2026-01-24 20:29:06 +08:00
e9ad701db4 <appbase>APK 15.15.10 release Publish. 2026-01-24 19:51:55 +08:00
0aaf71f285 添加对签名证书修改后的证书识别能力。 2026-01-24 19:50:43 +08:00
a93cad67a4 <positions>APK 15.12.11 release Publish. 2026-01-24 13:10:53 +08:00
db264eb85a 更新类库,类库应用联网验证模块有改进。 2026-01-24 13:09:20 +08:00
aae17b6cd2 <powerbell>APK 15.15.6 release Publish. 2026-01-24 12:44:08 +08:00
d6637e1c17 更新基础类库,新类库应用验证模块有改进。 2026-01-24 12:41:51 +08:00
4ea2b5fad0 <libappbase>Library Release 15.15.9 2026-01-24 12:32:11 +08:00
760fe4613f <appbase>APK 15.15.9 release Publish. 2026-01-24 12:31:02 +08:00
a656dfcc62 编译参数修复 2026-01-24 12:30:26 +08:00
e9605fa991 源码整理 2026-01-24 12:28:29 +08:00
8546b6c8ad 应用校验对话框UI显示调整完成。 2026-01-24 12:17:28 +08:00
f5ddefa895 <libappbase>Library Release 15.15.8 2026-01-24 11:26:46 +08:00
35527374da <appbase>APK 15.15.8 release Publish. 2026-01-24 11:26:28 +08:00
2751ce4a39 APK校验接口调试完成 2026-01-24 11:16:37 +08:00
730022a9f0 固定APK调试文件测试成功 2026-01-23 21:05:41 +08:00
8361cb0728 <positions>APK 15.12.10 release Publish. 2026-01-23 05:03:29 +08:00
92f94f462f 添加应用介绍窗口 2026-01-23 05:00:56 +08:00
22c719d87c Merge branch 'winboll' into positions 2026-01-23 03:23:15 +08:00
6d425cab5c <powerbell>APK 15.15.5 release Publish. 2026-01-23 03:18:09 +08:00
ab566f76ff 添加应用签名联网校验模块。 2026-01-23 03:15:06 +08:00
a3bc90d9b8 <libappbase>Library Release 15.15.7 2026-01-23 03:11:58 +08:00
32ee7c8845 <appbase>APK 15.15.7 release Publish. 2026-01-23 03:11:07 +08:00
6e34ee73e9 应用签名校验模块测试完成。 2026-01-23 03:09:19 +08:00
7eed7357f0 正在调整应用介绍窗口服务主机切换部分功能。。。 2026-01-23 00:29:17 +08:00
d20192cb36 正在改造WinBoLL主机切换对话框与按钮。。。 2026-01-22 21:19:39 +08:00
5846784940 应用签名联网验证模块完成。 2026-01-22 20:41:57 +08:00
ef64d6a317 添加按照tag编译Class的脚本,优化应用启动与单元测试流程。 2026-01-21 16:13:00 +08:00
cd04458c62 <powerbell>APK 15.15.4 release Publish. 2026-01-20 21:21:19 +08:00
6b46180da7 应用指纹校验对话框显示优化。 2026-01-20 21:19:53 +08:00
8b2a8328eb <libappbase>Library Release 15.15.6 2026-01-20 21:18:00 +08:00
88a20d9a85 <appbase>APK 15.15.6 release Publish. 2026-01-20 21:17:40 +08:00
aeaea253cb 应用指纹校验对话框显示优化。 2026-01-20 21:16:17 +08:00
af8b57b735 <powerbell>APK 15.15.3 release Publish. 2026-01-20 21:00:50 +08:00
21c02db577 更新基础类库,添加应用签名校验。 2026-01-20 20:59:01 +08:00
4890ca42cc <libappbase>Library Release 15.15.5 2026-01-20 20:54:58 +08:00
2896b6401b <appbase>APK 15.15.5 release Publish. 2026-01-20 20:54:24 +08:00
1aa270482e 添加应用指纹校验功能 2026-01-20 20:52:49 +08:00
49a35829d2 <powerbell>APK 15.15.2 release Publish. 2026-01-19 20:40:18 +08:00
8229ab099a 更新应用设置窗口的TTS服务选项卡的处理逻辑。 2026-01-19 20:38:46 +08:00
45400314af <powerbell>APK 15.15.1 release Publish. 2026-01-19 20:04:16 +08:00
f53113d0df TTS贴心服务加入当前电量语音提醒功能。 2026-01-19 20:01:28 +08:00
3f544f6097 <libaes>Library Release 15.15.7 2026-01-13 16:46:38 +08:00
6b44f852a8 <aes>APK 15.15.7 release Publish. 2026-01-13 16:46:27 +08:00
952c8d8017 移除BaseWinBoLLActivity作为类库使用,应用需自定义基础窗口类。 2026-01-13 16:45:31 +08:00
80b4b87e95 <libaes>Library Release 15.15.6 2026-01-13 16:27:36 +08:00
8b99844d0c <aes>APK 15.15.6 release Publish. 2026-01-13 16:27:29 +08:00
9f46f400b0 bugfix 2026-01-13 16:26:46 +08:00
40ea79c6b7 <libaes>Library Release 15.15.5 2026-01-13 16:12:37 +08:00
64693e384e <aes>APK 15.15.5 release Publish. 2026-01-13 16:11:30 +08:00
aebf83bc44 完善基础窗口类的公开方法 2026-01-13 16:10:31 +08:00
7ae716bccb <libaes>Library Release 15.15.4 2026-01-13 15:37:39 +08:00
3e67a5d0a4 <aes>APK 15.15.4 release Publish. 2026-01-13 15:37:28 +08:00
05a1fb1302 取消窗口创建时的吐司调试信息。 2026-01-13 15:36:23 +08:00
aa2e8e1a72 <libaes>Library Release 15.15.3 2026-01-13 15:29:53 +08:00
622d474410 <aes>APK 15.15.3 release Publish. 2026-01-13 15:29:29 +08:00
504b78c04e 优化基础窗口管理类。 2026-01-13 15:25:01 +08:00
53fb1ac835 <powerbell>APK 15.15.0 release Publish. 2026-01-13 11:47:03 +08:00
be9855b29d 更新基础类库与应用介绍页。 2026-01-13 11:44:12 +08:00
7ee79a44c7 <libaes>Library Release 15.15.2 2026-01-13 11:19:29 +08:00
e459791c67 <aes>APK 15.15.2 release Publish. 2026-01-13 11:19:15 +08:00
749ec3d562 APPBase 类库版本更新为 15.15.4 2026-01-13 11:13:54 +08:00
94483067cb <positions>APK 15.12.9 release Publish. 2026-01-13 06:44:22 +08:00
f21b69c64c 编译测试 2026-01-13 06:35:31 +08:00
30123efd4e 添加 https://gitea.winboll.cc/Studio/WinBoLL_Bck20260112_122031_590.git Positions项目分支源码 2026-01-13 06:34:30 +08:00
fec41fd737 移除类库外置编译配置易冲突文件,合并AES与APPBase。移除易冲突文件提交与修改。 2026-01-13 04:42:29 +08:00
2f58577537 Merge branch 'appbase' into originmaster 2026-01-13 04:28:40 +08:00
75e047f1d5 添加 https://gitea.winboll.cc/Studio/WinBoLL_Bck20260112_122031_590.git PowerBell 分支源码。 2026-01-13 04:09:05 +08:00
1f154de3ee <libaes>Library Release 15.15.1 2026-01-13 03:37:58 +08:00
98c51e01c6 <aes>APK 15.15.1 release Publish. 2026-01-13 03:37:01 +08:00
bc908f5d7c 编译测试 2026-01-13 03:33:51 +08:00
b9613efca3 复制 https://gitea.winboll.cc/Studio/AES_Bck20260112_122031_590.git 项目最新AES模块源码 2026-01-13 03:32:37 +08:00
3f96da4987 <libappbase>Library Release 15.15.4 2026-01-13 03:23:37 +08:00
1b265d50b2 <appbase>APK 15.15.4 release Publish. 2026-01-13 03:23:17 +08:00
89b8f975bb 编译测试 2026-01-13 03:20:35 +08:00
28d5f8823c 编译测试 2026-01-13 02:56:40 +08:00
735f842661 复制 https://gitea.winboll.cc/Studio/APPBase_Bck20260112_122031_590.git 项目最新appbase源码 2026-01-13 02:55:23 +08:00
1021 changed files with 89169 additions and 239 deletions

6
.gitignore vendored
View File

@@ -94,8 +94,8 @@ lint-results.html
## 忽略 AndroidIDE 临时文件夹
.androidide
## 忽略模块应用编译配置
/settings.gradle
/gradle.properties
## WinBoLL 基础应用(避免上传敏感配置)
/winboll.properties
/local.properties
/settings.gradle
/gradle.properties

View File

@@ -66,8 +66,8 @@ android {
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_11
targetCompatibility JavaVersion.VERSION_11
sourceCompatibility JavaVersion.VERSION_1_7
targetCompatibility JavaVersion.VERSION_1_7
}
// 应用包输出配置
@@ -101,12 +101,15 @@ android {
// 创建 WinBoLL Studio 发布接口文件夹
File fWinBoLLStudioDir = file("/sdcard/WinBoLLStudio/APKs");
// 如果配置了APK接口文件夹路径就设置应用APK输出文件夹为接口文件夹。
if(winbollProps != null && winbollProps['APKOutputPath'] != null ) {
fWinBoLLStudioDir = file(winbollProps['APKOutputPath']);
}
if(!fWinBoLLStudioDir.exists()) {
//fWinBoLLStudioDir.mkdirs();
// 如果没有发布接口文件就不用进行APK发布和源码管理操作
// 当前编译环境不是 WinBoLL 主机, 以下将忽略APK发布和源码管理操作。
println 'The current compilation environment is not in WinBoLL host, and the following APK publishing and source management operations will be ignore.'
} else {
println "[ WinBoLLStudio ] : " + fWinBoLLStudioDir.getAbsolutePath() + " Folder does not exist."
println '[ WinBoLLStudio ] : The APKOutputPath property is not defined in winboll.properties, please configure APK output folder first.'
} else {
/// WINBOLL 主机的 APK 发布和源码管理操作 ///
variant.getAssembleProvider().get().doFirst {
/* 后期管理预留代码 */

97
LICENSE-Private-Demo Normal file
View File

@@ -0,0 +1,97 @@
# WinBoLL 源码 LICENSE-Private-Demo 规范说明书
# LICENSE-Private-Demo
# WinBoLL 源码公共转私有继承开发规范守则
## 核心声明
本文档**唯一核心设计目的**:通过文件标识、分支隔离、操作规范、责任界定四重约束,**从根源规避私有开发分支代码被人为合并、推送、提交至公共开源主流分支的风险**,明确人为操作失误、违规合并的全部责任归属,同时保证私有分支可正常同步、拉取公共主流分支的上游更新。
## 一、文件宗旨与风险防控说明
本文件为 WinBoLL 项目公共开源分支转为私有独立分支开发的**强制标准化操作手册与责任界定文件**,核心风控目标:
1. 严格隔离公共开源分支与私有开发分支,通过授权文件标记实现分支属性一眼可辨,杜绝人为操作混淆
2. **重点防控人为操作导致的私有分支代码违规合并、回合、推送至公共 ****`winboll`**** 主流分支**,从流程上封堵合并风险
3. 明确所有开发提交者的操作责任,违规合并公共分支的行为由操作人承担全部代码泄露、合规风险
4. 规范私有分支初始化全流程,保证私有分支仅可单向同步公共分支更新,禁止任何反向代码流入公共分支
## 二、公私分支授权标识文件定义(风控核心依据)
### 1. 公共开源分支唯一标识
**文件名LICENSE**
- 仅允许存在于公共主流分支 `winboll` 及官方公共衍生分支
- 标识当前分支为**开源公开可贡献分支**,遵循原开源授权协议
- **严禁私有分支内保留、恢复此文件**,出现即判定分支属性异常
### 2. 私有开发分支唯一标识
**文件名LICENSE-Private**
- 仅允许存在于私有开发分支,**绝对禁止出现在公共 ****`winboll`**** 分支**
- 标识当前分支为**私有闭源分支**,代码仅限内部使用,禁止公开、禁止对外贡献
- 为本分支私有属性的法定判定依据,也是禁止合并至公共分支的核心标记
## 三、分支管理与合并风控规则(强制遵守)
1. **公共主流分支**:固定为 `winboll`,为项目唯一开源主线,仅保留 `LICENSE` 文件,**禁止接收任何私有分支的合并、提交、推送请求**。
2. **私有开发分支**:统一从 `winboll` 分支检出,命名固定格式为 `private-demo-*`,与公共分支物理隔离。
3. **核心合并风控铁则**
- 私有分支 → 公共分支:**永久禁止任何形式的合并、推送、PR 提交、代码回合,人为操作也绝不允许**
- 公共分支 → 私有分支:允许正常拉取、同步上游更新,不影响私有开发迭代
4. 所有仓库提交者、合并操作者,均视为已阅读并完全认可本规则,**人为执行私有分支向公共分支的合并操作,由操作人承担全部代码泄露、合规违约、项目安全风险**。
## 四、公共转私有标准化操作步骤(锁死合并风险)
请严格按顺序执行,每一步均为风控必要环节,不可跳过、不可修改顺序。
1. 基于公共主流分支 `winboll`,新建私有开发分支,严格使用 `private-demo-*` 命名,从名称上明确分支私有属性,避免人为混淆。
2. 本地仓库切换至新建私有分支,确认当前分支名称、检出来源无误。
3. **永久删除项目根目录公共授权文件 ****`LICENSE`**,彻底移除公共分支标识,断绝误合并的标识漏洞。
4. 将本规范文件 `LICENSE-Private-Demo` 复制并重命名为 `LICENSE-Private`,作为私有分支生效授权文件。
5. 将以上所有变更执行一次性 Git 提交,**提交信息必须固定使用以下内容,不可修改**
> 初始化私有开发分支,已切换私有授权文件,本分支禁止任何人为合并、推送至 winboll 公共分支
>
>
6. 提交完成后,本分支正式转为私有开发状态,后续所有代码提交、分支合并、版本迭代,均严禁指向公共 `winboll` 分支。
## 五、人为操作责任界定(核心补充条款)
1. 本分支所有开发者、代码提交者、分支合并操作者,均视为**完全知晓本分支的私有属性与合并禁令**,自愿遵守本规范全部约束。
2. **无论故意或过失,凡是人为执行私有分支向公共 ****`winboll`**** 分支的合并、推送、PR 提交、代码回合操作,全部责任由执行操作的本人独立承担**,项目方不承担任何因人为违规操作导致的代码泄露、开源合规、版本污染风险。
3. 仓库管理员需严格校验合并请求的分支标识与授权文件,发现带有 `LICENSE-Private` 标记的分支申请合并至公共分支,一律直接拒绝,并记录操作人信息。
4. 分支属性校验以根目录授权文件为唯一标准:只要分支内存在 `LICENSE-Private` 文件,就绝对禁止向公共分支发起任何合并操作。
## 六、分支状态校验与异常处理
- 合规公共分支:仅存在 `LICENSE`,无 `LICENSE-Private`
- 合规私有分支:仅存在 `LICENSE-Private`,无 `LICENSE`
- 异常状态:两个文件同时存在 / 均不存在 → 立即停止开发与提交,按本规范重置分支状态,严禁执行任何合并操作
> (注:文档部分内容可能由 AI 生成)

View File

@@ -0,0 +1,97 @@
# WinBoLL 源码 LICENSE-Private-Demo 规范说明书
# LICENSE-Private-Demo
# WinBoLL 源码公共转私有继承开发规范守则
## 核心声明
本文档**唯一核心设计目的**:通过文件标识、分支隔离、操作规范、责任界定四重约束,**从根源规避私有开发分支代码被人为合并、推送、提交至公共开源主流分支的风险**,明确人为操作失误、违规合并的全部责任归属,同时保证私有分支可正常同步、拉取公共主流分支的上游更新。
## 一、文件宗旨与风险防控说明
本文件为 WinBoLL 项目公共开源分支转为私有独立分支开发的**强制标准化操作手册与责任界定文件**,核心风控目标:
1. 严格隔离公共开源分支与私有开发分支,通过授权文件标记实现分支属性一眼可辨,杜绝人为操作混淆
2. **重点防控人为操作导致的私有分支代码违规合并、回合、推送至公共 ****`winboll`**** 主流分支**,从流程上封堵合并风险
3. 明确所有开发提交者的操作责任,违规合并公共分支的行为由操作人承担全部代码泄露、合规风险
4. 规范私有分支初始化全流程,保证私有分支仅可单向同步公共分支更新,禁止任何反向代码流入公共分支
## 二、公私分支授权标识文件定义(风控核心依据)
### 1. 公共开源分支唯一标识
**文件名LICENSE**
- 仅允许存在于公共主流分支 `winboll` 及官方公共衍生分支
- 标识当前分支为**开源公开可贡献分支**,遵循原开源授权协议
- **严禁私有分支内保留、恢复此文件**,出现即判定分支属性异常
### 2. 私有开发分支唯一标识
**文件名LICENSE-Private**
- 仅允许存在于私有开发分支,**绝对禁止出现在公共 ****`winboll`**** 分支**
- 标识当前分支为**私有闭源分支**,代码仅限内部使用,禁止公开、禁止对外贡献
- 为本分支私有属性的法定判定依据,也是禁止合并至公共分支的核心标记
## 三、分支管理与合并风控规则(强制遵守)
1. **公共主流分支**:固定为 `winboll`,为项目唯一开源主线,仅保留 `LICENSE` 文件,**禁止接收任何私有分支的合并、提交、推送请求**。
2. **私有开发分支**:统一从 `winboll` 分支检出,命名固定格式为 `private-demo-*`,与公共分支物理隔离。
3. **核心合并风控铁则**
- 私有分支 → 公共分支:**永久禁止任何形式的合并、推送、PR 提交、代码回合,人为操作也绝不允许**
- 公共分支 → 私有分支:允许正常拉取、同步上游更新,不影响私有开发迭代
4. 所有仓库提交者、合并操作者,均视为已阅读并完全认可本规则,**人为执行私有分支向公共分支的合并操作,由操作人承担全部代码泄露、合规违约、项目安全风险**。
## 四、公共转私有标准化操作步骤(锁死合并风险)
请严格按顺序执行,每一步均为风控必要环节,不可跳过、不可修改顺序。
1. 基于公共主流分支 `winboll`,新建私有开发分支,严格使用 `private-demo-*` 命名,从名称上明确分支私有属性,避免人为混淆。
2. 本地仓库切换至新建私有分支,确认当前分支名称、检出来源无误。
3. **永久删除项目根目录公共授权文件 ****`LICENSE`**,彻底移除公共分支标识,断绝误合并的标识漏洞。
4. 将本规范文件 `LICENSE-Private-Demo` 复制并重命名为 `LICENSE-Private`,作为私有分支生效授权文件。
5. 将以上所有变更执行一次性 Git 提交,**提交信息必须固定使用以下内容,不可修改**
> 初始化私有开发分支,已切换私有授权文件,本分支禁止任何人为合并、推送至 winboll 公共分支
>
>
6. 提交完成后,本分支正式转为私有开发状态,后续所有代码提交、分支合并、版本迭代,均严禁指向公共 `winboll` 分支。
## 五、人为操作责任界定(核心补充条款)
1. 本分支所有开发者、代码提交者、分支合并操作者,均视为**完全知晓本分支的私有属性与合并禁令**,自愿遵守本规范全部约束。
2. **无论故意或过失,凡是人为执行私有分支向公共 ****`winboll`**** 分支的合并、推送、PR 提交、代码回合操作,全部责任由执行操作的本人独立承担**,项目方不承担任何因人为违规操作导致的代码泄露、开源合规、版本污染风险。
3. 仓库管理员需严格校验合并请求的分支标识与授权文件,发现带有 `LICENSE-Private` 标记的分支申请合并至公共分支,一律直接拒绝,并记录操作人信息。
4. 分支属性校验以根目录授权文件为唯一标准:只要分支内存在 `LICENSE-Private` 文件,就绝对禁止向公共分支发起任何合并操作。
## 六、分支状态校验与异常处理
- 合规公共分支:仅存在 `LICENSE`,无 `LICENSE-Private`
- 合规私有分支:仅存在 `LICENSE-Private`,无 `LICENSE`
- 异常状态:两个文件同时存在 / 均不存在 → 立即停止开发与提交,按本规范重置分支状态,严禁执行任何合并操作
> (注:文档部分内容可能由 AI 生成)

View File

@@ -0,0 +1,97 @@
# WinBoLL 源码 LICENSE\-Private\-Demo 规范说明书
# LICENSE\-Private\-Demo
# WinBoLL 源码公共转私有继承开发规范守则
## 核心声明
本文档**唯一核心设计目的**:通过文件标识、分支隔离、操作规范、责任界定四重约束,**从根源规避私有开发分支代码被人为合并、推送、提交至公共开源主流分支的风险**,明确人为操作失误、违规合并的全部责任归属,同时保证私有分支可正常同步、拉取公共主流分支的上游更新。
## 一、文件宗旨与风险防控说明
本文件为 WinBoLL 项目公共开源分支转为私有独立分支开发的**强制标准化操作手册与责任界定文件**,核心风控目标:
1. 严格隔离公共开源分支与私有开发分支,通过授权文件标记实现分支属性一眼可辨,杜绝人为操作混淆
2. **重点防控人为操作导致的私有分支代码违规合并、回合、推送至公共 ****`winboll`**** 主流分支**,从流程上封堵合并风险
3. 明确所有开发提交者的操作责任,违规合并公共分支的行为由操作人承担全部代码泄露、合规风险
4. 规范私有分支初始化全流程,保证私有分支仅可单向同步公共分支更新,禁止任何反向代码流入公共分支
## 二、公私分支授权标识文件定义(风控核心依据)
### 1\. 公共开源分支唯一标识
**文件名LICENSE**
- 仅允许存在于公共主流分支 `winboll` 及官方公共衍生分支
- 标识当前分支为**开源公开可贡献分支**,遵循原开源授权协议
- **严禁私有分支内保留、恢复此文件**,出现即判定分支属性异常
### 2\. 私有开发分支唯一标识
**文件名LICENSE\-Private**
- 仅允许存在于私有开发分支,**绝对禁止出现在公共 ****`winboll`**** 分支**
- 标识当前分支为**私有闭源分支**,代码仅限内部使用,禁止公开、禁止对外贡献
- 为本分支私有属性的法定判定依据,也是禁止合并至公共分支的核心标记
## 三、分支管理与合并风控规则(强制遵守)
1. **公共主流分支**:固定为 `winboll`,为项目唯一开源主线,仅保留 `LICENSE` 文件,**禁止接收任何私有分支的合并、提交、推送请求**。
2. **私有开发分支**:统一从 `winboll` 分支检出,命名固定格式为 `private\-demo\-\*`,与公共分支物理隔离。
3. **核心合并风控铁则**
- 私有分支 → 公共分支:**永久禁止任何形式的合并、推送、PR 提交、代码回合,人为操作也绝不允许**
- 公共分支 → 私有分支:允许正常拉取、同步上游更新,不影响私有开发迭代
4. 所有仓库提交者、合并操作者,均视为已阅读并完全认可本规则,**人为执行私有分支向公共分支的合并操作,由操作人承担全部代码泄露、合规违约、项目安全风险**。
## 四、公共转私有标准化操作步骤(锁死合并风险)
请严格按顺序执行,每一步均为风控必要环节,不可跳过、不可修改顺序。
1. 基于公共主流分支 `winboll`,新建私有开发分支,严格使用 `private\-demo\-\*` 命名,从名称上明确分支私有属性,避免人为混淆。
2. 本地仓库切换至新建私有分支,确认当前分支名称、检出来源无误。
3. **永久删除项目根目录公共授权文件 ****`LICENSE`**,彻底移除公共分支标识,断绝误合并的标识漏洞。
4. 将本规范文件 `LICENSE\-Private\-Demo` 复制并重命名为 `LICENSE\-Private`,作为私有分支生效授权文件。
5. 将以上所有变更执行一次性 Git 提交,**提交信息必须固定使用以下内容,不可修改**
> 初始化私有开发分支,已切换私有授权文件,本分支禁止任何人为合并、推送至 winboll 公共分支
>
>
6. 提交完成后,本分支正式转为私有开发状态,后续所有代码提交、分支合并、版本迭代,均严禁指向公共 `winboll` 分支。
## 五、人为操作责任界定(核心补充条款)
1. 本分支所有开发者、代码提交者、分支合并操作者,均视为**完全知晓本分支的私有属性与合并禁令**,自愿遵守本规范全部约束。
2. **无论故意或过失,凡是人为执行私有分支向公共 ****`winboll`**** 分支的合并、推送、PR 提交、代码回合操作,全部责任由执行操作的本人独立承担**,项目方不承担任何因人为违规操作导致的代码泄露、开源合规、版本污染风险。
3. 仓库管理员需严格校验合并请求的分支标识与授权文件,发现带有 `LICENSE\-Private` 标记的分支申请合并至公共分支,一律直接拒绝,并记录操作人信息。
4. 分支属性校验以根目录授权文件为唯一标准:只要分支内存在 `LICENSE\-Private` 文件,就绝对禁止向公共分支发起任何合并操作。
## 六、分支状态校验与异常处理
- 合规公共分支:仅存在 `LICENSE`,无 `LICENSE\-Private`
- 合规私有分支:仅存在 `LICENSE\-Private`,无 `LICENSE`
- 异常状态:两个文件同时存在 / 均不存在 → 立即停止开发与提交,按本规范重置分支状态,严禁执行任何合并操作
> (注:文档部分内容可能由 AI 生成)

Binary file not shown.

After

Width:  |  Height:  |  Size: 710 KiB

207
README.md
View File

@@ -1,104 +1,105 @@
WinBoLL 源生态计划项目说明书
# WinBoLL 源生态计划项目说明书
## 一、项目概述
### 1. 核心定位
WinBoLL 手机源码计划,旨在通过核心项目 WinBoLL 构建手机端与服务器端的 Android 项目的开发源码生态。实现手机与服务器的源码的联合开发。
### 2. 仓库架构
#### **仓库类型:功能说明**
☆ 基础项目分支 WinBoLL手机端安卓应用开发基础模板。
☆ 应用项目分支 APPBase、AES、PowerBell、Positions**:安卓应用单一管理系列项目。
☆ 源码汇总管理 OriginMaster**:各类分支源码合并存档,不适宜作为开发库使用。
### 3. 源码合并管理推送路线图
⚠️ **注意**:仅仅展示不同应用模块源码的综合管理路线。分支合并操作时,必须具备 Git 管理经验。
★ WinBoLL → APPBase → OriginMaster
★ WinBoLL → AES → OriginMaster
★ WinBoLL → PowerBell → OriginMaster
★ WinBoLL → Positions → OriginMaster
## 二、WinBoLL 项目核心信息
### 1. 项目简介
☆ WinBoLL 项目是为手机端开发Android 项目的需求而设计的项目。
### 2. 官方资源
#### ☆ 官方网站**https://www.winboll.cc/
#### ☆ 源码地址:
★ Giteahttps://gitea.winboll.cc/Studio/WinBoLL.git
★ GitHubhttps://github.com/ZhanGSKen/WinBoLL.git
★ 码云https://gitee.com/zhangsken/winboll.git
## 三、应用编译环境检查问题
### 核心判断条件:
☆ WinBoLL 项目以文件夹 `"/sdcard/WinBoLLStudio/APKs"` 是否存在为判断环境编译输出条件因为编译输出的APK文件需要一个可供保存的环境。
☆ 文件夹"/sdcard/WinBoLLStudio/APKs" 目录条件设置方法:
***Linux 服务器端方面***:建立 `/sdcard/WinBoLLStudio/APKs` 目录即可。
***手机开发端方面***:建立 `"/sdcard/WinBoLLStudio/APKs"` 目录(即 `"/storage/emulated/0/WinBoLLStudio/APKs"` 目录) 即可。
## 四、前置条件
### 1. WinBoLL APP 开发环境配置介绍
#### WinBoLL APK 编译输出内容包括:
☆ "/sdcard/WinBoLLStudio/APKs"` 目录内的所有应用分支的 APK 文件。
winboll.properties 文件的 APKOutputPath 属性可配置这个 APK 输出目录的路径。
☆ "/sdcard/AppProjects/app.apk"文件。
winboll.properties 文件的 ExtraAPKOutputPath 属性可配置这个 APK 额外输出文件的路径
#### WinBoLL APK 源码命名空间规范
☆ WinBoLL 项目使用 "cc.winboll.studio" 作为源码命名空间。在此命名空间下进行源码定义。
## 五、核心需求规划
### 1. WinBoLL 应用安全验证需求
#### ☆ 支持访问 https://console.winboll.cc/ 服务器以校验应用包签名与版本。
### 2. 手机端源码开发管理需求
#### ☆ 支持切换不同 WinBoLL 分支,以开发不同安卓应用。
## 六、编译与使用指南
### 1. 项目初始化(必须)
#### 1. 复制 `settings.gradle-demo` 为 `settings.gradle`。编辑 `settings.gradle` 文件内容,取消对应项目模块注释。
#### 2. 复制 `gradle.properties-androidx-demo` (Android X 项目) 或 `gradle.properties-android-demo` (基本 Android 项目) 为 `gradle.properties`。
#### 3. 复制(可选)`local.properties-demo` 为 `local.properties`,编辑 `local.properties` 文件内容,配置 Android SDK 目录。
#### 4. **签名设置**
☆ **调试编译秘钥制作**:使用 Termux 应用终端cd 进入 GenKeyStore 目录,运行 `bash gen_debug_keystore.sh` 脚本即可生成应用调试秘钥。
☆ **应用秘钥配置方法**:拷贝调试编译秘钥制作生成的 `appkey.jks` 与 `appkey.keystore` 文件到项目根目录即可。
## 七、应用编译命令介绍
### 1类库型模块配置要点
#### 1. **优先修改配置文件**:优先修改应用测试项目(目录为 `"<WinBoLl根目录>/<类库测试应用>/"`)内 `build.properties` 文件,设置对应的类库项目名称:`libraryProject=<类库项目模块名>`。
#### 2. **编译优先启动步骤**:使用 Termux 应用,进入 `"<WinBoLl根目录>"`,运行 `$ bash .winboll/bashPublishAPKAddTag.sh <类库测试项目模块名>` 命令。运行后可生成测试项目与类库项目的编译参数文件 `build.properties`。生成的 `build.properties` 文件有两份,一份在测试项目模块的文件夹内,一份在类库项目本身的模块文件夹内。
#### 3. **最后类库编译发布步骤**:使用 Termux 应用,进入 `"<WinBoLl根目录>"`,运行 `$ bash .winboll/bashPublishLIBAddTag.sh <类库项目模块名>` 命令。运行后可发布至 WinBoLL Nexus Maven 库、本地 maven 目录或者是通用默认的 Gradle Maven 库。
### 2单一应用型模块与类库测试型模块配置要点
#### ☆ APK 编译方法:
使用 Termux 应用,进入 `"<WinBoLl根目录>"`,运行 `$ bash .winboll/bashPublishAPKAddTag.sh <应用项目模块名>`。
#### ☆ 运行后的 APK 输出路径:
★ 默认路径 (`$ bash gradlew assembleBetaDebug` 任务)APK 在 `/sdcard/WinBoLLStudio/APKs/<项目根目录名称>/debug/` 目录。
★ 默认路径 (`$ bash assembleStageRelease` 任务)APK 在 `/sdcard/WinBoLLStudio/APKs/<项目根目录名称>/tag/` 目录。
★ 额外输出路径:(假设 `winboll.properties` 文件已配置 `ExtraAPKOutputPath` 属性) 输出至 `ExtraAPKOutputPath` 属性配置的目录下。
### 3手机端应用调试命令介绍
#### ☆ Beta 渠道调试命令
$bash gradlew assembleBetaDebug
#### ☆ Stage 渠道调试命令
$bash gradlew assembleStageDebug
### 4服务器端开发命令介绍
##### ☆ Stage 渠道应用发布命令为:
"<WinBoLl根目录>/settings.gradle"文件需要配置编译模块开启参数,拷贝 settings.gradle-demo 为 settings.gradle 文件取消对应的分支配置部分即可。)
$bash .winboll/bashPublishAPKAddTag.sh <应用项目模块名> 
或者是
$bash gradlew assembleStageRelease
一、项目概述
1. 核心定位
【OriginMaster】WinBoLL 源生态计划,旨在通过核心项目 WinBoLL 联动系列开发库,构建手机端 Android 项目开发与多端编译同步的完整生态,实现手机与电脑的源码同步开发。
2. 仓库架构
仓库类型 包含仓库 功能说明
开发库 WinBoLL、APPBase、AES、PowerBell、Positions 核心开发依赖库,其中 WinBoLL 可作为应用开发的基础继承模板
分支汇总存档库 OriginMaster 仅用于汇总各开发库分支,不适宜作为开发库克隆使用,非应用开发基础库
3. 源码推送路径
- WinBoLL → APPBase → OriginMaster
- WinBoLL → AES → OriginMaster
- WinBoLL → PowerBell → OriginMaster
- WinBoLL → Positions → OriginMaster
二、WinBoLL APP 核心信息
1. 项目简介
WinBoLL Studio Android 应用开源项目,专注于手机端 Android 开发与多端编译同步。
2. 官方资源
- 官方网站https://www.winboll.cc/
- 源码地址:
- Giteahttps://gitea.winboll.cc/Studio/WinBoLL.git
- GitHubhttps://github.com/ZhanGSKen/WinBoLL.git
- 码云https://gitee.com/zhangsken/winboll.git
- 托管类库源码:
- APPBasejitpack.iohttps://github.com/ZhanGSKen/APPBase.git
- AESjitpack.iohttps://github.com/ZhanGSKen/AES.git
三、通用特征文件夹前置(/sdcard
- Linux 系统文件夹直接使用  /sdcard 
- 手机 SD 卡存储( /storage/emulated/0 挂载的别名也可为  /sdcard 
四、前置条件
1. WinBoLL-APP 配置
- APK 编译输出目录: /sdcard/WinBoLLStudio/APKs/ ,以及  /sdcard/AppProjects/ (命名为  app.apk 
- 签名与命名空间:支持应用签名验证定制化,与衍生 APP 共享  cc.winboll.studio  命名空间
五、核心需求规划
1. 主机端需求
- 支持  winboll.cc  域名的用户注册登录服务
- 支持  https://console.winboll.cc/api  访问
2. APP 端需求
- 实现手机端 Android 应用开发与管理功能
六、编译与使用指南
1. 项目初始化(必须)
1. 复制  settings.gradle-demo  settings.gradle 取消对应项目模块注释
2. 复制  gradle.properties-androidx-demo  gradle.properties-android-demo  gradle.properties 
3. (可选)复制  local.properties-demo  local.properties 配置 Android SDK 目录
4. 签名设置:
- 调试编译:进入 GenKeyStore 目录执行  bash gen_debug_keystore.sh 
- 非必须clone keystore 模块,拷贝  appkey.jks  appkey.keystore  到项目根目录
2. 编译命令
1类库型项目
1. 修改测试项目  build.properties 设置  libraryProject=<类库项目模块名> 
2. 编译测试项目 bash .winboll/bashPublishAPKAddTag.sh <应用项目模块名> 
3. 编译类库项目 bash .winboll/bashPublishLIBAddTag.sh <类库项目模块名> (发布至 WinBoLL Nexus Maven 库)
2应用型项目
- 编译命令 bash .winboll/bashPublishAPKAddTag.sh <应用项目模块名> 
3调试编译
- Beta 调试 bash gradlew assembleBetaDebug 
- Stage 调试 bash gradlew assembleStageDebug
 
4发布编译
- Stage 发布bash .winboll/bashPublishAPKAddTag.sh <应用项目模块名> 
或者执行  bash gradlew assembleStageRelease
3. 编译输出路径
- 默认路径(assembleBetaDebug任务) /sdcard/WinBoLLStudio/APKs/<项目根目录名称>/debug/ 
- 默认路径(assembleStageRelease任务) /sdcard/WinBoLLStudio/APKs/<项目根目录名称>/tag/ 
- 额外路径:若  winboll.properties  配置  ExtraAPKOutputPath APK 同步拷贝至该ExtraAPKOutputPath路径
4. 版本号命名规则
- Stage 渠道 V<应用开发环境编号><应用功能变更号><应用调试阶段号> 示例 APPBase_15.7.0 
- Beta 渠道 V<应用开发环境编号><应用功能变更号><应用调试阶段号>-beta<调试编译计数>_<调试编译时间(分钟+秒钟)> 示例 APPBase_15.9.6-beta8_5413 
## 八、WinBoLL 应用 APK 版本号命名规则
### ☆ Stage 渠道:
#### V<应用开发环境编号><应用功能变更号><应用调试阶段号> 示例 APPBase_15.7.0 
### ☆ Beta 渠道:
#### V<应用开发环境编号><应用功能变更号><应用调试阶段号>-beta<调试编译计数>_<调试编译时间(分钟+秒钟)> 示例 APPBase_15.9.6-beta8_5413 

1
aes/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
/build

36
aes/README.md Normal file
View File

@@ -0,0 +1,36 @@
# AES
[![](https://jitpack.io/v/ZhanGSKen/AES.svg)](https://jitpack.io/#ZhanGSKen/AES)
#### 介绍
WinBoLL AndroidX 可视化元素类库。
#### 软件架构
适配安卓应用 [AIDE Pro] 的 Gradle 编译结构。
也适配安卓应用 [AndroidIDE] 的 Gradle 编译结构。
#### Gradle 编译说明
调试版编译命令 gradle assembleBetaDebug
阶段版编译命令 bash .winboll/bashPublishAPKAddTag.sh aes
阶段版类库发布命令 git pull &&bash .winboll/bashPublishLIBAddTag.sh libaes
#### 使用说明
#### 参与贡献
1. Fork 本仓库
2. 新建 Feat_xxx 分支
3. 提交代码 : ZhanGSKen(ZhanGSKen<zhangsken@188.com>)
4. 新建 Pull Request
#### 特技
1. 使用 Readme\_XXX.md 来支持不同的语言,例如 Readme\_en.md, Readme\_zh.md
2. Gitee 官方博客 [blog.gitee.com](https://blog.gitee.com)
3. 你可以 [https://gitee.com/explore](https://gitee.com/explore) 这个地址来了解 Gitee 上的优秀开源项目
4. [GVP](https://gitee.com/gvp) 全称是 Gitee 最有价值开源项目,是综合评定出的优秀开源项目
5. Gitee 官方提供的使用手册 [https://gitee.com/help](https://gitee.com/help)
6. Gitee 封面人物是一档用来展示 Gitee 会员风采的栏目 [https://gitee.com/gitee-stars/](https://gitee.com/gitee-stars/)
#### 参考文档

View File

@@ -0,0 +1 @@

48
aes/build.gradle Normal file
View File

@@ -0,0 +1,48 @@
apply plugin: 'com.android.application'
apply from: '../.winboll/winboll_app_build.gradle'
apply from: '../.winboll/winboll_lint_build.gradle'
def genVersionName(def versionName){
// 检查编译标志位配置
assert (winbollBuildProps['stageCount'] != null)
assert (winbollBuildProps['baseVersion'] != null)
// 保存基础版本号
winbollBuildProps.setProperty("baseVersion", "${versionName}");
//保存编译标志配置
FileOutputStream fos = new FileOutputStream(winbollBuildPropsFile)
winbollBuildProps.store(fos, "${winbollBuildPropsDesc}");
fos.close();
// 返回编译版本号
return "${versionName}." + winbollBuildProps['stageCount']
}
android {
// 适配MIUI12
compileSdkVersion 30
buildToolsVersion "30.0.3"
defaultConfig {
applicationId "cc.winboll.studio.aes"
minSdkVersion 26
targetSdkVersion 30
versionCode 1
// versionName 更新后需要手动设置
// 项目模块目录的 build.gradle 文件的 stageCount=0
// Gradle编译环境下合起来的 versionName 就是 "${versionName}.0"
versionName "15.20"
if(true) {
versionName = genVersionName("${versionName}")
}
}
// 米盟 SDK
packagingOptions {
doNotStrip "*/*/libmimo_1011.so"
}
}
dependencies {
api project(':libaes')
api fileTree(dir: 'libs', include: ['*.jar'])
}

8
aes/build.properties Normal file
View File

@@ -0,0 +1,8 @@
#Created by .winboll/winboll_app_build.gradle
#Tue May 12 13:11:28 HKT 2026
stageCount=4
libraryProject=libaes
baseVersion=15.20
publishVersion=15.20.3
buildCount=0
baseBetaVersion=15.20.4

137
aes/proguard-rules.pro vendored Normal file
View File

@@ -0,0 +1,137 @@
# Add project specific ProGuard rules here.
# By default, the flags in this file are appended to flags specified
# in C:\tools\adt-bundle-windows-x86_64-20131030\sdk/tools/proguard/proguard-android.txt
# You can edit the include path and order by changing the proguardFiles
# directive in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# Add any project specific keep options here:
# ============================== 基础通用规则 ==============================
# 保留系统组件
-keep public class * extends android.app.Activity
-keep public class * extends android.app.Service
-keep public class * extends android.content.BroadcastReceiver
-keep public class * extends android.content.ContentProvider
-keep public class * extends android.app.backup.BackupAgentHelper
-keep public class * extends android.preference.Preference
# 保留 WinBoLL 核心包及子类(合并简化规则)
-keep class cc.winboll.studio.** { *; }
-keepclassmembers class cc.winboll.studio.** { *; }
# 保留所有类中的 public static final String TAG 字段(便于日志定位)
-keepclassmembers class * {
public static final java.lang.String TAG;
}
# 保留序列化类避免Parcelable/Gson解析异常
-keep class * implements android.os.Parcelable {
public static final android.os.Parcelable$Creator *;
}
-keepclassmembers class * implements java.io.Serializable {
static final long serialVersionUID;
private static final java.io.ObjectStreamField[] serialPersistentFields;
private void writeObject(java.io.ObjectOutputStream);
private void readObject(java.io.ObjectInputStream);
java.lang.Object writeReplace();
java.lang.Object readResolve();
}
# 保留 R 文件避免资源ID混淆
-keepclassmembers class **.R$* {
public static <fields>;
}
# 保留 native 方法避免JNI调用失败
-keepclasseswithmembernames class * {
native <methods>;
}
# 保留注解和泛型(避免反射/序列化异常)
-keepattributes *Annotation*
-keepattributes Signature
# 屏蔽 Java 8+ 警告(适配 Java 7 语法)
-dontwarn java.lang.invoke.*
-dontwarn android.support.v8.renderscript.*
-dontwarn java.util.function.**
# ============================== 第三方框架专项规则 ==============================
# OkHttp 4.4.1米盟广告请求依赖完善Lambda兼容
-keep class okhttp3.** { *; }
-keep interface okhttp3.** { *; }
-keep class okhttp3.internal.** { *; }
-keep class okio.** { *; }
-dontwarn okhttp3.internal.platform.**
-dontwarn okio.**
# Glide 4.9.0(米盟广告图片加载依赖)
-keep public class * implements com.bumptech.glide.module.GlideModule
-keep public class * extends com.bumptech.glide.module.AppGlideModule
-keep public enum com.bumptech.glide.load.ImageHeaderParser$ImageType {
**[] $VALUES;
public *;
}
-keepclassmembers class * implements com.bumptech.glide.module.AppGlideModule {
<init>();
}
-dontwarn com.bumptech.glide.**
# Gson 2.8.5(米盟广告数据序列化依赖)
-keep class com.google.gson.** { *; }
-keep interface com.google.gson.** { *; }
-keepclassmembers class * {
@com.google.gson.annotations.SerializedName <fields>;
}
# 米盟 SDK(核心广告组件,完整保留避免加载失败)
-keep class com.miui.zeus.** { *; }
-keep interface com.miui.zeus.** { *; }
# 保留米盟日志字段(便于广告加载失败排查)
-keepclassmembers class com.miui.zeus.mimo.sdk.** {
public static final java.lang.String TAG;
}
# RecyclerView 1.0.0(米盟广告布局渲染依赖)
-keep class androidx.recyclerview.** { *; }
-keep interface androidx.recyclerview.** { *; }
-keepclassmembers class androidx.recyclerview.widget.RecyclerView$Adapter {
public *;
}
# 其他第三方框架(按引入依赖保留,无则可删除)
# XXPermissions 18.63
-keep class com.hjq.permissions.** { *; }
-keep interface com.hjq.permissions.** { *; }
# ZXing 二维码(核心解析组件)
-keep class com.google.zxing.** { *; }
-keep class com.journeyapps.zxing.** { *; }
# Jsoup HTML解析
-keep class org.jsoup.** { *; }
# Pinyin4j 拼音搜索
-keep class net.sourceforge.pinyin4j.** { *; }
# JSch SSH组件
-keep class com.jcraft.jsch.** { *; }
# AndroidX 基础组件
-keep class androidx.appcompat.** { *; }
-keep interface androidx.appcompat.** { *; }
# ============================== 优化与调试配置 ==============================
# 优化级别(平衡混淆效果与性能)
-optimizationpasses 5
-optimizations !code/simplification/arithmetic,!code/simplification/cast,!field/*,!class/merging/*
# 调试辅助(保留行号便于崩溃定位)
-verbose
-dontpreverify
-dontusemixedcaseclassnames
-keepattributes SourceFile,LineNumberTable

View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" >
<application>
<!-- Put flavor specific code here -->
</application>
</manifest>

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Put flavor specific strings here -->
<string name="app_name">AES+</string>
</resources>

View File

@@ -0,0 +1,45 @@
<?xml version='1.0' encoding='utf-8'?>
<manifest
xmlns:android="http://schemas.android.com/apk/res/android"
package="cc.winboll.studio.aes">
<!-- 对正在运行的应用重新排序 -->
<uses-permission android:name="android.permission.REORDER_TASKS"/>
<application
android:name=".App"
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/MyAESTheme"
android:requestLegacyExternalStorage="true"
android:supportsRtl="true"
android:networkSecurityConfig="@xml/network_security_config">
<activity
android:name=".MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<meta-data
android:name="android.max_aspect"
android:value="4.0"/>
<activity android:name=".TestActivityManagerActivity"/>
<activity android:name=".SettingsActivity"/>
<activity android:name=".AboutActivity"/>
</application>
</manifest>

View File

@@ -0,0 +1,78 @@
package cc.winboll.studio.aes;
import android.os.Bundle;
import android.view.View;
import androidx.appcompat.widget.Toolbar;
import cc.winboll.studio.aes.R;
import cc.winboll.studio.libaes.utils.WinBoLLActivityManager;
import cc.winboll.studio.libappbase.LogUtils;
import cc.winboll.studio.libappbase.models.APPInfo;
import cc.winboll.studio.libappbase.views.AboutView;
/**
* @Author 豆包&ZhanGSKen<zhangsken@qq.com>
* @Date 2026/01/13 11:25
* @Describe 应用介绍窗口
*/
public class AboutActivity extends BaseWinBoLLActivity {
public static final String TAG = "AboutActivity";
private Toolbar mToolbar;
@Override
public String getTag() {
return TAG;
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_about);
// 设置工具栏
initToolbar();
AboutView aboutView = findViewById(R.id.aboutview);
aboutView.setAPPInfo(genDefaultAppInfo());
}
private void initToolbar() {
LogUtils.d(TAG, "initToolbar() 开始初始化");
mToolbar = findViewById(R.id.toolbar);
if (mToolbar == null) {
LogUtils.e(TAG, "initToolbar() | Toolbar未找到");
return;
}
setSupportActionBar(mToolbar);
mToolbar.setSubtitle(getTag());
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
mToolbar.setNavigationOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
LogUtils.d(TAG, "导航栏 点击返回按钮");
WinBoLLActivityManager.getInstance().resumeActivity(MainActivity.class);
WinBoLLActivityManager.getInstance().finish(AboutActivity.this);
}
});
LogUtils.d(TAG, "initToolbar() 配置完成");
}
private APPInfo genDefaultAppInfo() {
LogUtils.d(TAG, "genDefaultAppInfo() 调用");
String branchName = "aes";
APPInfo appInfo = new APPInfo();
appInfo.setAppName(getString(R.string.app_name));
appInfo.setAppIcon(R.drawable.ic_winboll);
appInfo.setAppDescription(getString(R.string.app_description));
appInfo.setAppGitName("AES");
appInfo.setAppGitOwner("Studio");
appInfo.setAppGitAPPBranch(branchName);
appInfo.setAppGitAPPSubProjectFolder(branchName);
appInfo.setAppHomePage("https://www.winboll.cc/apks/index.php?project=AES");
appInfo.setAppAPKName("AES");
appInfo.setAppAPKFolderName("AES");
LogUtils.d(TAG, "genDefaultAppInfo: 应用信息已生成");
return appInfo;
}
}

View File

@@ -0,0 +1,34 @@
package cc.winboll.studio.aes;
/**
* @Author ZhanGSKen<zhangsken@qq.com>
* @Date 2024/06/13 19:03:58
* @Describe AES应用类
*/
import android.view.Gravity;
import cc.winboll.studio.libaes.utils.WinBoLLActivityManager;
import cc.winboll.studio.libappbase.GlobalApplication;
import cc.winboll.studio.libappbase.ToastUtils;
public class App extends GlobalApplication {
public static final String TAG = "App";
@Override
public void onCreate() {
super.onCreate();
setIsDebugging(BuildConfig.DEBUG);
//setIsDebugging(false);
WinBoLLActivityManager.init(this);
// 初始化 Toast 框架
ToastUtils.init(this);
}
@Override
public void onTerminate() {
super.onTerminate();
ToastUtils.release();
}
}

View File

@@ -0,0 +1,45 @@
package cc.winboll.studio.aes;
import android.app.Activity;
import android.os.Bundle;
import androidx.appcompat.app.AppCompatActivity;
import cc.winboll.studio.libaes.interfaces.IWinBoLLActivity;
import cc.winboll.studio.libaes.models.AESThemeBean;
import cc.winboll.studio.libaes.utils.AESThemeUtil;
import cc.winboll.studio.libaes.utils.WinBoLLActivityManager;
/**
* @Author 豆包&ZhanGSKen<zhangsken@qq.com>
* @Date 2026/01/13 16:35
* @Describe BaseWinBollActivity 【继承AppCompatActivity保留核心能力不额外暴露方法】
* 继承链路BaseWinBoLLActivity → AppCompatActivity → FragmentActivityAppCompat能力天然继承可用
*/
public abstract class BaseWinBoLLActivity extends AppCompatActivity implements IWinBoLLActivity {
public static final String TAG = "BaseWinBoLLActivity";
protected volatile AESThemeBean.ThemeType mThemeType;
@Override
protected void onCreate(Bundle savedInstanceState) {
mThemeType = AESThemeBean.getThemeStyleType(AESThemeUtil.getThemeTypeID(getApplicationContext()));
setTheme(AESThemeUtil.getThemeTypeID(getApplicationContext()));
super.onCreate(savedInstanceState);
WinBoLLActivityManager.getInstance().add(this);
}
@Override
protected void onDestroy() {
WinBoLLActivityManager.getInstance().registeRemove(this);
super.onDestroy();
}
// 子类必须实现getTag(),确保唯一标识
@Override
public abstract String getTag();
@Override
public Activity getActivity() {
return this;
}
}

View File

@@ -0,0 +1,196 @@
package cc.winboll.studio.aes;
/**
* @Author ZhanGSKen<zhangsken@qq.com>
* @Date 2024/06/13 19:05:52
* @Describe 应用主窗口
*/
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.AdapterView;
import android.widget.Toast;
import cc.winboll.studio.aes.R;
import cc.winboll.studio.libaes.activitys.DrawerFragmentActivity;
import cc.winboll.studio.libaes.dialogs.LocalFileSelectDialog;
import cc.winboll.studio.libaes.dialogs.StoragePathDialog;
import cc.winboll.studio.libaes.interfaces.IWinBoLLActivity;
import cc.winboll.studio.libaes.models.DrawerMenuBean;
import cc.winboll.studio.libaes.unittests.SecondaryLibraryActivity;
import cc.winboll.studio.libaes.unittests.TestAButtonFragment;
import cc.winboll.studio.libaes.unittests.TestASupportToolbarActivity;
import cc.winboll.studio.libaes.unittests.TestAToolbarActivity;
import cc.winboll.studio.libaes.unittests.TestDrawerFragmentActivity;
import cc.winboll.studio.libaes.unittests.TestViewPageFragment;
import cc.winboll.studio.libaes.utils.WinBoLLActivityManager;
import cc.winboll.studio.libappbase.LogUtils;
import com.a4455jkjh.colorpicker.ColorPickerDialog;
import java.util.ArrayList;
public class MainActivity extends DrawerFragmentActivity {
public static final String TAG = "MainActivity";
TestAButtonFragment mTestAButtonFragment;
TestViewPageFragment mTestViewPageFragment;
@Override
public String getTag() {
return TAG;
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (mTestAButtonFragment == null) {
mTestAButtonFragment = new TestAButtonFragment();
addFragment(mTestAButtonFragment);
}
showFragment(mTestAButtonFragment);
//setSubtitle(TAG);
//ToastUtils.show("onCreate");
}
@Override
public void initDrawerMenuItemList(ArrayList<DrawerMenuBean> listDrawerMenu) {
super.initDrawerMenuItemList(listDrawerMenu);
LogUtils.d(TAG, "initDrawerMenuItemList");
//listDrawerMenu.clear();
// 添加抽屉菜单项
listDrawerMenu.add(new DrawerMenuBean(R.drawable.ic_launcher, TestAButtonFragment.TAG));
listDrawerMenu.add(new DrawerMenuBean(R.drawable.ic_launcher, TestViewPageFragment.TAG));
notifyDrawerMenuDataChanged();
}
@Override
public void reinitDrawerMenuItemList(ArrayList<DrawerMenuBean> listDrawerMenu) {
super.reinitDrawerMenuItemList(listDrawerMenu);
LogUtils.d(TAG, "reinitDrawerMenuItemList");
//listDrawerMenu.clear();
// 添加抽屉菜单项
listDrawerMenu.add(new DrawerMenuBean(R.drawable.ic_launcher, TestAButtonFragment.TAG));
listDrawerMenu.add(new DrawerMenuBean(R.drawable.ic_launcher, TestViewPageFragment.TAG));
notifyDrawerMenuDataChanged();
}
@Override
public DrawerFragmentActivity.ActivityType initActivityType() {
return DrawerFragmentActivity.ActivityType.Main;
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.toolbar_main, menu);
// if(App.isDebugging()) {
// getMenuInflater().inflate(cc.winboll.studio.libaes.R.menu.toolbar_studio_debug, menu);
// }
return super.onCreateOptionsMenu(menu);
}
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
super.onItemClick(parent, view, position, id);
switch (position) {
case 0 : {
if (mTestAButtonFragment == null) {
mTestAButtonFragment = new TestAButtonFragment();
addFragment(mTestAButtonFragment);
}
showFragment(mTestAButtonFragment);
break;
}
case 1 : {
if (mTestViewPageFragment == null) {
mTestViewPageFragment = new TestViewPageFragment();
addFragment(mTestViewPageFragment);
}
showFragment(mTestViewPageFragment);
break;
}
}
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
int nItemId = item.getItemId();
if (item.getItemId() == R.id.item_testactivitymanager) {
WinBoLLActivityManager.getInstance().startWinBoLLActivity(this, TestActivityManagerActivity.class);
//ToastUtils.show("item_testactivitymanager");
} else
if (nItemId == R.id.item_atoast) {
Toast.makeText(getApplication(), "item_testatoast", Toast.LENGTH_SHORT).show();
} else if (nItemId == R.id.item_atoolbar) {
Intent intent = new Intent(this, TestAToolbarActivity.class);
startActivity(intent);
} else if (nItemId == R.id.item_asupporttoolbar) {
Intent intent = new Intent(this, TestASupportToolbarActivity.class);
startActivity(intent);
} else if (nItemId == R.id.item_colordialog) {
ColorPickerDialog dlg = new ColorPickerDialog(this, getResources().getColor(R.color.colorPrimary));
dlg.setOnColorChangedListener(new com.a4455jkjh.colorpicker.view.OnColorChangedListener() {
@Override
public void beforeColorChanged() {
}
@Override
public void onColorChanged(int color) {
}
@Override
public void afterColorChanged() {
}
});
dlg.show();
} else if (nItemId == R.id.item_dialogstoragepath) {
final StoragePathDialog dialog = new StoragePathDialog(this, 0);
dialog.setOnOKClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
dialog.dismiss();
}
});
dialog.show();
} else if (nItemId == R.id.item_localfileselectdialog) {
final LocalFileSelectDialog dialog = new LocalFileSelectDialog(this);
dialog.setOnOKClickListener(new LocalFileSelectDialog.OKClickListener() {
@Override
public void onOKClick(String sz) {
Toast.makeText(getApplication(), sz, Toast.LENGTH_SHORT).show();
//dialog.dismiss();
}
});
dialog.open();
} else if (nItemId == R.id.item_secondarylibraryactivity) {
Intent intent = new Intent(this, SecondaryLibraryActivity.class);
startActivity(intent);
} else if (nItemId == R.id.item_drawerfragmentactivity) {
Intent intent = new Intent(this, TestDrawerFragmentActivity.class);
startActivity(intent);
} else if (nItemId == R.id.item_settings) {
Intent intent = new Intent(this, SettingsActivity.class);
startActivity(intent);
} else if (nItemId == R.id.item_about) {
// Intent intent = new Intent(this, AboutActivity.class);
// startActivity(intent);
WinBoLLActivityManager.getInstance().startWinBoLLActivity(this, AboutActivity.class);
}
return super.onOptionsItemSelected(item);
}
}

View File

@@ -0,0 +1,39 @@
package cc.winboll.studio.aes;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import cc.winboll.studio.libaes.views.ADsControlView;
/**
* @Author ZhanGSKen&豆包大模型<zhangsken@qq.com>
* @Date 2025/11/26 18:01
* @Describe SettingsActivity
*/
public class SettingsActivity extends Activity {
public static final String TAG = "SettingsActivity";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_settings);
ADsControlView adsControlView = (ADsControlView) findViewById(R.id.ads_control_view);
// adsControlView.setOnAdsModeSelectedListener(new ADsControlView.OnAdsModeSelectedListener() {
// @Override
// public void onModeSelected(ADsMode selectedMode) {
// if (selectedMode == ADsMode.STANDALONE) {
// // 处理单机模式逻辑(如释放米盟资源)
// ToastUtils.show("STANDALONE");
// } else if (selectedMode == ADsMode.MIMO_SDK) {
// // 处理米盟SDK模式逻辑如初始化SDK
// ToastUtils.show("MIMO_SDK");
// }
// }
// });
}
}

View File

@@ -0,0 +1,33 @@
package cc.winboll.studio.aes;
import android.app.Activity;
import android.os.Bundle;
import cc.winboll.studio.libaes.interfaces.IWinBoLLActivity;
/**
* @Author ZhanGSKen&豆包大模型<zhangsken@qq.com>
* @Date 2025/09/28 21:07
* @Describe 窗口管理类测试窗口
*/
public class TestActivityManagerActivity extends WinBoLLActivity implements IWinBoLLActivity {
public static final String TAG = "TestActivityManagerActivity";
@Override
public Activity getActivity() {
return this;
}
@Override
public String getTag() {
return TAG;
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_testactivitymanager);
}
}

View File

@@ -0,0 +1,60 @@
package cc.winboll.studio.aes;
/**
* @Author ZhanGSKen&豆包大模型<zhangsken@qq.com>
* @Date 2025/09/29 00:11
* @Describe WinBoLL 窗口基础类
*/
import android.app.Activity;
import android.os.Bundle;
import android.view.MenuItem;
import androidx.appcompat.app.AppCompatActivity;
import cc.winboll.studio.libaes.interfaces.IWinBoLLActivity;
import cc.winboll.studio.libaes.utils.WinBoLLActivityManager;
import cc.winboll.studio.libappbase.LogUtils;
public class WinBoLLActivity extends AppCompatActivity implements IWinBoLLActivity {
public static final String TAG = "WinBoLLActivity";
@Override
public Activity getActivity() {
return this;
}
@Override
public String getTag() {
return TAG;
}
@Override
protected void onResume() {
super.onResume();
LogUtils.d(TAG, String.format("onResume %s", getTag()));
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
/*if (item.getItemId() == R.id.item_log) {
WinBoLLActivityManager.getInstance().startLogActivity(this);
return true;
} else if (item.getItemId() == R.id.item_home) {
startActivity(new Intent(this, MainActivity.class));
return true;
}*/
// 在switch语句中处理每个ID并在处理完后返回true未处理的情况返回false。
return super.onOptionsItemSelected(item);
}
@Override
protected void onPostCreate(Bundle savedInstanceState) {
super.onPostCreate(savedInstanceState);
WinBoLLActivityManager.getInstance().add(this);
}
@Override
protected void onDestroy() {
super.onDestroy();
WinBoLLActivityManager.getInstance().finish(this);
}
}

View File

@@ -0,0 +1,32 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android" >
<item
android:left="2dp"
android:top="2dp"
android:right="2dp"
android:bottom="2dp">
<shape android:shape="rectangle" >
<gradient
android:angle="270"
android:endColor="#0F000000"
android:startColor="#0F000000" />
<corners android:radius="?attr/borderCornerRadius" />
</shape>
</item>
<item
android:left="3dp"
android:top="3dp"
android:right="3dp"
android:bottom="5dp">
<shape android:shape="rectangle" >
<gradient
android:angle="270"
android:endColor="#00000000"
android:startColor="#1AFFFFFF" />
<stroke
android:width="1dp"
android:color="#FF666666" />
<corners android:radius="?attr/borderCornerRadius" />
</shape>
</item>
</layer-list>

View File

@@ -0,0 +1,33 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android" >
<!-- 阴影部分 -->
<!-- 个人觉得更形象的表达top代表下边的阴影高度left代表右边的阴影宽度。其实也就是相对应的offsetsolid中的颜色是阴影的颜色也可以设置角度等等 -->
<item
android:left="2dp"
android:top="2dp"
android:right="2dp"
android:bottom="2dp">
<shape android:shape="rectangle" >
<gradient
android:angle="270"
android:endColor="#0F000000"
android:startColor="#0F000000" />
<corners android:radius="?attr/borderCornerRadius" />
</shape>
</item>
<!-- 背景部分 -->
<!-- 形象的表达bottom代表背景部分在上边缘超出阴影的高度right代表背景部分在左边超出阴影的宽度相对应的offset -->
<item
android:left="3dp"
android:top="3dp"
android:right="3dp"
android:bottom="5dp">
<shape android:shape="rectangle" >
<gradient
android:angle="270"
android:endColor="#0FFFFFFF"
android:startColor="#FFFFFFFF" />
<corners android:radius="?attr/borderCornerRadius" />
</shape>
</item>
</layer-list>

View File

@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent" android:background="@drawable/bg_container_border">
<cc.winboll.studio.libaes.views.ASupportToolbar
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/toolbar"/>
<cc.winboll.studio.libappbase.views.AboutView
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1.0"
android:id="@+id/aboutview"/>
</LinearLayout>

View File

@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent" android:background="@drawable/bg_container_border">
<cc.winboll.studio.libaes.views.ADsControlView
android:id="@+id/ads_control_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/bg_frame"
android:padding="10dp"/>
</LinearLayout>

View File

@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent" android:background="@drawable/bg_container_border">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="窗口管理类测试窗口"/>
</LinearLayout>

View File

@@ -0,0 +1,41 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/item_testactivitymanager"
android:title="TestActivityManager"/>
<item
android:id="@+id/item_log"
android:title="LogActivity"/>
<item
android:id="@+id/item_colordialog"
android:title="ColorDialog"/>
<item
android:id="@+id/item_dialogstoragepath"
android:title="StoragePathDialog"/>
<item
android:id="@+id/item_localfileselectdialog"
android:title="LocalFileSelectDialog"/>
<item
android:id="@+id/item_atoolbar"
android:title="Test AToolbar"/>
<item
android:id="@+id/item_asupporttoolbar"
android:title="Test ASupportToolbar"/>
<item
android:id="@+id/item_atoast"
android:title="Test AToast"/>
<item
android:id="@+id/item_secondarylibraryactivity"
android:title="Test SecondaryLibraryActivity"/>
<item
android:id="@+id/item_drawerfragmentactivity"
android:title="Test DrawerFragmentActivity"/>
<item
android:id="@+id/item_settings"
android:title="Settings"/>
<item
android:id="@+id/item_about"
android:title="About"/>
</menu>

View File

@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="MyAESTheme" parent="AESTheme">
<item name="themeDebug">@style/MyDebugActivityTheme</item>
</style>
<style name="MyDebugActivityTheme" parent="Theme.AppCompat.NoActionBar">
<item name="android:statusBarColor">@color/toolbarBackgroundColor</item>
<item name="colorTittle">@color/mainWindowTextColor</item>
<item name="colorTittleBackgound">@color/toolbarBackgroundColor</item>
<item name="colorText">@color/debugTextColor</item>
<item name="colorTextBackgound">@color/mainWindowBackgroundColor</item>
<item name="debugTextColor">@color/debugTextColor</item>
<item name="toolbarTextColor">@color/toolbarTextColor</item>
</style>
</resources>

View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="colorPrimary">#FF00B322</color>
<color name="colorPrimaryDark">#FF005C12</color>
<color name="colorAccent">#FF8DFFA2</color>
<color name="colorText">#FFFFFB8D</color>
</resources>

View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">AES</string>
<string name="app_description">WinBoLL AndroidX 可视化元素类库。</string>
</resources>

View File

@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="MyAESTheme" parent="AESTheme">
<item name="themeDebug">@style/MyDebugActivityTheme</item>
</style>
<style name="MyDebugActivityTheme" parent="Theme.AppCompat.Light.NoActionBar">
<item name="android:statusBarColor">@color/toolbarBackgroundColor</item>
<item name="colorTittle">@color/mainWindowTextColor</item>
<item name="colorTittleBackgound">@color/toolbarBackgroundColor</item>
<item name="colorText">@color/debugTextColor</item>
<item name="colorTextBackgound">@color/mainWindowBackgroundColor</item>
<item name="debugTextColor">@color/debugTextColor</item>
<item name="toolbarTextColor">@color/toolbarTextColor</item>
</style>
</resources>

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<domain-config cleartextTrafficPermitted="true">
<domain includeSubdomains="true">winboll.cc</domain>
</domain-config>
</network-security-config>

View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" >
<application>
<!-- Put flavor specific code here -->
</application>
</manifest>

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Put flavor specific strings here -->
</resources>

1
appbase/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
/build

36
appbase/README.md Normal file
View File

@@ -0,0 +1,36 @@
# APPBase
[![](https://jitpack.io/v/ZhanGSKen/APPBase.svg)](https://jitpack.io/#ZhanGSKen/APPBase)
#### 介绍
WinBoLL 安卓手机端安卓应用开发基础类库。
#### 软件架构
适配安卓应用 [AIDE Pro] 的 Gradle 编译结构。
也适配安卓应用 [AndroidIDE] 的 Gradle 编译结构。
#### Gradle 编译说明
调试版编译命令 gradle assembleBetaDebug
阶段版编译命令 bash .winboll/bashPublishAPKAddTag.sh appbase
阶段版类库发布命令 git pull &&bash .winboll/bashPublishLIBAddTag.sh libappbase
#### 使用说明
#### 参与贡献
1. Fork 本仓库
2. 新建 Feat_xxx 分支
3. 提交代码 : ZhanGSKen(ZhanGSKen<zhangsken@188.com>)
4. 新建 Pull Request
#### 特技
1. 使用 Readme\_XXX.md 来支持不同的语言,例如 Readme\_en.md, Readme\_zh.md
2. Gitee 官方博客 [blog.gitee.com](https://blog.gitee.com)
3. 你可以 [https://gitee.com/explore](https://gitee.com/explore) 这个地址来了解 Gitee 上的优秀开源项目
4. [GVP](https://gitee.com/gvp) 全称是 Gitee 最有价值开源项目,是综合评定出的优秀开源项目
5. Gitee 官方提供的使用手册 [https://gitee.com/help](https://gitee.com/help)
6. Gitee 封面人物是一档用来展示 Gitee 会员风采的栏目 [https://gitee.com/gitee-stars/](https://gitee.com/gitee-stars/)
#### 参考文档

View File

@@ -0,0 +1 @@

50
appbase/build.gradle Normal file
View File

@@ -0,0 +1,50 @@
apply plugin: 'com.android.application'
apply from: '../.winboll/winboll_app_build.gradle'
apply from: '../.winboll/winboll_lint_build.gradle'
def genVersionName(def versionName){
// 检查编译标志位配置
assert (winbollBuildProps['stageCount'] != null)
assert (winbollBuildProps['baseVersion'] != null)
// 保存基础版本号
winbollBuildProps.setProperty("baseVersion", "${versionName}");
//保存编译标志配置
FileOutputStream fos = new FileOutputStream(winbollBuildPropsFile)
winbollBuildProps.store(fos, "${winbollBuildPropsDesc}");
fos.close();
// 返回编译版本号
return "${versionName}." + winbollBuildProps['stageCount']
}
android {
// 适配MIUI12
compileSdkVersion 30
buildToolsVersion "30.0.3"
defaultConfig {
applicationId "cc.winboll.studio.appbase"
minSdkVersion 26
targetSdkVersion 30
versionCode 1
// versionName 更新后需要手动设置
// .winboll/winbollBuildProps.properties 文件的 stageCount=0
// Gradle编译环境下合起来的 versionName 就是 "${versionName}.0"
versionName "15.20"
if(true) {
versionName = genVersionName("${versionName}")
}
}
// 确保 Java 7 兼容性(已适配项目技术栈)
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_7
targetCompatibility JavaVersion.VERSION_1_7
}
}
dependencies {
api project(':libappbase')
api fileTree(dir: 'libs', include: ['*.jar'])
}

8
appbase/build.properties Normal file
View File

@@ -0,0 +1,8 @@
#Created by .winboll/winboll_app_build.gradle
#Tue May 12 09:17:15 HKT 2026
stageCount=10
libraryProject=libappbase
baseVersion=15.20
publishVersion=15.20.9
buildCount=0
baseBetaVersion=15.20.10

126
appbase/proguard-rules.pro vendored Normal file
View File

@@ -0,0 +1,126 @@
# Add project specific ProGuard rules here.
# By default, the flags in this file are appended to flags specified
# in C:\tools\adt-bundle-windows-x86_64-20131030\sdk/tools/proguard/proguard-android.txt
# You can edit the include path and order by changing the proguardFiles
# directive in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# Add any project specific keep options here:
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# ============================== 基础通用规则 ==============================
# 保留系统组件
-keep public class * extends android.app.Activity
-keep public class * extends android.app.Service
-keep public class * extends android.content.BroadcastReceiver
-keep public class * extends android.content.ContentProvider
-keep public class * extends android.app.backup.BackupAgentHelper
-keep public class * extends android.preference.Preference
# 保留 WinBoLL 核心包及子类(适配你的两个包名)
#-keep public class * extends com.winboll.WinBoLLActivity
#-keep public class * extends com.winboll.WinBoLLFragment
# 主包名
-keep class cc.winboll.studio.*.** { *; }
# beta包名
-keep class cc.winboll.studio.*.beta.** { *; }
-keepclassmembers class cc.winboll.studio.*.** { *; }
-keepclassmembers class cc.winboll.studio.*.beta.** { *; }
# 保留所有类中的 public static final String TAG 字段
-keepclassmembers class * {
public static final java.lang.String TAG;
}
# 保留序列化类
-keep class * implements android.os.Parcelable {
public static final android.os.Parcelable$Creator *;
}
-keepclassmembers class * implements java.io.Serializable {
static final long serialVersionUID;
private static final java.io.ObjectStreamField[] serialPersistentFields;
private void writeObject(java.io.ObjectOutputStream);
private void readObject(java.io.ObjectInputStream);
java.lang.Object writeReplace();
java.lang.Object readResolve();
}
# 保留 R 文件
-keepclassmembers class **.R$* {
public static <fields>;
}
# 保留 native 方法
-keepclasseswithmembernames class * {
native <methods>;
}
# 保留注解和泛型
-keepattributes *Annotation*
-keepattributes Signature
# 屏蔽 Java 8+ 警告(适配 Java 7
-dontwarn java.lang.invoke.*
-dontwarn android.support.v8.renderscript.*
-dontwarn java.util.function.**
# ============================== 第三方框架规则 ==============================
# Retrofit + OkHttp
-keep class retrofit2.** { *; }
-keep interface retrofit2.** { *; }
-keep class okhttp3.** { *; }
-keep interface okhttp3.** { *; }
-keep class okio.** { *; }
-keepclasseswithmembers class * {
@retrofit2.http.* <methods>;
}
# Glide 4.x
-keep public class * implements com.bumptech.glide.module.GlideModule
-keep public class * extends com.bumptech.glide.module.AppGlideModule
-keep public enum com.bumptech.glide.load.ImageHeaderParser$ImageType {
**[] $VALUES;
public *;
}
-dontwarn com.bumptech.glide.load.resource.bitmap.VideoDecoder
# GreenDAO 3.x
-keepclassmembers class * extends org.greenrobot.greendao.AbstractDao {
public static java.lang.String TABLENAME;
}
-keep class **$Properties
# 实体类包名(按实际调整)
#-keep class cc.winboll.studio.appbase.model.** { *; }
# ButterKnife 8.x
-keep class butterknife.** { *; }
-dontwarn butterknife.internal.**
-keep class **$$ViewBinder { *; }
-keepclasseswithmembernames class * {
@butterknife.BindView <fields>;
@butterknife.OnClick <methods>;
}
# EventBus 3.x
-keepclassmembers class ** {
@org.greenrobot.eventbus.Subscribe <methods>;
}
-keep enum org.greenrobot.eventbus.ThreadMode { *; }
# ============================== 优化与调试 ==============================
-optimizations !code/simplification/arithmetic,!code/simplification/cast,!field/*,!class/merging/*
-optimizationpasses 5
-verbose
-dontpreverify
-dontusemixedcaseclassnames
# 保留行号(便于崩溃定位)
-keepattributes SourceFile,LineNumberTable

View File

@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" >
<application
tools:replace="android:icon"
android:icon="@drawable/ic_winboll_beta">
<!-- Put flavor specific code here -->
</application>
</manifest>

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">APPBase+</string>
</resources>

View File

@@ -0,0 +1,57 @@
<?xml version='1.0' encoding='utf-8'?>
<manifest
xmlns:android="http://schemas.android.com/apk/res/android"
package="cc.winboll.studio.appbase">
<application
android:name=".App"
android:icon="@drawable/ic_winboll"
android:label="@string/app_name"
android:theme="@style/MyAPPBaseTheme"
android:resizeableActivity="true"
android:process=":App"
android:sharedUserId="@string/shared_user_id"
android:sharedUserLabel="@string/shared_user_label">
<activity
android:name=".MainActivity"
android:label="@string/app_name"
android:exported="true"
android:resizeableActivity="true"
android:launchMode="singleTop"
android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
</activity>
<activity
android:name=".Main2Activity"
android:label="@string/app_name"
android:exported="true"
android:resizeableActivity="true"
android:launchMode="singleTop"
android:taskAffinity="cc.winboll.studio.appbase.Main2Activity"
android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation">
</activity>
<activity android:name=".GlobalApplication$CrashActivity"/>
<meta-data
android:name="android.max_aspect"
android:value="4.0"/>
<activity android:name="cc.winboll.studio.libappbase.activities.CrashCopyReceiverActivity"/>
<activity android:name=".AboutActivity"/>
</application>
</manifest>

View File

@@ -0,0 +1,50 @@
package cc.winboll.studio.appbase;
import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.widget.Toolbar;
import cc.winboll.studio.appbase.R;
import cc.winboll.studio.libappbase.LogUtils;
import cc.winboll.studio.libappbase.models.APPInfo;
import cc.winboll.studio.libappbase.views.AboutView;
/**
* @Author 豆包&ZhanGSKen<zhangsken@qq.com>
* @Date 2026/01/11 12:55
* @Describe AboutActivity
*/
public class AboutActivity extends Activity {
public static final String TAG = "AboutActivity";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_about);
Toolbar toolbar = findViewById(R.id.toolbar);
setActionBar(toolbar);
AboutView aboutView = findViewById(R.id.aboutview);
aboutView.setAPPInfo(genDefaultAppInfo());
}
private APPInfo genDefaultAppInfo() {
LogUtils.d(TAG, "genDefaultAppInfo() 调用");
String branchName = "appbase";
APPInfo appInfo = new APPInfo();
appInfo.setAppName("APPBase");
appInfo.setAppIcon(R.drawable.ic_winboll);
appInfo.setAppDescription(getString(R.string.app_description));
appInfo.setAppGitName("WinBoLL");
appInfo.setAppGitOwner("Studio");
appInfo.setAppGitAPPBranch(branchName);
appInfo.setAppGitAPPSubProjectFolder(branchName);
appInfo.setAppHomePage("https://www.winboll.cc/apks/index.php?project=APPBase");
appInfo.setAppAPKName("APPBase");
appInfo.setAppAPKFolderName("APPBase");
LogUtils.d(TAG, "genDefaultAppInfo: 应用信息已生成");
return appInfo;
}
}

View File

@@ -0,0 +1,48 @@
package cc.winboll.studio.appbase;
import cc.winboll.studio.libappbase.GlobalApplication;
import cc.winboll.studio.libappbase.ToastUtils;
import cc.winboll.studio.libappbase.BuildConfig;
/**
* @Author ZhanGSKen<zhangsken@qq.com>
* @Date 2025/01/05 09:54:42
* @Describe 应用全局入口类(继承基础库 GlobalApplication
* 负责应用初始化、全局资源管理与生命周期回调处理,是整个应用的核心入口
*/
public class App extends GlobalApplication {
/** 当前应用类的日志 TAG用于调试输出标识日志来源 */
public static final String TAG = "App";
/**
* 应用创建时回调(全局初始化入口)
* 在应用进程启动时执行,仅调用一次,用于初始化全局工具类、第三方库等
*/
@Override
public void onCreate() {
super.onCreate();
// 如果应用不在调试状态,就根据编译类型设置调试状态
if (isDebugging() != true) {
setIsDebugging(BuildConfig.DEBUG);
}
// release 版调试码
//setIsDebugging(!BuildConfig.DEBUG);
// 初始化 Toast 工具类(传入应用全局上下文,确保 Toast 可在任意地方调用)
ToastUtils.init(getApplicationContext());
}
/**
* 应用终止时回调(资源释放入口)
* 仅在模拟环境(如 Android Studio 模拟器)中可靠触发,真机上可能因系统回收进程不执行
* 用于释放全局资源,避免内存泄漏
*/
@Override
public void onTerminate() {
super.onTerminate(); // 调用父类终止逻辑(如基础库资源释放)
// 释放 Toast 工具类资源(销毁全局 Toast 实例,避免内存泄漏)
ToastUtils.release();
}
}

View File

@@ -0,0 +1,28 @@
package cc.winboll.studio.appbase;
import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import cc.winboll.studio.libappbase.LogUtils;
import cc.winboll.studio.libappbase.ToastUtils;
public class CrashTestActivity extends Activity {
public static final String TAG = "CrashTestActivity";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_crash_test);
LogUtils.d(TAG, "CrashTestActivity onCreate()");
}
public void onBack(View view) {
finish();
}
public void onTestCrash(View view) {
LogUtils.d(TAG, "onTestCrash()");
ToastUtils.show("测试布局崩溃...");
}
}

View File

@@ -0,0 +1,20 @@
package cc.winboll.studio.appbase;
import android.os.Bundle;
import android.widget.Toolbar;
import cc.winboll.studio.appbase.R;
public class Main2Activity extends MainActivity {
public static final String TAG = "Main2Activity";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main2);
Toolbar toolbar = findViewById(R.id.toolbar);
if (toolbar != null) {
setActionBar(toolbar);
}
}
}

View File

@@ -0,0 +1,177 @@
package cc.winboll.studio.appbase;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.Toolbar;
import cc.winboll.studio.appbase.R;
import cc.winboll.studio.appbase.model.TestBean;
import cc.winboll.studio.libappbase.LogActivity;
import cc.winboll.studio.libappbase.LogUtils;
import cc.winboll.studio.libappbase.ToastUtils;
/**
* @Author ZhanGSKen<zhangsken@qq.com>
* @Date 未标注(建议补充创建日期)
* @Describe 应用主界面 Activity入口界面
* 包含功能测试按钮崩溃测试、日志查看、Toast测试、顶部工具栏菜单功能是应用交互的核心入口
*/
public class MainActivity extends Activity {
/** 当前 Activity 的日志 TAG用于调试输出标识日志来源 */
public static final String TAG = "MainActivity";
/** 顶部工具栏(用于展示标题、菜单,绑定布局中的 Toolbar 控件) */
private Toolbar mToolbar;
/**
* Activity 创建时回调(初始化界面)
* 在 Activity 首次创建时执行,用于加载布局、初始化控件、设置事件监听
* @param savedInstanceState 保存 Activity 状态的 Bundle如屏幕旋转时的数据恢复
*/
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//ToastUtils.show("onCreate"); // 显示 Activity 创建提示(调试用)
setContentView(R.layout.activity_main); // 加载主界面布局
// 初始化 Toolbar 并设置为 ActionBar
mToolbar = findViewById(R.id.toolbar);
setActionBar(mToolbar); // 将 Toolbar 替代系统默认 ActionBar
initTestData();
}
void initTestData() {
TestBean bean1 = new TestBean();
bean1.setTestNum1(456);
TestBean.saveBeanToFile(getFilesDir().getAbsolutePath() + getTestBeanRelativePath(), bean1);
TestBean bean2 = new TestBean();
bean2.setTestNum1(789);
TestBean.saveBeanToFile(getExternalFilesDir(null).getAbsolutePath() + getTestBeanRelativePath(), bean2);
}
String getTestBeanRelativePath() {
return "/BaseBaen/"+TestBean.class.getName()+".json";
}
/**
* 创建菜单时回调(加载工具栏菜单)
* 初始化 ActionBar 菜单,加载自定义菜单布局
* @param menu 菜单对象(用于承载菜单项)
* @return true显示菜单false不显示菜单
*/
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// 加载菜单布局R.menu.toolbar_main 为自定义菜单文件)
getMenuInflater().inflate(R.menu.toolbar_main, menu);
return super.onCreateOptionsMenu(menu);
}
/**
* 菜单 item 点击时回调(处理菜单事件)
* 响应 Toolbar 菜单项的点击事件,执行对应业务逻辑
* @param item 被点击的菜单项
* @return true消费点击事件false不消费传递给父类
*/
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.item_home:
// 点击 "首页/官网" 菜单项,唤起浏览器打开指定网站
openWebsiteInBrowser(this);
break;
// 可扩展其他菜单项(如设置、关于等)的处理逻辑
}
return super.onOptionsItemSelected(item);
}
/**
* 崩溃测试按钮点击事件(触发应用崩溃,用于调试异常捕获)
* 故意执行非法操作(循环获取不存在的字符串资源),强制应用崩溃
* @param view 触发事件的 View对应布局中的崩溃测试按钮
*/
public void onCrashTest(View view) {
// 循环从 Integer.MIN_VALUE 到 Integer.MAX_VALUE获取不存在的字符串资源 ID触发崩溃
for (int i = Integer.MIN_VALUE; i < Integer.MAX_VALUE; i++) {
getString(i); // i 超出资源 ID 范围,抛出 Resources.NotFoundException 导致崩溃
}
}
public void onLogTestNewTask(View view) {
LogActivity.startLogActivity(this, true);
}
/**
* 日志测试按钮点击事件(打开日志查看界面)
* 启动 LogActivity用于查看应用运行日志
* @param view 触发事件的 View对应布局中的日志测试按钮
*/
public void onLogTest(View view) {
LogActivity.startLogActivity(this, false);
}
/**
* Toast 工具测试按钮点击事件(测试全局 Toast 功能)
* 测试主线程、子线程中 Toast 的显示效果,验证 ToastUtils 的可用性
* @param view 触发事件的 View对应布局中的 Toast 测试按钮)
*/
public void onToastUtilsTest(View view) {
LogUtils.d(TAG, "onToastUtilsTest"); // 打印调试日志,标识进入 Toast 测试
ToastUtils.show("Hello, WinBoLL!"); // 主线程显示 Toast
// 开启子线程,延迟 2 秒后显示 Toast测试子线程 Toast 兼容性)
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(2000); // 线程休眠 2 秒
// 若 ToastUtils 已处理主线程切换,此处可直接调用;否则需通过 Handler 切换到主线程
ToastUtils.show("Thread.sleep(2000);ToastUtils.show...");
} catch (InterruptedException e) {
// 捕获线程中断异常(如线程被销毁时),不做处理(测试场景)
e.printStackTrace();
}
}
}).start();
}
/**
* 唤起系统默认浏览器打开指定网站(跳转至应用官网)
* 通过 Intent.ACTION_VIEW 隐式意图,触发浏览器打开目标 URL
* @param context 上下文对象(如 Activity、Application此处为 MainActivity
*/
public void openWebsiteInBrowser(Context context) {
String url = "https://www.winboll.cc"; // 目标网站 URL应用官网
// 构建隐式意图ACTION_VIEW 表示查看指定数据Uri 为网站地址)
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
// 设置标志:在新的任务栈中启动 Activity避免与当前应用任务栈混淆
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
// 启动意图(唤起浏览器)
context.startActivity(intent);
}
public void onAboutActivity(View view) {
LogUtils.d(TAG, "onAboutActivity() 调用");
Intent aboutIntent = new Intent(getApplicationContext(), AboutActivity.class);
startActivity(aboutIntent);
}
public void onMultiInstance(View view) {
LogUtils.d(TAG, "onMultiInstance() 多开窗口按钮已点击");
ToastUtils.show("多开窗口:已启动新窗口");
android.content.Intent intent = new android.content.Intent(this, Main2Activity.class);
intent.setFlags(android.content.Intent.FLAG_ACTIVITY_NEW_TASK);
LogUtils.d(TAG, "onMultiInstance() 准备启动Main2Activity");
startActivity(intent);
LogUtils.d(TAG, "onMultiInstance() Main2Activity已启动");
}
}

View File

@@ -0,0 +1,154 @@
package cc.winboll.studio.appbase.model;
import android.util.JsonReader;
import android.util.JsonWriter;
import cc.winboll.studio.libappbase.BaseBean;
import cc.winboll.studio.libappbase.LogUtils;
import java.io.IOException;
/**
* 测试实体类
* 继承BaseBean实现JSON序列化/反序列化能力提供基础int类型属性的封装与数据持久化支持
* 适配Java7语法遵循BaseBean统一的反射识别、JSON读写规范
* @Author 豆包&ZhanGSKen<zhangsken@qq.com>
* @Date 2026/01/31 19:16:00
* @LastEditTime 2026/02/01 10:46:00
*/
public class TestBean extends BaseBean {
// ====================================== 常量定义 ======================================
/** 当前类的日志 TAG用于调试输出 */
public static final String TAG = "TestBean";
// ====================================== 成员属性 ======================================
/**
* 测试数字属性默认值123
* 基础int类型属性用于测试BaseBean的JSON序列化/反序列化能力
*/
private int testNum1;
// ====================================== 构造方法 ======================================
/**
* 无参构造器(默认初始化)
* 给testNum1赋值默认值123满足反射实例化、JSON解析的无参构造要求
*/
public TestBean() {
this.testNum1 = 123;
LogUtils.d(TAG, "TestBean无参构造器调用testNum1默认初始化值" + this.testNum1);
}
/**
* 有参构造器(自定义初始化)
* @param testNum1 测试数字初始值
*/
public TestBean(int testNum1) {
this.testNum1 = testNum1;
LogUtils.d(TAG, "TestBean有参构造器调用传入testNum1" + testNum1);
}
// ====================================== Get/Set 方法 ======================================
/**
* 设置测试数字属性值
* @param testNum1 待设置的int类型值
*/
public void setTestNum1(int testNum1) {
LogUtils.d(TAG, "setTestNum1调用传入参数" + testNum1);
this.testNum1 = testNum1;
}
/**
* 获取测试数字属性值
* @return 当前testNum1的int类型值
*/
public int getTestNum1() {
LogUtils.d(TAG, "getTestNum1调用返回值" + this.testNum1);
return testNum1;
}
// ====================================== 重写父类BaseBean方法 ======================================
/**
* 重写父类方法:获取当前类的全限定名
* 用于BaseBean反射识别、类名匹配等统一逻辑
* @return 类全限定名cc.winboll.studio.appbase.model.TestBean
*/
@Override
public String getName() {
LogUtils.d(TAG, "getName方法调用返回类全限定名" + TestBean.class.getName());
return TestBean.class.getName();
}
/**
* 重写父类方法将当前对象序列化为JSON持久化存储专用
* 遵循BaseBean规范先执行父类序列化逻辑再处理子类专属字段
* @param jsonWriter JSON写入器外部传入的JSON流操作实例
* @throws IOException JSON写入异常流关闭、格式错误等
*/
@Override
public void writeThisToJsonWriter(JsonWriter jsonWriter) throws IOException {
LogUtils.d(TAG, "writeThisToJsonWriter调用传入参数JsonWriter" + jsonWriter);
// 执行父类公共字段的序列化逻辑
super.writeThisToJsonWriter(jsonWriter);
// 序列化子类专属字段testNum1
jsonWriter.name("testNum1").value(this.getTestNum1());
LogUtils.d(TAG, "writeThisToJsonWriter执行完成已序列化testNum1" + this.getTestNum1());
}
/**
* 重写父类方法从JSON字段初始化当前对象属性解析JSON专用
* 先让父类处理公共字段再匹配子类专属字段不匹配则返回false跳过
* @param jsonReader JSON读取器外部传入的JSON流操作实例
* @param name 当前解析的JSON字段名
* @return true-字段解析成功false-字段不匹配,需跳过/父类处理
* @throws IOException JSON读取异常字段类型不匹配、流中断等
*/
@Override
public boolean initObjectsFromJsonReader(JsonReader jsonReader, String name) throws IOException {
LogUtils.d(TAG, "initObjectsFromJsonReader调用传入参数name=" + name + "JsonReader=" + jsonReader);
// 父类优先处理公共字段,处理成功则直接返回
if (super.initObjectsFromJsonReader(jsonReader, name)) {
LogUtils.d(TAG, "initObjectsFromJsonReader字段" + name + "由父类BaseBean处理成功");
return true;
}
// 解析子类专属字段
if ("testNum1".equals(name)) {
this.setTestNum1(jsonReader.nextInt());
LogUtils.d(TAG, "initObjectsFromJsonReader解析testNum1成功值为" + this.getTestNum1());
} else {
LogUtils.w(TAG, "initObjectsFromJsonReader字段" + name + "不匹配返回false跳过解析");
// 字段不匹配返回false表示跳过
return false;
}
return true;
}
/**
* 重写父类方法从JSON读取器完整解析并初始化当前对象JSON解析入口
* 负责JSON对象的开始/结束标识处理,遍历所有字段并调用字段解析方法
* 严格遵循writeThisToJsonWriter的序列化结构保证解析一致性
* @param jsonReader JSON读取器外部传入的JSON流操作实例
* @return 解析后的当前TestBean实例支持链式调用
* @throws IOException JSON解析异常格式错误、字段缺失、流异常等
*/
@Override
public BaseBean readBeanFromJsonReader(JsonReader jsonReader) throws IOException {
LogUtils.d(TAG, "readBeanFromJsonReader调用传入参数JsonReader" + jsonReader);
// 开始解析JSON对象与序列化结构保持一致
jsonReader.beginObject();
// 遍历所有JSON字段
while (jsonReader.hasNext()) {
String fieldName = jsonReader.nextName();
LogUtils.d(TAG, "readBeanFromJsonReader开始解析字段fieldName=" + fieldName);
// 解析字段,不匹配则跳过该值
if (!this.initObjectsFromJsonReader(jsonReader, fieldName)) {
jsonReader.skipValue();
LogUtils.w(TAG, "readBeanFromJsonReader字段" + fieldName + "解析失败,已跳过该值");
}
}
// 结束JSON对象解析必须调用避免流异常
jsonReader.endObject();
LogUtils.d(TAG, "readBeanFromJsonReader执行完成JSON解析结束当前TestBean实例testNum1" + this.getTestNum1());
// 返回当前实例,支持链式调用
return this;
}
}

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
<solid android:color="@android:color/transparent" />
<stroke android:width="1dp" android:color="#FFB0B0B0" />
<corners android:radius="?attr/borderCornerRadius" />
</shape>

View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="#81C7F5"/> <!-- 浅蓝色填充 -->
<corners android:radius="8dp"/> <!-- 8dp 圆角 -->
</shape>

View File

@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?attr/activityBackgroundColor">
<android.widget.Toolbar
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/toolbarBackgroundColor"
android:id="@+id/toolbar"/>
<cc.winboll.studio.libappbase.views.AboutView
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1.0"
android:id="@+id/aboutview"/>
</LinearLayout>

View File

@@ -0,0 +1,106 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="0dp"
android:background="?attr/activityBackgroundColor">
<android.widget.Toolbar
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/toolbarBackgroundColor"
android:id="@+id/toolbar"/>
<ScrollView
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1.0">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:gravity="center_vertical"
android:spacing="12dp">
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="关于应用"
android:textSize="16sp"
android:textColor="?attr/activityTextColor"
android:background="?attr/toolbarBackgroundColor"
android:paddingVertical="12dp"
android:layout_marginHorizontal="24dp"
android:onClick="onAboutActivity"
android:layout_margin="10dp"/>
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="应用崩溃测试"
android:textSize="16sp"
android:textColor="?attr/activityTextColor"
android:background="?attr/toolbarBackgroundColor"
android:paddingVertical="12dp"
android:layout_marginHorizontal="24dp"
android:onClick="onCrashTest"
android:layout_margin="10dp"/>
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="应用日志测试"
android:textSize="16sp"
android:textColor="?attr/activityTextColor"
android:background="?attr/toolbarBackgroundColor"
android:paddingVertical="12dp"
android:layout_marginHorizontal="24dp"
android:onClick="onLogTest"
android:layout_margin="10dp"/>
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="应用日志测试(新窗口)"
android:textSize="16sp"
android:textColor="?attr/activityTextColor"
android:background="?attr/toolbarBackgroundColor"
android:paddingVertical="12dp"
android:layout_marginHorizontal="24dp"
android:onClick="onLogTestNewTask"
android:layout_margin="10dp"/>
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="应用吐司测试"
android:textSize="16sp"
android:textColor="?attr/activityTextColor"
android:background="?attr/toolbarBackgroundColor"
android:paddingVertical="12dp"
android:layout_marginHorizontal="24dp"
android:onClick="onToastUtilsTest"
android:layout_margin="10dp"/>
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="多开窗口"
android:textSize="16sp"
android:textColor="?attr/activityTextColor"
android:background="?attr/toolbarBackgroundColor"
android:paddingVertical="12dp"
android:layout_marginHorizontal="24dp"
android:onClick="onMultiInstance"
android:layout_margin="10dp"/>
</LinearLayout>
</ScrollView>
</LinearLayout>

View File

@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:background="@android:color/white">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Main2Activity"
android:textSize="24sp"
android:textColor="@color/gray_900"/>
</LinearLayout>

View File

@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?attr/activityBackgroundColor">
<android.widget.Toolbar
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/toolbarBackgroundColor"
android:id="@+id/toolbar"/>
<cc.winboll.studio.libappbase.views.AboutView
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1.0"
android:id="@+id/aboutview"/>
</LinearLayout>

View File

@@ -0,0 +1,60 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="0dp"
android:background="?attr/activityBackgroundColor">
<android.widget.Toolbar
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/toolbarBackgroundColor"
android:id="@+id/toolbar"/>
<ScrollView
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1.0">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:gravity="center_vertical">
<cc.winboll.studio.appbase.UndefinedCustomView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="10dp"/>
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="返回"
android:textSize="16sp"
android:textColor="?attr/activityTextColor"
android:background="?attr/toolbarBackgroundColor"
android:paddingVertical="12dp"
android:layout_marginHorizontal="24dp"
android:onClick="onBack"
android:layout_margin="10dp"/>
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="测试崩溃"
android:textSize="16sp"
android:textColor="?attr/activityTextColor"
android:background="?attr/toolbarBackgroundColor"
android:paddingVertical="12dp"
android:layout_marginHorizontal="24dp"
android:onClick="onTestCrash"
android:layout_margin="10dp"/>
</LinearLayout>
</ScrollView>
</LinearLayout>

View File

@@ -0,0 +1,106 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="0dp"
android:background="?attr/activityBackgroundColor">
<android.widget.Toolbar
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/toolbarBackgroundColor"
android:id="@+id/toolbar"/>
<ScrollView
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1.0">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:gravity="center_vertical"
android:spacing="12dp">
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="关于应用"
android:textSize="16sp"
android:textColor="?attr/activityTextColor"
android:background="?attr/toolbarBackgroundColor"
android:paddingVertical="12dp"
android:layout_marginHorizontal="24dp"
android:onClick="onAboutActivity"
android:layout_margin="10dp"/>
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="应用崩溃测试"
android:textSize="16sp"
android:textColor="?attr/activityTextColor"
android:background="?attr/toolbarBackgroundColor"
android:paddingVertical="12dp"
android:layout_marginHorizontal="24dp"
android:onClick="onCrashTest"
android:layout_margin="10dp"/>
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="应用日志测试"
android:textSize="16sp"
android:textColor="?attr/activityTextColor"
android:background="?attr/toolbarBackgroundColor"
android:paddingVertical="12dp"
android:layout_marginHorizontal="24dp"
android:onClick="onLogTest"
android:layout_margin="10dp"/>
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="应用日志测试(新窗口)"
android:textSize="16sp"
android:textColor="?attr/activityTextColor"
android:background="?attr/toolbarBackgroundColor"
android:paddingVertical="12dp"
android:layout_marginHorizontal="24dp"
android:onClick="onLogTestNewTask"
android:layout_margin="10dp"/>
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="应用吐司测试"
android:textSize="16sp"
android:textColor="?attr/activityTextColor"
android:background="?attr/toolbarBackgroundColor"
android:paddingVertical="12dp"
android:layout_marginHorizontal="24dp"
android:onClick="onToastUtilsTest"
android:layout_margin="10dp"/>
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="多开窗口"
android:textSize="16sp"
android:textColor="?attr/activityTextColor"
android:background="?attr/toolbarBackgroundColor"
android:paddingVertical="12dp"
android:layout_marginHorizontal="24dp"
android:onClick="onMultiInstance"
android:layout_margin="10dp"/>
</LinearLayout>
</ScrollView>
</LinearLayout>

View File

@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:background="?attr/activityBackgroundColor">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Main2Activity"
android:textSize="24sp"
android:textColor="?attr/activityTextColor"/>
</LinearLayout>

View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
</LinearLayout>

View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/item_home"
android:title="Home"
android:icon="@drawable/ic_winboll"
android:showAsAction="always"/>
</menu>

View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="colorPrimary">#FF1B8B29</color>
<color name="colorPrimaryDark">#FF0A5520</color>
<color name="colorAccent">#FF6EE87C</color>
<color name="colorText">#FFB8FF7D</color>
</resources>

View File

@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="MyAPPBaseTheme" parent="APPBaseTheme">
<item name="themeDebug">@style/MyDebugActivityTheme</item>
</style>
<style name="MyDebugActivityTheme" parent="DebugActivityTheme">
<item name="colorTittle">?attr/mainWindowDarkTextColor</item>
<item name="colorTittleBackgound">?attr/toolbarBackgroundColor</item>
<item name="colorText">?attr/debugTextColor</item>
<item name="colorTextBackgound">?attr/mainWindowDarkBackgroundColor</item>
<item name="toolbarTextColor">@color/toolbarTextColor</item>
</style>
</resources>

View File

@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<attr name="borderCornerRadius" format="dimension" />
<declare-styleable name="AboutView">
<attr name="app_name" format="string" />
<attr name="app_apkfoldername" format="string" />
<attr name="app_apkname" format="string" />
<attr name="app_gitname" format="string" />
<attr name="app_gitowner" format="string" />
<attr name="app_gitappbranch" format="string" />
<attr name="app_gitappsubprojectfolder" format="string" />
<attr name="appdescription" format="string" />
<attr name="appicon" format="reference" />
<attr name="is_adddebugtools" format="boolean" />
</declare-styleable>
</resources>

View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="colorPrimary">#FF00B322</color>
<color name="colorPrimaryDark">#FF005C12</color>
<color name="colorAccent">#FF8DFFA2</color>
<color name="colorText">#FFFFFB8D</color>
</resources>

View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">APPBase</string>
<string name="app_description">WinBoLL 安卓手机端安卓应用开发基础类库。</string>
<string name="app_normal">Click here is switch to Normal APP</string>
<string name="app_debug">Click here is switch to APP DEBUG</string>
<string name="gitea_home">GITEA HOME</string>
<string name="app_update">APP UPDATE</string>
</resources>

View File

@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="MyAPPBaseTheme" parent="APPBaseTheme">
<item name="themeDebug">@style/MyDebugActivityTheme</item>
</style>
<style name="MyDebugActivityTheme" parent="DebugActivityTheme">
<item name="colorTittle">?attr/mainWindowTextColor</item>
<item name="colorTittleBackgound">?attr/toolbarBackgroundColor</item>
<item name="colorText">?attr/debugTextColor</item>
<item name="colorTextBackgound">?attr/mainWindowBackgroundColor</item>
<item name="toolbarTextColor">@color/toolbarTextColor</item>
</style>
</resources>

View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" >
<application>
<!-- Put flavor specific code here -->
</application>
</manifest>

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Put flavor specific strings here -->
</resources>

View File

@@ -1,6 +1,15 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
repositories {
maven { url 'https://maven.aliyun.com/repository/public/' }
maven { url 'https://maven.aliyun.com/repository/google/' }
maven { url 'https://maven.aliyun.com/repository/gradle-plugin/' }
maven { url 'https://dl.bintray.com/ppartisan/maven/' }
maven { url "https://clojars.org/repo/" }
maven { url "https://jitpack.io" }
mavenCentral()
google()
mavenLocal {
// 设置本地Maven仓库路径
url 'file:///sdcard/.m2/repository/'
@@ -11,19 +20,6 @@ buildscript {
maven { url "https://nexus.winboll.cc/repository/maven-public/" }
// "WinBoLL Snapshot"
maven { url "https://nexus.winboll.cc/repository/maven-snapshots/" }
maven { url 'https://maven.aliyun.com/repository/public/' }
maven { url 'https://maven.aliyun.com/repository/google/' }
maven { url 'https://maven.aliyun.com/repository/gradle-plugin/' }
maven { url 'https://dl.bintray.com/ppartisan/maven/' }
maven { url "https://clojars.org/repo/" }
maven { url "https://jitpack.io" }
mavenCentral()
google()
//println "mavenLocal : ==========="
//println mavenLocal().url
//println "mavenLocal : ==========="
//mavenLocal()
}
dependencies {
// 适配MIUI12
@@ -35,6 +31,15 @@ buildscript {
allprojects {
repositories {
maven { url 'https://maven.aliyun.com/repository/public/' }
maven { url 'https://maven.aliyun.com/repository/google/' }
maven { url 'https://maven.aliyun.com/repository/gradle-plugin/' }
maven { url 'https://dl.bintray.com/ppartisan/maven/' }
maven { url "https://clojars.org/repo/" }
maven { url "https://jitpack.io" }
mavenCentral()
google()
mavenLocal {
// 设置本地Maven仓库路径
url 'file:///sdcard/.m2/repository/'
@@ -45,19 +50,6 @@ allprojects {
maven { url "https://nexus.winboll.cc/repository/maven-public/" }
// "WinBoLL Snapshot"
maven { url "https://nexus.winboll.cc/repository/maven-snapshots/" }
maven { url 'https://maven.aliyun.com/repository/public/' }
maven { url 'https://maven.aliyun.com/repository/google/' }
maven { url 'https://maven.aliyun.com/repository/gradle-plugin/' }
maven { url 'https://dl.bintray.com/ppartisan/maven/' }
maven { url "https://clojars.org/repo/" }
maven { url "https://jitpack.io" }
mavenCentral()
google()
//println "mavenLocal : ==========="
//println mavenLocal().url
//println "mavenLocal : ==========="
//mavenLocal()
}
ext {
// 定义全局变量,常用于版本管理
@@ -101,12 +93,7 @@ allprojects {
}
subprojects {
// 1. 对纯 Java 模块的 JavaCompile 任务配置(升级为 Java 11
tasks.withType(JavaCompile) {
options.compilerArgs << "-parameters"
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
// 可选:确保编码一致
options.encoding = "UTF-8"
}
}

40
contacts/README.md Normal file
View File

@@ -0,0 +1,40 @@
# Contacts
源码参考自:
https://github.com/aJIEw/PhoneCallApp.git
#### 介绍
这是可以根据正则表达式匹配拦截骚扰电话的手机拨号应用。
#### 软件架构
适配安卓应用 [AIDE Pro] 的 Gradle 编译结构。
也适配安卓应用 [AndroidIDE] 的 Gradle 编译结构。
#### Gradle 编译说明
调试版编译命令 gradle assembleBetaDebug
阶段版编译命令 gradle assembleStageRelease
#### 使用说明
在安卓系统中需要设置两个权限允许。
1.自启动权限允许。
2.省电策略-无限制权限允许。
#### 参与贡献
1. Fork 本仓库
2. 新建 Feat_xxx 分支
3. 提交代码 : ZhanGSKen(ZhanGSKen<zhangsken@188.com>)
4. 新建 Pull Request
#### 特技
1. 使用 Readme\_XXX.md 来支持不同的语言,例如 Readme\_en.md, Readme\_zh.md
2. Gitee 官方博客 [blog.gitee.com](https://blog.gitee.com)
3. 你可以 [https://gitee.com/explore](https://gitee.com/explore) 这个地址来了解 Gitee 上的优秀开源项目
4. [GVP](https://gitee.com/gvp) 全称是 Gitee 最有价值开源项目,是综合评定出的优秀开源项目
5. Gitee 官方提供的使用手册 [https://gitee.com/help](https://gitee.com/help)
6. Gitee 封面人物是一档用来展示 Gitee 会员风采的栏目 [https://gitee.com/gitee-stars/](https://gitee.com/gitee-stars/)
#### 参考文档

View File

@@ -0,0 +1 @@

100
contacts/build.gradle Normal file
View File

@@ -0,0 +1,100 @@
apply plugin: 'com.android.application'
apply from: '../.winboll/winboll_app_build.gradle'
apply from: '../.winboll/winboll_lint_build.gradle'
def genVersionName(def versionName){
// 检查编译标志位配置
assert (winbollBuildProps['stageCount'] != null)
assert (winbollBuildProps['baseVersion'] != null)
// 保存基础版本号
winbollBuildProps.setProperty("baseVersion", "${versionName}");
//保存编译标志配置
FileOutputStream fos = new FileOutputStream(winbollBuildPropsFile)
winbollBuildProps.store(fos, "${winbollBuildPropsDesc}");
fos.close();
// 返回编译版本号
return "${versionName}." + winbollBuildProps['stageCount']
}
android {
// 适配MIUI12
compileSdkVersion 30
buildToolsVersion "30.0.3"
defaultConfig {
applicationId "cc.winboll.studio.contacts"
minSdkVersion 23
// 适配MIUI12
targetSdkVersion 30
versionCode 2
// versionName 更新后需要手动设置
// 项目模块目录的 build.gradle 文件的 stageCount=0
// Gradle编译环境下合起来的 versionName 就是 "${versionName}.0"
versionName "15.14"
if(true) {
versionName = genVersionName("${versionName}")
}
}
// 米盟 SDK
packagingOptions {
doNotStrip "*/*/libmimo_1011.so"
}
}
dependencies {
// 米盟
api 'com.miui.zeus:mimo-ad-sdk:5.3.+'//请使用最新版sdk
//注意以下5个库必须要引入
//api 'androidx.appcompat:appcompat:1.4.1'
api 'androidx.recyclerview:recyclerview:1.0.0'
api 'com.google.code.gson:gson:2.8.5'
api 'com.github.bumptech.glide:glide:4.9.0'
//annotationProcessor 'com.github.bumptech.glide:compiler:4.9.0'
// 权限请求框架https://github.com/getActivity/XXPermissions
//api 'com.github.getActivity:XXPermissions:18.63'
// 下拉控件
api 'com.baoyz.pullrefreshlayout:library:1.2.0'
// 拼音搜索
// https://mvnrepository.com/artifact/com.github.open-android/pinyin4j
api 'com.github.open-android:pinyin4j:2.5.0'
// SSH
api 'com.jcraft:jsch:0.1.55'
// Html 解析
api 'org.jsoup:jsoup:1.13.1'
// 二维码类库
api 'com.google.zxing:core:3.4.1'
api 'com.journeyapps:zxing-android-embedded:3.6.0'
// 应用介绍页类库
api 'io.github.medyo:android-about-page:2.0.0'
// 网络连接类库
api 'com.squareup.okhttp3:okhttp:4.4.1'
// AndroidX 类库
/*implementation 'androidx.appcompat:appcompat:1.1.0'
implementation 'androidx.viewpager:viewpager:1.0.0'
implementation 'androidx.vectordrawable:vectordrawable:1.1.0'
implementation 'androidx.vectordrawable:vectordrawable-animated:1.1.0'
implementation 'androidx.fragment:fragment:1.1.0'
implementation 'com.google.android.material:material:1.4.0'
*/
api 'androidx.appcompat:appcompat:1.1.0'
api 'com.google.android.material:material:1.4.0'
//api 'androidx.viewpager:viewpager:1.0.0'
//api 'androidx.vectordrawable:vectordrawable:1.1.0'
//api 'androidx.vectordrawable:vectordrawable-animated:1.1.0'
//api 'androidx.fragment:fragment:1.1.0'
// WinBoLL库 nexus.winboll.cc 地址
api 'cc.winboll.studio:libaes:15.12.13'
api 'cc.winboll.studio:libappbase:15.14.2'
// WinBoLL备用库 jitpack.io 地址
//api 'com.github.ZhanGSKen:AES:aes-v15.12.9'
//api 'com.github.ZhanGSKen:APPBase:appbase-v15.14.1'
api fileTree(dir: 'libs', include: ['*.jar'])
}

View File

@@ -0,0 +1,8 @@
#Created by .winboll/winboll_app_build.gradle
#Sat Apr 18 21:14:59 HKT 2026
stageCount=13
libraryProject=
baseVersion=15.14
publishVersion=15.14.12
buildCount=0
baseBetaVersion=15.14.13

143
contacts/proguard-rules.pro vendored Normal file
View File

@@ -0,0 +1,143 @@
# Add project specific ProGuard rules here.
# By default, the flags in this file are appended to flags specified
# in C:\tools\adt-bundle-windows-x86_64-20131030\sdk/tools/proguard/proguard-android.txt
# You can edit the include path and order by changing the proguardFiles
# directive in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# Add any project specific keep options here:
# ============================== 基础通用规则 ==============================
# 保留系统组件
-keep public class * extends android.app.Activity
-keep public class * extends android.app.Service
-keep public class * extends android.content.BroadcastReceiver
-keep public class * extends android.content.ContentProvider
-keep public class * extends android.app.backup.BackupAgentHelper
-keep public class * extends android.preference.Preference
# 保留 WinBoLL 核心包及子类(合并简化规则)
-keep class cc.winboll.studio.** { *; }
-keepclassmembers class cc.winboll.studio.** { *; }
# 保留所有类中的 public static final String TAG 字段(便于日志定位)
-keepclassmembers class * {
public static final java.lang.String TAG;
}
# 保留序列化类避免Parcelable/Gson解析异常
-keep class * implements android.os.Parcelable {
public static final android.os.Parcelable$Creator *;
}
-keepclassmembers class * implements java.io.Serializable {
static final long serialVersionUID;
private static final java.io.ObjectStreamField[] serialPersistentFields;
private void writeObject(java.io.ObjectOutputStream);
private void readObject(java.io.ObjectInputStream);
java.lang.Object writeReplace();
java.lang.Object readResolve();
}
# 保留 R 文件避免资源ID混淆
-keepclassmembers class **.R$* {
public static <fields>;
}
# 保留 native 方法避免JNI调用失败
-keepclasseswithmembernames class * {
native <methods>;
}
# 保留注解和泛型(避免反射/序列化异常)
-keepattributes *Annotation*
-keepattributes Signature
# 屏蔽 Java 8+ 警告(适配 Java 7 语法)
-dontwarn java.lang.invoke.*
-dontwarn android.support.v8.renderscript.*
-dontwarn java.util.function.**
# ============================== 第三方框架专项规则 ==============================
# OkHttp 4.4.1米盟广告请求依赖完善Lambda兼容
-keep class okhttp3.** { *; }
-keep interface okhttp3.** { *; }
-keep class okhttp3.internal.** { *; }
-keep class okio.** { *; }
-dontwarn okhttp3.internal.platform.**
-dontwarn okio.**
# ============================== 必要补充规则 ==============================
# OkHttp 4.4.1 补充规则Java 7 兼容)
-keep class okhttp3.internal.concurrent.** { *; }
-keep class okhttp3.internal.connection.** { *; }
-dontwarn okhttp3.internal.concurrent.TaskRunner
-dontwarn okhttp3.internal.connection.RealCall
# Glide 4.9.0(米盟广告图片加载依赖)
-keep public class * implements com.bumptech.glide.module.GlideModule
-keep public class * extends com.bumptech.glide.module.AppGlideModule
-keep public enum com.bumptech.glide.load.ImageHeaderParser$ImageType {
**[] $VALUES;
public *;
}
-keepclassmembers class * implements com.bumptech.glide.module.AppGlideModule {
<init>();
}
-dontwarn com.bumptech.glide.**
# Gson 2.8.5(米盟广告数据序列化依赖)
-keep class com.google.gson.** { *; }
-keep interface com.google.gson.** { *; }
-keepclassmembers class * {
@com.google.gson.annotations.SerializedName <fields>;
}
# 米盟 SDK(核心广告组件,完整保留避免加载失败)
-keep class com.miui.zeus.** { *; }
-keep interface com.miui.zeus.** { *; }
# 保留米盟日志字段(便于广告加载失败排查)
-keepclassmembers class com.miui.zeus.mimo.sdk.** {
public static final java.lang.String TAG;
}
# RecyclerView 1.0.0(米盟广告布局渲染依赖)
-keep class androidx.recyclerview.** { *; }
-keep interface androidx.recyclerview.** { *; }
-keepclassmembers class androidx.recyclerview.widget.RecyclerView$Adapter {
public *;
}
# 其他第三方框架(按引入依赖保留,无则可删除)
# XXPermissions 18.63
-keep class com.hjq.permissions.** { *; }
-keep interface com.hjq.permissions.** { *; }
# ZXing 二维码(核心解析组件)
-keep class com.google.zxing.** { *; }
-keep class com.journeyapps.zxing.** { *; }
# Jsoup HTML解析
-keep class org.jsoup.** { *; }
# Pinyin4j 拼音搜索
-keep class net.sourceforge.pinyin4j.** { *; }
# JSch SSH组件
-keep class com.jcraft.jsch.** { *; }
# AndroidX 基础组件
-keep class androidx.appcompat.** { *; }
-keep interface androidx.appcompat.** { *; }
# ============================== 优化与调试配置 ==============================
# 优化级别(平衡混淆效果与性能)
-optimizationpasses 5
-optimizations !code/simplification/arithmetic,!code/simplification/cast,!field/*,!class/merging/*
# 调试辅助(保留行号便于崩溃定位)
-verbose
-dontpreverify
-dontusemixedcaseclassnames
-keepattributes SourceFile,LineNumberTable

View File

@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" >
<application
tools:replace="android:icon"
android:icon="@drawable/ic_winbollbeta">
<!-- Put flavor specific code here -->
</application>
</manifest>

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">Contacts+</string>
</resources>

View File

@@ -0,0 +1,258 @@
<?xml version='1.0' encoding='utf-8'?>
<manifest
xmlns:android="http://schemas.android.com/apk/res/android"
package="cc.winboll.studio.contacts">
<!-- BIND_AUTOFILL_SERVICE -->
<uses-permission android:name="android.permission.BIND_AUTOFILL_SERVICE"/>
<!-- 拨打电话 -->
<uses-permission android:name="android.permission.CALL_PHONE"/>
<!-- 读取手机状态和身份 -->
<uses-permission android:name="android.permission.READ_PHONE_STATE"/>
<!-- 读取电话号码 -->
<uses-permission android:name="android.permission.READ_PHONE_NUMBERS"/>
<!-- 修改系统设置 -->
<uses-permission android:name="android.permission.WRITE_SETTINGS"/>
<!-- 读取联系人 -->
<uses-permission android:name="android.permission.READ_CONTACTS"/>
<!-- 修改您的通讯录 -->
<uses-permission android:name="android.permission.WRITE_CONTACTS"/>
<!-- GET_CONTACTS -->
<uses-permission android:name="android.permission.GET_CONTACTS"/>
<!-- 此应用可显示在其他应用上方 -->
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
<!-- 更改您的音频设置 -->
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS"/>
<!-- 读取通话记录 -->
<uses-permission android:name="android.permission.READ_CALL_LOG"/>
<!-- 新建/修改/删除通话记录 -->
<uses-permission android:name="android.permission.WRITE_CALL_LOG"/>
<!-- GET_CALL_LOG -->
<uses-permission android:name="android.permission.GET_CALL_LOG"/>
<!-- 录音 -->
<uses-permission android:name="android.permission.RECORD_AUDIO"/>
<!-- 运行前台服务 -->
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
<!-- 运行“dataSync”类型的前台服务 -->
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC"/>
<!-- 运行“phoneCall”类型的前台服务 -->
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_PHONE_CALL"/>
<!-- 运行“microphone”类型的前台服务 -->
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MICROPHONE"/>
<!-- BIND_CALL_SCREENING_SERVICE -->
<uses-permission android:name="android.permission.BIND_CALL_SCREENING_SERVICE"/>
<!-- 读取您共享存储空间中的内容 -->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<!-- 修改或删除您共享存储空间中的内容 -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<!-- MANAGE_EXTERNAL_STORAGE -->
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"/>
<application
android:name=".App"
android:allowBackup="true"
android:icon="@drawable/ic_winboll"
android:label="@string/app_name"
android:theme="@style/MyAppTheme"
android:requestLegacyExternalStorage="true"
android:supportsRtl="true"
android:networkSecurityConfig="@xml/network_security_config">
<activity
android:name=".MainActivity"
android:label="@string/app_name"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<activity
android:name=".activities.CallActivity"
android:label="CallActivity"
android:launchMode="singleTask"
android:exported="true">
</activity>
<activity
android:name=".phonecallui.PhoneCallActivity"
android:launchMode="singleTask"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.DIAL"/>
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
<data android:scheme="tel"/>
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.DIAL"/>
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
</activity>
<activity android:name="cc.winboll.studio.contacts.activities.SettingsActivity"/>
<service
android:name=".services.MainService"
android:foregroundServiceType="dataSync"
android:exported="false"
android:stopWithTask="false"/>
<service
android:name=".services.AssistantService"
android:exported="false"
android:stopWithTask="false"/>
<service
android:name=".phonecallui.PhoneCallService"
android:permission="android.permission.BIND_INCALL_SERVICE"
android:exported="false"
android:stopWithTask="false">
<meta-data
android:name="android.telecom.IN_CALL_SERVICE_UI"
android:value="true"/>
<intent-filter>
<action android:name="android.telecom.InCallService"/>
</intent-filter>
</service>
<service
android:name=".listenphonecall.CallListenerService"
android:enabled="true"
android:exported="false"
android:stopWithTask="false">
<intent-filter android:priority="1000">
<action android:name=".service.CallShowService"/>
</intent-filter>
</service>
<service
android:name=".services.MyCallScreeningService"
android:permission="android.permission.BIND_CALL_SCREENING_SERVICE"
android:exported="true"
android:stopWithTask="false">
<intent-filter>
<action android:name="android.telecom.CallScreeningService"/>
</intent-filter>
</service>
<receiver
android:name=".receivers.MainReceiver"
android:stopWithTask="false">
<intent-filter>
<action android:name="cc.winboll.studio.contacts.receivers.MainReceiver"/>
</intent-filter>
</receiver>
<receiver
android:name=".widgets.APPStatusWidget"
android:exported="true"
android:stopWithTask="false">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE"/>
<action android:name="cc.winboll.studio.contacts.widgets.APPStatusWidget.ACTION_STATUS_ACTIVE"/>
<action android:name="cc.winboll.studio.contacts.widgets.APPStatusWidget.ACTION_STATUS_NOACTIVE"/>
</intent-filter>
<meta-data
android:name="android.appwidget.provider"
android:resource="@xml/appwidget_provider_info"/>
</receiver>
<receiver
android:name=".widgets.APPStatusWidgetClickListener"
android:stopWithTask="false">
<intent-filter>
<action android:name="cc.winboll.studio.contacts.widgets.APPStatusWidgetClickListener.ACTION_APPICON_CLICK"/>
</intent-filter>
</receiver>
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_provider"/>
</provider>
<activity android:name="cc.winboll.studio.contacts.activities.UnitTestActivity"/>
<activity android:name="cc.winboll.studio.contacts.activities.AboutActivity"/>
<service android:name="cc.winboll.studio.contacts.services.LimitedTimeSpecialChannelService"/>
</application>
</manifest>

View File

@@ -0,0 +1,313 @@
package cc.winboll.studio.contacts;
import android.app.Activity;
import android.os.Handler;
import android.os.Looper;
import cc.winboll.studio.libappbase.LogUtils;
import java.util.ArrayList;
import java.util.List;
/**
* @Author ZhanGSKen&豆包大模型<zhangsken@qq.com>
* @Date 2025/02/13 06:58:04
* @Describe Activity 栈管理工具,统一管理应用内 Activity 生命周期
* 适配Java7 + Android API29-30 + 小米机型,优化并发安全与通话场景稳定性
*/
public class ActivityStack {
// 常量定义(核心标识+版本兼容常量)
public static final String TAG = "ActivityStack";
private static final int API_VERSION_O = 26; // Android 8.0 API26isDestroyed适配用
// 单例与核心成员变量(按优先级排序)
private static final ActivityStack INSTANCE = new ActivityStack();
// 替换为ArrayList+同步锁解决CopyOnWriteArrayList迭代器不能删除的崩溃兼顾并发安全
private final List<Activity> mActivityList = new ArrayList<Activity>();
private final Handler mMainHandler = new Handler(Looper.getMainLooper()); // 复用主线程Handler避免内存泄漏
// 单例对外暴露方法
public static ActivityStack getInstance() {
return INSTANCE;
}
// 私有构造,禁止外部实例化
private ActivityStack() {
LogUtils.d(TAG, "ActivityStack 初始化完成");
}
// ====================== 栈基础操作(添加/移除) ======================
/**
* 添加Activity到栈中避免重复入栈
* @param activity 待添加的Activity
*/
public void addActivity(Activity activity) {
if (activity == null) {
LogUtils.w(TAG, "addActivity: activity is null, skip");
return;
}
// 同步锁:解决多线程并发添加冲突(小米机型多线程场景适配)
synchronized (mActivityList) {
if (!mActivityList.contains(activity)) {
mActivityList.add(activity);
LogUtils.d(TAG, "addActivity: " + activity.getClass().getSimpleName() + ", stack size: " + mActivityList.size());
}
}
}
/**
* 移除Activity不销毁用于正常退出场景
* @param activity 待移除的Activity
*/
public void removeActivity(Activity activity) {
if (activity == null) {
LogUtils.w(TAG, "removeActivity: activity is null, skip");
return;
}
synchronized (mActivityList) {
if (mActivityList.remove(activity)) {
LogUtils.d(TAG, "removeActivity: " + activity.getClass().getSimpleName() + ", stack size: " + mActivityList.size());
}
}
}
// ====================== Activity状态查询获取/判断存活) ======================
/**
* 获取栈顶有效Activity迭代遍历替代递归避免栈溢出适配小米多页面场景
* @return 栈顶有效Activity无则返回null
*/
public Activity getTopActivity() {
synchronized (mActivityList) {
if (mActivityList.isEmpty()) {
LogUtils.w(TAG, "getTopActivity: stack is empty, return null");
return null;
}
Activity validTopActivity = null;
// 倒序遍历优先取最顶层有效Activity同时清理无效残留
for (int i = mActivityList.size() - 1; i >= 0; i--) {
Activity activity = mActivityList.get(i);
// 版本兼容校验API26+才支持isDestroyed
if (activity != null && !activity.isFinishing() && (getSdkVersion() < API_VERSION_O || !activity.isDestroyed())) {
validTopActivity = activity;
break;
} else {
mActivityList.remove(i);
String className = (activity != null) ? activity.getClass().getSimpleName() : "null";
LogUtils.w(TAG, "getTopActivity: remove invalid activity: " + className);
}
}
if (validTopActivity != null) {
LogUtils.d(TAG, "getTopActivity: top activity: " + validTopActivity.getClass().getSimpleName());
}
return validTopActivity;
}
}
/**
* 获取指定类的有效Activity实例通话场景核心方法判断页面是否存活
* @param activityClass 目标Activity类
* @return 有效实例无则返回null
*/
public Activity getActivity(Class<?> activityClass) {
if (activityClass == null) {
LogUtils.w(TAG, "getActivity: activityClass is null, return null");
return null;
}
synchronized (mActivityList) {
if (mActivityList.isEmpty()) {
LogUtils.w(TAG, "getActivity: stack empty, return null");
return null;
}
for (Activity activity : mActivityList) {
if (activity != null && activity.getClass().equals(activityClass) && !activity.isFinishing() && (getSdkVersion() < API_VERSION_O || !activity.isDestroyed())) {
LogUtils.d(TAG, "getActivity: find valid activity: " + activityClass.getSimpleName());
return activity;
}
}
LogUtils.w(TAG, "getActivity: no valid activity: " + activityClass.getSimpleName());
return null;
}
}
/**
* 判断指定Activity是否存活简化通话场景调用避免重复判空
* @param activityClass 目标Activity类
* @return true存活false未存活
*/
public boolean isActivityAlive(Class<?> activityClass) {
boolean isAlive = getActivity(activityClass) != null;
LogUtils.d(TAG, "isActivityAlive: " + activityClass.getSimpleName() + ", result: " + isAlive);
return isAlive;
}
// ====================== Activity销毁操作单/批量/全部) ======================
/**
* 销毁栈顶Activity主线程执行适配小米机型线程限制
*/
public void finishTopActivity() {
runOnMainThread(new Runnable() {
@Override
public void run() {
synchronized (mActivityList) {
if (mActivityList.isEmpty()) {
LogUtils.w(TAG, "finishTopActivity: stack is empty, skip");
return;
}
// 先移除再校验,避免并发冲突(小米多线程场景适配)
Activity topActivity = mActivityList.remove(mActivityList.size() - 1);
if (topActivity == null) {
LogUtils.w(TAG, "finishTopActivity: top activity is null, skip");
return;
}
if (!topActivity.isFinishing() && (getSdkVersion() < API_VERSION_O || !topActivity.isDestroyed())) {
topActivity.finish();
LogUtils.d(TAG, "finishTopActivity: destroy top activity: " + topActivity.getClass().getSimpleName() + ", stack size: " + mActivityList.size());
}
}
}
});
}
/**
* 销毁指定Activity主线程执行避免跨线程异常
* @param activity 待销毁的Activity
*/
public void finishActivity(final Activity activity) {
runOnMainThread(new Runnable() {
@Override
public void run() {
if (activity == null) {
LogUtils.w(TAG, "finishActivity: activity is null, skip");
return;
}
synchronized (mActivityList) {
if (mActivityList.contains(activity) && !activity.isFinishing() && (getSdkVersion() < API_VERSION_O || !activity.isDestroyed())) {
mActivityList.remove(activity);
activity.finish();
LogUtils.d(TAG, "finishActivity: destroy activity: " + activity.getClass().getSimpleName() + ", stack size: " + mActivityList.size());
}
}
}
});
}
/**
* 销毁指定类的所有Activity核心修复迭代器删除崩溃通话场景核心
* @param activityClass 目标Activity类
*/
public void finishActivity(final Class<?> activityClass) {
runOnMainThread(new Runnable() {
@Override
public void run() {
if (activityClass == null) {
LogUtils.w(TAG, "finishActivity: activityClass is null, skip");
return;
}
synchronized (mActivityList) {
if (mActivityList.isEmpty()) {
LogUtils.w(TAG, "finishActivity: stack empty, skip");
return;
}
// 核心修复:用索引遍历+倒序删除替代迭代器删除避免UnsupportedOperationException
for (int i = mActivityList.size() - 1; i >= 0; i--) {
Activity activity = mActivityList.get(i);
if (activity != null && activity.getClass().equals(activityClass)) {
if (!activity.isFinishing() && (getSdkVersion() < API_VERSION_O || !activity.isDestroyed())) {
mActivityList.remove(i); // 索引删除支持ArrayList
activity.finish();
LogUtils.d(TAG, "finishActivity: destroy class activity: " + activityClass.getSimpleName() + ", stack size: " + mActivityList.size());
} else {
mActivityList.remove(i); // 清理无效残留
}
}
}
}
}
});
}
/**
* 销毁栈中所有Activity退出应用/清空栈场景用)
*/
public void finishAllActivity() {
runOnMainThread(new Runnable() {
@Override
public void run() {
synchronized (mActivityList) {
if (mActivityList.isEmpty()) {
LogUtils.w(TAG, "finishAllActivity: stack is empty, skip");
return;
}
// 遍历销毁所有有效Activity逐个状态校验小米机型稳定性适配
for (Activity activity : mActivityList) {
if (activity != null && !activity.isFinishing() && (getSdkVersion() < API_VERSION_O || !activity.isDestroyed())) {
activity.finish();
LogUtils.d(TAG, "finishAllActivity: destroy activity: " + activity.getClass().getSimpleName());
}
}
mActivityList.clear();
LogUtils.d(TAG, "finishAllActivity: all activity destroyed, stack cleared");
}
}
});
}
// ====================== 栈优化与工具方法 ======================
/**
* 清理栈中所有无效Activitynull/已销毁/已结束),优化小米机型内存占用
*/
public void clearInvalidActivities() {
runOnMainThread(new Runnable() {
@Override
public void run() {
synchronized (mActivityList) {
if (mActivityList.isEmpty()) {
return;
}
// 倒序索引删除,避免遍历过程中索引错乱
for (int i = mActivityList.size() - 1; i >= 0; i--) {
Activity activity = mActivityList.get(i);
if (activity == null || activity.isFinishing() || (getSdkVersion() >= API_VERSION_O && activity.isDestroyed())) {
mActivityList.remove(i);
String className = (activity != null) ? activity.getClass().getSimpleName() : "null";
LogUtils.d(TAG, "clearInvalidActivities: remove invalid activity: " + className);
}
}
LogUtils.d(TAG, "clearInvalidActivities: done, stack size: " + mActivityList.size());
}
}
});
}
/**
* 确保任务在主线程执行Activity操作必须主线程小米机型严格限制
* @param runnable 待执行任务
*/
private void runOnMainThread(Runnable runnable) {
if (runnable == null) {
return;
}
// 避免不必要的线程切换,优化性能(小米机型流畅度适配)
if (Looper.getMainLooper() == Looper.myLooper()) {
runnable.run();
} else {
mMainHandler.post(runnable);
LogUtils.d(TAG, "runOnMainThread: post task to main thread");
}
}
/**
* 辅助方法获取当前系统SDK版本简化版本判断逻辑统一调用
* @return SDK版本号
*/
private int getSdkVersion() {
return android.os.Build.VERSION.SDK_INT;
}
}

View File

@@ -0,0 +1,33 @@
package cc.winboll.studio.contacts;
/**
* @Author ZhanGSKen<zhangsken@qq.com>
* @Date 2024/12/08 15:10:51
* @Describe 全局应用类
*/
import cc.winboll.studio.libaes.utils.WinBoLLActivityManager;
import cc.winboll.studio.libappbase.GlobalApplication;
import cc.winboll.studio.libappbase.ToastUtils;
public class App extends GlobalApplication {
public static final String TAG = "App";
@Override
public void onCreate() {
super.onCreate();
// 设置应用调试标志
setIsDebugging(BuildConfig.DEBUG);
// 初始化窗口管理类
WinBoLLActivityManager.init(this);
// 初始化 Toast 框架
ToastUtils.init(this);
}
@Override
public void onTerminate() {
super.onTerminate();
ToastUtils.release();
}
}

View File

@@ -0,0 +1,529 @@
package cc.winboll.studio.contacts;
import android.Manifest;
import android.app.Activity;
import android.app.ActivityManager;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.graphics.Color;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.telecom.TelecomManager;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.CheckBox;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.Toast;
import androidx.appcompat.widget.Toolbar;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentPagerAdapter;
import androidx.viewpager.widget.ViewPager;
import cc.winboll.studio.contacts.activities.SettingsActivity;
import cc.winboll.studio.contacts.activities.WinBollActivity;
import cc.winboll.studio.contacts.dun.Rules;
import cc.winboll.studio.contacts.fragments.CallLogFragment;
import cc.winboll.studio.contacts.fragments.ContactsFragment;
import cc.winboll.studio.contacts.fragments.LogFragment;
import cc.winboll.studio.contacts.model.MainServiceBean;
import cc.winboll.studio.contacts.services.MainService;
import cc.winboll.studio.contacts.utils.PermissionUtils;
import cc.winboll.studio.contacts.views.DunTemperatureView;
import cc.winboll.studio.libaes.interfaces.IWinBoLLActivity;
import cc.winboll.studio.libaes.views.ADsBannerView;
import cc.winboll.studio.libappbase.LogUtils;
import cc.winboll.studio.libappbase.LogView;
import com.google.android.material.tabs.TabLayout;
import java.util.ArrayList;
import java.util.List;
/**
* @Author ZhanGSKen&豆包大模型<zhangsken@qq.com>
* @Date 2025/08/30 14:32
* @Describe Contacts 主窗口(完全适配 API 30 + Java 7 语法)
* 核心优化1. 移除电话状态监听 2. 移除通话筛选服务 3. 移除 MainService 所有相关逻辑 4. ViewPager 实现 Fragment 懒加载(仅首屏初始化)
* 问题修复:解决首屏 Fragment 空白问题(删除 setPrimaryItem 冲突逻辑+延迟首屏初始化)
*/
public final class MainActivity extends WinBollActivity implements IWinBoLLActivity, ViewPager.OnPageChangeListener, View.OnClickListener {
// ====================== 1. 常量定义区硬编码API版本避免高版本依赖 ======================
public static final String TAG = "MainActivity";
public static final int REQUEST_HOME_ACTIVITY = 0;
public static final int REQUEST_ABOUT_ACTIVITY = 1;
public static final int REQUEST_APP_SETTINGS = 2;
public static final String ACTION_SOS = "cc.winboll.studio.libappbase.WinBoLL.ACTION_SOS";
private static final int DIALER_REQUEST_CODE = 1;
private static final int REQUEST_REQUIRED_PERMISSIONS = 1002;
private static final int REQUEST_OVERLAY_PERMISSION = 1003;
// API版本硬编码常量Java 7兼容杜绝Build.VERSION_CODES高版本引用
private static final int ANDROID_6_API = 23;
private static final int ANDROID_8_API = 26;
private static final int ANDROID_10_API = 29;
private static final int ANDROID_14_API = 34;
// ====================== 2. 静态成员区 ======================
static MainActivity _MainActivity;
// ====================== 3. 权限常量区 ======================
private final String[] REQUIRED_PERMISSIONS = PermissionUtils.BASE_PERMISSIONS;
// ====================== 4. UI控件成员区 ======================
private ADsBannerView mADsBannerView;
private LogView mLogView;
private Toolbar mToolbar;
private CheckBox cbMainService;
private TabLayout tabLayout;
private ViewPager viewPager;
private List<View> views;
private ImageView[] imageViews;
private LinearLayout linearLayout;
// ====================== 5. 业务逻辑成员区 ======================
private int currentPoint = 0;
private List<Fragment> fragmentList;
private List<String> tabTitleList;
// 记录已初始化的Fragment位置避免重复初始化
private boolean[] isFragmentInit;
// ====================== 6. 接口实现区 ======================
@Override
public Activity getActivity() {
return this;
}
@Override
public String getTag() {
return TAG;
}
// ====================== 7. 生命周期函数区 ======================
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
LogUtils.d(TAG, "===== onCreate: 主Activity开始创建 =====");
_MainActivity = this;
// 直接初始化UI原权限检查逻辑注释保留按需启用
initUIAndLogic(savedInstanceState);
MainServiceBean mainServiceBean = MainServiceBean.loadBean(this, MainServiceBean.class);
if (mainServiceBean != null && mainServiceBean.isEnable()) {
Intent intent = new Intent(this, MainService.class);
// 根据应用前后台状态选择启动方式Android 12+ 后台用 startForegroundService
if (Build.VERSION.SDK_INT >= 31) {
startForegroundService(intent);
} else {
startService(intent);
}
}
LogUtils.d(TAG, "===== onCreate: 主Activity创建流程结束 =====");
}
@Override
protected void onPostCreate(Bundle savedInstanceState) {
super.onPostCreate(savedInstanceState);
LogUtils.d(TAG, "onPostCreate: 主Activity创建完成");
}
@Override
protected void onResume() {
super.onResume();
if (mADsBannerView != null) {
mADsBannerView.resumeADs(MainActivity.this);
LogUtils.d(TAG, "onResume: 广告栏资源已恢复");
}
}
@Override
protected void onPause() {
super.onPause();
}
@Override
protected void onDestroy() {
super.onDestroy();
LogUtils.d(TAG, "===== onDestroy: 主Activity开始销毁 =====");
// 释放广告资源
if (mADsBannerView != null) {
mADsBannerView.releaseAdResources();
LogUtils.d(TAG, "onDestroy: 广告栏资源已释放");
}
// 清空Fragment相关引用避免内存泄漏
if (fragmentList != null) {
fragmentList.clear();
fragmentList = null;
}
if (tabTitleList != null) {
tabTitleList.clear();
tabTitleList = null;
}
isFragmentInit = null;
LogUtils.d(TAG, "===== onDestroy: 主Activity销毁完成 =====");
}
// ====================== 8. 权限相关回调函数区 ======================
@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
LogUtils.d(TAG, "onRequestPermissionsResult: 权限请求回调requestCode=" + requestCode);
if (requestCode == REQUEST_REQUIRED_PERMISSIONS) {
String deniedPerms = PermissionUtils.getDeniedPermissions(this, permissions);
if (deniedPerms.length() == 0) {
LogUtils.d(TAG, "onRequestPermissionsResult: 所有危险权限授予成功");
checkAndRequestRemainingPermissions();
} else {
LogUtils.e(TAG, "onRequestPermissionsResult: 被拒权限:" + deniedPerms);
showPermissionDeniedDialogAndExit("应用需要「" + deniedPerms + "」权限才能正常运行,请授予权限后重新打开应用。");
}
}
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
LogUtils.d(TAG, "onActivityResult: 页面回调触发requestCode=" + requestCode + "resultCode=" + resultCode);
switch (requestCode) {
case DIALER_REQUEST_CODE:
if (resultCode == Activity.RESULT_OK) {
LogUtils.d(TAG, "onActivityResult: 设为默认拨号应用成功");
Toast.makeText(MainActivity.this, getString(R.string.app_name) + " 已成为默认电话应用", Toast.LENGTH_SHORT).show();
}
break;
case REQUEST_APP_SETTINGS:
LogUtils.d(TAG, "onActivityResult: 从设置页返回重建Activity");
recreate();
break;
case REQUEST_OVERLAY_PERMISSION:
handleOverlayPermissionResult();
break;
default:
LogUtils.w(TAG, "onActivityResult: 未知requestCode=" + requestCode);
break;
}
}
/**
* 处理悬浮窗权限申请结果
*/
private void handleOverlayPermissionResult() {
if (PermissionUtils.isOverlayPermissionGranted(this)) {
LogUtils.d(TAG, "handleOverlayPermissionResult: 悬浮窗权限申请成功");
LogUtils.d(TAG, "handleOverlayPermissionResult: 所有权限已授予");
initUIAndLogic(null);
} else {
LogUtils.e(TAG, "handleOverlayPermissionResult: 悬浮窗权限申请失败");
showPermissionDeniedDialogAndExit("应用需要悬浮窗权限才能展示来电弹窗,请授予后重新打开应用。");
}
}
/**
* 检查并申请剩余权限(仅保留悬浮窗)
*/
private void checkAndRequestRemainingPermissions() {
if (!PermissionUtils.isOverlayPermissionGranted(this)) {
LogUtils.d(TAG, "checkAndRequestRemainingPermissions: 悬浮窗权限未授予,跳转设置页");
PermissionUtils.requestOverlayPermission(this, REQUEST_OVERLAY_PERMISSION);
} else {
LogUtils.d(TAG, "checkAndRequestRemainingPermissions: 所有权限已授予");
initUIAndLogic(null);
}
}
/**
* 权限拒绝提示对话框Java 7 匿名内部类实现禁止Lambda
*/
private void showPermissionDeniedDialogAndExit(String tip) {
LogUtils.d(TAG, "showPermissionDeniedDialogAndExit: 弹出权限不足提示框");
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle("权限不足,无法使用");
builder.setMessage(tip);
builder.setCancelable(false);
builder.setNegativeButton("去设置", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
LogUtils.d(TAG, "showPermissionDeniedDialogAndExit: 用户选择去设置权限");
PermissionUtils.goAppDetailsSettings(MainActivity.this);
}
});
builder.setPositiveButton("确定退出", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
LogUtils.d(TAG, "showPermissionDeniedDialogAndExit: 用户选择退出应用");
finishAndRemoveTask();
}
});
builder.show();
}
// ====================== 9. UI与业务逻辑初始化区 ======================
private void initUIAndLogic(Bundle savedInstanceState) {
if (mToolbar != null) {
LogUtils.d(TAG, "initUIAndLogic: UI已初始化无需重复执行");
return;
}
LogUtils.d(TAG, "===== initUIAndLogic: 开始初始化UI与业务逻辑 =====");
setContentView(R.layout.activity_main);
// 1. 工具栏初始化
mToolbar = (Toolbar) findViewById(R.id.activitymainToolbar1);
setSupportActionBar(mToolbar);
getSupportActionBar().setSubtitle(TAG);
LogUtils.d(TAG, "initUIAndLogic: 工具栏初始化完成");
// 2. TabLayout与ViewPager初始化
tabLayout = (TabLayout) findViewById(R.id.tabLayout);
viewPager = (ViewPager) findViewById(R.id.viewPager);
initViewPagerAndTabs();
tabLayout.setupWithViewPager(viewPager);
LogUtils.d(TAG, "initUIAndLogic: ViewPager与TabLayout初始化完成");
// 3. 广告栏初始化
mADsBannerView = (ADsBannerView) findViewById(R.id.adsbanner);
LogUtils.d(TAG, "initUIAndLogic: 广告栏控件初始化完成");
// 左边盾值视图初始化Java7分步写法禁止链式调用
DunTemperatureView tempViewLeft = (DunTemperatureView) findViewById(R.id.dun_temp_view_left);
tempViewLeft.setMaxValue(Rules.getInstance(this).getSettingsModel().getDunTotalCount());
tempViewLeft.setCurrentValue(Rules.getInstance(this).getSettingsModel().getDunCurrentCount());
int[] customColors = new int[2];
customColors[0] = Color.parseColor("#FF3366FF");
customColors[1] = Color.parseColor("#FF9900CC");
float[] positions = new float[2];
positions[0] = 0.0f;
positions[1] = 1.0f;
tempViewLeft.setGradientColors(customColors, positions);
// 文本放在温度条右侧(默认,可省略)
tempViewLeft.setTextPosition(true);
// 右边盾值视图初始化Java7分步写法禁止链式调用
DunTemperatureView tempViewRight = (DunTemperatureView) findViewById(R.id.dun_temp_view_right);
tempViewRight.setMaxValue(Rules.getInstance(this).getSettingsModel().getDunTotalCount());
tempViewRight.setCurrentValue(Rules.getInstance(this).getSettingsModel().getDunCurrentCount());
tempViewRight.setGradientColors(customColors, positions);
// 文本放在温度条左侧
tempViewRight.setTextPosition(false);
LogUtils.d(TAG, "initUIAndLogic: 盾值视图初始化完成");
LogUtils.d(TAG, "===== initUIAndLogic: 初始化流程全部结束 =====");
}
/**
* 初始化ViewPager与Tab数据Java7规范泛型完整声明添加懒加载标记
* 关键修改延迟50ms初始化首屏确保Fragment控件就绪删除setPrimaryItem冲突逻辑
*/
private void initViewPagerAndTabs() {
LogUtils.d(TAG, "initViewPagerAndTabs: 开始初始化ViewPager数据");
fragmentList = new ArrayList<Fragment>();
tabTitleList = new ArrayList<String>();
// 添加Fragment实例仅创建对象不初始化业务逻辑
fragmentList.add(CallLogFragment.newInstance(0));
fragmentList.add(ContactsFragment.newInstance(1));
fragmentList.add(LogFragment.newInstance(2));
tabTitleList.add("通话记录");
tabTitleList.add("联系人");
tabTitleList.add("应用日志");
// 初始化懒加载标记数组(默认均未初始化)
int fragmentCount = fragmentList.size();
isFragmentInit = new boolean[fragmentCount];
for (int i = 0; i < fragmentCount; i++) {
isFragmentInit[i] = false;
}
// 设置自定义适配器已删除setPrimaryItem避免初始化冲突
LazyLoadPagerAdapter adapter = new LazyLoadPagerAdapter(getSupportFragmentManager(), fragmentList, tabTitleList);
viewPager.setAdapter(adapter);
// 关闭预加载设为0仅加载当前页关键
viewPager.setOffscreenPageLimit(0);
viewPager.addOnPageChangeListener(this);
// 关键优化延迟50ms初始化首屏确保Fragment已完成onCreateView控件绑定就绪
new Handler(Looper.getMainLooper()).postDelayed(new Runnable() {
@Override
public void run() {
initFragmentByPosition(0);
LogUtils.d(TAG, "initViewPagerAndTabs: 延迟初始化首屏Fragment位置=0");
}
}, 50);
LogUtils.d(TAG, "initViewPagerAndTabs: ViewPager初始化完成等待延迟初始化首屏");
}
/**
* 根据位置初始化Fragment调用Fragment的初始化逻辑避免重复执行
* 优化添加isAdded判断确保Fragment已附加到Activity防止上下文空指针
*/
private void initFragmentByPosition(int position) {
// 校验位置合法性 + 避免重复初始化 + 确保Fragment已附加到Activity
if (position < 0 || position >= fragmentList.size() || isFragmentInit[position]) {
return;
}
Fragment targetFragment = fragmentList.get(position);
if (targetFragment != null && targetFragment.isAdded()) {
// 触发Fragment初始化调用各Fragment的initData方法
if (targetFragment instanceof CallLogFragment) {
((CallLogFragment) targetFragment).initData();
} else if (targetFragment instanceof ContactsFragment) {
((ContactsFragment) targetFragment).initData();
} else if (targetFragment instanceof LogFragment) {
((LogFragment) targetFragment).initData();
}
// 标记为已初始化
isFragmentInit[position] = true;
LogUtils.d(TAG, "initFragmentByPosition: 初始化Fragment位置=" + position + ",标题=" + tabTitleList.get(position));
} else {
LogUtils.w(TAG, "initFragmentByPosition: Fragment未附加到Activity/实例为空,位置=" + position);
}
}
// ====================== 10. 菜单相关函数区 ======================
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.toolbar_main, menu);
LogUtils.d(TAG, "onCreateOptionsMenu: 菜单加载完成");
return super.onCreateOptionsMenu(menu);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
if (item.getItemId() == R.id.item_settings) {
LogUtils.d(TAG, "onOptionsItemSelected: 用户点击设置菜单");
startActivity(new Intent(this, SettingsActivity.class));
return true;
}
return super.onOptionsItemSelected(item);
}
// ====================== 11. ViewPager页面回调区切换时初始化对应Fragment ======================
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {}
@Override
public void onPageSelected(int position) {
currentPoint = position;
LogUtils.d(TAG, "onPageSelected: 页面切换至[" + position + "],标题=" + tabTitleList.get(position));
// 切换页面时初始化当前页Fragment未初始化过才执行
initFragmentByPosition(position);
}
@Override
public void onPageScrollStateChanged(int state) {}
@Override
public void onClick(View v) {}
// ====================== 12. 工具函数区 ======================
/**
* 拨号工具方法(添加空指针防护)
*/
public static void dialPhoneNumber(String phoneNumber) {
if (_MainActivity == null) {
LogUtils.e(TAG, "dialPhoneNumber: MainActivity实例为空无法拨号");
return;
}
if (phoneNumber == null || phoneNumber.trim().isEmpty()) {
LogUtils.e(TAG, "dialPhoneNumber: 拨号号码为空");
return;
}
if (PermissionUtils.checkPermission(_MainActivity, Manifest.permission.CALL_PHONE)) {
Intent intent = new Intent(Intent.ACTION_DIAL);
intent.setData(Uri.parse("tel:" + phoneNumber));
LogUtils.d(TAG, "dialPhoneNumber: 发起拨号,号码=" + phoneNumber);
_MainActivity.startActivity(intent);
} else {
LogUtils.e(TAG, "dialPhoneNumber: 拨号权限不足,无法发起拨号");
Toast.makeText(_MainActivity, "拨号权限不足", Toast.LENGTH_SHORT).show();
}
}
/**
* 判断是否为默认拨号应用适配API30硬编码版本判断
*/
public boolean isDefaultPhoneCallApp() {
if (Build.VERSION.SDK_INT >= ANDROID_6_API) {
TelecomManager manager = (TelecomManager) getSystemService(Context.TELECOM_SERVICE);
if (manager != null && manager.getDefaultDialerPackage() != null) {
boolean isDefault = manager.getDefaultDialerPackage().equals(getPackageName());
LogUtils.d(TAG, "isDefaultPhoneCallApp: 是否为默认拨号应用=" + isDefault);
return isDefault;
}
}
LogUtils.d(TAG, "isDefaultPhoneCallApp: 系统版本低于Android 6无法判断");
return false;
}
/**
* 检查服务是否正在运行(通用工具方法,添加空指针防护)
*/
public boolean isServiceRunning(Class<?> serviceClass) {
if (serviceClass == null) {
LogUtils.e(TAG, "isServiceRunning: 服务类参数为null");
return false;
}
ActivityManager manager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
if (manager == null) {
LogUtils.w(TAG, "isServiceRunning: ActivityManager获取失败");
return false;
}
for (ActivityManager.RunningServiceInfo service : manager.getRunningServices(Integer.MAX_VALUE)) {
if (serviceClass.getName().equals(service.service.getClassName())) {
LogUtils.d(TAG, "isServiceRunning: 服务[" + serviceClass.getSimpleName() + "]正在运行");
return true;
}
}
LogUtils.d(TAG, "isServiceRunning: 服务[" + serviceClass.getSimpleName() + "]未运行");
return false;
}
// ====================== 13. 内部类定义区Java 7 规范禁止Lambda ======================
/**
* 自定义懒加载ViewPager适配器删除setPrimaryItem方法解决首屏初始化冲突
*/
private class LazyLoadPagerAdapter extends FragmentPagerAdapter {
private final List<Fragment> fragmentList;
private final List<String> tabTitleList;
public LazyLoadPagerAdapter(FragmentManager fm, List<Fragment> fragmentList, List<String> tabTitleList) {
super(fm);
this.fragmentList = fragmentList;
this.tabTitleList = tabTitleList;
LogUtils.d(MainActivity.TAG, "LazyLoadPagerAdapter: 初始化完成Fragment数量=" + fragmentList.size());
}
@Override
public Fragment getItem(int position) {
return fragmentList.get(position);
}
@Override
public int getCount() {
return fragmentList.size();
}
@Override
public CharSequence getPageTitle(int position) {
return tabTitleList.get(position);
}
// 【已删除】移除setPrimaryItem方法避免与手动初始化+onPageSelected回调冲突
}
}

View File

@@ -0,0 +1,116 @@
package cc.winboll.studio.contacts.activities;
import android.app.Activity;
import android.content.Context;
import android.os.Bundle;
import android.view.ViewGroup;
import android.widget.LinearLayout;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
import cc.winboll.studio.contacts.R;
import cc.winboll.studio.libaes.interfaces.IWinBoLLActivity;
import cc.winboll.studio.libaes.models.APPInfo;
import cc.winboll.studio.libaes.utils.WinBoLLActivityManager;
import cc.winboll.studio.libaes.views.AboutView;
import cc.winboll.studio.libappbase.LogUtils;
/**
* @Author ZhanGSKen&豆包大模型<zhangsken@qq.com>
* @Date 2025/03/31 15:15:54
* @Describe 应用介绍窗口
*/
public class AboutActivity extends WinBollActivity implements IWinBoLLActivity {
// ====================== 常量定义区 ======================
public static final String TAG = "AboutActivity";
private static final String BRANCH_NAME = "contacts";
// ====================== 成员变量区 ======================
private Context mContext;
private Toolbar mToolbar;
// ====================== 接口实现区 ======================
@Override
public Activity getActivity() {
return this;
}
@Override
public String getTag() {
return TAG;
}
// ====================== 生命周期函数区 ======================
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
LogUtils.d(TAG, "onCreate: 关于页面开始创建");
mContext = this;
setContentView(R.layout.activity_about);
// 初始化工具栏
initToolbar();
// 初始化关于页面视图
initAboutView();
// 注册Activity管理
WinBoLLActivityManager.getInstance().add(this);
LogUtils.d(TAG, "onCreate: 关于页面初始化完成");
}
@Override
protected void onDestroy() {
super.onDestroy();
LogUtils.d(TAG, "onDestroy: 关于页面开始销毁");
WinBoLLActivityManager.getInstance().registeRemove(this);
LogUtils.d(TAG, "onDestroy: 关于页面销毁完成");
}
// ====================== 控件初始化函数区 ======================
private void initToolbar() {
LogUtils.d(TAG, "initToolbar: 初始化工具栏");
// Java7 适配:添加强制类型转换
mToolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(mToolbar);
mToolbar.setSubtitle(TAG);
// 非空判断,避免空指针异常
if (getSupportActionBar() != null) {
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
}
}
private void initAboutView() {
LogUtils.d(TAG, "initAboutView: 初始化关于页面内容视图");
AboutView aboutView = createAboutView();
LinearLayout layout = (LinearLayout) findViewById(R.id.aboutviewroot_ll);
ViewGroup.LayoutParams params = new ViewGroup.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT
);
layout.addView(aboutView, params);
LogUtils.d(TAG, "initAboutView: AboutView已添加到布局");
}
// ====================== 业务逻辑函数区 ======================
private AboutView createAboutView() {
LogUtils.d(TAG, "createAboutView: 构建APP信息并创建AboutView");
APPInfo appInfo = new APPInfo();
appInfo.setAppName("Contacts");
appInfo.setAppIcon(cc.winboll.studio.libaes.R.drawable.ic_winboll);
appInfo.setAppDescription("这是可以根据正则表达式匹配拦截骚扰电话的手机拨号应用。");
appInfo.setAppGitName("WinBoLL");
appInfo.setAppGitOwner("Studio");
appInfo.setAppGitAPPBranch(BRANCH_NAME);
appInfo.setAppGitAPPSubProjectFolder(BRANCH_NAME);
appInfo.setAppHomePage("https://www.winboll.cc/apks/index.php?project=Contacts");
appInfo.setAppAPKName("Contacts");
appInfo.setAppAPKFolderName("Contacts");
return new AboutView(mContext, appInfo);
}
}

View File

@@ -0,0 +1,159 @@
package cc.winboll.studio.contacts.activities;
import android.Manifest;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.telephony.PhoneStateListener;
import android.telephony.TelephonyManager;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import cc.winboll.studio.contacts.R;
import cc.winboll.studio.libappbase.LogUtils;
/**
* @Author ZhanGSKen&豆包大模型<zhangsken@qq.com>
* @Date 2025/02/20 17:15:46
* @Describe 拨号窗口
*/
public class CallActivity extends AppCompatActivity {
// ====================== 常量定义区 ======================
public static final String TAG = "CallActivity";
private static final int REQUEST_CALL_PHONE = 1;
// ====================== UI控件区 ======================
private EditText phoneNumberEditText;
private TextView callStatusTextView;
private Button dialButton;
// ====================== 业务成员区 ======================
private TelephonyManager telephonyManager;
private MyPhoneStateListener phoneStateListener;
// ====================== 生命周期函数区 ======================
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
LogUtils.d(TAG, "onCreate: 拨号页面开始创建");
setContentView(R.layout.activity_call);
// 初始化控件
initViews();
// 初始化电话状态监听
initPhoneStateListener();
LogUtils.d(TAG, "onCreate: 拨号页面初始化完成");
}
@Override
protected void onDestroy() {
super.onDestroy();
LogUtils.d(TAG, "onDestroy: 拨号页面开始销毁");
// 取消电话状态监听,避免内存泄漏
if (telephonyManager != null && phoneStateListener != null) {
telephonyManager.listen(phoneStateListener, PhoneStateListener.LISTEN_NONE);
LogUtils.d(TAG, "onDestroy: 电话状态监听已取消");
}
LogUtils.d(TAG, "onDestroy: 拨号页面销毁完成");
}
// ====================== 权限回调函数区 ======================
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
LogUtils.d(TAG, "onRequestPermissionsResult: 权限请求回调requestCode=" + requestCode);
if (requestCode == REQUEST_CALL_PHONE) {
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
LogUtils.d(TAG, "onRequestPermissionsResult: 拨打电话权限授予成功");
String phoneNumber = phoneNumberEditText.getText().toString().trim();
dialPhoneNumber(phoneNumber);
} else {
LogUtils.w(TAG, "onRequestPermissionsResult: 拨打电话权限被拒绝");
Toast.makeText(this, "未授予拨打电话权限", Toast.LENGTH_SHORT).show();
}
}
}
// ====================== 控件初始化函数区 ======================
private void initViews() {
LogUtils.d(TAG, "initViews: 初始化UI控件");
// Java7 适配:添加强制类型转换
phoneNumberEditText = (EditText) findViewById(R.id.phone_number);
dialButton = (Button) findViewById(R.id.dial_button);
callStatusTextView = (TextView) findViewById(R.id.call_status);
// 设置拨号按钮点击事件
dialButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
String phoneNumber = phoneNumberEditText.getText().toString().trim();
LogUtils.d(TAG, "initViews: 拨号按钮点击,号码=" + phoneNumber);
if (phoneNumber.isEmpty()) {
Toast.makeText(CallActivity.this, "请输入电话号码", Toast.LENGTH_SHORT).show();
return;
}
// 权限检查
if (ContextCompat.checkSelfPermission(CallActivity.this, Manifest.permission.CALL_PHONE)
!= PackageManager.PERMISSION_GRANTED) {
LogUtils.w(TAG, "initViews: 拨打电话权限未授予,发起权限申请");
ActivityCompat.requestPermissions(CallActivity.this,
new String[]{Manifest.permission.CALL_PHONE},
REQUEST_CALL_PHONE);
} else {
dialPhoneNumber(phoneNumber);
}
}
});
}
// ====================== 电话状态监听初始化函数区 ======================
private void initPhoneStateListener() {
LogUtils.d(TAG, "initPhoneStateListener: 初始化电话状态监听");
telephonyManager = (TelephonyManager) getSystemService(TELEPHONY_SERVICE);
phoneStateListener = new MyPhoneStateListener();
telephonyManager.listen(phoneStateListener, PhoneStateListener.LISTEN_CALL_STATE);
}
// ====================== 核心业务函数区 ======================
private void dialPhoneNumber(String phoneNumber) {
LogUtils.d(TAG, "dialPhoneNumber: 发起拨号,号码=" + phoneNumber);
Intent intent = new Intent(Intent.ACTION_CALL);
intent.setData(android.net.Uri.parse("tel:" + phoneNumber));
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.CALL_PHONE) != PackageManager.PERMISSION_GRANTED) {
LogUtils.e(TAG, "dialPhoneNumber: 拨打电话权限缺失,拨号失败");
return;
}
startActivity(intent);
}
// ====================== 内部电话状态监听类 ======================
private class MyPhoneStateListener extends PhoneStateListener {
@Override
public void onCallStateChanged(int state, String incomingNumber) {
super.onCallStateChanged(state, incomingNumber);
switch (state) {
case TelephonyManager.CALL_STATE_IDLE:
callStatusTextView.setText("电话已挂断");
LogUtils.d(TAG, "MyPhoneStateListener: 通话状态-挂断");
break;
case TelephonyManager.CALL_STATE_OFFHOOK:
callStatusTextView.setText("正在通话中");
LogUtils.d(TAG, "MyPhoneStateListener: 通话状态-通话中");
break;
case TelephonyManager.CALL_STATE_RINGING:
callStatusTextView.setText("来电: " + incomingNumber);
LogUtils.d(TAG, "MyPhoneStateListener: 通话状态-来电,号码=" + incomingNumber);
break;
}
}
}
}

View File

@@ -0,0 +1,80 @@
package cc.winboll.studio.contacts.activities;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
import cc.winboll.studio.contacts.R;
import cc.winboll.studio.libappbase.LogUtils;
/**
* @Author ZhanGSKen&豆包大模型<zhangsken@qq.com>
* @Date 2025/02/20 20:18:26
* @Describe 拨号盘窗口(跳转到系统拨号界面)
*/
public class DialerActivity extends AppCompatActivity {
// ====================== 常量定义区 ======================
public static final String TAG = "DialerActivity";
// ====================== UI控件区 ======================
private EditText phoneNumberEditText;
private Button dialButton;
// ====================== 生命周期函数区 ======================
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
LogUtils.d(TAG, "onCreate: 拨号盘页面开始创建");
setContentView(R.layout.activity_dialer);
// 初始化UI控件与点击事件
initViews();
LogUtils.d(TAG, "onCreate: 拨号盘页面初始化完成");
}
@Override
protected void onDestroy() {
super.onDestroy();
LogUtils.d(TAG, "onDestroy: 拨号盘页面已销毁");
}
// ====================== 控件初始化函数区 ======================
private void initViews() {
LogUtils.d(TAG, "initViews: 初始化UI控件");
// Java7 适配:添加强制类型转换
phoneNumberEditText = (EditText) findViewById(R.id.phone_number_edit_text);
dialButton = (Button) findViewById(R.id.dial_button);
// 设置拨号按钮点击事件
dialButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
String phoneNumber = phoneNumberEditText.getText().toString().trim();
LogUtils.d(TAG, "initViews: 拨号按钮点击,输入号码=" + phoneNumber);
// 空号码校验
if (phoneNumber.isEmpty()) {
LogUtils.w(TAG, "initViews: 拨号失败,号码为空");
Toast.makeText(DialerActivity.this, "请输入有效电话号码", Toast.LENGTH_SHORT).show();
return;
}
// 跳转到系统拨号界面
Intent intent = new Intent(Intent.ACTION_DIAL, Uri.parse("tel:" + phoneNumber));
if (intent.resolveActivity(getPackageManager()) != null) {
startActivity(intent);
LogUtils.d(TAG, "initViews: 成功跳转到系统拨号界面");
} else {
LogUtils.e(TAG, "initViews: 跳转失败,无可用拨号应用");
Toast.makeText(DialerActivity.this, "未找到可用拨号应用", Toast.LENGTH_SHORT).show();
}
}
});
}
}

View File

@@ -0,0 +1,613 @@
package cc.winboll.studio.contacts.activities;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.media.AudioManager;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.provider.Settings;
import android.view.View;
import android.view.WindowManager;
import android.widget.EditText;
import android.widget.SeekBar;
import android.widget.Switch;
import android.widget.TextView;
import android.widget.Toast;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import cc.winboll.studio.contacts.R;
import cc.winboll.studio.contacts.adapters.PhoneConnectRuleAdapter;
import cc.winboll.studio.contacts.bobulltoon.TomCat;
import cc.winboll.studio.contacts.dun.Rules;
import cc.winboll.studio.contacts.model.MainServiceBean;
import cc.winboll.studio.contacts.model.PhoneConnectRuleBean;
import cc.winboll.studio.contacts.model.RingTongBean;
import cc.winboll.studio.contacts.model.SettingsBean;
import cc.winboll.studio.contacts.services.MainService;
import cc.winboll.studio.contacts.views.DuInfoTextView;
import cc.winboll.studio.libaes.interfaces.IWinBoLLActivity;
import cc.winboll.studio.libaes.utils.WinBoLLActivityManager;
import cc.winboll.studio.libappbase.LogUtils;
import cc.winboll.studio.libappbase.ToastUtils;
import java.lang.reflect.Field;
import java.util.List;
/**
* @Author ZhanGSKen&豆包大模型<zhangsken@qq.com>
* @Date 2025/02/21 05:37:42
* @Describe Contacts 设置页面(完全适配 API 30 + Java 7 语法)
* 核心优化1. 移除高版本API依赖 2. Java7规范写法 3. 强化内存泄漏防护 4. 版本判断硬编码 5. LogUtils统一日志管理
*/
public class SettingsActivity extends WinBollActivity implements IWinBoLLActivity {
// ====================== 常量定义区(置顶,统一管理) ======================
public static final String TAG = "SettingsActivity";
// API版本硬编码替代Build.VERSION_CODES适配Java7
private static final int ANDROID_6_API = 23;
// ====================== 静态成员属性区 ======================
private static DuInfoTextView sDuInfoTextView; // 规范命名静态属性加s前缀
// ====================== 数据业务属性区 ======================
private int mStreamMaxVolume; // 铃音最大音量
private int mStreamVolume; // 当前铃音音量
private List<PhoneConnectRuleBean> mRuleList; // 通话规则列表
private PhoneConnectRuleAdapter mRuleAdapter; // 规则列表适配器
// ====================== UI控件属性区统一归类规范命名 ======================
private Toolbar mToolbar; // 顶部工具栏
private Switch mSwMainService; // 主服务开关
private SeekBar mSbVolume; // 音量调节条
private TextView mTvVolume; // 音量显示文本
private Switch mSwEnableDun; // 云盾功能开关
private EditText mEtDunTotalCount; // 云盾总次数输入框
private EditText mEtDunResumeSecondCount; // 云盾恢复秒数输入框
private EditText mEtDunResumeCount; // 云盾恢复次数输入框
private RecyclerView mRvRuleList; // 规则列表RecyclerView
private EditText mEtBoBullToonUrl; // BoBullToon地址输入框
private EditText mEtSearchPhone; // 号码查询输入框
// ====================== 接口实现区IWinBoLLActivity规范实现 ======================
@Override
public AppCompatActivity getActivity() {
return this;
}
@Override
public String getTag() {
return TAG;
}
// ====================== 生命周期函数区(按执行顺序排列) ======================
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
LogUtils.d(TAG, "onCreate: 设置页面启动");
setContentView(R.layout.activity_settings);
// 初始化核心流程(按优先级执行)
initToolbar(); // 工具栏初始化(优先)
initMainServiceSwitch();// 主服务开关初始化
initVolumeControl(); // 音量控制初始化
initRuleRecyclerView(); // 规则列表初始化
initDunSettings(); // 云盾设置初始化
initBoBullToonViews(); // BoBullToon功能初始化
LogUtils.d(TAG, "onCreate: 设置页面初始化完成");
}
@Override
protected void onDestroy() {
super.onDestroy();
LogUtils.d(TAG, "onDestroy: 设置页面销毁");
// 内存泄漏防护:清空所有引用(静态+成员+UI
sDuInfoTextView = null;
mRuleList = null;
mRuleAdapter = null;
mToolbar = null;
mSwMainService = null;
mSbVolume = null;
mTvVolume = null;
mSwEnableDun = null;
mEtDunTotalCount = null;
mEtDunResumeSecondCount = null;
mEtDunResumeCount = null;
mRvRuleList = null;
mEtBoBullToonUrl = null;
mEtSearchPhone = null;
LogUtils.d(TAG, "onDestroy: 设置页面资源清理完成");
}
// ====================== 初始化函数区(按功能模块归类) ======================
/**
* 初始化顶部工具栏(后退按钮+标题)
*/
private void initToolbar() {
LogUtils.d(TAG, "initToolbar: 初始化工具栏");
mToolbar = (Toolbar) findViewById(R.id.activitymainToolbar1);
setSupportActionBar(mToolbar);
// 显示后退按钮(空指针防护)
if (getSupportActionBar() != null) {
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
getSupportActionBar().setSubtitle(TAG);
}
// 后退按钮点击事件Java7匿名内部类
mToolbar.setNavigationOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
LogUtils.d(TAG, "initToolbar: 点击后退按钮,关闭页面");
finish();
}
});
}
/**
* 初始化主服务开关联动MainService启停
*/
private void initMainServiceSwitch() {
LogUtils.d(TAG, "initMainServiceSwitch: 初始化主服务开关");
mSwMainService = (Switch) findViewById(R.id.sw_mainservice);
MainServiceBean serviceBean = MainServiceBean.loadBean(this, MainServiceBean.class);
// 加载开关状态(空指针防护)
boolean isServiceEnable = serviceBean != null && serviceBean.isEnable();
mSwMainService.setChecked(isServiceEnable);
LogUtils.d(TAG, "initMainServiceSwitch: 主服务当前状态:" + (isServiceEnable ? "启用" : "禁用"));
// 开关点击事件
mSwMainService.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
boolean isChecked = mSwMainService.isChecked();
LogUtils.d(TAG, "initMainServiceSwitch: 主服务开关切换:" + (isChecked ? "启用" : "禁用"));
if (isChecked) {
MainService.startMainServiceAndSaveStatus(SettingsActivity.this);
} else {
MainService.stopMainServiceAndSaveStatus(SettingsActivity.this);
}
}
});
}
/**
* 初始化音量控制SeekBar+音量显示+配置保存)
*/
private void initVolumeControl() {
LogUtils.d(TAG, "initVolumeControl: 初始化音量控制");
mSbVolume = (SeekBar) findViewById(R.id.bellvolume);
mTvVolume = (TextView) findViewById(R.id.tv_volume);
final AudioManager audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
// 空指针防护AudioManager获取失败直接返回
if (audioManager == null) {
LogUtils.e(TAG, "initVolumeControl: AudioManager获取失败音量控制初始化失败");
return;
}
// 初始化音量参数
mStreamMaxVolume = audioManager.getStreamMaxVolume(AudioManager.STREAM_RING);
mStreamVolume = audioManager.getStreamVolume(AudioManager.STREAM_RING);
mSbVolume.setMax(mStreamMaxVolume);
mSbVolume.setProgress(mStreamVolume);
updateVolumeDisplay(); // 更新音量文本显示
// 音量调节监听
mSbVolume.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
if (fromUser) {
LogUtils.d(TAG, "initVolumeControl: 音量调节至:" + progress + "/" + mStreamMaxVolume);
// 实时更新系统音量+保存配置
audioManager.setStreamVolume(AudioManager.STREAM_RING, progress, 0);
RingTongBean ringBean = RingTongBean.loadBean(SettingsActivity.this, RingTongBean.class);
if (ringBean == null) {
ringBean = new RingTongBean();
}
ringBean.setStreamVolume(progress);
RingTongBean.saveBean(SettingsActivity.this, ringBean);
mStreamVolume = progress;
updateVolumeDisplay();
}
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {}
@Override
public void onStopTrackingTouch(SeekBar seekBar) {}
});
}
/**
* 初始化通话规则列表(加载黑白名单规则)
*/
private void initRuleRecyclerView() {
LogUtils.d(TAG, "initRuleRecyclerView: 初始化规则列表");
mRvRuleList = (RecyclerView) findViewById(R.id.recycler_view);
mRvRuleList.setLayoutManager(new LinearLayoutManager(this));
// 加载规则数据
Rules rules = Rules.getInstance(this);
if (rules == null) {
LogUtils.e(TAG, "initRuleRecyclerView: Rules实例获取失败列表初始化失败");
return;
}
mRuleList = rules.getPhoneBlacRuleBeanList();
mRuleAdapter = new PhoneConnectRuleAdapter(this, mRuleList);
mRvRuleList.setAdapter(mRuleAdapter);
LogUtils.d(TAG, "initRuleRecyclerView: 规则列表加载完成,共" + mRuleList.size() + "条规则");
}
/**
* 初始化云盾设置(参数加载+开关联动)
*/
private void initDunSettings() {
LogUtils.d(TAG, "initDunSettings: 初始化云盾设置");
sDuInfoTextView = (DuInfoTextView) findViewById(R.id.tv_DunInfo);
mSwEnableDun = (Switch) findViewById(R.id.sw_IsEnableDun);
mEtDunTotalCount = (EditText) findViewById(R.id.et_DunTotalCount);
mEtDunResumeSecondCount = (EditText) findViewById(R.id.et_DunResumeSecondCount);
mEtDunResumeCount = (EditText) findViewById(R.id.et_DunResumeCount);
// 加载云盾配置
Rules rules = Rules.getInstance(this);
if (rules == null) {
LogUtils.e(TAG, "initDunSettings: Rules实例获取失败云盾初始化失败");
return;
}
SettingsBean dunSettings = rules.getSettingsModel();
if (dunSettings == null) {
LogUtils.e(TAG, "initDunSettings: 云盾配置获取失败");
return;
}
// 填充配置参数
mEtDunTotalCount.setText(String.valueOf(dunSettings.getDunTotalCount()));
mEtDunResumeSecondCount.setText(String.valueOf(dunSettings.getDunResumeSecondCount()));
mEtDunResumeCount.setText(String.valueOf(dunSettings.getDunResumeCount()));
mSwEnableDun.setChecked(dunSettings.isEnableDun());
// 开关联动:启用云盾时禁用参数编辑
boolean isDunEnable = dunSettings.isEnableDun();
mEtDunTotalCount.setEnabled(!isDunEnable);
mEtDunResumeSecondCount.setEnabled(!isDunEnable);
mEtDunResumeCount.setEnabled(!isDunEnable);
LogUtils.d(TAG, "initDunSettings: 云盾当前状态:" + (isDunEnable ? "启用" : "禁用"));
}
/**
* 初始化BoBullToon功能地址配置+号码查询)
*/
private void initBoBullToonViews() {
LogUtils.d(TAG, "initBoBullToonViews: 初始化BoBullToon功能");
mEtBoBullToonUrl = (EditText) findViewById(R.id.bobulltoonurl_et);
mEtSearchPhone = (EditText) findViewById(R.id.activitysettingsEditText1);
// 加载保存的地址
Rules rules = Rules.getInstance(this);
if (rules != null) {
mEtBoBullToonUrl.setText(rules.getBoBullToonURL());
LogUtils.d(TAG, "initBoBullToonViews: 加载BoBullToon地址完成");
} else {
LogUtils.e(TAG, "initBoBullToonViews: Rules实例获取失败地址加载失败");
}
}
// ====================== 点击事件回调区(按功能模块归类) ======================
/**
* 云盾开关点击事件(联动参数编辑权限+配置保存)
*/
public void onSW_IsEnableDun(View view) {
boolean isChecked = mSwEnableDun.isChecked();
LogUtils.d(TAG, "onSW_IsEnableDun: 云盾开关切换:" + (isChecked ? "启用" : "禁用"));
// 联动参数编辑权限
mEtDunTotalCount.setEnabled(!isChecked);
mEtDunResumeSecondCount.setEnabled(!isChecked);
mEtDunResumeCount.setEnabled(!isChecked);
// 保存配置
Rules rules = Rules.getInstance(this);
if (rules == null) {
LogUtils.e(TAG, "onSW_IsEnableDun: Rules实例获取失败配置保存失败");
mSwEnableDun.setChecked(false);
return;
}
SettingsBean dunSettings = rules.getSettingsModel();
if (dunSettings == null) {
LogUtils.e(TAG, "onSW_IsEnableDun: 云盾配置获取失败,保存失败");
mSwEnableDun.setChecked(false);
return;
}
// 启用云盾时校验参数合法性
if (isChecked) {
try {
String totalCountStr = mEtDunTotalCount.getText().toString().trim();
String resumeSecStr = mEtDunResumeSecondCount.getText().toString().trim();
String resumeCountStr = mEtDunResumeCount.getText().toString().trim();
// 空参数校验
if (totalCountStr.isEmpty() || resumeSecStr.isEmpty() || resumeCountStr.isEmpty()) {
throw new NumberFormatException("参数不能为空");
}
// 转换参数并保存
int totalCount = Integer.parseInt(totalCountStr);
int resumeSec = Integer.parseInt(resumeSecStr);
int resumeCount = Integer.parseInt(resumeCountStr);
dunSettings.setDunTotalCount(totalCount);
dunSettings.setDunResumeSecondCount(resumeSec);
dunSettings.setDunResumeCount(resumeCount);
LogUtils.d(TAG, "onSW_IsEnableDun: 云盾参数保存完成,总次数:" + totalCount + ",恢复秒数:" + resumeSec);
// 提示信息
String toastMsg = totalCount == 1 ? "电话骚扰防御力几乎为0" : "连拨" + totalCount + "次后接通电话";
ToastUtils.show(toastMsg);
} catch (NumberFormatException e) {
LogUtils.e(TAG, "onSW_IsEnableDun: 云盾参数格式错误", e);
ToastUtils.show("参数格式错误,请输入整数");
mSwEnableDun.setChecked(false);
return;
}
}
// 保存开关状态并刷新配置
dunSettings.setIsEnableDun(isChecked);
rules.saveDun();
rules.reload();
LogUtils.d(TAG, "onSW_IsEnableDun: 云盾配置保存完成");
}
/**
* 添加新通话规则(黑白名单)
*/
public void onAddNewConnectionRule(View view) {
LogUtils.d(TAG, "onAddNewConnectionRule: 添加新通话规则");
Rules rules = Rules.getInstance(this);
if (rules == null) {
LogUtils.e(TAG, "onAddNewConnectionRule: Rules实例获取失败添加失败");
return;
}
mRuleList.add(new PhoneConnectRuleBean());
rules.saveRules();
mRuleAdapter.notifyDataSetChanged();
LogUtils.d(TAG, "onAddNewConnectionRule: 规则添加完成,当前共" + mRuleList.size() + "条规则");
}
/**
* 跳转默认电话应用设置
*/
public void onDefaultPhone(View view) {
LogUtils.d(TAG, "onDefaultPhone: 跳转默认电话应用设置");
startActivity(new Intent(Settings.ACTION_MANAGE_DEFAULT_APPS_SETTINGS));
}
/**
* 悬浮窗权限检查与请求
*/
public void onCanDrawOverlays(View view) {
LogUtils.d(TAG, "onCanDrawOverlays: 检查悬浮窗权限");
// API6.0+校验权限
if (Build.VERSION.SDK_INT >= ANDROID_6_API && !Settings.canDrawOverlays(this)) {
LogUtils.d(TAG, "onCanDrawOverlays: 未开启悬浮窗权限,发起请求");
showDrawOverlayRequestDialog();
} else {
ToastUtils.show("悬浮窗权限已开启");
}
}
/**
* 清理BoBullToon本地数据
*/
public void onCleanBoBullToonData(View view) {
LogUtils.d(TAG, "onCleanBoBullToonData: 清理BoBullToon数据");
TomCat tomCat = TomCat.getInstance(this);
if (tomCat != null) {
tomCat.cleanBoBullToon();
ToastUtils.show("BoBullToon数据已清理");
LogUtils.d(TAG, "onCleanBoBullToonData: 数据清理完成");
} else {
LogUtils.e(TAG, "onCleanBoBullToonData: TomCat实例获取失败清理失败");
}
}
/**
* 重置BoBullToon默认地址
*/
public void onResetBoBullToonURL(View view) {
LogUtils.d(TAG, "onResetBoBullToonURL: 重置BoBullToon地址");
Rules rules = Rules.getInstance(this);
if (rules == null) {
LogUtils.e(TAG, "onResetBoBullToonURL: Rules实例获取失败重置失败");
return;
}
rules.resetDefaultBoBullToonURL();
mEtBoBullToonUrl.setText(rules.getBoBullToonURL());
ToastUtils.show("BoBullToon地址已重置为默认");
LogUtils.d(TAG, "onResetBoBullToonURL: 地址重置完成");
}
/**
* 下载BoBullToon数据子线程执行避免阻塞UI
*/
public void onDownloadBoBullToon(View view) {
LogUtils.d(TAG, "onDownloadBoBullToon: 开始下载BoBullToon数据");
Rules rules = Rules.getInstance(this);
if (rules == null) {
LogUtils.e(TAG, "onDownloadBoBullToon: Rules实例获取失败下载失败");
return;
}
// 校验并更新地址
String inputUrl = mEtBoBullToonUrl.getText().toString().trim();
String savedUrl = rules.getBoBullToonURL();
if (!inputUrl.equals(savedUrl)) {
rules.setBoBullToonURL(inputUrl);
LogUtils.d(TAG, "onDownloadBoBullToon: BoBullToon地址更新为" + inputUrl);
}
// 子线程下载Java7匿名内部类
final TomCat tomCat = TomCat.getInstance(this);
new Thread(new Runnable() {
@Override
public void run() {
boolean downloadSuccess = tomCat != null && tomCat.downloadBoBullToon();
if (downloadSuccess) {
LogUtils.d(TAG, "onDownloadBoBullToon: 数据下载成功");
// 主线程更新UI
runOnUiThread(new Runnable() {
@Override
public void run() {
ToastUtils.show("BoBullToon下载成功");
}
});
// 重启主服务+刷新配置
MainService.restartMainService(SettingsActivity.this);
Rules.getInstance(SettingsActivity.this).reload();
} else {
LogUtils.e(TAG, "onDownloadBoBullToon: 数据下载失败");
runOnUiThread(new Runnable() {
@Override
public void run() {
ToastUtils.show("BoBullToon下载失败");
}
});
}
}
}).start();
}
/**
* 查询号码是否为BoBullToon号码
*/
public void onSearchBoBullToonPhone(View view) {
LogUtils.d(TAG, "onSearchBoBullToonPhone: 执行号码查询");
String phone = mEtSearchPhone.getText().toString().trim();
// 空号码校验
if (phone.isEmpty()) {
LogUtils.w(TAG, "onSearchBoBullToonPhone: 查询号码为空,取消查询");
ToastUtils.show("请输入查询号码");
return;
}
// 执行查询
TomCat tomCat = TomCat.getInstance(this);
if (tomCat == null || !tomCat.loadPhoneBoBullToon()) {
LogUtils.w(TAG, "onSearchBoBullToonPhone: BoBullToon数据未加载查询失败");
ToastUtils.show("请先下载BoBullToon数据");
return;
}
boolean isBoBullToon = tomCat.isPhoneBoBullToon(phone);
String resultMsg = isBoBullToon ? "是BoBullToon号码" : "非BoBullToon号码";
ToastUtils.show(resultMsg);
LogUtils.d(TAG, "onSearchBoBullToonPhone: 号码" + phone + "查询结果:" + resultMsg);
}
/**
* 跳转单元测试页面
*/
public void onUnitTest(View view) {
LogUtils.d(TAG, "onUnitTest: 跳转单元测试页面");
startActivity(new Intent(this, UnitTestActivity.class));
}
/**
* 跳转关于页面
*/
public void onAbout(View view) {
LogUtils.d(TAG, "onAbout: 跳转关于页面");
WinBoLLActivityManager.getInstance().startWinBoLLActivity(this, AboutActivity.class);
}
/**
* 跳转日志查看页面
*/
public void onLogView(View view) {
LogUtils.d(TAG, "onLogView: 跳转日志页面");
WinBoLLActivityManager.getInstance().startLogActivity(this);
}
// ====================== 工具方法区(通用功能+权限相关) ======================
/**
* 更新音量显示文本(当前音量/最大音量)
*/
private void updateVolumeDisplay() {
mTvVolume.setText(mStreamVolume + "/" + mStreamMaxVolume);
}
/**
* 显示悬浮窗权限请求对话框
*/
private void showDrawOverlayRequestDialog() {
AlertDialog dialog = new AlertDialog.Builder(this)
.setTitle("权限请求")
.setMessage("为保证通话监听功能正常,需开启悬浮窗权限")
.setPositiveButton("去设置", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
jumpToDrawOverlaySettings();
}
})
.setNegativeButton("稍后", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
}
})
.create();
// 解决对话框焦点问题
if (dialog.getWindow() != null) {
dialog.getWindow().setFlags(
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE);
}
dialog.show();
}
/**
* 跳转悬浮窗权限设置页面(反射适配低版本)
*/
private void jumpToDrawOverlaySettings() {
LogUtils.d(TAG, "jumpToDrawOverlaySettings: 跳转悬浮窗权限设置");
try {
// 反射获取设置页面Action避免高版本API依赖
Class<?> settingsClazz = Settings.class;
Field actionField = settingsClazz.getDeclaredField("ACTION_MANAGE_OVERLAY_PERMISSION");
String action = (String) actionField.get(null);
// 跳转当前应用权限设置页
Intent intent = new Intent(action);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.setData(Uri.parse("package:" + getPackageName()));
startActivity(intent);
} catch (Exception e) {
LogUtils.e(TAG, "jumpToDrawOverlaySettings: 跳转权限设置失败", e);
Toast.makeText(this, "请手动在设置中开启悬浮窗权限", Toast.LENGTH_LONG).show();
}
}
// ====================== 静态通知方法区(云盾信息更新) ======================
/**
* 通知云盾信息刷新(外部调用)
*/
public static void notifyDunInfoUpdate() {
if (sDuInfoTextView != null) {
LogUtils.d(TAG, "notifyDunInfoUpdate: 刷新云盾信息显示");
sDuInfoTextView.notifyInfoUpdate();
} else {
LogUtils.w(TAG, "notifyDunInfoUpdate: 云盾信息控件未初始化,刷新失败");
}
}
}

View File

@@ -0,0 +1,154 @@
package cc.winboll.studio.contacts.activities;
import android.os.Bundle;
import android.view.View;
import android.widget.EditText;
import androidx.appcompat.app.AppCompatActivity;
import cc.winboll.studio.contacts.R;
import cc.winboll.studio.contacts.activities.UnitTestActivity;
import cc.winboll.studio.contacts.dun.Rules;
import cc.winboll.studio.contacts.services.LimitedTimeSpecialChannelService;
import cc.winboll.studio.contacts.utils.IntUtils;
import cc.winboll.studio.libaes.interfaces.IWinBoLLActivity;
import cc.winboll.studio.libappbase.LogUtils;
import cc.winboll.studio.libappbase.LogView;
/**
* @Author ZhanGSKen&豆包大模型<zhangsken@qq.com>
* @Date 2025/03/02 16:07:04
* @Describe 规则单元测试页面
*/
public class UnitTestActivity extends WinBollActivity implements IWinBoLLActivity {
// ====================== 常量定义区 ======================
public static final String TAG = "UnitTestActivity";
// ====================== UI控件区 ======================
private LogView logView;
private EditText etPhone;
// ====================== 接口实现区 ======================
@Override
public AppCompatActivity getActivity() {
return this;
}
@Override
public String getTag() {
return TAG;
}
// ====================== 生命周期函数区 ======================
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
LogUtils.d(TAG, "onCreate: 单元测试页面开始创建");
setContentView(R.layout.activity_unittest);
// 初始化控件
initViews();
LogUtils.d(TAG, "onCreate: 单元测试页面初始化完成");
}
@Override
protected void onDestroy() {
super.onDestroy();
LogUtils.d(TAG, "onDestroy: 单元测试页面开始销毁");
if (logView != null) {
// 若LogView有停止方法建议调用避免资源泄漏
// logView.stop();
LogUtils.d(TAG, "onDestroy: LogView资源已处理");
}
LogUtils.d(TAG, "onDestroy: 单元测试页面销毁完成");
}
// ====================== 控件初始化函数区 ======================
private void initViews() {
LogUtils.d(TAG, "initViews: 初始化UI控件");
// Java7 适配:添加强制类型转换
logView = (LogView) findViewById(R.id.logview);
etPhone = (EditText) findViewById(R.id.phone_et);
// 启动日志视图
logView.start();
LogUtils.d(TAG, "initViews: LogView已启动");
}
// ====================== 点击事件测试函数区 ======================
/**
* 测试单个号码匹配规则
*/
public void onTestPhone(View view) {
LogUtils.d(TAG, "onTestPhone: 开始测试单个号码规则匹配");
String phone = etPhone.getText().toString().trim();
if (phone.isEmpty()) {
LogUtils.w(TAG, "onTestPhone: 测试号码为空,跳过匹配");
return;
}
Rules rules = Rules.getInstance(this);
boolean isAllowed = rules.isAllowed(phone);
LogUtils.d(TAG, String.format("onTestPhone: 测试号码: %s | 匹配结果: %s", phone, isAllowed));
}
/**
* 批量测试预设号码规则匹配
*/
public void onTestMain(View view) {
LogUtils.d(TAG, "onTestMain: 开始批量测试号码规则匹配");
// 测试IntUtils工具类方法
LogUtils.d(TAG, "onTestMain: 执行 IntUtils.unittest_getIntInRange() 测试");
IntUtils.unittest_getIntInRange();
// 初始化规则实例
Rules rules = Rules.getInstance(this);
// 无规则时添加测试规则集
initTestRulesIfEmpty(rules);
// 预设测试号码列表
String[] testPhones = {
"16769764848", "16856582777", "17519703124",
"0205658955", "0108965253", "+8616769764848",
"4005816769764848", "95566"
};
// 遍历测试号码并输出结果
for (String phone : testPhones) {
boolean isAllowed = rules.isAllowed(phone);
LogUtils.d(TAG, String.format("onTestMain: 测试号码: %s | 匹配结果: %s", phone, isAllowed));
}
LogUtils.d(TAG, "onTestMain: 批量号码规则测试完成");
new Thread(new Runnable(){
@Override
public void run() {
LimitedTimeSpecialChannelService.unitTest(UnitTestActivity.this);
}
}).start();
}
// ====================== 私有工具函数区 ======================
/**
* 规则集为空时初始化测试规则
*/
private void initTestRulesIfEmpty(Rules rules) {
if (rules.getPhoneBlacRuleBeanList().size() == 0) {
LogUtils.d(TAG, "initTestRulesIfEmpty: 当前无规则,添加测试规则集");
// 规则1中国手机号允许
rules.add("^1[3-9]\\d{9}$", true, true);
// 规则20660区号号码允许
rules.add("^0660\\d+$", true, true);
// 规则3020区号号码允许
rules.add("^020\\d+$", true, true);
// 规则4默认拒接所有号码
rules.add(".*", false, true);
// 保存规则到本地
rules.saveRules();
LogUtils.d(TAG, "initTestRulesIfEmpty: 测试规则集已保存");
} else {
LogUtils.d(TAG, "initTestRulesIfEmpty: 当前已有规则,跳过初始化");
}
}
}

View File

@@ -0,0 +1,84 @@
package cc.winboll.studio.contacts.activities;
import android.app.Activity;
import android.os.Bundle;
import android.view.MenuItem;
import androidx.appcompat.app.AppCompatActivity;
import cc.winboll.studio.libaes.interfaces.IWinBoLLActivity;
import cc.winboll.studio.libaes.models.AESThemeBean;
import cc.winboll.studio.libaes.utils.AESThemeUtil;
import cc.winboll.studio.libappbase.LogUtils;
/**
* @Author ZhanGSKen&豆包大模型<zhangsken@qq.com>
* @Date 2025/03/31 15:16:45
* @Describe 应用窗口基类,统一处理主题设置与导航返回
*/
public class WinBollActivity extends AppCompatActivity implements IWinBoLLActivity {
// ====================== 常量定义区 ======================
public static final String TAG = "WinBollActivity";
// ====================== 成员变量区 ======================
protected volatile AESThemeBean.ThemeType mThemeType;
// ====================== 接口实现区 ======================
@Override
public Activity getActivity() {
return this;
}
@Override
public String getTag() {
return TAG;
}
// ====================== 生命周期函数区 ======================
@Override
protected void onCreate(Bundle savedInstanceState) {
//LogUtils.d(TAG, "onCreate: 基类页面开始创建");
// 优先设置主题,再执行父类初始化
// mThemeType = getThemeType();
// setThemeStyle();
super.onCreate(savedInstanceState);
//LogUtils.d(TAG, "onCreate: 基类主题设置完成,当前主题类型=" + mThemeType);
}
// ====================== 主题相关函数区 ======================
/**
* 获取当前应用主题类型
*/
AESThemeBean.ThemeType getThemeType() {
LogUtils.d(TAG, "getThemeType: 获取应用主题类型");
// 注释的SharedPreferences逻辑保留便于后续扩展
/*SharedPreferences sharedPreferences = getSharedPreferences(
SHAREDPREFERENCES_NAME, MODE_PRIVATE);
return AESThemeBean.ThemeType.values()[((sharedPreferences.getInt(DRAWER_THEME_TYPE, AESThemeBean.ThemeType.DEFAULT.ordinal())))];
*/
return AESThemeBean.getThemeStyleType(AESThemeUtil.getThemeTypeID(getApplicationContext()));
}
/**
* 应用当前主题样式
*/
void setThemeStyle() {
LogUtils.d(TAG, "setThemeStyle: 开始设置应用主题");
// 替换原注释逻辑使用AESThemeUtil获取的主题ID
setTheme(AESThemeUtil.getThemeTypeID(getApplicationContext()));
LogUtils.d(TAG, "setThemeStyle: 主题设置完成");
}
// ====================== 菜单与导航函数区 ======================
@Override
public boolean onOptionsItemSelected(MenuItem item) {
LogUtils.d(TAG, "onOptionsItemSelected: 菜单选项点击itemId=" + item.getItemId());
// 处理导航栏返回按钮点击事件
// if (item.getItemId() == android.R.id.home) {
// LogUtils.d(TAG, "onOptionsItemSelected: 点击导航返回按钮,关闭当前页面");
// finish();
// return true;
// }
return super.onOptionsItemSelected(item);
}
}

View File

@@ -0,0 +1,183 @@
package cc.winboll.studio.contacts.adapters;
import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.Context;
import android.content.Intent;
import android.view.LayoutInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.PopupMenu;
import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import cc.winboll.studio.contacts.R;
import cc.winboll.studio.contacts.model.CallLogModel;
import cc.winboll.studio.contacts.utils.ContactUtils;
import cc.winboll.studio.libaes.views.AOHPCTCSeekBar;
import cc.winboll.studio.libappbase.LogUtils;
import cc.winboll.studio.libappbase.ToastUtils;
import java.text.SimpleDateFormat;
import java.util.List;
import java.util.Locale;
import cc.winboll.studio.contacts.dun.Rules;
/**
* @Author ZhanGSKen&豆包大模型<zhangsken@qq.com>
* @Date 2025/02/26 13:09:32
* @Describe 通话记录列表适配器
*/
public class CallLogAdapter extends RecyclerView.Adapter<CallLogAdapter.CallLogViewHolder> {
// ====================== 常量定义区 ======================
public static final String TAG = "CallLogAdapter";
private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault());
// ====================== 成员变量区 ======================
private Context mContext;
private List<CallLogModel> callLogList;
private ContactUtils mContactUtils;
// ====================== 构造函数区 ======================
public CallLogAdapter(Context context, List<CallLogModel> callLogList) {
LogUtils.d(TAG, "CallLogAdapter: 初始化适配器,数据量=" + callLogList.size());
this.mContext = context;
this.callLogList = callLogList;
this.mContactUtils = ContactUtils.getInstance(mContext);
}
// ====================== 公共方法区 ======================
/**
* 重新加载联系人数据
*/
public void relaodContacts() {
LogUtils.d(TAG, "relaodContacts: 开始重新加载联系人数据");
this.mContactUtils.reloadContacts();
notifyDataSetChanged();
LogUtils.d(TAG, "relaodContacts: 联系人数据加载完成,列表已刷新");
}
// ====================== RecyclerView 重写方法区 ======================
@NonNull
@Override
public CallLogViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
LogUtils.d(TAG, "onCreateViewHolder: 创建列表项ViewHolder");
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_call_log, parent, false);
return new CallLogViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull CallLogViewHolder holder, int position) {
LogUtils.d(TAG, "onBindViewHolder: 绑定列表项数据position=" + position);
final CallLogModel callLog = callLogList.get(position);
// 绑定通话号码与联系人名称
String contactName = mContactUtils.getContactName(callLog.getPhoneNumber());
String phoneText = callLog.getPhoneNumber() + "" + (contactName == null ? "" : contactName);
holder.phoneNumber.setText(phoneText);
// 号码长按弹出菜单事件
holder.phoneNumber.setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View p1) {
showPhonePopupMenu(holder.phoneNumber, callLog);
return true;
}
});
// 绑定通话状态与时间
holder.callStatus.setText(callLog.getCallStatus());
holder.callDate.setText(DATE_FORMAT.format(callLog.getCallDate()));
// 初始化滑动拨号SeekBar
initDialSeekBar(holder.dialAOHPCTCSeekBar, callLog);
}
@Override
public int getItemCount() {
return callLogList == null ? 0 : callLogList.size();
}
// ====================== 私有工具方法区 ======================
/**
* 显示号码操作弹窗菜单
*/
private void showPhonePopupMenu(View anchorView, final CallLogModel callLog) {
LogUtils.d(TAG, "showPhonePopupMenu: 弹出号码操作菜单");
PopupMenu menu = new PopupMenu(mContext, anchorView);
menu.getMenuInflater().inflate(R.menu.toolbar_calllog_phonenumber, menu.getMenu());
menu.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(MenuItem menuItem) {
int itemId = menuItem.getItemId();
if (itemId == R.id.item_calllog_phonenumber_copy) {
// 复制号码到剪贴板
ClipboardManager clipboard = (ClipboardManager) mContext.getSystemService(Context.CLIPBOARD_SERVICE);
ClipData clip = ClipData.newPlainText("call_log_phone", callLog.getPhoneNumber());
clipboard.setPrimaryClip(clip);
Toast.makeText(mContext, "Copy to clipboard.", Toast.LENGTH_SHORT).show();
LogUtils.d(TAG, "showPhonePopupMenu: 号码" + callLog.getPhoneNumber() + "已复制到剪贴板");
} else if (itemId == R.id.item_calllog_phonenumber_yundun_test) {
// 跳转到添加联系人页面
//if (Rules.getInstance(mContext).isAllowed(callLog.getPhoneNumber(), false)) {
if (Rules.getInstance(mContext).isAllowed(callLog.getPhoneNumber(), true)) {
ToastUtils.show("(✔)" + callLog.getPhoneNumber() + " Is Allowed By YunDun.");
} else {
ToastUtils.show("(✘)YunDun Defense The Phone " + callLog.getPhoneNumber() + "");
}
} else if (itemId == R.id.item_calllog_phonenumber_add_contact) {
// 跳转到添加联系人页面
ContactUtils.jumpToAddContact(mContext, callLog.getPhoneNumber());
LogUtils.d(TAG, "showPhonePopupMenu: 跳转添加联系人页面,号码=" + callLog.getPhoneNumber());
}
return true;
}
});
menu.show();
}
/**
* 初始化滑动拨号SeekBar
*/
private void initDialSeekBar(AOHPCTCSeekBar seekBar, final CallLogModel callLog) {
LogUtils.d(TAG, "initDialSeekBar: 初始化滑动拨号控件");
seekBar.setThumb(seekBar.getContext().getDrawable(R.drawable.ic_call));
seekBar.setBlurRightDP(80);
seekBar.setThumbOffset(0);
seekBar.setOnOHPCListener(new AOHPCTCSeekBar.OnOHPCListener() {
@Override
public void onOHPCommit() {
String phoneNumber = callLog.getPhoneNumber().replaceAll("\\s", "");
LogUtils.d(TAG, "initDialSeekBar: 滑动拨号触发,号码=" + phoneNumber);
ToastUtils.show(phoneNumber);
Intent intent = new Intent(Intent.ACTION_CALL);
intent.setData(android.net.Uri.parse("tel:" + phoneNumber));
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
mContext.startActivity(intent);
}
});
}
// ====================== ViewHolder 内部类 ======================
public class CallLogViewHolder extends RecyclerView.ViewHolder {
TextView phoneNumber;
TextView callStatus;
TextView callDate;
AOHPCTCSeekBar dialAOHPCTCSeekBar;
public CallLogViewHolder(@NonNull View itemView) {
super(itemView);
// Java7 适配:添加强制类型转换
phoneNumber = (TextView) itemView.findViewById(R.id.phone_number);
callStatus = (TextView) itemView.findViewById(R.id.call_status);
callDate = (TextView) itemView.findViewById(R.id.call_date);
dialAOHPCTCSeekBar = (AOHPCTCSeekBar) itemView.findViewById(R.id.aohpctcseekbar_dial);
}
}
}

View File

@@ -0,0 +1,157 @@
package cc.winboll.studio.contacts.adapters;
import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.Context;
import android.content.Intent;
import android.view.LayoutInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.LinearLayout;
import android.widget.PopupMenu;
import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import cc.winboll.studio.contacts.R;
import cc.winboll.studio.contacts.model.ContactModel;
import cc.winboll.studio.contacts.utils.ContactUtils;
import cc.winboll.studio.libaes.views.AOHPCTCSeekBar;
import cc.winboll.studio.libappbase.LogUtils;
import cc.winboll.studio.libappbase.ToastUtils;
import java.util.List;
/**
* @Author ZhanGSKen&豆包大模型<zhangsken@qq.com>
* @Date 2025/02/26 13:35:44
* @Describe 联系人列表适配器
*/
public class ContactAdapter extends RecyclerView.Adapter<ContactAdapter.ContactViewHolder> {
// ====================== 常量定义区 ======================
public static final String TAG = "ContactAdapter";
// 移除未使用的 REQUEST_CALL_PHONE 常量,精简冗余代码
// ====================== 成员变量区 ======================
private Context mContext;
private List<ContactModel> contactList;
// ====================== 构造函数区 ======================
public ContactAdapter(Context context, List<ContactModel> contactList) {
LogUtils.d(TAG, "ContactAdapter: 初始化适配器,联系人数量=" + contactList.size());
this.mContext = context;
this.contactList = contactList;
}
// ====================== RecyclerView 重写方法区 ======================
@NonNull
@Override
public ContactViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
LogUtils.d(TAG, "onCreateViewHolder: 创建联系人列表项ViewHolder");
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_contact, parent, false);
return new ContactViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull ContactViewHolder holder, int position) {
LogUtils.d(TAG, "onBindViewHolder: 绑定联系人列表项数据position=" + position);
final ContactModel contact = contactList.get(position);
// 绑定联系人名称与号码
holder.contactName.setText(contact.getName());
holder.contactNumber.setText(contact.getNumber());
// 长按联系人条目弹出操作菜单
holder.llPhoneNumberMain.setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
showContactPopupMenu(holder.llPhoneNumberMain, contact);
return true;
}
});
// 初始化滑动拨号SeekBar
initDialSeekBar(holder.dialAOHPCTCSeekBar, contact);
}
@Override
public int getItemCount() {
// 增加空指针判断,避免空列表崩溃
return contactList == null ? 0 : contactList.size();
}
// ====================== 私有工具方法区 ======================
/**
* 显示联系人操作弹窗菜单
*/
private void showContactPopupMenu(View anchorView, final ContactModel contact) {
LogUtils.d(TAG, "showContactPopupMenu: 弹出联系人操作菜单");
PopupMenu menu = new PopupMenu(mContext, anchorView);
menu.getMenuInflater().inflate(R.menu.toolbar_contact_phonenumber, menu.getMenu());
menu.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(MenuItem menuItem) {
int itemId = menuItem.getItemId();
if (itemId == R.id.item_contact_phonenumber_copy) {
// 复制联系人号码到剪贴板
ClipboardManager clipboard = (ClipboardManager) mContext.getSystemService(Context.CLIPBOARD_SERVICE);
ClipData clip = ClipData.newPlainText("contact_phone", contact.getNumber());
clipboard.setPrimaryClip(clip);
Toast.makeText(mContext, "Copy to clipboard.", Toast.LENGTH_SHORT).show();
LogUtils.d(TAG, "showContactPopupMenu: 联系人号码" + contact.getNumber() + "已复制到剪贴板");
} else if (itemId == R.id.item_calllog_phonenumber_edit_contact) {
// 跳转到编辑联系人页面
Long contactId = ContactUtils.getContactIdByPhone(mContext, contact.getNumber());
ContactUtils.jumpToEditContact(mContext, contact.getNumber(), contactId);
LogUtils.d(TAG, "showContactPopupMenu: 跳转编辑联系人页面,号码=" + contact.getNumber() + "ID=" + contactId);
}
return true;
}
});
menu.show();
}
/**
* 初始化滑动拨号SeekBar
*/
private void initDialSeekBar(AOHPCTCSeekBar seekBar, final ContactModel contact) {
LogUtils.d(TAG, "initDialSeekBar: 初始化滑动拨号控件");
seekBar.setThumb(seekBar.getContext().getDrawable(R.drawable.ic_call));
seekBar.setBlurRightDP(80);
seekBar.setThumbOffset(0);
seekBar.setOnOHPCListener(new AOHPCTCSeekBar.OnOHPCListener() {
@Override
public void onOHPCommit() {
String phoneNumber = contact.getNumber().replaceAll("\\s", "");
LogUtils.d(TAG, "initDialSeekBar: 滑动拨号触发,号码=" + phoneNumber);
ToastUtils.show(phoneNumber);
Intent intent = new Intent(Intent.ACTION_CALL);
intent.setData(android.net.Uri.parse("tel:" + phoneNumber));
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
mContext.startActivity(intent);
}
});
}
// ====================== ViewHolder 内部类 ======================
public class ContactViewHolder extends RecyclerView.ViewHolder {
LinearLayout llPhoneNumberMain;
TextView contactName;
TextView contactNumber;
AOHPCTCSeekBar dialAOHPCTCSeekBar;
public ContactViewHolder(@NonNull View itemView) {
super(itemView);
// Java7 适配:添加强制类型转换
llPhoneNumberMain = (LinearLayout) itemView.findViewById(R.id.itemcontactLinearLayout1);
contactName = (TextView) itemView.findViewById(R.id.contact_name);
contactNumber = (TextView) itemView.findViewById(R.id.contact_number);
dialAOHPCTCSeekBar = (AOHPCTCSeekBar) itemView.findViewById(R.id.aohpctcseekbar_dial);
}
}
}

View File

@@ -0,0 +1,257 @@
package cc.winboll.studio.contacts.adapters;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import cc.winboll.studio.contacts.R;
import cc.winboll.studio.contacts.model.PhoneConnectRuleBean;
import cc.winboll.studio.contacts.dun.Rules;
import cc.winboll.studio.contacts.views.LeftScrollView;
import cc.winboll.studio.libaes.dialogs.YesNoAlertDialog;
import cc.winboll.studio.libappbase.LogUtils;
import cc.winboll.studio.libappbase.ToastUtils;
import java.util.ArrayList;
import java.util.List;
/**
* @Author ZhanGSKen&豆包大模型<zhangsken@qq.com>
* @Date 2025/03/02 17:27:41
* @Describe 通话规则列表适配器,支持简单查看/编辑两种视图切换
*/
public class PhoneConnectRuleAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
// ====================== 常量定义区 ======================
public static final String TAG = "PhoneConnectRuleAdapter";
private static final int VIEW_TYPE_SIMPLE = 0;
private static final int VIEW_TYPE_EDIT = 1;
private static final String NULL_RULE_TEXT = "[NULL]";
// ====================== 成员变量区 ======================
private Context mContext;
private List<PhoneConnectRuleBean> mRuleList;
// ====================== 构造函数区 ======================
public PhoneConnectRuleAdapter(Context context, List<PhoneConnectRuleBean> ruleList) {
LogUtils.d(TAG, "PhoneConnectRuleAdapter: 初始化适配器,规则数量=" + ruleList.size());
this.mContext = context;
this.mRuleList = ruleList;
}
// ====================== RecyclerView 重写方法区 ======================
@NonNull
@Override
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
LayoutInflater inflater = LayoutInflater.from(mContext);
if (viewType == VIEW_TYPE_SIMPLE) {
LogUtils.d(TAG, "onCreateViewHolder: 创建简单视图ViewHolder");
View view = inflater.inflate(R.layout.view_phone_connect_rule_simple, parent, false);
return new SimpleViewHolder(parent, view);
} else {
LogUtils.d(TAG, "onCreateViewHolder: 创建编辑视图ViewHolder");
View view = inflater.inflate(R.layout.view_phone_connect_rule, parent, false);
return new EditViewHolder(view);
}
}
@Override
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, final int position) {
final PhoneConnectRuleBean model = mRuleList.get(position);
LogUtils.d(TAG, "onBindViewHolder: 绑定规则数据position=" + position + ",视图类型=" + getItemViewType(position));
if (holder instanceof SimpleViewHolder) {
bindSimpleViewHolder((SimpleViewHolder) holder, model, position);
} else if (holder instanceof EditViewHolder) {
bindEditViewHolder((EditViewHolder) holder, model, position);
}
}
@Override
public int getItemCount() {
return mRuleList == null ? 0 : mRuleList.size();
}
@Override
public int getItemViewType(int position) {
return mRuleList.get(position).isSimpleView() ? VIEW_TYPE_SIMPLE : VIEW_TYPE_EDIT;
}
// ====================== 私有视图绑定方法区 ======================
/**
* 绑定简单视图数据
*/
private void bindSimpleViewHolder(final SimpleViewHolder holder, final PhoneConnectRuleBean model, final int position) {
// 绑定规则文本,空值显示[NULL]
String ruleText = model.getRuleText().trim().isEmpty() ? NULL_RULE_TEXT : model.getRuleText().trim();
holder.tvRuleText.setText(ruleText);
// 设置复选框状态并禁用编辑
holder.checkBoxAllow.setChecked(model.isAllowConnection());
holder.checkBoxAllow.setEnabled(false);
holder.checkBoxEnable.setChecked(model.isEnable());
holder.checkBoxEnable.setEnabled(false);
// 设置左滑操作监听
holder.scrollView.setOnActionListener(new LeftScrollView.OnActionListener() {
@Override
public void onUp() {
LogUtils.d(TAG, "onUp: 规则上移position=" + position);
moveRuleUp(position);
holder.scrollView.smoothScrollTo(0, 0);
}
@Override
public void onDown() {
LogUtils.d(TAG, "onDown: 规则下移position=" + position);
moveRuleDown(position);
holder.scrollView.smoothScrollTo(0, 0);
}
@Override
public void onEdit() {
LogUtils.d(TAG, "onEdit: 切换到编辑视图position=" + position);
model.setIsSimpleView(false);
notifyItemChanged(position);
holder.scrollView.smoothScrollTo(0, 0);
}
@Override
public void onDelete() {
LogUtils.d(TAG, "onDelete: 触发规则删除确认position=" + position);
showDeleteConfirmDialog(holder.scrollView.getContext(), model, position);
}
});
}
/**
* 绑定编辑视图数据
*/
private void bindEditViewHolder(final EditViewHolder holder, final PhoneConnectRuleBean model, final int position) {
// 绑定规则文本到输入框
holder.editText.setText(model.getRuleText());
// 绑定复选框状态
holder.checkBoxAllow.setChecked(model.isAllowConnection());
holder.checkBoxEnable.setChecked(model.isEnable());
// 确认按钮点击事件
holder.buttonConfirm.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
String newRuleText = holder.editText.getText().toString().trim();
model.setRuleText(newRuleText);
model.setIsAllowConnection(holder.checkBoxAllow.isChecked());
model.setIsEnable(holder.checkBoxEnable.isChecked());
model.setIsSimpleView(true);
// 保存规则并刷新视图
Rules.getInstance(mContext).saveRules();
notifyItemChanged(position);
Toast.makeText(mContext, "保存成功", Toast.LENGTH_SHORT).show();
LogUtils.d(TAG, "bindEditViewHolder: 规则保存成功position=" + position + ",规则内容=" + newRuleText);
}
});
}
// ====================== 私有业务工具方法区 ======================
/**
* 规则上移
*/
private void moveRuleUp(int position) {
if (position <= 0) {
ToastUtils.show("已到顶部,无法上移");
return;
}
ArrayList<PhoneConnectRuleBean> ruleList = Rules.getInstance(mContext).getPhoneBlacRuleBeanList();
swapRulePosition(ruleList, position, position - 1);
}
/**
* 规则下移
*/
private void moveRuleDown(int position) {
ArrayList<PhoneConnectRuleBean> ruleList = Rules.getInstance(mContext).getPhoneBlacRuleBeanList();
if (position >= ruleList.size() - 1) {
ToastUtils.show("已到底部,无法下移");
return;
}
swapRulePosition(ruleList, position, position + 1);
}
/**
* 交换规则位置
*/
private void swapRulePosition(ArrayList<PhoneConnectRuleBean> list, int fromPos, int toPos) {
PhoneConnectRuleBean temp = list.get(fromPos);
list.set(fromPos, list.get(toPos));
list.set(toPos, temp);
Rules.getInstance(mContext).saveRules();
notifyDataSetChanged();
LogUtils.d(TAG, "swapRulePosition: 规则位置交换完成from=" + fromPos + "to=" + toPos);
}
/**
* 显示删除确认弹窗
*/
private void showDeleteConfirmDialog(Context dialogContext, final PhoneConnectRuleBean model, final int position) {
YesNoAlertDialog.show(dialogContext, "删除确认", "是否删除该通话规则?", new YesNoAlertDialog.OnDialogResultListener() {
@Override
public void onYes() {
ArrayList<PhoneConnectRuleBean> ruleList = Rules.getInstance(mContext).getPhoneBlacRuleBeanList();
ruleList.remove(position);
Rules.getInstance(mContext).saveRules();
notifyDataSetChanged();
LogUtils.d(TAG, "showDeleteConfirmDialog: 规则删除成功position=" + position);
}
@Override
public void onNo() {
LogUtils.d(TAG, "showDeleteConfirmDialog: 用户取消删除规则position=" + position);
}
});
}
// ====================== ViewHolder 内部类区 ======================
static class SimpleViewHolder extends RecyclerView.ViewHolder {
LeftScrollView scrollView;
TextView tvRuleText;
CheckBox checkBoxAllow;
CheckBox checkBoxEnable;
public SimpleViewHolder(@NonNull ViewGroup parent, @NonNull View itemView) {
super(itemView);
scrollView = (LeftScrollView) itemView.findViewById(R.id.scrollView);
// 初始化简单视图内容布局
LayoutInflater inflater = LayoutInflater.from(itemView.getContext());
View viewContent = inflater.inflate(R.layout.view_phone_connect_rule_simple_content, parent, false);
tvRuleText = (TextView) viewContent.findViewById(R.id.ruletext_tv);
checkBoxAllow = (CheckBox) viewContent.findViewById(R.id.checkbox_allow);
checkBoxEnable = (CheckBox) viewContent.findViewById(R.id.checkbox_enable);
// 设置内容宽度并添加到滚动视图
scrollView.setContentWidth(parent.getWidth());
scrollView.addContentLayout(viewContent);
}
}
static class EditViewHolder extends RecyclerView.ViewHolder {
EditText editText;
CheckBox checkBoxAllow;
CheckBox checkBoxEnable;
Button buttonConfirm;
public EditViewHolder(@NonNull View itemView) {
super(itemView);
// Java7 适配:添加强制类型转换
editText = (EditText) itemView.findViewById(R.id.edit_text);
checkBoxAllow = (CheckBox) itemView.findViewById(R.id.checkbox_allow);
checkBoxEnable = (CheckBox) itemView.findViewById(R.id.checkbox_enable);
buttonConfirm = (Button) itemView.findViewById(R.id.button_confirm);
}
}
}

View File

@@ -0,0 +1,260 @@
package cc.winboll.studio.contacts.bobulltoon;
/**
* @Author ZhanGSKen<zhangsken@qq.com>
* @Date 2025/03/02 13:47:48
* @Describe 汤姆猫管家 :使用 BoBullToon 项目,对通讯地址进行筛选判断的好朋友。
*/
import android.content.Context;
import cc.winboll.studio.contacts.R;
import cc.winboll.studio.contacts.dun.Rules;
import cc.winboll.studio.libappbase.LogUtils;
import cc.winboll.studio.libappbase.ToastUtils;
import java.io.File;
import java.io.FileFilter;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
public class TomCat {
public static final String TAG = "TomCat";
List<String> listPhoneBoBullToon = new ArrayList<String>();
String mszBoBullToon_URL;
static volatile TomCat _TomCat;
Context mContext;
TomCat(Context context) {
mContext = context;
}
public static synchronized TomCat getInstance(Context context) {
if (_TomCat == null) {
_TomCat = new TomCat(context);
}
return _TomCat;
}
public String getDefaultBobulltoonUrl() {
return mContext.getString(R.string.default_bobulltoon_url);
}
boolean downloadAndExtractZip(String zipUrl, String destinationFolder) throws IOException {
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder()
.url(zipUrl)
.build();
try {
Response response = client.newCall(request).execute();
if (!response.isSuccessful()) {
throw new IOException("Unexpected code " + response);
}
// 下载 ZIP 文件到临时位置
File tempZipFile = File.createTempFile("temp", ".zip");
try {
InputStream inputStream = response.body().byteStream();
FileOutputStream outputStream = new FileOutputStream(tempZipFile);
byte[] buffer = new byte[1024];
int length;
while ((length = inputStream.read(buffer)) > 0) {
outputStream.write(buffer, 0, length);
}
} catch (Exception e) {
LogUtils.d(TAG, e, Thread.currentThread().getStackTrace());
}
// 解压 ZIP 文件到指定文件夹
try {
ZipInputStream zipInputStream = new ZipInputStream(Files.newInputStream(tempZipFile.toPath()));
ZipEntry zipEntry;
while ((zipEntry = zipInputStream.getNextEntry()) != null) {
Path targetFilePath = Paths.get(destinationFolder, zipEntry.getName());
if (zipEntry.isDirectory()) {
Files.createDirectories(targetFilePath);
} else {
Files.createDirectories(targetFilePath.getParent());
try (FileOutputStream fos = new FileOutputStream(targetFilePath.toFile())) {
byte[] buffer = new byte[1024];
int len;
while ((len = zipInputStream.read(buffer)) > 0) {
fos.write(buffer, 0, len);
}
}
}
zipInputStream.closeEntry();
}
} catch (Exception e) {
LogUtils.d(TAG, e, Thread.currentThread().getStackTrace());
}
// 删除临时 ZIP 文件
tempZipFile.delete();
LogUtils.d(TAG, "已更新 BoBullToon 数据");
return true;
} catch (Exception e) {
ToastUtils.show(e.getMessage());
LogUtils.d(TAG, e, Thread.currentThread().getStackTrace());
return false;
}
}
public boolean downloadBoBullToon() {
String zipUrl = Rules.getInstance(mContext).getBoBullToonURL(); // 替换为实际的 ZIP 文件 URL
String destinationFolder = getWorkingFolder().getPath(); // 替换为实际的目标文件夹路径
try {
// 删除旧文件
File fOldFolder = new File(destinationFolder);
if (fOldFolder.exists()) {
deleteFolderRecursive(fOldFolder);
fOldFolder.mkdirs();
LogUtils.d(TAG, "已清空 BoBullToon 数据");
}
// 更新新文件
if (downloadAndExtractZip(zipUrl, destinationFolder)) {
LogUtils.d(TAG, "ZIP 文件下载并解压成功。");
return true;
}
return false;
} catch (IOException e) {
LogUtils.d(TAG, e, Thread.currentThread().getStackTrace());
}
return false;
}
// 递归删除文件夹及其内容的方法
public static void deleteFolderRecursive(File file) {
// 判断是否为文件夹
if (file.isDirectory()) {
// 列出文件夹中的所有文件和子文件夹
File[] files = file.listFiles();
if (files != null) {
// 遍历并递归删除每个文件和子文件夹
for (File f : files) {
deleteFolderRecursive(f);
}
}
}
// 删除文件或空文件夹
file.delete();
}
File getWorkingFolder() {
return mContext.getExternalFilesDir(TAG);
}
public File getBoBullToonDataFolder() {
File fCheckRoot = getWorkingFolder();
if (fCheckRoot == null || !fCheckRoot.exists()) {
return fCheckRoot;
}
// 递归查找符合条件的文件夹
File targetFolder = findTargetFolder(fCheckRoot);
return targetFolder != null ? targetFolder : fCheckRoot;
}
/**
* 递归查找同时包含LICENSE和README.md文件的文件夹
*/
private File findTargetFolder(File currentFolder) {
// 检查当前文件夹是否符合条件
if (hasRequiredFiles(currentFolder)) {
return currentFolder;
}
// 查找子文件夹Java 7不支持方法引用用匿名内部类过滤
File[] subFolders = currentFolder.listFiles(new FileFilter() {
@Override
public boolean accept(File file) {
return file.isDirectory(); // 仅保留子文件夹
}
});
if (subFolders != null) {
for (File subFolder : subFolders) {
File result = findTargetFolder(subFolder);
if (result != null) {
return result;
}
}
}
return null;
}
/**
* 检查文件夹中是否同时存在LICENSE和README.md文件
*/
private boolean hasRequiredFiles(File folder) {
if (folder == null || !folder.isDirectory()) {
return false;
}
// 检查两个文件是否同时存在且均为文件(非文件夹)
File licenseFile = new File(folder, "LICENSE");
File readmeFile = new File(folder, "README.md");
return licenseFile.exists() && licenseFile.isFile()
&& readmeFile.exists() && readmeFile.isFile();
}
public void cleanBoBullToon() {
String destinationFolder = getWorkingFolder().getPath(); // 替换为实际的目标文件夹路径
// 删除旧文件
File fOldFolder = new File(destinationFolder);
if (fOldFolder.exists()) {
deleteFolderRecursive(fOldFolder);
fOldFolder.mkdirs();
}
ToastUtils.show("已清空 BoBullToon 数据!");
LogUtils.d(TAG, "已清空 BoBullToon 数据");
}
public boolean loadPhoneBoBullToon() {
listPhoneBoBullToon.clear();
File fBoBullToon = getBoBullToonDataFolder();
if (fBoBullToon.exists()) {
LogUtils.d(TAG, String.format("getBoBullToonDataFolder() %s", getWorkingFolder()));
for (File userFolder : fBoBullToon.listFiles()) {
if (userFolder.isDirectory()) {
for (File recordFile : userFolder.listFiles()) {
listPhoneBoBullToon.add(recordFile.getName());
}
}
}
for (int i = 0; i < listPhoneBoBullToon.size(); i++) {
LogUtils.d(TAG, String.format("listPhoneBoBullToon add : %s", listPhoneBoBullToon.get(i)));
}
return true;
} else {
LogUtils.d(TAG, "fBoBullToon not exists。");
}
return false;
}
public boolean isPhoneBoBullToon(String phone) {
for (int i = 0; i < listPhoneBoBullToon.size(); i++) {
LogUtils.d(TAG, String.format("isPhoneBoBullToon(...) get(i) phone : %s", listPhoneBoBullToon.get(i)));
if (listPhoneBoBullToon.get(i).equals(phone)) {
return true;
}
}
return false;
}
}

View File

@@ -0,0 +1,280 @@
package cc.winboll.studio.contacts.dun;
import android.content.Context;
import cc.winboll.studio.contacts.activities.SettingsActivity;
import cc.winboll.studio.contacts.bobulltoon.TomCat;
import cc.winboll.studio.contacts.model.PhoneConnectRuleBean;
import cc.winboll.studio.contacts.model.SettingsBean;
import cc.winboll.studio.contacts.services.LimitedTimeSpecialChannelService;
import cc.winboll.studio.contacts.services.MainService;
import cc.winboll.studio.contacts.utils.ContactUtils;
import cc.winboll.studio.contacts.utils.IntUtils;
import cc.winboll.studio.contacts.utils.RegexPPiUtils;
import cc.winboll.studio.contacts.views.DunTemperatureView;
import cc.winboll.studio.libappbase.LogUtils;
import java.util.ArrayList;
import java.util.Timer;
import java.util.TimerTask;
import java.util.regex.Pattern;
/**
* @Author ZhanGSKen&豆包大模型<zhangsken@qq.com>
* @Date 2025/02/21 06:15:10
* @Describe 云盾防御规则(双重校验锁单例模式)
*/
public class Rules {
public static final String TAG = "Rules";
// 单例核心volatile 保证多线程可见性,禁止指令重排
private static volatile Rules sInstance;
// 上下文需使用 ApplicationContext 避免内存泄漏
private static Context sApplicationContext;
ArrayList<PhoneConnectRuleBean> _PhoneConnectRuleModelList;
Context mContext;
SettingsBean mSettingsModel;
Timer mDunResumeTimer;
/**
* 私有化构造方法,禁止外部 new 实例
*/
private Rules(Context context) {
mContext = context.getApplicationContext();
_PhoneConnectRuleModelList = new ArrayList<PhoneConnectRuleBean>();
reload();
}
/**
* 获取单例实例(双重校验锁,线程安全)
* @param context 上下文,建议传入 ApplicationContext
* @return Rules 唯一实例
*/
public static Rules getInstance(Context context) {
// 第一次校验:无锁,提高性能
if (sInstance == null) {
// 加锁:保证多线程下仅初始化一次
synchronized (Rules.class) {
// 第二次校验:防止多线程并发时重复创建
if (sInstance == null) {
sInstance = new Rules(context);
}
}
}
return sInstance;
}
public void reload() {
LogUtils.d(TAG, "reload()");
loadRules();
loadDun();
setDunResumTimer();
}
public void setDunResumTimer() {
if (mDunResumeTimer != null) {
mDunResumeTimer.cancel();
}
// 盾牌恢复定时器
mDunResumeTimer = new Timer();
int ss = IntUtils.getIntInRange(mSettingsModel.getDunResumeSecondCount() * 1000, SettingsBean.MIN_INTRANGE, SettingsBean.MAX_INTRANGE);
mDunResumeTimer.schedule(new TimerTask() {
@Override
public void run() {
if (mSettingsModel.getDunCurrentCount() != mSettingsModel.getDunTotalCount()) {
LogUtils.d(TAG, String.format("当前防御值为%d最大防御值为%d", mSettingsModel.getDunCurrentCount(), mSettingsModel.getDunTotalCount()));
int newDunCount = mSettingsModel.getDunCurrentCount() + mSettingsModel.getDunResumeCount();
// 设置盾值在[0DunTotalCount]之内其他值一律重置为 DunTotalCount。
newDunCount = (newDunCount > mSettingsModel.getDunTotalCount()) ?mSettingsModel.getDunTotalCount(): newDunCount;
mSettingsModel.setDunCurrentCount(newDunCount);
LogUtils.d(TAG, String.format("设置防御值为%d", newDunCount));
saveDun();
// 一键更新所有 DunTemperatureView 实例的盾值
DunTemperatureView.updateDunValue(mSettingsModel.getDunTotalCount(), mSettingsModel.getDunCurrentCount());
SettingsActivity.notifyDunInfoUpdate();
}
}
}, 1000, ss);
}
public void loadRules() {
_PhoneConnectRuleModelList.clear();
PhoneConnectRuleBean.loadBeanList(mContext, _PhoneConnectRuleModelList, PhoneConnectRuleBean.class);
}
public void saveRules() {
LogUtils.d(TAG, String.format("saveRules()"));
PhoneConnectRuleBean.saveBeanList(mContext, _PhoneConnectRuleModelList, PhoneConnectRuleBean.class);
}
public void resetDefaultBoBullToonURL() {
mSettingsModel.setBoBullToon_URL(TomCat.getInstance(mContext).getDefaultBobulltoonUrl());
saveDun();
}
public void setBoBullToonURL(String szUrl) {
mSettingsModel.setBoBullToon_URL(szUrl);
saveDun();
}
public String getBoBullToonURL() {
return mSettingsModel.getBoBullToon_URL();
}
public void loadDun() {
mSettingsModel = SettingsBean.loadBean(mContext, SettingsBean.class);
if (mSettingsModel == null) {
mSettingsModel = new SettingsBean();
SettingsBean.saveBean(mContext, mSettingsModel);
}
}
public void saveDun() {
LogUtils.d(TAG, String.format("saveDun()"));
SettingsBean.saveBean(mContext, mSettingsModel);
}
public boolean isAllowed(String phoneNumber) {
return isAllowed(phoneNumber, false);
}
public boolean isAllowed(String phoneNumber, boolean isTest) {
// 没有启用云盾,默认允许接通任何电话
if (!mSettingsModel.isEnableDun()) {
LogUtils.d(TAG, String.format("没有启用云盾默认允许接通任何电话。isAllowed(...) return true"));
return true;
}
// 云盾防御体系
boolean isDefend = false; // 盾牌是否生效
boolean isConnect = true; // 防御结果是否连接
// 进行盾牌层数预计缩减计算
int nDunCurrentCount = mSettingsModel.getDunCurrentCount() - 1;
LogUtils.d(TAG, String.format("nDunCurrentCount : %d", nDunCurrentCount));
// 如果盾值小于1则解除防御
if (!isDefend && nDunCurrentCount < 1) {
// 盾层为1以下防御解除
LogUtils.d(TAG, "盾层为1以下防御解除");
isDefend = true;
isConnect = true;
LogUtils.d(TAG, String.format("isDefend == %s\nisConnect == %s", isDefend, isConnect));
}
// 正则运算预防针
if (!isDefend && !RegexPPiUtils.isPPiOK(phoneNumber)) {
LogUtils.d(TAG, "正则运算预防针生效。");
isDefend = true;
isConnect = false;
LogUtils.d(TAG, String.format("isDefend == %s\nisConnect == %s", isDefend, isConnect));
}
// 限时特殊通道打开时返回连接
if (!isDefend && LimitedTimeSpecialChannelService.isServiceRunning()) {
LogUtils.d(TAG, String.format("PhoneNumber %s\n and Limited Time Special Channel Service Is Running.", phoneNumber));
isDefend = true;
isConnect = true;
LogUtils.d(TAG, String.format("isDefend == %s\nisConnect == %s", isDefend, isConnect));
}
// 检验拨不通号码群
if (!isDefend && MainService.isPhoneInBoBullToon(phoneNumber)) {
LogUtils.d(TAG, String.format("PhoneNumber %s\n Is In BoBullToon", phoneNumber));
isDefend = true;
isConnect = false;
LogUtils.d(TAG, String.format("isDefend == %s\nisConnect == %s", isDefend, isConnect));
}
// 查询通讯录是否有该联系人
boolean isPhoneInContacts = ContactUtils.getInstance(mContext).isPhoneInContacts(mContext, phoneNumber);
if (!isDefend) {
if (isPhoneInContacts) {
LogUtils.d(TAG, String.format("Phone %s is in contacts.", phoneNumber));
isDefend = true;
isConnect = true;
LogUtils.d(TAG, String.format("isDefend == %s\nisConnect == %s", isDefend, isConnect));
} else {
LogUtils.d(TAG, String.format("Phone %s is not in contacts.", phoneNumber));
}
}
// 正则匹配规则名单校验
if (!isDefend) {
for (int i = 0; i < _PhoneConnectRuleModelList.size(); i++) {
if (_PhoneConnectRuleModelList.get(i).isEnable()) {
String regex = _PhoneConnectRuleModelList.get(i).getRuleText();
if (Pattern.matches(regex, phoneNumber)) {
LogUtils.d(TAG, String.format("Phone Number [%s] is matched by rule : %s", phoneNumber, _PhoneConnectRuleModelList.get(i)));
isDefend = true;
isConnect = _PhoneConnectRuleModelList.get(i).isAllowConnection();
LogUtils.d(TAG, String.format("isDefend == %s\nisConnect == %s", isDefend, isConnect));
break;
}
}
}
}
// 如果不是规则测试时,就执行云盾防御机能。
if (isTest == false) {
if (isConnect) {
// 如果防御结果为连接,则恢复防御盾牌最大值层数
mSettingsModel.setDunCurrentCount(mSettingsModel.getDunTotalCount());
LogUtils.d(TAG, String.format("防御结果为连接,恢复防御盾牌最大值层数 %d", mSettingsModel.getDunTotalCount()));
saveDun();
SettingsActivity.notifyDunInfoUpdate();
} else if (isDefend) {
// 如果触发了以上某个防御模块,减少防御盾牌层数
int newDunCount = nDunCurrentCount;
LogUtils.d(TAG, String.format("新的防御层数预计为 %d", newDunCount));
// 保证盾值在[1DunTotalCount]之内其他值一律重置为 DunTotalCount。
if (newDunCount > 0 && newDunCount < mSettingsModel.getDunTotalCount()) {
mSettingsModel.setDunCurrentCount(newDunCount);
LogUtils.d(TAG, String.format("设置防御层数为 %d", newDunCount));
} else {
mSettingsModel.setDunCurrentCount(mSettingsModel.getDunTotalCount());
LogUtils.d(TAG, String.format("盾值不在[0%d]区间,恢复防御最大值%d", mSettingsModel.getDunTotalCount(), mSettingsModel.getDunTotalCount()));
}
saveDun();
SettingsActivity.notifyDunInfoUpdate();
}
// 一键更新所有 DunTemperatureView 实例的盾值
DunTemperatureView.updateDunValue(mSettingsModel.getDunTotalCount(), mSettingsModel.getDunCurrentCount());
}
// 返回校验结果
LogUtils.d(TAG, String.format("返回校验结果 isConnect == %s", isConnect));
return isConnect;
}
public void add(String szPhoneConnectRule, boolean isAllowConnection, boolean isEnable) {
_PhoneConnectRuleModelList.add(new PhoneConnectRuleBean(szPhoneConnectRule, isAllowConnection, isEnable));
}
public ArrayList<PhoneConnectRuleBean> getPhoneBlacRuleBeanList() {
return _PhoneConnectRuleModelList;
}
public SettingsBean getSettingsModel() {
return mSettingsModel;
}
/**
* 可选:释放单例资源(如退出应用时调用)
*/
public static void releaseInstance() {
if (sInstance != null) {
sInstance.mDunResumeTimer.cancel();
sInstance._PhoneConnectRuleModelList.clear();
sInstance.mSettingsModel = null;
sInstance.mContext = null;
sInstance = null;
}
}
}

View File

@@ -0,0 +1,258 @@
package cc.winboll.studio.contacts.fragments;
import android.Manifest;
import android.content.pm.PackageManager;
import android.database.Cursor;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.provider.CallLog;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.app.ActivityCompat;
import androidx.fragment.app.Fragment;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import cc.winboll.studio.contacts.R;
import cc.winboll.studio.contacts.adapters.CallLogAdapter;
import cc.winboll.studio.contacts.model.CallLogModel;
import cc.winboll.studio.libappbase.LogUtils;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
/**
* @Author ZhanGSKen&豆包大模型<zhangsken@qq.com>
* @Date 2025/02/20 12:57:00
* @Describe 通话记录区域视图(支持懒加载,仅切换到当前页才加载数据)
*/
public class CallLogFragment extends Fragment {
// ====================== 常量定义区 ======================
public static final String TAG = "CallLogFragment";
public static final int MSG_UPDATE = 1;
private static final String ARG_PAGE = "ARG_PAGE";
private static final int REQUEST_READ_CALL_LOG = 1;
// ====================== 静态成员区 ======================
static volatile CallLogFragment _CallLogFragment;
// ====================== 页面参数区 ======================
private int mPage;
// ====================== UI控件与适配器区 ======================
private RecyclerView recyclerView;
private CallLogAdapter callLogAdapter;
private List<CallLogModel> callLogList = new ArrayList<CallLogModel>();
// ====================== 业务逻辑成员区 ======================
private Handler mHandler;
// 懒加载标记记录当前Fragment是否已初始化数据避免重复加载
private boolean isDataInited = false;
// ====================== 单例与实例化函数区 ======================
CallLogFragment() {
super();
}
public static CallLogFragment newInstance(int page) {
LogUtils.d(TAG, "newInstance: 创建通话记录Fragment实例页码=" + page);
Bundle args = new Bundle();
args.putInt(ARG_PAGE, page);
CallLogFragment fragment = new CallLogFragment();
fragment.setArguments(args);
_CallLogFragment = fragment;
return fragment;
}
// ====================== 生命周期函数区 ======================
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
LogUtils.d(TAG, "onCreate: Fragment创建开始");
if (getArguments() != null) {
mPage = getArguments().getInt(ARG_PAGE);
LogUtils.d(TAG, "onCreate: 读取页面参数mPage=" + mPage);
}
// Java7 兼容移除Lambda使用匿名内部类初始化Handler
mHandler = new Handler(Looper.getMainLooper()) {
@Override
public void handleMessage(Message msg) {
if (msg.what == MSG_UPDATE) {
LogUtils.d(TAG, "handleMessage: 收到更新消息,开始读取通话记录");
readCallLog();
}
}
};
LogUtils.d(TAG, "onCreate: Fragment创建完成");
}
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
LogUtils.d(TAG, "onCreateView: 加载Fragment布局");
return inflater.inflate(R.layout.fragment_call_log, container, false);
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
LogUtils.d(TAG, "onViewCreated: 视图创建完成,仅初始化控件(不加载数据)");
// 初始化RecyclerView仅绑定控件、设置布局管理器不设置数据/发起请求)
recyclerView = (RecyclerView) view.findViewById(R.id.recyclerView);
recyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
// 初始化适配器(传入空列表,后续懒加载时更新数据)
callLogAdapter = new CallLogAdapter(getContext(), callLogList);
recyclerView.setAdapter(callLogAdapter);
LogUtils.d(TAG, "onViewCreated: RecyclerView控件初始化完成未加载数据");
}
@Override
public void onResume() {
super.onResume();
LogUtils.d(TAG, "onResume: Fragment进入前台");
// 已初始化过数据 → 仅刷新(避免重复初始化,优化性能)
if (isDataInited && callLogAdapter != null) {
LogUtils.d(TAG, "onResume: 数据已初始化,仅刷新列表");
callLogAdapter.relaodContacts();
readCallLog(); // 刷新最新通话记录
LogUtils.d(TAG, "onResume: 通话记录数据刷新完成");
}
// 未初始化 → 不操作等待MainActivity调用initData触发初始化
}
@Override
public void onDestroy() {
super.onDestroy();
LogUtils.d(TAG, "onDestroy: Fragment开始销毁");
if (mHandler != null) {
mHandler.removeCallbacksAndMessages(null);
LogUtils.d(TAG, "onDestroy: Handler消息已清空");
}
// 释放资源,避免内存泄漏
if (callLogList != null) {
callLogList.clear();
callLogList = null;
}
callLogAdapter = null;
recyclerView = null;
_CallLogFragment = null;
isDataInited = false;
LogUtils.d(TAG, "onDestroy: Fragment销毁完成");
}
// ====================== 权限回调函数区 ======================
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
LogUtils.d(TAG, "onRequestPermissionsResult: 权限请求回调requestCode=" + requestCode);
if (requestCode == REQUEST_READ_CALL_LOG) {
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
LogUtils.d(TAG, "onRequestPermissionsResult: 通话记录权限授予成功,开始加载数据");
mHandler.sendEmptyMessage(MSG_UPDATE);
} else {
LogUtils.e(TAG, "onRequestPermissionsResult: 通话记录权限被拒绝,无法加载数据");
}
}
}
// ====================== 懒加载核心方法供MainActivity调用 ======================
public void initData() {
// 避免重复初始化(双重防护:标记+判断)
if (isDataInited || getContext() == null) {
LogUtils.d(TAG, "initData: 数据已初始化/上下文为空,跳过");
return;
}
LogUtils.d(TAG, "initData: 开始懒加载初始化通话记录数据");
// 权限检查与数据加载原onViewCreated中的核心逻辑迁移至此
if (ActivityCompat.checkSelfPermission(requireContext(), Manifest.permission.READ_CALL_LOG) != PackageManager.PERMISSION_GRANTED) {
LogUtils.w(TAG, "initData: 读取通话记录权限未授予,发起权限申请");
ActivityCompat.requestPermissions(requireActivity(), new String[]{Manifest.permission.READ_CALL_LOG}, REQUEST_READ_CALL_LOG);
} else {
LogUtils.d(TAG, "initData: 权限已授予,发送更新消息加载数据");
mHandler.sendEmptyMessage(MSG_UPDATE);
}
// 标记为已初始化(后续仅刷新,不重复初始化)
isDataInited = true;
LogUtils.d(TAG, "initData: 懒加载初始化流程完成");
}
// ====================== 业务核心函数区 ======================
private void readCallLog() {
LogUtils.d(TAG, "readCallLog: 开始读取系统通话记录");
// 避免空指针(懒加载场景下,控件可能未初始化完成)
if (callLogList == null || callLogAdapter == null || getContext() == null) {
LogUtils.w(TAG, "readCallLog: 控件/列表为空,跳过读取");
return;
}
callLogList.clear();
Cursor cursor = null;
try {
cursor = requireContext().getContentResolver().query(
CallLog.Calls.CONTENT_URI,
null,
null,
null,
CallLog.Calls.DATE + " DESC"
);
if (cursor != null) {
LogUtils.d(TAG, "readCallLog: 成功获取通话记录游标,数据条数=" + cursor.getCount());
while (cursor.moveToNext()) {
String phoneNumber = cursor.getString(cursor.getColumnIndex(CallLog.Calls.NUMBER));
int callType = cursor.getInt(cursor.getColumnIndex(CallLog.Calls.TYPE));
long callDateLong = cursor.getLong(cursor.getColumnIndex(CallLog.Calls.DATE));
Date callDate = new Date(callDateLong);
String callStatus = getCallStatus(callType);
callLogList.add(new CallLogModel(phoneNumber, callStatus, callDate));
}
callLogAdapter.notifyDataSetChanged();
LogUtils.d(TAG, "readCallLog: 通话记录数据解析完成,共" + callLogList.size() + "");
} else {
LogUtils.w(TAG, "readCallLog: 通话记录游标为空");
}
} catch (Exception e) {
LogUtils.e(TAG, "readCallLog: 读取通话记录异常", e);
} finally {
if (cursor != null) {
cursor.close();
LogUtils.d(TAG, "readCallLog: 游标已关闭");
}
}
}
private String getCallStatus(int callType) {
switch (callType) {
case CallLog.Calls.OUTGOING_TYPE:
return "Outgoing";
case CallLog.Calls.INCOMING_TYPE:
return "Incoming";
case CallLog.Calls.MISSED_TYPE:
return "Missed";
default:
return "Unknown";
}
}
// ====================== 外部调用函数区 ======================
public void triggerUpdate() {
LogUtils.d(TAG, "triggerUpdate: 外部触发通话记录更新");
if (isDataInited) { // 已初始化才触发更新(避免未加载时调用)
mHandler.sendEmptyMessage(MSG_UPDATE);
}
}
public static void updateCallLogFragment() {
if (_CallLogFragment != null) {
LogUtils.d(TAG, "updateCallLogFragment: 静态方法触发Fragment更新");
_CallLogFragment.triggerUpdate();
} else {
LogUtils.w(TAG, "updateCallLogFragment: Fragment实例为空无法更新");
}
}
}

View File

@@ -0,0 +1,401 @@
package cc.winboll.studio.contacts.fragments;
import android.Manifest;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.database.Cursor;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.provider.ContactsContract;
import android.text.Editable;
import android.text.TextWatcher;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.EditText;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.app.ActivityCompat;
import androidx.fragment.app.Fragment;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import cc.winboll.studio.contacts.R;
import cc.winboll.studio.contacts.adapters.ContactAdapter;
import cc.winboll.studio.contacts.model.ContactModel;
import cc.winboll.studio.libappbase.LogUtils;
import cc.winboll.studio.libappbase.ToastUtils;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* @Author ZhanGSKen&豆包大模型<zhangsken@qq.com>
* @Date 2025/08/30 14:32
* @Describe 联系人区域视图(支持懒加载,仅切换到当前页才加载数据)
*/
public class ContactsFragment extends Fragment {
// ====================== 常量定义区 ======================
public static final String TAG = "ContactsFragment";
private static final String ARG_PAGE = "ARG_PAGE";
private static final int REQUEST_READ_CONTACTS = 1;
private static final long DEBOUNCE_DELAY = 300; // 搜索防抖延迟
// ====================== 静态缓存区 ======================
// 全局复用联系人数据,减少重复查询
private static List<ContactModel> sCachedOriginalList = new ArrayList<ContactModel>();
private static List<ContactModel> sCachedFilteredList = new ArrayList<ContactModel>();
// ====================== 页面参数区 ======================
private int mPage;
private boolean isViewInitialized = false; // 视图初始化标记(控件绑定完成)
private boolean isDataLoaded = false; // 数据加载标记(数据+功能初始化完成)
private boolean isLazyInitCompleted = false; // 懒加载总标记供MainActivity判断
// ====================== UI控件区 ======================
private RecyclerView recyclerView;
private ContactAdapter contactAdapter;
private EditText searchEditText;
private Button btnDial;
// ====================== 数据容器区 ======================
private List<ContactModel> contactList = new ArrayList<ContactModel>();
private List<ContactModel> originalContactList = new ArrayList<ContactModel>();
// ====================== 异步工具区 ======================
private final ExecutorService executor = Executors.newSingleThreadExecutor();
private final Handler mainHandler = new Handler(Looper.getMainLooper());
// ====================== 实例化函数区 ======================
public static ContactsFragment newInstance(int page) {
LogUtils.d(TAG, "newInstance: 创建联系人Fragment实例页码=" + page);
Bundle args = new Bundle();
args.putInt(ARG_PAGE, page);
ContactsFragment fragment = new ContactsFragment();
fragment.setArguments(args);
return fragment;
}
// ====================== 生命周期函数区 ======================
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
LogUtils.d(TAG, "onCreate: Fragment创建开始");
if (getArguments() != null) {
mPage = getArguments().getInt(ARG_PAGE);
LogUtils.d(TAG, "onCreate: 读取页面参数mPage=" + mPage);
}
LogUtils.d(TAG, "onCreate: Fragment创建完成");
}
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
LogUtils.d(TAG, "onCreateView: 加载Fragment布局");
return inflater.inflate(R.layout.fragment_contacts, container, false);
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
LogUtils.d(TAG, "onViewCreated: 开始初始化UI控件仅绑定不加载数据/功能)");
// 初始化RecyclerView仅绑定控件、设适配器隐藏列表
recyclerView = (RecyclerView) view.findViewById(R.id.contacts_recycler_view);
recyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
contactAdapter = new ContactAdapter(getActivity(), contactList);
recyclerView.setAdapter(contactAdapter);
recyclerView.setVisibility(View.GONE);
// 绑定搜索框和拨号按钮(仅赋值,不显示、不绑定事件)
searchEditText = (EditText) view.findViewById(R.id.search_edit_text);
btnDial = (Button) view.findViewById(R.id.btn_dial);
searchEditText.setVisibility(View.GONE);
btnDial.setVisibility(View.GONE);
// 标记视图控件绑定完成
isViewInitialized = true;
LogUtils.d(TAG, "onViewCreated: UI控件初始化完成未加载数据/功能)");
}
@Override
public void onResume() {
super.onResume();
LogUtils.d(TAG, "onResume: Fragment进入前台");
// 已完成懒加载 → 仅恢复缓存数据(切回页面时刷新)
if (isLazyInitCompleted && isDataLoaded) {
LogUtils.d(TAG, "onResume: 懒加载已完成,恢复缓存数据");
contactList.clear();
contactList.addAll(sCachedFilteredList);
contactAdapter.notifyDataSetChanged();
recyclerView.setVisibility(View.VISIBLE);
}
// 未完成懒加载 → 不操作等待MainActivity调用initData触发
}
@Override
public void onDestroy() {
super.onDestroy();
LogUtils.d(TAG, "onDestroy: Fragment开始销毁");
executor.shutdown(); // 关闭线程池
mainHandler.removeCallbacksAndMessages(null); // 清空Handler任务
// 释放本地数据引用(保留静态缓存,全局复用)
if (contactList != null) {
contactList.clear();
contactList = null;
}
if (originalContactList != null) {
originalContactList.clear();
originalContactList = null;
}
// 重置标记
isViewInitialized = false;
isDataLoaded = false;
isLazyInitCompleted = false;
LogUtils.d(TAG, "onDestroy: 异步工具+本地资源已释放");
}
@Override
public void onHiddenChanged(boolean hidden) {
super.onHiddenChanged(hidden);
LogUtils.d(TAG, "onHiddenChanged: Fragment隐藏状态变更hidden=" + hidden);
// 已完成懒加载+显示状态 → 恢复缓存数据兼容Tab切换场景
if (!hidden && isLazyInitCompleted && isDataLoaded) {
contactList.clear();
contactList.addAll(sCachedFilteredList);
contactAdapter.notifyDataSetChanged();
recyclerView.setVisibility(View.VISIBLE);
LogUtils.d(TAG, "onHiddenChanged: 恢复缓存数据,列表已显示");
}
}
// ====================== 权限相关函数区 ======================
private void checkContactPermission() {
LogUtils.d(TAG, "checkContactPermission: 检查联系人读取权限");
if (ActivityCompat.checkSelfPermission(requireContext(), Manifest.permission.READ_CONTACTS) != PackageManager.PERMISSION_GRANTED) {
LogUtils.w(TAG, "checkContactPermission: 权限未授予,发起申请");
ActivityCompat.requestPermissions(requireActivity(), new String[]{Manifest.permission.READ_CONTACTS}, REQUEST_READ_CONTACTS);
} else {
LogUtils.d(TAG, "checkContactPermission: 权限已授予,开始加载数据");
loadContacts();
}
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
LogUtils.d(TAG, "onRequestPermissionsResult: 权限回调触发requestCode=" + requestCode);
if (requestCode == REQUEST_READ_CONTACTS) {
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
LogUtils.d(TAG, "onRequestPermissionsResult: 联系人权限授予成功");
loadContacts();
} else {
LogUtils.e(TAG, "onRequestPermissionsResult: 联系人权限被拒绝");
ToastUtils.show("请授予联系人权限以查看联系人列表");
recyclerView.setVisibility(View.VISIBLE);
// 权限拒绝也标记懒加载完成(避免重复触发)
isLazyInitCompleted = true;
}
}
}
// ====================== 懒加载核心方法供MainActivity调用 ======================
public void initData() {
// 双重防护:避免重复初始化(标记+视图就绪判断)
if (isLazyInitCompleted || !isViewInitialized || getContext() == null) {
LogUtils.d(TAG, "initData: 懒加载已完成/视图未就绪,跳过");
return;
}
LogUtils.d(TAG, "initData: 开始懒加载初始化(功能+数据)");
// 1. 初始化搜索、拨号功能原onResume首次进入逻辑迁移至此
initSearchAndDial();
// 2. 检查权限+加载数据原onResume首次进入逻辑迁移至此
checkContactPermission();
// 标记懒加载总流程完成(无论权限是否授予,仅执行一次)
isLazyInitCompleted = true;
LogUtils.d(TAG, "initData: 懒加载初始化流程启动完成");
}
// ====================== UI功能初始化区 ======================
private void initSearchAndDial() {
LogUtils.d(TAG, "initSearchAndDial: 初始化搜索和拨号功能");
// 显示控件
searchEditText.setVisibility(View.VISIBLE);
btnDial.setVisibility(View.VISIBLE);
// 搜索防抖监听
searchEditText.addTextChangedListener(new DebounceTextWatcher(DEBOUNCE_DELAY) {
@Override
public void onDebounceTextChanged(String query) {
filterContacts(query);
}
});
// 拨号按钮点击事件
btnDial.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
String phoneNumber = searchEditText.getText().toString().replaceAll("\\s", "");
if (phoneNumber.isEmpty()) {
ToastUtils.show("请输入号码");
return;
}
LogUtils.d(TAG, "initSearchAndDial: 发起拨号,号码=" + phoneNumber);
Intent intent = new Intent(Intent.ACTION_CALL);
intent.setData(android.net.Uri.parse("tel:" + phoneNumber));
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
}
});
LogUtils.d(TAG, "initSearchAndDial: 功能初始化完成");
}
// ====================== 数据加载与处理区 ======================
private void loadContacts() {
// 优先使用缓存数据(保留原有缓存逻辑,提升性能)
if (!sCachedOriginalList.isEmpty() && !sCachedFilteredList.isEmpty()) {
LogUtils.d(TAG, "loadContacts: 存在缓存数据,直接复用");
originalContactList.clear();
originalContactList.addAll(sCachedOriginalList);
contactList.clear();
contactList.addAll(sCachedFilteredList);
contactAdapter.notifyDataSetChanged();
recyclerView.setVisibility(View.VISIBLE);
isDataLoaded = true;
return;
}
// 无缓存时异步加载(保留原有异步逻辑,避免主线程阻塞)
if (!isDataLoaded) {
LogUtils.d(TAG, "loadContacts: 无缓存,异步读取联系人数据");
recyclerView.setVisibility(View.GONE);
executor.execute(new Runnable() {
@Override
public void run() {
final List<ContactModel> tempList = readContactsInBackground();
// 主线程更新UI和缓存
mainHandler.post(new Runnable() {
@Override
public void run() {
sCachedOriginalList.clear();
sCachedOriginalList.addAll(tempList);
sCachedFilteredList.clear();
sCachedFilteredList.addAll(tempList);
originalContactList.clear();
originalContactList.addAll(sCachedOriginalList);
contactList.clear();
contactList.addAll(sCachedFilteredList);
contactAdapter.notifyDataSetChanged();
recyclerView.setVisibility(View.VISIBLE);
isDataLoaded = true;
LogUtils.d(TAG, "loadContacts: 联系人数据加载完成,共" + contactList.size() + "");
}
});
}
});
}
}
private List<ContactModel> readContactsInBackground() {
LogUtils.d(TAG, "readContactsInBackground: 子线程读取联系人");
List<ContactModel> tempList = new ArrayList<ContactModel>();
Cursor cursor = null;
try {
cursor = requireContext().getContentResolver().query(
ContactsContract.CommonDataKinds.Phone.CONTENT_URI,
new String[]{
ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME,
ContactsContract.CommonDataKinds.Phone.NUMBER
},
null,
null,
ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME + " ASC"
);
if (cursor != null && cursor.moveToFirst()) {
int nameIndex = cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME);
int numberIndex = cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER);
do {
String name = cursor.getString(nameIndex);
String number = cursor.getString(numberIndex).replaceAll("\\s", "");
tempList.add(new ContactModel(name, number));
} while (cursor.moveToNext());
LogUtils.d(TAG, "readContactsInBackground: 成功读取" + tempList.size() + "条联系人数据");
} else {
LogUtils.w(TAG, "readContactsInBackground: 未读取到联系人数据");
}
} catch (Exception e) {
LogUtils.e(TAG, "readContactsInBackground: 读取联系人异常", e);
} finally {
if (cursor != null) {
cursor.close();
LogUtils.d(TAG, "readContactsInBackground: 游标已关闭");
}
}
return tempList;
}
private void filterContacts(String query) {
LogUtils.d(TAG, "filterContacts: 搜索过滤,关键词=" + query);
contactList.clear();
sCachedFilteredList.clear();
if (query.isEmpty()) {
contactList.addAll(originalContactList);
sCachedFilteredList.addAll(originalContactList);
} else {
String lowerQuery = query.toLowerCase();
for (ContactModel contact : originalContactList) {
boolean matchName = contact.getName().toLowerCase().contains(lowerQuery);
boolean matchPinyin = contact.getPinyin().toLowerCase().contains(lowerQuery);
boolean matchFirstLetter = contact.getPinyinFirstLetter().toLowerCase().contains(lowerQuery);
boolean matchNumber = contact.getNumber().contains(lowerQuery);
if (matchName || matchPinyin || matchFirstLetter || matchNumber) {
contactList.add(contact);
}
}
sCachedFilteredList.addAll(contactList);
}
contactAdapter.notifyDataSetChanged();
recyclerView.setVisibility(View.VISIBLE);
LogUtils.d(TAG, "filterContacts: 过滤完成,显示" + contactList.size() + "条数据");
}
// ====================== 内部防抖监听类 ======================
public abstract static class DebounceTextWatcher implements TextWatcher {
private final long debounceDelay;
private Handler handler = new Handler(Looper.getMainLooper());
private Runnable pendingRunnable;
public DebounceTextWatcher(long debounceDelay) {
this.debounceDelay = debounceDelay;
}
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
@Override
public void onTextChanged(final CharSequence s, int start, int before, int count) {
if (pendingRunnable != null) {
handler.removeCallbacks(pendingRunnable);
}
pendingRunnable = new Runnable() {
@Override
public void run() {
onDebounceTextChanged(s.toString());
}
};
handler.postDelayed(pendingRunnable, debounceDelay);
}
@Override
public void afterTextChanged(Editable s) {}
public abstract void onDebounceTextChanged(String query);
}
}

View File

@@ -0,0 +1,118 @@
package cc.winboll.studio.contacts.fragments;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import cc.winboll.studio.contacts.R;
import cc.winboll.studio.libappbase.LogUtils;
import cc.winboll.studio.libappbase.LogView;
/**
* @Author ZhanGSKen&豆包大模型<zhangsken@qq.com>
* @Date 2025/02/20 12:58:15
* @Describe 应用日志区域视图(支持懒加载,仅切换到当前页才启动日志)
*/
public class LogFragment extends Fragment {
// ====================== 常量定义区 ======================
public static final String TAG = "LogFragment";
private static final String ARG_PAGE = "ARG_PAGE";
// ====================== 页面参数区 ======================
private int mPage;
// ====================== UI控件区 ======================
private LogView mLogView;
// ====================== 懒加载标记区 ======================
private boolean isViewInitialized = false; // 视图控件绑定完成标记
private boolean isLazyInitCompleted = false; // 懒加载总流程完成标记
private boolean isLogViewStarted = false; // LogView启动状态标记
// ====================== 实例化函数区 ======================
public static LogFragment newInstance(int page) {
LogUtils.d(TAG, "newInstance: 创建日志Fragment实例页码=" + page);
Bundle args = new Bundle();
args.putInt(ARG_PAGE, page);
LogFragment fragment = new LogFragment();
fragment.setArguments(args);
return fragment;
}
// ====================== 生命周期函数区 ======================
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
LogUtils.d(TAG, "onCreate: Fragment创建开始");
if (getArguments() != null) {
mPage = getArguments().getInt(ARG_PAGE);
LogUtils.d(TAG, "onCreate: 读取页面参数mPage=" + mPage);
}
LogUtils.d(TAG, "onCreate: Fragment创建完成");
}
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
LogUtils.d(TAG, "onCreateView: 加载Fragment布局");
View view = inflater.inflate(R.layout.fragment_log, container, false);
// Java7 适配添加强制类型转换仅初始化LogView控件不启动
mLogView = (LogView) view.findViewById(R.id.logview);
LogUtils.d(TAG, "onCreateView: LogView控件初始化完成未启动");
// 标记视图控件绑定完成
isViewInitialized = true;
return view;
}
@Override
public void onResume() {
super.onResume();
LogUtils.d(TAG, "onResume: Fragment进入前台");
// 已完成懒加载 → 仅重启LogView切回页面时恢复日志显示
if (isLazyInitCompleted && mLogView != null && !isLogViewStarted) {
mLogView.start();
isLogViewStarted = true;
LogUtils.d(TAG, "onResume: LogView已重启恢复日志显示");
}
// 未完成懒加载 → 不操作等待MainActivity调用initData触发
}
@Override
public void onDestroy() {
super.onDestroy();
LogUtils.d(TAG, "onDestroy: Fragment开始销毁");
if (mLogView != null) {
// 若LogView有停止方法必须调用避免后台持续占用资源根据实际API调整
// mLogView.stop(); // 关键释放LogView资源防止内存泄漏
LogUtils.d(TAG, "onDestroy: LogView资源已释放");
}
// 重置所有标记,避免重建时状态异常
mLogView = null;
isViewInitialized = false;
isLazyInitCompleted = false;
isLogViewStarted = false;
LogUtils.d(TAG, "onDestroy: Fragment销毁完成");
}
// ====================== 懒加载核心方法供MainActivity调用 ======================
public void initData() {
// 双重防护:避免重复初始化(标记+视图就绪+控件非空)
if (isLazyInitCompleted || !isViewInitialized || mLogView == null || getContext() == null) {
LogUtils.d(TAG, "initData: 懒加载已完成/视图未就绪,跳过");
return;
}
LogUtils.d(TAG, "initData: 开始懒加载初始化启动LogView");
// 核心启动LogView原onCreateView中的start逻辑迁移至此
mLogView.start();
isLogViewStarted = true;
// 标记懒加载总流程完成(仅执行一次)
isLazyInitCompleted = true;
LogUtils.d(TAG, "initData: 懒加载初始化完成LogView正常启动");
}
}

View File

@@ -0,0 +1,38 @@
package cc.winboll.studio.contacts.handlers;
/**
* @Author ZhanGSKen<zhangsken@qq.com>
* @Date 2025/02/14 03:51:40
*/
import android.os.Handler;
import android.os.Message;
import cc.winboll.studio.contacts.services.MainService;
import java.lang.ref.WeakReference;
public class MainServiceHandler extends Handler {
public static final String TAG = "MainServiceHandler";
public static final int MSG_REMINDTHREAD = 0;
WeakReference<MainService> serviceWeakReference;
public MainServiceHandler(MainService service) {
serviceWeakReference = new WeakReference<MainService>(service);
}
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_REMINDTHREAD: // 处理下载完成消息更新UI
{
// 显示提醒消息
//
//LogUtils.d(TAG, "显示提醒消息");
MainService mainService = serviceWeakReference.get();
if (mainService != null) {
mainService.appenMessage((String)msg.obj);
}
break;
}
}
}
}

Some files were not shown because too many files have changed in this diff Show More