Compare commits

...

188 Commits

Author SHA1 Message Date
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
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
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
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
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
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
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
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
6c8867e15c 更正maven库引用顺序 2026-04-09 01:38:28 +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
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
2af6427ca8 精简nfc action 数量 2026-03-17 04:03:20 +08:00
b8c70bef98 调整NFC接口窗体根据动作类型指定对应的脚本运行。 2026-03-16 16:42:02 +08:00
7713d6c460 基本实现NFC Build View 模块功能。 2026-03-15 20:25:16 +08:00
73c69bd665 20260315_193958_991 2026-03-15 19:40:11 +08:00
a076fe50cd 调整Termux 调用模块 UI显示与环境参数配置。 2026-03-15 13:45:49 +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
a53a0cbcdc <winboll>APK 15.11.15 release Publish. 2026-03-13 13:41:48 +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
94ac2d9f9c 更新编译说明 2026-02-04 13:13:58 +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
11d7846cd2 更新项目说明书 2026-02-04 12:38:17 +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
21e464bf5e <winboll>APK 15.11.14 release Publish. 2026-01-29 17:03:53 +08:00
47f27b6bcb 修复调试工具配置误差 2026-01-29 17:00:59 +08:00
013ad7ff1b <winboll>APK 15.11.13 release Publish. 2026-01-24 21:19:19 +08:00
ab6fd0c6b5 增加对签名证书修改后的证书识别能力。 2026-01-24 21:17:54 +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
0aaf71f285 添加对签名证书修改后的证书识别能力。 2026-01-24 19:50:43 +08:00
3c1afc1c0b <winboll>APK 15.11.12 release Publish. 2026-01-24 13:25:26 +08:00
395920743b 更新类库,应用联网验证模块有改进。 2026-01-24 13:23:22 +08:00
25f4c79860 <winboll>APK 15.11.11 release Publish. 2026-01-23 06:00:53 +08:00
5440c1ad39 整理调试菜单配置 2026-01-23 05:52:42 +08:00
47dd4698b8 更新应用介绍窗口 2026-01-23 05:46:09 +08:00
861ccb5832 移除命令行里的文件夹创建命令。 2026-01-19 18:00:33 +08:00
8b1f119e44 更新 WinBoLL工作目录为/sdcard/WinBoLLStudio。添加 bash终端命令。 2026-01-19 17:39:11 +08:00
c6495d1859 Termux 下 Gradle 编译 WinBoLL 项目测试成功。 2026-01-19 15:47:18 +08:00
15197b123c gradle命令调用成功 2026-01-19 15:21:06 +08:00
df2e91d390 Termux 终端显示功能实现。 2026-01-19 14:51:24 +08:00
61ca0d2672 Termux应用包命令调用成功。 2026-01-19 14:43:50 +08:00
8c18710e36 正在制作模块“发送Action 让Termux自行Bash命令。”。。。 2026-01-19 12:04:58 +08:00
224bd243e2 设置WinBoLL在Termux的工作目录。 2026-01-19 11:39:35 +08:00
b30bdc6802 添加Termux环境测试 2026-01-19 11:35:00 +08:00
8f0973cb6c 添加Termux环境测试窗口 2026-01-19 11:24:07 +08:00
b9fab2d737 <winboll>APK 15.11.10 release Publish. 2026-01-13 16:53:24 +08:00
156af54eaa 应用窗口切换调试完成 2026-01-13 16:52:01 +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
fb9dd93162 正在调整工具栏与应用介绍窗口。。。 2026-01-13 12:26:11 +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
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
7e757a456a <winboll>APK 15.11.9 release Publish. 2026-01-13 03:11:40 +08:00
280 changed files with 16012 additions and 1429 deletions

8
.gitignore vendored
View File

@@ -97,9 +97,5 @@ lint-results.html
## WinBoLL 基础应用(避免上传敏感配置)
/winboll.properties
/local.properties
## WinBoLL 衍生应用,
## 外派类型类库应用需要注释掉以下部分,以便部署通用类库编译配置。
## APPBase,AES需要上传以下两种配置。
#/settings.gradle
#/gradle.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 {
/* 后期管理预留代码 */

238
README.md
View File

@@ -1,175 +1,105 @@
## OriginMaster
【OriginMaster】WinBoLL 源生态计划。正如话,我需要一个 Point, 去撬动成个地球。
WinBoLL系列项目各个项目源码合并推送方向
WinBoLL=>APPBase=>OriginMaster
WinBoLL=>AES=>OriginMaster
WinBoLL=>PowerBell=>OriginMaster
WinBoLL=>Positions=>OriginMaster
仓库规划概述:
☆ WinBoLL,APPBase,AES,PowerBell与Positions都是开发库。
☆ OriginMaster 是分支汇总存档库。
☆ OriginMaster 不适宜作为开发库克隆使用,不是应用开发基础库,而是汇总资料库。
☆ WinBoLL 库可以作为应用开发的基础继承模板。
# WinBoLL 源生态计划项目说明书
########
## ☁ ☁ ☁ WinBoLL APP ☁ ☁ ☁ ☁ ☁ ☁ ☁ ☁ ☁ ☁ ☁ ☁ ☁ ☁ ☁ ☁ ☁ ☁ ☁
# ☁ ☁ WinBoLL Studio Android 应用开源项目。☁ ☁ ☁ ☁ ☁ ☁ ☁ ☁ ☁ ☁ ☁ ☁ ☁
# ☁ ☁ ☁ WinBoLL 网站地址 https://www.winboll.cc/ ☁ ☁ ☁ ☁ ☁ ☁ ☁ ☁
# ☁ ☁ ☁ WinBoLL 源码地址 <https://gitea.winboll.cc/Studio/WinBoLL.git> ☁ ☁ ☁ ☁ ☁ ☁ ☁
# ☁ ☁ ☁ GitHub 源码地址 <https://github.com/ZhanGSKen/WinBoLL.git> ☁ ☁ ☁ ☁ ☁ ☁ ☁ ☁
# ☁ ☁ ☁ 码云 源码地址 <https://gitee.com/zhangsken/winboll.git> ☁ ☁ ☁ ☁ ☁ ☁ ☁ ☁
# ☁ ☁ ☁ 在 jitpack.io 托管的 APPBase 类库源码<https://github.com/ZhanGSKen/APPBase.git> ☁ ☁ ☁ ☁
# ☁ ☁ ☁ 在 jitpack.io 托管的 AES 类库源码<https://github.com/ZhanGSKen/AES.git> ☁ ☁ ☁ ☁
## WinBoLL 提问
同样是 /sdcard 目录,在开发 Android 应用时,
能否实现手机编译与电脑编译的源码同步。
☁因而 WinBoLL 项目组诞生了。
## 一、项目概述
## WinBoLL 项目组研发计划
致力于把 WinBoLL-APP 应用在手机端 Android 项目开发。
也在探索 https://gitea.winboll.cc/<WinBoLL 项目组>/APP.git 应用于 WinBoLL-APP APK 分发。
更想进阶 https://github.com/<WinBoLL 项目组>/APP.git 应用于 WinBoLL-APP Beta APK 分发。
### 1. 核心定位
WinBoLL 手机源码计划,旨在通过核心项目 WinBoLL 构建手机端与服务器端的 Android 项目的开发源码生态。实现手机与服务器的源码的联合开发。
## WinBoLL-APP 汗下...
#### ☁应用何置如此呢。且观用户云云。
### 2. 仓库架构
#### **仓库类型:功能说明**
☆ 基础项目分支 WinBoLL手机端安卓应用开发基础模板。
☆ 应用项目分支 APPBase、AES、PowerBell、Positions**:安卓应用单一管理系列项目。
☆ 源码汇总管理 OriginMaster**:各类分支源码合并存档,不适宜作为开发库使用。
#### ☁ 正当下 ☁ ###
#### ☁ 且容傻家叙说 ☁ WinBoLL-APP 应用场景
### ☁ WinBoLL 设备资源概述
#### ☁ 1. Raid Disk.
概述:这是一个矩阵存储类设备。
优点:该设备具有数据容错存储功能,
数据存储具有特长持久性。
缺点:设备使用能源消耗比较高,
设备存取速度一般。
#### ☁ 2. Data Disk.
概述:这是一个普通硬盘存储设备
优点:该设备独立于操作系统,
数据持久性一般,
存取能源消耗小于 Raid Disk。
缺点:数据存储速度一般,存储能源消耗一般。
### 3. 源码合并管理推送路线图
⚠️ **注意**:仅仅展示不同应用模块源码的综合管理路线。分支合并操作时,必须具备 Git 管理经验。
#### ☁ 3. SSD Disk.
概述:这是一个 SSD 硬盘存储设备。
优点:存取速度快于 Data Disk 与 Raid Disk
存取能源消耗小于 Data Disk 与 Raid Disk。
缺点:数据持久性一般,
设备位于操作系统内部文件系统。
数据持久性与操作系统挂钩。
#### ☁ 4. WinBoLL 用户资源概述。
1> /home/<用户名> 位于 WinBoLL 操作系统目录下。
2> /rdisk/<用户名> 挂载用户 Raid Disk.
3> /data/<用户名> 挂载用户 Data Disk.
4> /sdcard/<用户名> 挂载用户 SSD Disk.
★ WinBoLL → APPBase → OriginMaster
★ WinBoLL → AES → OriginMaster
★ WinBoLL → PowerBell → OriginMaster
★ WinBoLL → Positions → OriginMaster
#### ☁ 5. WinBoLL-APP 用户资源概述。
1> /sdcard 挂载用户手机 SD 存储/storage/emulated/0
## 二、WinBoLL 项目核心信息
### ☁ 稍稍歇 ☁ ###
### ☁ 急急停 ☁ WinBoLL 应用前置条件
☁ WinBoLL 主机建立 1Panel MySQL 应用。
☁ WinBoLL 主机建立 1Panel Gitea 应用。
☁ WinBoLL 主机设置 WinBoLL 应用为非登录状态。
☁ WinBoLL 主机建立 WinBoLL 账户与 WinBoLL 用户组。
☁ WinBoLL 账户 User ID 为: J。
☁ WinBoLL 用户组 Group ID 为: Studio。
☁ WinBoLL 主机 WinBoLL 1Panel Gitea 建立 WinBoLL 工作组。
☁ WinBoLL 主机 WinBoLL 1Panel Gitea 用户项目 APK 编译输出目录为 /sdcard/WinBoLLStudio/<用户名>/APKs/
☁ WinBoLL 项目配置文件示例为 "<WinBoLL 项目根目录>/.winboll/winboll.properties-demo"(WinBoLL 项目已设置)
☁ WinBoLL 项目配置文件为 "<WinBoLL 项目根目录>/.winboll/winboll.properties"
☁ WinBoLL 项目配置文件设定为源码提交时忽略。(WinBoLL 项目已设置)
☁ Gradle 项目配置文件示例为 "<WinBoLL 项目根目录>/.winboll/local.properties-demo"(WinBoLL 项目已设置)
☁ Gradle 项目配置文件为 "<WinBoLL 项目根目录>/local.properties"(WinBoLL 项目已设置)
☁ Gradle 项目配置文件设定为源码提交时忽略。(WinBoLL 项目已设置)
### 1. 项目简介
☆ WinBoLL 项目是为手机端开发Android 项目的需求而设计的项目。
### ☁ 登高处 ☁ WinBoLL 应用需求规划
☁ WinBoLL 主机建立 WinBoLL 客户端用户数据库为 MySQL winbollclient 数据库。
☁ WinBoLL 主机设置 WinBoLL 客户端用户信息存储在 winbollclient 数据库中。
☁ MySQL winbollclient 数据库中
WinBoLL 客户端用户信息设定为:
<用户名, 验证密码, 验证邮箱, 验证手机, 唯一存储令牌Token, 备用验证邮箱>
☁ WinBoLL 项目源码仓库托管在 WinBoLL 1Panel Gitea 目录 /opt/1panel/apps/gitea/gitea/data/git/repositories/studio/app.git中。
☁ WinBoLL 主机提供 WinBoLL 1Panel Gitea 应用的 WinBoLL 项目源码仓库存取功能。Gitea 应用已提供)
☁ WinBoLL 主机提供 WinBoLL Gitea 项目仓库存档功能。Gitea 应用已提供)
☁ 提供 WinBoLL 客户端用户登录功能。Gitea 应用已提供)
### 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-APP 应用前置需求
☁ WinBoLL-APP WinBoLL 项目根目录设定为手机的 /sdcard/WinBoLLStudio/Sources 目录。(需要用户手动建立文件夹)
☁ WinBoLL-APP 具有手机 /sdcard/WinBoLL 目录的存储权限。(需要手机操作系统授权)
☁ WinBoLL-APP WinBoLL 项目仓库源码存储路径为 /sdcard/WinBoLLStudio/Sources/APP.git需要用户手动建立文件夹
☁ WinBoLL-APP 项目 APK 编译输出目录为 /sdcard/WinBoLLStudio/APKs/
☁ WinBoLL-APP 应用签名验证可定制化。WinBoLL 项目已提供)
☁ WinBoLL-APP 与系列衍生 APP 应用共享 cc.winboll.studio 命名空间资源。WinBoLL 项目已提供)
☁ WinBoLL-APP 用户客户端信息存储在命名空间为 WinBoLL APP MySQLLite 应用的 winbollappclient 数据库中。
☁ WinBoLL-APP MySQLLite 应用的 winbollappclient 数据库中,
WinBoLL 用户客户端信息设定为:
<用户名, 唯一存储令牌Token>
## 三、应用编译环境检查问题
### 核心判断条件:
WinBoLL 项目以文件夹 `"/sdcard/WinBoLLStudio/APKs"` 是否存在为判断环境编译输出条件因为编译输出的APK文件需要一个可供保存的环境。
### ☁ 云游四方 ☁ ###
### ☁ 呔! ☁ WinBoLL-APP 应用需求规划
☁ 如要使用 WinBoLL Android 项目的 Gradle 编译功能,则需要设置以下两个文件夹。
☁ 1. 则需要建立数据存储目录 /sdcard/WinBoLLStudio/APKs。
WinBoLL 项目源码编译出来的安装包会拷贝一份到 /sdcard/WinBoLLStudio/APKs 目录下。
☁ 2. 则需要建立数据存储目录 /sdcard/AppProjects。
WinBoLL 项目源码编译出来的安装包会拷贝一份并命名 "app.apk" 的安装文件为到 /sdcard/AppProjects 目录下。
☆ 文件夹"/sdcard/WinBoLLStudio/APKs" 目录条件设置方法:
***Linux 服务器端方面***:建立 `/sdcard/WinBoLLStudio/APKs` 目录即可。
***手机开发端方面***:建立 `"/sdcard/WinBoLLStudio/APKs"` 目录(即 `"/storage/emulated/0/WinBoLLStudio/APKs"` 目录) 即可
## 四、前置条件
### ☁ 吁! ☁ WinBoLL-APP 共享计划前景
WinBoLL-APP 将会实现 https://winboll.cc/api 访问功能。
☁ WinBoLL-APP 将会实现手机端 Android 应用的开发与管理功能
### 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" 作为源码命名空间。在此命名空间下进行源码定义。
## ☁ WinBoLL ☁ WinBoLL 主机忧虑
☁ WinBoLL 将会提供 gitea.winboll.cc 域名用户注册登录功能。
☁ WinBoLL 将会提供 WinBoLL-APP 及其衍生应用的 Gitea 仓库管理服务。
☁ WinBoLL 将会提供 winboll.cc 域名 WinBoLL 项目组注册登录功能。
## 五、核心需求规划
# 本项目要实际运用需要注意以下几个步骤:
# 在项目根目录下:
## ★. 项目模块编译环境设置(必须)settings.gradle-demo 要复制为 settings.gradle并取消相应项目模块的注释。
## ★. 项目模块编译环境设置(必须) 在根目录拷贝 gradle.properties-androidx-demo 或者 gradle.properties-android-demo 文件为 gradle.properties。
## ★. 项目 Android SDK 编译环境设置(可选)local.properties-demo 要复制为 local.properties并按需要设置 Android SDK 目录。
## ★. 应用签名密钥 keystore 设置问题。一般调试编译只需用【Termux】cd 进 GenKeyStore 目录执行 $ bash gen_debug_keystore.sh 命令即可完成设置。
## ☆. 应用 WiBoLL 签名密钥配置问题<非必须考虑>。设置时需要 clone 【keystore】模块源码并拷贝模块目录的 appkey.jks 与 appkey.keystore 到项目根目录即可。
## ☆. 类库型模块编译环境设置(可选)winboll.properties-demo 要复制为 winboll.properties并按需要设置 WinBoLL Maven 库登录用户信息, 和 APK 文件额外输出路径。
### 1. WinBoLL 应用安全验证需求
#### ☆ 支持访问 https://console.winboll.cc/ 服务器以校验应用包签名与版本。
### 2. 手机端源码开发管理需求
#### ☆ 支持切换不同 WinBoLL 分支,以开发不同安卓应用。
# ☆类库型项目编译方法
## 先编译类库对应的模块测试项目
### 修改模块测试项目的 build.properties 文件
设置属性 libraryProject=<类库项目模块文件夹名称>
### 再编译测试项目
$ bash .winboll/bashPublishAPKAddTag.sh <应用项目模块文件夹名称>
#### 测试项目编译后,编译器会复制一份 APK 到 路径:"/sdcard/WinBoLLStudio/APKs/<项目根目录名称>/tag/" 文件夹。
#### 若是 winboll.properties 文件的 [ExtraAPKOutputPath] 属性设置了路径。编译器也会复制一份 APK 到这个路径。
### 最后编译类库项目
$ bash .winboll/bashPublishLIBAddTag.sh <类库项目模块文件夹名称>
#### 类库模块编译命令执行后,编译器会发布到 WinBoLL Nexus Maven 库Maven 库地址可以参阅根项目目录配置 build.gradle 文件。
# ☆应用型项目编译方法
## 直接调用以下命令编译应用型项目
$ bash .winboll/bashPublishAPKAddTag.sh <应用项目模块文件夹名称>
#### 应用模块编译命令执行后,编译器会复制一份 APK 到
#### 测试项目编译后,编译器会复制一份 APK 到 路径:"/sdcard/WinBoLLStudio/APKs/<项目根目录名称>/tag/" 文件夹。
#### 若是 winboll.properties 文件的 [ExtraAPKOutputPath] 属性设置了路径。编译器也会复制一份 APK 到这个路径。
## 六、编译与使用指南
## ☆应用调试编译方法
使用以下命令编译调试:
### 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` 文件到项目根目录即可。
### Beta调试使用
$ bash gradlew assembleBetaDebug
## 七、应用编译命令介绍
### Stage调试使用
$ bash gradlew assembleStageDebug
### 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 库。
### 若是 winboll.properties 文件的 [ExtraAPKOutputPath] 属性设置了路径。编译器也会复制一份 APK 到这个路径。
### 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` 属性配置的目录下。
# 应用版本号命名方式
## statge 渠道
V<应用开发环境编号><应用功能变更号><应用调试阶段号>
APPBase_15.7.0
## beta 渠道
V<应用开发环境编号><应用功能变更号><应用调试阶段号>-beta<调试编译计数>_<调试编译时间(分钟+秒钟)>
APPBase_15.9.6-beta8_5413
### 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
## 八、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 21
targetSdkVersion 30
versionCode 1
// versionName 更新后需要手动设置
// 项目模块目录的 build.gradle 文件的 stageCount=0
// Gradle编译环境下合起来的 versionName 就是 "${versionName}.0"
versionName "15.15"
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
#Sat Apr 25 04:16:42 HKT 2026
stageCount=10
libraryProject=libaes
baseVersion=15.15
publishVersion=15.15.9
buildCount=0
baseBetaVersion=15.15.10

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

@@ -1,23 +1,36 @@
package cc.winboll.studio.winboll.activities;
package cc.winboll.studio.aes;
/**
* @Author ZhanGSKen<zhangsken@qq.com>
* @Date 2025/05/10 09:48
* @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 {
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
@@ -36,12 +49,12 @@ public class WinBoLLActivity extends AppCompatActivity {
@Override
protected void onPostCreate(Bundle savedInstanceState) {
super.onPostCreate(savedInstanceState);
//WinBoLLActivityManager.getInstance().add(this);
WinBoLLActivityManager.getInstance().add(this);
}
@Override
protected void onDestroy() {
super.onDestroy();
//WinBoLLActivityManager.getInstance().registeRemove(this);
WinBoLLActivityManager.getInstance().finish(this);
}
}

View File

@@ -0,0 +1,41 @@
<?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:bottomLeftRadius="6dip"
android:bottomRightRadius="6dip"
android:topLeftRadius="6dip"
android:topRightRadius="6dip" />
</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:bottomLeftRadius="6dip"
android:bottomRightRadius="6dip"
android:topLeftRadius="6dip"
android:topRightRadius="6dip" />
</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">
<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">
<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">
<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,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,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="MyAESTheme" parent="AESTheme">
</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>

View File

@@ -1,8 +1,8 @@
#Created by .winboll/winboll_app_build.gradle
#Sat Jan 24 19:51:55 HKT 2026
stageCount=11
#Tue Apr 28 17:08:30 HKT 2026
stageCount=22
libraryProject=libappbase
baseVersion=15.15
publishVersion=15.15.10
publishVersion=15.15.21
buildCount=0
baseBetaVersion=15.15.11
baseBetaVersion=15.15.22

View File

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

View File

@@ -16,6 +16,17 @@
android:label="@string/app_name"
android:exported="true"
android:resizeableActivity="true"
android:launchMode="singleTop"
android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation">
</activity>
<activity
android:name=".MainActivityAlias"
android:label="@string/app_name"
android:exported="true"
android:resizeableActivity="true"
android:launchMode="singleTop"
android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation">
<intent-filter>
@@ -30,6 +41,17 @@
</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

View File

@@ -23,17 +23,8 @@ public class AboutActivity extends Activity {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_about);
// 设置工具栏
Toolbar toolbar = findViewById(R.id.toolbar);
setActionBar(toolbar);
getActionBar().setSubtitle(TAG);
getActionBar().setDisplayHomeAsUpEnabled(true);
toolbar.setNavigationOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
finish(); // 点击导航栏返回按钮,触发 finish()
}
});
setActionBar(toolbar);
AboutView aboutView = findViewById(R.id.aboutview);
aboutView.setAPPInfo(genDefaultAppInfo());
@@ -43,10 +34,10 @@ public class AboutActivity extends Activity {
LogUtils.d(TAG, "genDefaultAppInfo() 调用");
String branchName = "appbase";
APPInfo appInfo = new APPInfo();
appInfo.setAppName(getString(R.string.app_name));
appInfo.setAppName("APPBase");
appInfo.setAppIcon(R.drawable.ic_winboll);
appInfo.setAppDescription(getString(R.string.app_description));
appInfo.setAppGitName("APPBase");
appInfo.setAppGitName("WinBoLL");
appInfo.setAppGitOwner("Studio");
appInfo.setAppGitAPPBranch(branchName);
appInfo.setAppGitAPPSubProjectFolder(branchName);

View File

@@ -21,9 +21,12 @@ public class App extends GlobalApplication {
*/
@Override
public void onCreate() {
super.onCreate(); // 调用父类初始化逻辑(如基础库配置、全局上下文设置)
//setIsDebugging(false);
setIsDebugging(BuildConfig.DEBUG);
super.onCreate();
// 如果应用不在调试状态,就根据编译类型设置调试状态
if (isDebugging() != true) {
setIsDebugging(BuildConfig.DEBUG);
}
// 初始化 Toast 工具类(传入应用全局上下文,确保 Toast 可在任意地方调用)
ToastUtils.init(getApplicationContext());
}

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

@@ -10,6 +10,7 @@ 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;
@@ -36,13 +37,28 @@ public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ToastUtils.show("onCreate"); // 显示 Activity 创建提示(调试用)
//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";
}
/**
* 创建菜单时回调(加载工具栏菜单)
@@ -87,14 +103,17 @@ public class MainActivity extends Activity {
}
}
public void onLogTestNewTask(View view) {
LogActivity.startLogActivity(this, true);
}
/**
* 日志测试按钮点击事件(打开日志查看界面)
* 启动 LogActivity用于查看应用运行日志
* @param view 触发事件的 View对应布局中的日志测试按钮
*/
public void onLogTest(View view) {
// 启动日志查看 Activity通过静态方法传入上下文简化跳转逻辑
LogActivity.startLogActivity(this);
LogActivity.startLogActivity(this, false);
}
/**
@@ -136,12 +155,41 @@ public class MainActivity extends Activity {
// 启动意图(唤起浏览器)
context.startActivity(intent);
}
public void onAboutActivity(View view) {
LogUtils.d(TAG, "startAboutActivity() 调用");
LogUtils.d(TAG, "onAboutActivity() 调用");
Intent aboutIntent = new Intent(getApplicationContext(), AboutActivity.class);
startActivity(aboutIntent);
LogUtils.d(TAG, "startAboutActivity: 关于页面已启动");
}
public void onSplitScreenMode(View view) {
LogUtils.d(TAG, "onSplitScreenMode() 分屏测试按钮已点击");
ToastUtils.show("分屏测试:已启动新窗口");
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N) {
android.graphics.Rect bounds = new android.graphics.Rect();
getWindow().getDecorView().getDisplay().getRectSize(bounds);
int height = bounds.height();
int width = bounds.width();
bounds.set(0, 0, width, height / 2);
LogUtils.d(TAG, "onSplitScreenMode() 分屏窗口范围: " + bounds);
android.content.Intent intent = new android.content.Intent(this, MainActivityAlias.class);
intent.setFlags(android.content.Intent.FLAG_ACTIVITY_NEW_TASK);
LogUtils.d(TAG, "onSplitScreenMode() 准备启动MainActivityAlias");
android.app.ActivityOptions options = android.app.ActivityOptions.makeBasic();
options.setLaunchBounds(bounds);
startActivity(intent, options.toBundle());
LogUtils.d(TAG, "onSplitScreenMode() MainActivityAlias已启动");
}
}
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,17 @@
package cc.winboll.studio.appbase;
import android.os.Bundle;
import android.view.View;
import android.widget.Toolbar;
import cc.winboll.studio.appbase.R;
public class MainActivityAlias extends MainActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Toolbar toolbar = findViewById(R.id.toolbar);
setActionBar(toolbar);
}
}

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

@@ -23,6 +23,18 @@
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="@android:color/white"
android:background="#81C7F5"
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"
@@ -47,6 +59,18 @@
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="@android:color/white"
android:background="#81C7F5"
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"
@@ -62,18 +86,29 @@
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="关于应用"
android:text="分屏测试"
android:textSize="16sp"
android:textColor="@android:color/white"
android:background="#81C7F5"
android:paddingVertical="12dp"
android:layout_marginHorizontal="24dp"
android:onClick="onAboutActivity"
android:onClick="onSplitScreenMode"
android:layout_margin="10dp"/>
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="多开窗口"
android:textSize="16sp"
android:textColor="@android:color/white"
android:background="#81C7F5"
android:paddingVertical="12dp"
android:layout_marginHorizontal="24dp"
android:onClick="onMultiInstance"
android:layout_margin="10dp"/>
</LinearLayout>
</ScrollView>
</LinearLayout>
</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

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">AppBase</string>
<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>

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 {
// 定义全局变量,常用于版本管理

117
gpsrelaysentinel/README.md Normal file
View File

@@ -0,0 +1,117 @@
# GPSRelaySentinel
## 介绍
GPSRelaySentinel 是一款基于安卓平台的综合工具应用,集成 Termux 终端模拟器、二维码扫描、网络请求等功能。
## 技术栈
- **编程语言**: Java 7源码
- **编译环境**: Java 11Gradle 编译)
- **Gradle 插件**: 7.2.1
- **安卓 API**:
- 最低支持: API 26 (Android 8.0)
- 目标版本: API 30 (Android 11)
- 编译版本: API 30
## 软件架构
适配以下安卓开发环境的 Gradle 编译结构:
- AIDE Pro
- AndroidIDE
## 模块说明
本项目采用多模块结构:
- `gpsrelaysentinel` - 主应用模块
- `libappbase` - 基础库模块(提供 OkHttp、Gson、JSch 等基础能力)
- `libaes` - AES 加密库模块(提供权限请求、二维码、拼音搜索等扩展功能)
## 核心依赖库
### 网络相关
- OkHttp 4.4.1 / 3.14.9 - HTTP 客户端
- Gson 2.10.1 - JSON 解析
### 终端模拟
- Termux: terminal-emulator 0.118.0
- Termux: terminal-view 0.118.0
- Termux: termux-shared 0.118.0
### 功能组件
- ZXing 3.4.1 - 二维码生成与扫描
- JSch 0.1.55 - SSH/SFTP 客户端
- Jsoup 1.13.1 - HTML 解析
- FastJSON 1.2.76 - JSON 处理
### UI 组件
- Material Design 1.4.0
- AndroidX 组件库
- PullRefreshLayout 1.2.0 - 下拉刷新
## Gradle 编译说明
### 调试版编译
```bash
gradle assembleDebug
```
### 阶段版编译(发布)
```bash
bash .winboll/bashPublishAPKAddTag.sh gpsrelaysentinel
```
### 版本管理
版本信息由 `gpsrelaysentinel/build.properties` 管理:
- `baseVersion` - 基础版本号
- `stageCount` - 阶段构建次数
- `publishVersion` - 发布版本号
- `buildCount` - 构建次数
## 使用说明
### Termux 应用配置
1. 安装 Termux 应用(包名: `com.termux`
2. 配置允许外部应用访问:
```bash
echo "allow-external-apps = true" > ~/.termux/termux.properties
```
### 权限说明
应用需要以下权限:
- 网络访问权限
- 存储读写权限
- 相机权限(二维码扫描)
- 位置权限GPS 相关功能)
## 项目结构
```
gpsrelaysentinel/
├── src/main/
│ ├── java/ # Java 源码Java 7 语法)
│ ├── res/ # 资源文件
│ ├── libs/ # 本地库文件(含 JNI 库)
│ └── AndroidManifest.xml
├── build.gradle # 模块构建配置
└── build.properties # 版本配置文件
```
## 参与贡献
1. Fork 本仓库
2. 新建功能分支 (`git checkout -b feat_xxx`)
3. 提交代码(作者: ZhanGSKen <zhangsken@188.com>
4. 新建 Pull Request
## 许可证
[待添加许可证信息]
## 参考文档
- [Android Developer Documentation](https://developer.android.com/)
- [Termux Wiki](https://wiki.termux.com/)
- [Gradle User Manual](https://docs.gradle.org/)

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1,126 @@
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.gpsrelaysentinel"
minSdkVersion 26
// 适配MIUI12
targetSdkVersion 30
versionCode 1
// versionName 更新后需要手动设置
// .winboll/winbollBuildProps.properties 文件的 stageCount=0
// Gradle编译环境下合起来的 versionName 就是 "${versionName}.0"
versionName "15.11"
if(true) {
versionName = genVersionName("${versionName}")
}
}
// 米盟 SDK
packagingOptions {
doNotStrip "*/*/libmimo_1011.so"
}
sourceSets {
main {
jniLibs.srcDirs = ['libs'] // 若SO库放在libs目录下
}
}
// 确保 Java 7 兼容性
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_7
targetCompatibility JavaVersion.VERSION_1_7
}
}
dependencies {
api project(':libgpsrelaysentinel')
api 'com.google.code.gson:gson:2.10.1'
// 下拉控件
api 'com.baoyz.pullrefreshlayout:library:1.2.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'
// OkHttp网络请求
implementation 'com.squareup.okhttp3:okhttp:3.14.9'
// FastJSON解析
implementation 'com.alibaba:fastjson:1.2.76'
// AndroidX 类库
/*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'*/
// 米盟
api 'com.miui.zeus:mimo-ad-sdk:5.3.+'//请使用最新版sdk
//注意以下5个库必须要引入
//implementation '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'
implementation "androidx.annotation:annotation:1.3.0"
implementation "androidx.core:core:1.6.0"
implementation "androidx.drawerlayout:drawerlayout:1.1.1"
implementation "androidx.preference:preference:1.1.1"
implementation "androidx.viewpager:viewpager:1.0.0"
implementation "com.google.android.material:material:1.4.0"
implementation "com.google.guava:guava:24.1-jre"
/*
implementation "io.noties.markwon:core:$markwonVersion"
implementation "io.noties.markwon:ext-strikethrough:$markwonVersion"
implementation "io.noties.markwon:linkify:$markwonVersion"
implementation "io.noties.markwon:recycler:$markwonVersion"
*/
implementation 'com.termux:terminal-emulator:0.118.0'
implementation 'com.termux:terminal-view:0.118.0'
implementation 'com.termux:termux-shared:0.118.0'
// WinBoLL库 nexus.winboll.cc 地址
api 'cc.winboll.studio:libaes:15.15.9'
api 'cc.winboll.studio:libappbase:15.15.21'
// WinBoLL备用库 jitpack.io 地址
//api 'com.github.ZhanGSKen:AES:aes-v15.15.7'
//api 'com.github.ZhanGSKen:APPBase:appbase-v15.15.4'
api fileTree(dir: 'libs', include: ['*.jar'])
}

View File

@@ -0,0 +1,8 @@
#Created by .winboll/winboll_app_build.gradle
#Thu May 07 15:04:39 CST 2026
stageCount=27
libraryProject=
baseVersion=15.11
publishVersion=15.11.26
buildCount=33
baseBetaVersion=15.11.27

137
gpsrelaysentinel/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>
<string name="app_name">GPSRelaySentinel★</string>
</resources>

View File

@@ -0,0 +1,60 @@
<?xml version='1.0' encoding='utf-8'?>
<manifest
xmlns:android="http://schemas.android.com/apk/res/android"
package="cc.winboll.studio.gpsrelaysentinel">
<!-- 只能在前台获取精确的位置信息 -->
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<!-- 只有在前台运行时才能获取大致位置信息 -->
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
<!-- 在后台使用位置信息 -->
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION"/>
<!-- 运行前台服务 -->
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:roundIcon="@mipmap/ic_launcher_round"
android:label="@string/app_name"
android:theme="@style/MyAppTheme"
android:resizeableActivity="true"
android:name=".App">
<activity
android:name=".MainActivity"
android:label="@string/app_name">
<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=".GlobalApplication$CrashActivity"/>
<service
android:name=".MainService"
android:enabled="true"
android:exported="false"/>
<service android:name=".GpsReceiverChildService1"/>
<service android:name=".GpsReceiverChildService2"/>
<service android:name=".GpsReceiverChildService3"/>
</application>
</manifest>

View File

@@ -0,0 +1,340 @@
package cc.winboll.studio.gpsrelaysentinel;
import android.app.Activity;
import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageInfo;
import android.content.res.Resources;
import android.graphics.Typeface;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.text.TextUtils;
import android.util.Log;
import android.view.Gravity;
import android.view.Menu;
import android.view.MenuItem;
import android.view.ViewGroup;
import android.widget.HorizontalScrollView;
import android.widget.ScrollView;
import android.widget.TextView;
import android.widget.Toast;
import cc.winboll.studio.libappbase.GlobalApplication;
import cc.winboll.studio.libappbase.ToastUtils;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.Closeable;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.Thread.UncaughtExceptionHandler;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.concurrent.atomic.AtomicBoolean;
public class App extends GlobalApplication {
private static Handler MAIN_HANDLER = new Handler(Looper.getMainLooper());
@Override
public void onCreate() {
super.onCreate();
// 初始化 Toast 框架
ToastUtils.init(this);
//CrashHandler.getInstance().registerGlobal(this);
//CrashHandler.getInstance().registerPart(this);
}
public static void write(InputStream input, OutputStream output) throws IOException {
byte[] buf = new byte[1024 * 8];
int len;
while ((len = input.read(buf)) != -1) {
output.write(buf, 0, len);
}
}
public static void write(File file, byte[] data) throws IOException {
File parent = file.getParentFile();
if (parent != null && !parent.exists()) parent.mkdirs();
ByteArrayInputStream input = new ByteArrayInputStream(data);
FileOutputStream output = new FileOutputStream(file);
try {
write(input, output);
} finally {
closeIO(input, output);
}
}
public static String toString(InputStream input) throws IOException {
ByteArrayOutputStream output = new ByteArrayOutputStream();
write(input, output);
try {
return output.toString("UTF-8");
} finally {
closeIO(input, output);
}
}
public static void closeIO(Closeable... closeables) {
for (Closeable closeable : closeables) {
try {
if (closeable != null) closeable.close();
} catch (IOException ignored) {}
}
}
public static class CrashHandler {
public static final UncaughtExceptionHandler DEFAULT_UNCAUGHT_EXCEPTION_HANDLER = Thread.getDefaultUncaughtExceptionHandler();
private static CrashHandler sInstance;
private PartCrashHandler mPartCrashHandler;
public static CrashHandler getInstance() {
if (sInstance == null) {
sInstance = new CrashHandler();
}
return sInstance;
}
public void registerGlobal(Context context) {
registerGlobal(context, null);
}
public void registerGlobal(Context context, String crashDir) {
Thread.setDefaultUncaughtExceptionHandler(new UncaughtExceptionHandlerImpl(context.getApplicationContext(), crashDir));
}
public void unregister() {
Thread.setDefaultUncaughtExceptionHandler(DEFAULT_UNCAUGHT_EXCEPTION_HANDLER);
}
public void registerPart(Context context) {
unregisterPart(context);
mPartCrashHandler = new PartCrashHandler(context.getApplicationContext());
MAIN_HANDLER.postAtFrontOfQueue(mPartCrashHandler);
}
public void unregisterPart(Context context) {
if (mPartCrashHandler != null) {
mPartCrashHandler.isRunning.set(false);
mPartCrashHandler = null;
}
}
private static class PartCrashHandler implements Runnable {
private final Context mContext;
public AtomicBoolean isRunning = new AtomicBoolean(true);
public PartCrashHandler(Context context) {
this.mContext = context;
}
@Override
public void run() {
while (isRunning.get()) {
try {
Looper.loop();
} catch (final Throwable e) {
e.printStackTrace();
if (isRunning.get()) {
MAIN_HANDLER.post(new Runnable(){
@Override
public void run() {
Toast.makeText(mContext, e.toString(), Toast.LENGTH_LONG).show();
}
});
} else {
if (e instanceof RuntimeException) {
throw (RuntimeException)e;
} else {
throw new RuntimeException(e);
}
}
}
}
}
}
private static class UncaughtExceptionHandlerImpl implements UncaughtExceptionHandler {
private static DateFormat DATE_FORMAT = new SimpleDateFormat("yyyy_MM_dd-HH_mm_ss");
private final Context mContext;
private final File mCrashDir;
public UncaughtExceptionHandlerImpl(Context context, String crashDir) {
this.mContext = context;
this.mCrashDir = TextUtils.isEmpty(crashDir) ? new File(mContext.getExternalCacheDir(), "crash") : new File(crashDir);
}
@Override
public void uncaughtException(Thread thread, Throwable throwable) {
try {
String log = buildLog(throwable);
writeLog(log);
try {
Intent intent = new Intent(mContext, CrashActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.putExtra(Intent.EXTRA_TEXT, log);
mContext.startActivity(intent);
} catch (Throwable e) {
e.printStackTrace();
writeLog(e.toString());
}
throwable.printStackTrace();
android.os.Process.killProcess(android.os.Process.myPid());
System.exit(0);
} catch (Throwable e) {
if (DEFAULT_UNCAUGHT_EXCEPTION_HANDLER != null) DEFAULT_UNCAUGHT_EXCEPTION_HANDLER.uncaughtException(thread, throwable);
}
}
private String buildLog(Throwable throwable) {
String time = DATE_FORMAT.format(new Date());
String versionName = "unknown";
long versionCode = 0;
try {
PackageInfo packageInfo = mContext.getPackageManager().getPackageInfo(mContext.getPackageName(), 0);
versionName = packageInfo.versionName;
versionCode = Build.VERSION.SDK_INT >= 28 ? packageInfo.getLongVersionCode() : packageInfo.versionCode;
} catch (Throwable ignored) {}
LinkedHashMap<String, String> head = new LinkedHashMap<String, String>();
head.put("Time Of Crash", time);
head.put("Device", String.format("%s, %s", Build.MANUFACTURER, Build.MODEL));
head.put("Android Version", String.format("%s (%d)", Build.VERSION.RELEASE, Build.VERSION.SDK_INT));
head.put("App Version", String.format("%s (%d)", versionName, versionCode));
head.put("Kernel", getKernel());
head.put("Support Abis", Build.VERSION.SDK_INT >= 21 && Build.SUPPORTED_ABIS != null ? Arrays.toString(Build.SUPPORTED_ABIS): "unknown");
head.put("Fingerprint", Build.FINGERPRINT);
StringBuilder builder = new StringBuilder();
for (String key : head.keySet()) {
if (builder.length() != 0) builder.append("\n");
builder.append(key);
builder.append(" : ");
builder.append(head.get(key));
}
builder.append("\n\n");
builder.append(Log.getStackTraceString(throwable));
return builder.toString();
}
private void writeLog(String log) {
String time = DATE_FORMAT.format(new Date());
File file = new File(mCrashDir, "crash_" + time + ".txt");
try {
write(file, log.getBytes("UTF-8"));
} catch (Throwable e) {
e.printStackTrace();
}
}
private static String getKernel() {
try {
return App.toString(new FileInputStream("/proc/version")).trim();
} catch (Throwable e) {
return e.getMessage();
}
}
}
}
public static final class CrashActivity extends Activity {
private String mLog;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setTheme(android.R.style.Theme_DeviceDefault);
setTitle("App Crash");
mLog = getIntent().getStringExtra(Intent.EXTRA_TEXT);
ScrollView contentView = new ScrollView(this);
contentView.setFillViewport(true);
HorizontalScrollView horizontalScrollView = new HorizontalScrollView(this);
TextView textView = new TextView(this);
int padding = dp2px(16);
textView.setPadding(padding, padding, padding, padding);
textView.setText(mLog);
textView.setTextIsSelectable(true);
textView.setTypeface(Typeface.DEFAULT);
textView.setLinksClickable(true);
horizontalScrollView.addView(textView);
contentView.addView(horizontalScrollView, ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
setContentView(contentView);
}
private void restart() {
Intent intent = getPackageManager().getLaunchIntentForPackage(getPackageName());
if (intent != null) {
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
}
finish();
android.os.Process.killProcess(android.os.Process.myPid());
System.exit(0);
}
private static int dp2px(float dpValue) {
final float scale = Resources.getSystem().getDisplayMetrics().density;
return (int) (dpValue * scale + 0.5f);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
menu.add(0, android.R.id.copy, 0, android.R.string.copy)
.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
return super.onCreateOptionsMenu(menu);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.copy:
ClipboardManager cm = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);
cm.setPrimaryClip(ClipData.newPlainText(getPackageName(), mLog));
return true;
}
return super.onOptionsItemSelected(item);
}
@Override
public void onBackPressed() {
restart();
}
}
}

View File

@@ -0,0 +1,27 @@
package cc.winboll.studio.gpsrelaysentinel;
import android.content.Intent;
import cc.winboll.studio.libappbase.LogUtils;
import cc.winboll.studio.libgpsrelaysentinel.model.GpsSubscribeMsg;
import cc.winboll.studio.libgpsrelaysentinel.model.LocationPoint;
import cc.winboll.studio.libgpsrelaysentinel.service.GpsSubscribeReceiverService;
public final class GpsReceiverChildService1 extends GpsSubscribeReceiverService {
public static final String TAG = "GpsReceiverChildService1";
@Override
public void onReceiveGpsData(LocationPoint point, GpsSubscribeMsg config) {
super.onReceiveGpsData(point, config);
//当前独立接收日志
LogUtils.d(TAG,"独立接收服务1 成功收到GPS消息");
LogUtils.d(TAG,"纬度:"+point.getLatitude()+" 经度:"+point.getLongitude());
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
return START_NOT_STICKY;
}
}

View File

@@ -0,0 +1,26 @@
package cc.winboll.studio.gpsrelaysentinel;
import android.content.Intent;
import cc.winboll.studio.libappbase.LogUtils;
import cc.winboll.studio.libgpsrelaysentinel.model.GpsSubscribeMsg;
import cc.winboll.studio.libgpsrelaysentinel.model.LocationPoint;
import cc.winboll.studio.libgpsrelaysentinel.service.GpsSubscribeReceiverService;
public final class GpsReceiverChildService2 extends GpsSubscribeReceiverService {
public static final String TAG = "GpsReceiverChildService2";
@Override
public void onReceiveGpsData(LocationPoint point, GpsSubscribeMsg config) {
super.onReceiveGpsData(point, config);
LogUtils.d(TAG,"独立接收服务2 成功收到GPS消息");
LogUtils.d(TAG,"纬度:"+point.getLatitude()+" 经度:"+point.getLongitude());
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
return START_NOT_STICKY;
}
}

View File

@@ -0,0 +1,26 @@
package cc.winboll.studio.gpsrelaysentinel;
import android.content.Intent;
import cc.winboll.studio.libappbase.LogUtils;
import cc.winboll.studio.libgpsrelaysentinel.model.GpsSubscribeMsg;
import cc.winboll.studio.libgpsrelaysentinel.model.LocationPoint;
import cc.winboll.studio.libgpsrelaysentinel.service.GpsSubscribeReceiverService;
public final class GpsReceiverChildService3 extends GpsSubscribeReceiverService {
public static final String TAG = "GpsReceiverChildService3";
@Override
public void onReceiveGpsData(LocationPoint point, GpsSubscribeMsg config) {
super.onReceiveGpsData(point, config);
LogUtils.d(TAG,"独立接收服务3 成功收到GPS消息");
LogUtils.d(TAG,"纬度:"+point.getLatitude()+" 经度:"+point.getLongitude());
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
return START_NOT_STICKY;
}
}

View File

@@ -0,0 +1,358 @@
package cc.winboll.studio.gpsrelaysentinel;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.EditText;
import android.widget.Spinner;
import android.widget.Switch;
import android.widget.TextView;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
import cc.winboll.studio.gpsrelaysentinel.R;
import cc.winboll.studio.libappbase.LogUtils;
import cc.winboll.studio.libappbase.LogView;
import cc.winboll.studio.libappbase.ToastUtils;
/**
* WinBoLL Studio
* GPSRelaySentinel 主控制页面
* Java7 | API26~30
* 新增:模拟模式勾选控制 + 按钮互斥可用状态
*/
public final class MainActivity extends AppCompatActivity {
//原有控件
private Toolbar mToolbar;
private LogView mLogView;
private Switch mSwitchService;
//新增
private CheckBox mCheckBoxSimMode;
private Button btnSendLastGps;
private Spinner spinDirection;
private EditText etSimDistance;
private TextView tvTargetPreview;
private Button btnSimSend;
//全局模式标识 供给MainService判断
public static boolean IS_GPS_SIM_MODE = false;
//最后真实GPS坐标
public static double lastLat = 30.5928;
public static double lastLng = 114.3055;
//全局模拟坐标 供给MainService使用
public static double simLat = 30.5928;
public static double simLng = 114.3055;
//方位对应角度(正北0° 顺时针)
private double currentAngle = 0.0D;
//权限请求常量
private static final int REQUEST_LOCATION_PERMISSION = 1;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initView();
initToolbar();
initSwitchEvent();
initSimPanelEvent();
initSimModeCheck();
ToastUtils.show("onCreate");
}
/**
* 全部控件绑定
*/
private void initView() {
//原有
mToolbar = findViewById(R.id.toolbar);
mLogView = findViewById(R.id.logview);
mSwitchService = findViewById(R.id.switch_service);
//新增
mCheckBoxSimMode = findViewById(R.id.checkbox_sim_mode);
btnSendLastGps = findViewById(R.id.btn_send_last_gps);
spinDirection = findViewById(R.id.spin_direction);
etSimDistance = findViewById(R.id.et_sim_distance);
tvTargetPreview = findViewById(R.id.tv_target_point_preview);
btnSimSend = findViewById(R.id.btn_sim_send_gps);
//方位下拉 全局灰色文字
ArrayAdapter<CharSequence> dirAdapter = ArrayAdapter.createFromResource(
this,
R.array.direction_list,
R.layout.spinner_item_gray
);
dirAdapter.setDropDownViewResource(R.layout.spinner_item_gray);
spinDirection.setAdapter(dirAdapter);
//初始化开关状态
mSwitchService.setChecked(hasLocationPermission());
refreshButtonEnableStatus();
refreshTargetPreview();
}
//模拟勾选框监听
private void initSimModeCheck() {
mCheckBoxSimMode.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
IS_GPS_SIM_MODE = isChecked;
refreshButtonEnableStatus();
if (isChecked) {
ToastUtils.show("已进入GPS模拟模式");
} else {
ToastUtils.show("退出模拟模式使用真实GPS");
}
}
});
}
//刷新按钮互斥可用状态
private void refreshButtonEnableStatus() {
if (IS_GPS_SIM_MODE) {
//模拟模式:真实按钮禁用、模拟按钮可用
btnSendLastGps.setEnabled(false);
btnSimSend.setEnabled(true);
} else {
//正常模式:真实可用、模拟禁用
btnSendLastGps.setEnabled(true);
btnSimSend.setEnabled(false);
}
}
/**
* 初始化标题栏
*/
private void initToolbar() {
setSupportActionBar(mToolbar);
}
/**
* GPS服务开关监听
*/
private void initSwitchEvent() {
mSwitchService.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
if (isChecked) {
if (hasLocationPermission()) {
startGpsService();
} else {
requestLocationPermission();
mSwitchService.setChecked(false);
}
} else {
stopGpsService();
}
}
});
}
/**
* 模拟发送面板 全部事件初始化
*/
private void initSimPanelEvent() {
//1.原按钮发送最后一条真实GPS
btnSendLastGps.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
sendLastRealGpsBroadcast();
}
});
//2.方位下拉选择 -> 切换角度并刷新预览
spinDirection.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
currentAngle = getDirectionAngle(position);
refreshTargetPreview();
}
@Override
public void onNothingSelected(AdapterView<?> parent) {}
});
//3.距离输入变化自动预览
etSimDistance.setOnFocusChangeListener(new View.OnFocusChangeListener() {
@Override
public void onFocusChange(View v, boolean hasFocus) {
if (!hasFocus) {
refreshTargetPreview();
}
}
});
//4.模拟发送按钮:计算偏移并赋值全局模拟坐标
btnSimSend.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
saveSimGpsData();
ToastUtils.show("已设置当前模拟GPS坐标");
}
});
}
/**
* 保存模拟坐标到全局静态变量 供给MainService使用
*/
private void saveSimGpsData() {
String disText = etSimDistance.getText().toString().trim();
double distance = 10D;
try {
distance = Double.parseDouble(disText);
} catch (Exception e) {
ToastUtils.show("请输入合法距离");
return;
}
double[] target = calculateOffsetLatLng(lastLat, lastLng, distance, currentAngle);
simLat = target[0];
simLng = target[1];
refreshTargetPreview();
}
/**
* 根据下拉position获取对应方位角度
*/
private double getDirectionAngle(int pos) {
switch (pos) {
case 0: return 0.0D; //正北
case 1: return 180.0D; //正南
case 2: return 90.0D; //正东
case 3: return 270.0D; //正西
case 4: return 45.0D; //东北
case 5: return 315.0D; //西北
case 6: return 135.0D; //东南
case 7: return 225.0D; //西南
default:return 0.0D;
}
}
/**
* 根据基准坐标+距离+角度 计算偏移经纬度
*/
private double[] calculateOffsetLatLng(double lat, double lng, double distanceMeter, double angle) {
double radAngle = Math.toRadians(angle);
double radLat = Math.toRadians(lat);
double meterPerLat = 111320D;
double meterPerLng = Math.cos(radLat) * 111320D;
double offsetLat = (distanceMeter * Math.cos(radAngle)) / meterPerLat;
double offsetLng = (distanceMeter * Math.sin(radAngle)) / meterPerLng;
return new double[]{lat + offsetLat , lng + offsetLng};
}
/**
* 刷新目标坐标预览
*/
private void refreshTargetPreview() {
String disText = etSimDistance.getText().toString().trim();
double distance = 10D;
try {
distance = Double.parseDouble(disText);
} catch (Exception e) {}
double[] target = calculateOffsetLatLng(lastLat, lastLng, distance, currentAngle);
String info = "目标模拟坐标:"
+ String.format("%.6f", target[0])
+ " , "
+ String.format("%.6f", target[1]);
tvTargetPreview.setText(info);
}
/**
* 发送【最后真实GPS】广播
*/
private void sendLastRealGpsBroadcast() {
Intent broadcast = new Intent("GPS_DATA_BROADCAST");
broadcast.putExtra("isSim", false);
broadcast.putExtra("lat", lastLat);
broadcast.putExtra("lng", lastLng);
sendBroadcast(broadcast);
LogUtils.d("GPS_SEND", "发送真实GPS -> lat:" + lastLat + " lng:" + lastLng);
}
//—————— 原有权限 & 服务启停 完全原样保留 ——————
private boolean hasLocationPermission() {
boolean basicPermission = checkSelfPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED
|| checkSelfPermission(android.Manifest.permission.ACCESS_COARSE_LOCATION) == PackageManager.PERMISSION_GRANTED;
if (basicPermission && android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.Q) {
return checkSelfPermission(android.Manifest.permission.ACCESS_BACKGROUND_LOCATION) == PackageManager.PERMISSION_GRANTED;
}
return basicPermission;
}
private void requestLocationPermission() {
String[] permissionArray;
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.Q) {
permissionArray = new String[]{
android.Manifest.permission.ACCESS_FINE_LOCATION,
android.Manifest.permission.ACCESS_COARSE_LOCATION,
android.Manifest.permission.ACCESS_BACKGROUND_LOCATION
};
} else {
permissionArray = new String[]{
android.Manifest.permission.ACCESS_FINE_LOCATION,
android.Manifest.permission.ACCESS_COARSE_LOCATION
};
}
requestPermissions(permissionArray, REQUEST_LOCATION_PERMISSION);
}
private void startGpsService() {
Intent serviceIntent = new Intent(MainActivity.this, MainService.class);
startForegroundService(serviceIntent);
ToastUtils.show("GPS Service started");
LogUtils.d(MainService.TAG, "GPS Service started from MainActivity");
}
private void stopGpsService() {
getSharedPreferences(MainService.PREF_NAME, Context.MODE_PRIVATE)
.edit()
.putBoolean(MainService.KEY_SERVICE_ENABLED, false)
.apply();
Intent serviceIntent = new Intent(MainActivity.this, MainService.class);
stopService(serviceIntent);
ToastUtils.show("GPS Service stopped");
LogUtils.d(MainService.TAG, "GPS Service stopped from MainActivity");
}
@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode == REQUEST_LOCATION_PERMISSION) {
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
mSwitchService.setChecked(true);
startGpsService();
} else {
ToastUtils.show("需要位置权限才能使用GPS服务");
mSwitchService.setChecked(false);
}
}
}
@Override
protected void onResume() {
super.onResume();
mLogView.start();
}
}

View File

@@ -0,0 +1,269 @@
package cc.winboll.studio.gpsrelaysentinel;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.os.Build;
import android.os.Bundle;
import android.os.IBinder;
import androidx.core.app.NotificationCompat;
import cc.winboll.studio.libappbase.LogUtils;
import cc.winboll.studio.libgpsrelaysentinel.manager.GpsSubscribeManager;
import cc.winboll.studio.libgpsrelaysentinel.manager.SubscribeLocationManager;
import cc.winboll.studio.libgpsrelaysentinel.model.GpsSubscribeMsg;
import java.util.Map;
/**
* WinBoLL Studio
* GPS定位核心前台服务
* 负责GPS持续监听、订阅者步长判断、基准坐标刷新、前台常驻通知
* Java7 | API26~30
* 新增实时同步最新GPS到MainActivity静态坐标
*/
public final class MainService extends Service {
//日志标签
public static final String TAG = "MainService";
//前台通知常量
private static final String CHANNEL_ID = "gps_relay_channel";
private static final int NOTIFICATION_ID = 1;
//SP配置常量
static final String PREF_NAME = "gps_relay_service_prefs";
static final String KEY_SERVICE_ENABLED = "service_enabled";
//系统定位 & 通知控件
private LocationManager mLocationManager;
private LocationListener mLocationListener;
private NotificationManager mNotificationManager;
private NotificationCompat.Builder mNotificationBuilder;
//运行状态 & 计数
private boolean mIsRunning = false;
private int mGpsLocationCount = 0;
//订阅管理器
private GpsSubscribeManager mSubscribeManager;
private SubscribeLocationManager mLocationRuleManager;
@Override
public void onCreate() {
super.onCreate();
LogUtils.d(TAG, "Service onCreate");
initManager();
initNotificationConfig();
//上次开启状态则自动重启GPS监听
if (checkServiceEnableStatus()) {
LogUtils.d(TAG, "历史服务已启用自动启动GPS监听");
startGpsLocationListen();
}
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
LogUtils.d(TAG, "Service onStartCommand");
saveServiceEnableStatus(true);
startGpsLocationListen();
return START_STICKY;
}
/**
* 初始化订阅规则管理器
*/
private void initManager() {
mSubscribeManager = GpsSubscribeManager.getInstance();
mLocationRuleManager = SubscribeLocationManager.getInstance();
}
/**
* 初始化通知渠道与管理类
*/
private void initNotificationConfig() {
mNotificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
createSystemNotificationChannel();
}
/**
* 读取服务启用状态
*/
private boolean checkServiceEnableStatus() {
return getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE)
.getBoolean(KEY_SERVICE_ENABLED, false);
}
/**
* 保存服务启用状态
*/
private void saveServiceEnableStatus(boolean enabled) {
getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE)
.edit()
.putBoolean(KEY_SERVICE_ENABLED, enabled)
.apply();
LogUtils.d(TAG, "服务启用状态已设置:" + enabled);
}
/**
* 启动GPS定位监听核心逻辑
*/
private void startGpsLocationListen() {
if (mIsRunning) {
LogUtils.d(TAG, "GPS监听已正在运行无需重复启动");
return;
}
mLocationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);
initLocationListener();
try {
if (mLocationManager.isProviderEnabled(LocationManager.GPS_PROVIDER)) {
//定位间隔1000毫秒 / 最小位移1米
mLocationManager.requestLocationUpdates(
LocationManager.GPS_PROVIDER,
1000,
1,
mLocationListener
);
mIsRunning = true;
startServiceForegroundNotification();
LogUtils.d(TAG, "GPS定位监听已成功注册");
}
} catch (SecurityException e) {
LogUtils.e(TAG, "定位权限缺失,监听启动失败:" + e.getMessage());
}
}
/**
* 初始化定位监听回调
*/
private void initLocationListener() {
mLocationListener = new LocationListener() {
@Override
public void onLocationChanged(Location location) {
handleLocationUpdate(location);
}
@Override
public void onStatusChanged(String provider, int status, Bundle extras) {
LogUtils.d(TAG, "GPS状态变更 -> 提供者:" + provider + " 状态:" + status);
}
@Override
public void onProviderEnabled(String provider) {
LogUtils.d(TAG, "GPS提供者已启用" + provider);
}
@Override
public void onProviderDisabled(String provider) {
LogUtils.d(TAG, "GPS提供者已禁用" + provider);
}
};
}
/**
* 处理每次定位刷新|核心:步长判断 + 基准坐标更新
* 新增同步最新坐标到MainActivity静态变量
*/
private void handleLocationUpdate(Location location) {
mGpsLocationCount ++;
String locationInfo = "纬度:" + location.getLatitude() + " , 经度:" + location.getLongitude();
LogUtils.d(TAG, "定位刷新 -> " + locationInfo);
//========== 新增关键代码实时同步最新真实GPS坐标 ==========
MainActivity.lastLat = location.getLatitude();
MainActivity.lastLng = location.getLongitude();
//==========================================================
//更新前台通知文案
updateForegroundNotification(locationInfo);
//遍历全部订阅者进行推送规则判断
Map<String, GpsSubscribeMsg> subscribeAllMap = mSubscribeManager.getSubscribeMap();
for (Map.Entry<String, GpsSubscribeMsg> entry : subscribeAllMap.entrySet()) {
final String subscribeSid = entry.getKey();
final GpsSubscribeMsg subscribeConfig = entry.getValue();
double currentLat = location.getLatitude();
double currentLng = location.getLongitude();
//判断是否满足推送条件(全订阅/步长阈值)
boolean allowPush = mLocationRuleManager.isNeedPush(subscribeSid, currentLat, currentLng);
if (allowPush) {
//推送成功后刷新该订阅者基准定点坐标
mLocationRuleManager.updateSubscriberPoint(subscribeSid, currentLat, currentLng);
}
}
}
/**
* 创建系统通知渠道
*/
private void createSystemNotificationChannel() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationChannel notificationChannel = new NotificationChannel(
CHANNEL_ID,
"GPS Relay Service",
NotificationManager.IMPORTANCE_LOW
);
notificationChannel.setDescription("GPSRelaySentinel 后台常驻服务通知");
mNotificationManager.createNotificationChannel(notificationChannel);
}
}
/**
* 开启前台常驻通知
*/
private void startServiceForegroundNotification() {
mNotificationBuilder = new NotificationCompat.Builder(this, CHANNEL_ID)
.setContentTitle("GPS 中继服务")
.setContentText("等待GPS定位数据...")
.setSmallIcon(android.R.drawable.ic_menu_mylocation)
.setOngoing(true);
Notification notification = mNotificationBuilder.build();
startForeground(NOTIFICATION_ID, notification);
}
/**
* 动态更新通知内容
*/
private void updateForegroundNotification(String locationText) {
if (mNotificationBuilder != null) {
mNotificationBuilder.setContentText(locationText + " | 定位次数:" + mGpsLocationCount);
mNotificationManager.notify(NOTIFICATION_ID, mNotificationBuilder.build());
}
}
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public void onDestroy() {
super.onDestroy();
//注销定位监听
if (mLocationManager != null && mLocationListener != null) {
try {
mLocationManager.removeUpdates(mLocationListener);
} catch (SecurityException e) {
LogUtils.e(TAG, "移除定位监听权限异常:" + e.getMessage());
}
}
mIsRunning = false;
LogUtils.d(TAG, "MainService 已销毁GPS监听已停止");
}
}

View File

@@ -0,0 +1,34 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:width="108dp"
android:height="108dp"
android:viewportHeight="108"
android:viewportWidth="108">
<path
android:fillType="evenOdd"
android:pathData="M32,64C32,64 38.39,52.99 44.13,50.95C51.37,48.37 70.14,49.57 70.14,49.57L108.26,87.69L108,109.01L75.97,107.97L32,64Z"
android:strokeColor="#00000000"
android:strokeWidth="1">
<aapt:attr name="android:fillColor">
<gradient
android:endX="78.5885"
android:endY="90.9159"
android:startX="48.7653"
android:startY="61.0927"
android:type="linear">
<item
android:color="#44000000"
android:offset="0.0" />
<item
android:color="#00000000"
android:offset="1.0" />
</gradient>
</aapt:attr>
</path>
<path
android:fillColor="#FFFFFF"
android:fillType="nonZero"
android:pathData="M66.94,46.02L66.94,46.02C72.44,50.07 76,56.61 76,64L32,64C32,56.61 35.56,50.11 40.98,46.06L36.18,41.19C35.45,40.45 35.45,39.3 36.18,38.56C36.91,37.81 38.05,37.81 38.78,38.56L44.25,44.05C47.18,42.57 50.48,41.71 54,41.71C57.48,41.71 60.78,42.57 63.68,44.05L69.11,38.56C69.84,37.81 70.98,37.81 71.71,38.56C72.44,39.3 72.44,40.45 71.71,41.19L66.94,46.02ZM62.94,56.92C64.08,56.92 65,56.01 65,54.88C65,53.76 64.08,52.85 62.94,52.85C61.8,52.85 60.88,53.76 60.88,54.88C60.88,56.01 61.8,56.92 62.94,56.92ZM45.06,56.92C46.2,56.92 47.13,56.01 47.13,54.88C47.13,53.76 46.2,52.85 45.06,52.85C43.92,52.85 43,53.76 43,54.88C43,56.01 43.92,56.92 45.06,56.92Z"
android:strokeColor="#00000000"
android:strokeWidth="1" />
</vector>

View File

@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
android:shape="rectangle">
<!-- 灰色边框 -->
<stroke
android:width="1dp"
android:color="#555555"/>
<!-- 内部深色背景 -->
<solid android:color="#222222"/>
<!-- 轻微圆角 -->
<corners android:radius="4dp"/>
</shape>

View File

@@ -0,0 +1,170 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportHeight="108"
android:viewportWidth="108">
<path
android:fillColor="#26A69A"
android:pathData="M0,0h108v108h-108z" />
<path
android:fillColor="#00000000"
android:pathData="M9,0L9,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M19,0L19,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M29,0L29,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M39,0L39,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M49,0L49,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M59,0L59,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M69,0L69,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M79,0L79,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M89,0L89,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M99,0L99,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,9L108,9"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,19L108,19"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,29L108,29"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,39L108,39"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,49L108,49"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,59L108,59"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,69L108,69"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,79L108,79"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,89L108,89"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,99L108,99"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M19,29L89,29"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M19,39L89,39"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M19,49L89,49"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M19,59L89,59"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M19,69L89,69"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M19,79L89,79"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M29,19L29,89"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M39,19L39,89"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M49,19L49,89"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M59,19L59,89"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M69,19L69,89"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M79,19L79,89"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
</vector>

View File

@@ -0,0 +1,198 @@
<?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:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:background="#1c1c1c">
<com.google.android.material.appbar.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar">
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:popupTheme="@style/ThemeOverlay.AppCompat.Light"/>
</com.google.android.material.appbar.AppBarLayout>
<!-- 数据面板容器 -->
<LinearLayout
android:id="@+id/container_data_panel"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_horizontal">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="GPSRelaySentinel"
android:textColor="#888888"
android:padding="6dp"
android:background="@drawable/border_gray"
android:textAppearance="?android:attr/textAppearanceLarge"/>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical"
android:layout_marginTop="8dp"
android:spacing="12dp">
<CheckBox
android:id="@+id/checkbox_sim_mode"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="模拟模式"
android:textColor="#999999"
android:padding="4dp"
android:background="@drawable/border_gray"
android:textSize="11sp"/>
<Switch
android:id="@+id/switch_service"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="GPS Service"
android:textColor="#999999"
android:padding="4dp"
android:background="@drawable/border_gray"
android:checked="false"/>
<Button
android:id="@+id/btn_send_last_gps"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="发送最后GPS"
android:textColor="#bbbbbb"
android:background="@drawable/border_gray"
android:textSize="12sp"/>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_marginTop="16dp"
android:padding="12dp"
android:background="@drawable/border_gray">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="模拟移动GPS发送面板"
android:textColor="#999999"
android:textSize="12sp"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_marginTop="8dp"
android:spacing="8dp">
<Spinner
android:id="@+id/spin_direction"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:background="@drawable/border_gray"/>
<EditText
android:id="@+id/et_sim_distance"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:hint="移动距离(米)"
android:inputType="numberDecimal"
android:text="10"
android:background="@drawable/border_gray"
android:textColor="#aaaaaa"
android:textColorHint="#666666"/>
</LinearLayout>
<TextView
android:id="@+id/tv_target_point_preview"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="目标坐标:等待计算..."
android:textColor="#999999"
android:background="@drawable/border_gray"
android:padding="6dp"
android:textSize="11sp"
android:layout_marginTop="8dp"/>
<Button
android:id="@+id/btn_sim_send_gps"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="发送模拟移动GPS"
android:textColor="#bbbbbb"
android:background="@drawable/border_gray"
android:layout_marginTop="10dp"/>
</LinearLayout>
</LinearLayout>
<ScrollView
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1.0">
<!-- 订阅面板容器 -->
<LinearLayout
android:id="@+id/container_subscribe_panel"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
android:padding="12dp">
<cc.winboll.studio.libgpsrelaysentinel.view.GpsSubscribeControlView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:background="@drawable/border_gray"/>
<cc.winboll.studio.libgpsrelaysentinel.view.GpsSubscribeControlView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:background="@drawable/border_gray"/>
<cc.winboll.studio.libgpsrelaysentinel.view.GpsSubscribeControlView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:background="@drawable/border_gray"/>
</LinearLayout>
</ScrollView>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="200dp"
android:orientation="vertical"
android:id="@+id/container_log_show"
android:background="@drawable/border_gray">
<cc.winboll.studio.libappbase.LogView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/logview"/>
</LinearLayout>
</LinearLayout>

View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@android:id/text1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="8dp"
android:textColor="#999999"
android:gravity="center_vertical"/>

View File

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background" />
<foreground android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>

View File

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background" />
<foreground android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View File

@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string-array name="direction_list">
<item>正北</item>
<item>正南</item>
<item>正东</item>
<item>正西</item>
<item>东北</item>
<item>西北</item>
<item>东南</item>
<item>西南</item>
</string-array>
</resources>

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="colorPrimary">#009688</color>
<color name="colorPrimaryDark">#00796B</color>
<color name="colorAccent">#FF9800</color>
</resources>

View File

@@ -0,0 +1,4 @@
<resources>
<string name="app_name">GPSRelaySentinel</string>
</resources>

View File

@@ -0,0 +1,11 @@
<resources>
<!-- Base application theme. -->
<style name="MyAppTheme" parent="Theme.AppCompat.Light.NoActionBar">
<!-- Customize your theme here. -->
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</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,21 +0,0 @@
# Project-wide Gradle settings.
# IDE (e.g. Android Studio) users:
# Gradle settings configured through the IDE *will override*
# any settings specified in this file.
# For more details on how to configure your build environment visit
# http://www.gradle.org/docs/current/userguide/build_environment.html
# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
org.gradle.jvmargs=-Xmx2048m
# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
# org.gradle.parallel=true
# AndroidX package structure to make it clearer which packages are bundled with the
# Android operating system, and which are packaged with your app"s APK
# https://developer.android.com/topic/libraries/support-library/androidx-rn
android.useAndroidX=true
# Automatically convert third-party libraries to use AndroidX
android.enableJetifier=true
# 保持与旧版Gradle插件的兼容
android.disableAutomaticComponentCreation=true

0
gradlew vendored Normal file → Executable file
View File

1
libaes/.gitignore vendored Normal file
View File

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

71
libaes/build.gradle Normal file
View File

@@ -0,0 +1,71 @@
apply plugin: 'com.android.library'
apply plugin: 'maven-publish'
apply from: '../.winboll/winboll_lib_build.gradle'
apply from: '../.winboll/winboll_lint_build.gradle'
android {
// 适配MIUI12
compileSdkVersion 30
buildToolsVersion "30.0.3"
defaultConfig {
minSdkVersion 21
targetSdkVersion 30
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
// 米盟 SDK
packagingOptions {
doNotStrip "*/*/libmimo_1011.so"
}
}
dependencies {
// 权限请求框架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 类库
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'
// 米盟
api 'com.miui.zeus:mimo-ad-sdk:5.3.+'//请使用最新版sdk
//注意以下5个库必须要引入
//implementation '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'
// WinBoLL库 nexus.winboll.cc 地址
api 'cc.winboll.studio:libappbase:15.15.19'
// 备用库 jitpack.io 地址
//api 'com.github.ZhanGSKen:APPBase:appbase-v15.15.3'
api fileTree(dir: 'libs', include: ['*.jar'])
}

8
libaes/build.properties Normal file
View File

@@ -0,0 +1,8 @@
#Created by .winboll/winboll_app_build.gradle
#Sat Apr 25 04:16:30 HKT 2026
stageCount=10
libraryProject=libaes
baseVersion=15.15
publishVersion=15.15.9
buildCount=0
baseBetaVersion=15.15.10

Binary file not shown.

17
libaes/proguard-rules.pro vendored Normal file
View File

@@ -0,0 +1,17 @@
# 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 *;
#}

View File

@@ -0,0 +1,57 @@
<?xml version='1.0' encoding='utf-8'?>
<manifest
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="cc.winboll.studio.libaes">
<uses-permission android:name="android.permission.CAMERA" />
<uses-feature android:name="android.hardware.camera" android:required="true" />
<application
android:networkSecurityConfig="@xml/network_security_config"
android:usesCleartextTraffic="true"
tools:ignore="GoogleAppIndexingWarning">
<activity
android:name="cc.winboll.studio.libaes.unittests.SecondaryLibraryActivity"
android:exported="true"/>
<activity
android:name="cc.winboll.studio.libaes.unittests.TestDrawerFragmentActivity"
android:exported="true"/>
<activity
android:name="cc.winboll.studio.libaes.unittests.TestAToolbarActivity"
android:exported="true"/>
<activity
android:name="cc.winboll.studio.libaes.unittests.TestASupportToolbarActivity"
android:exported="true"/>
<service
android:name="cc.winboll.studio.libaes.winboll.WinBoLLClientService"
android:exported="true"/>
<service
android:name="cc.winboll.studio.libaes.winboll.AssistantService"
android:exported="true"/>
<service
android:name="cc.winboll.studio.libaes.winboll.WinBoLLMail"
android:exported="true"/>
<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>
</application>
</manifest>

View File

@@ -0,0 +1,207 @@
package cc.winboll.studio.libaes;
import android.content.Context;
import android.util.SparseArray;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.TextView;
import java.util.ArrayList;
public abstract class DrawerMenuDataAdapter<T> extends BaseAdapter {
private ArrayList<T> mData;
private int mLayoutResource; //布局id
public DrawerMenuDataAdapter() {
}
public DrawerMenuDataAdapter(ArrayList<T> mData, int mLayoutRes) {
this.mData = mData;
this.mLayoutResource = mLayoutRes;
}
@Override
public int getCount() {
return mData != null ? mData.size() : 0;
}
@Override
public T getItem(int position) {
return mData.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder viewHolder = ViewHolder.bind(parent.getContext(), convertView, parent, mLayoutResource
, position);
bindView(viewHolder, getItem(position));
return viewHolder.getItemView();
}
public abstract void bindView(ViewHolder holder, T obj);
// 添加数据项
//
public void add(T item) {
if (mData == null) {
mData = new ArrayList<>();
}
mData.add(item);
notifyDataSetChanged();
}
// 添加数据项在指定位置
//
public void add(int position, T item) {
if (mData == null) {
mData = new ArrayList<>();
}
mData.add(position, item);
notifyDataSetChanged();
}
// 删除数据项
//
public void remove(T item) {
if (mData != null) {
mData.remove(item);
}
notifyDataSetChanged();
}
// 删除指定位置数据项
//
public void remove(int position) {
if (mData != null) {
mData.remove(position);
}
notifyDataSetChanged();
}
// 清理所有数据项
//
public void clear() {
if (mData != null) {
mData.clear();
}
notifyDataSetChanged();
}
public static class ViewHolder {
// 存储在 ListView 的 item 中的 View
SparseArray<View> mSparseArrayView;
// 存放convertView
View mViewItem;
// 游标
int mnPosition;
Context mContext;
//构造方法
//
private ViewHolder(Context context, ViewGroup parent, int layoutResource) {
mSparseArrayView = new SparseArray<>();
this.mContext = context;
View convertView = LayoutInflater.from(context).inflate(layoutResource, parent, false);
convertView.setTag(this);
mViewItem = convertView;
}
//绑定 ViewHolder 与数据项
//
public static ViewHolder bind(Context context, View convertView, ViewGroup parent,
int layoutResource, int position) {
ViewHolder viewHolder;
if (convertView == null) {
viewHolder = new ViewHolder(context, parent, layoutResource);
} else {
viewHolder = (ViewHolder) convertView.getTag();
viewHolder.mViewItem = convertView;
}
viewHolder.mnPosition = position;
return viewHolder;
}
@SuppressWarnings("unchecked")
public <T extends View> T getView(int id) {
T t = (T) mSparseArrayView.get(id);
if (t == null) {
t = (T) mViewItem.findViewById(id);
mSparseArrayView.put(id, t);
}
return t;
}
// 获取当前条目
//
public View getItemView() {
return mViewItem;
}
// 获取条目位置
//
public int getItemPosition() {
return mnPosition;
}
// 设置文字
//
public ViewHolder setText(int id, CharSequence text) {
View view = getView(id);
if (view instanceof TextView) {
((TextView) view).setText(text);
}
return this;
}
// 设置图片
//
public ViewHolder setImageResource(int id, int drawableResource) {
View view = getView(id);
if (view instanceof ImageView) {
((ImageView) view).setImageResource(drawableResource);
} else {
view.setBackgroundResource(drawableResource);
}
return this;
}
// 设置点击监听
//
public ViewHolder setOnClickListener(int id, View.OnClickListener listener) {
getView(id).setOnClickListener(listener);
return this;
}
// 设置可见
//
public ViewHolder setVisibility(int id, int visible) {
getView(id).setVisibility(visible);
return this;
}
// 设置标签
//
public ViewHolder setTag(int id, Object obj) {
getView(id).setTag(obj);
return this;
}
}
}

View File

@@ -0,0 +1,75 @@
package cc.winboll.studio.libaes;
import android.view.View;
import android.view.ViewGroup;
import androidx.viewpager.widget.PagerAdapter;
import java.util.List;
public class ImagePagerAdapter extends PagerAdapter {
/*
* 四个必须重写的方法,否则会报错
*
*/
private List<View> views;
//构造方法拿到views
public ImagePagerAdapter(List<View> views) {
this.views = views;
}
//以下四个是重写的方法
// 获取要滑动的控件的数量在这里我们以滑动的广告栏为例那么这里就应该是展示的广告图片的ImageView数量
@Override
public int getCount() {
// TODO Auto-generated method stub
return this.views.size();
}
// 来判断显示的是否是同一张图片,这里我们将两个参数相比较返回即可
@Override
public boolean isViewFromObject(View arg0, Object arg1) {
// TODO Auto-generated method stub
return arg0 == arg1;
}
/**
* position是在viewPager中显示图片的下标
* 把对应的图片放到对应的位置就好了
* instantiateItem和destroyItem是对应的
* 一个是创建item一个是销毁item
* 当要显示的图片可以进行缓存的时候会调用instantiateItem进行显示图片的初始化
* 我们将要显示的ImageView加入到ViewGroup中然后作为返回值返回即可
*
* ViewPager 是扩展于 ViewGroupcontainer参数是当前的ViewPager对象
* 所有的item都会被加入到ViewPager中
* position就是 每个item对应的下标
*/
@Override
public Object instantiateItem(ViewGroup container, int position) {
container.addView(views.get(position));
return views.get(position);
}
//如果出现IllegalStateException: The specified child already has a parent. 这样的错误则可替换为以下的try catch 代码
/*try{
    if(views.get(position).getParent()==null){
  container.addView(views.get(position));
    }else{
((ViewGroup)views.get(position).getParent()).removeView(views.get(position));
container.addView(views.get(position));
}
}catch(Exception e){
e.printStackTrace();
}*/
// PagerAdapter只缓存5张要显示的图片如果滑动的图片超出了缓存的范围就会调用destroyItem将图片销毁
@Override
public void destroyItem(ViewGroup container, int position, Object object) {
container.removeView(views.get(position));
}
}

View File

@@ -0,0 +1,394 @@
package cc.winboll.studio.libaes.activitys;
/**
* @Author ZhanGSKen<zhangsken@qq.com>
* @Date 2024/06/13 18:58:54
* @Describe 可以加入Fragment的有抽屉的活动窗口抽象类
*/
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.widget.AdapterView;
import androidx.appcompat.app.ActionBarDrawerToggle;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
import androidx.drawerlayout.widget.DrawerLayout;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentTransaction;
import cc.winboll.studio.libaes.DrawerMenuDataAdapter;
import cc.winboll.studio.libaes.R;
import cc.winboll.studio.libaes.models.AESThemeBean;
import cc.winboll.studio.libaes.models.DrawerMenuBean;
import cc.winboll.studio.libaes.utils.AESThemeUtil;
import cc.winboll.studio.libaes.utils.DevelopUtils;
import cc.winboll.studio.libaes.utils.WinBoLLActivityManager;
import cc.winboll.studio.libaes.views.ADrawerMenuListView;
import cc.winboll.studio.libaes.views.ADsBannerView;
import cc.winboll.studio.libappbase.GlobalApplication;
import cc.winboll.studio.libappbase.LogUtils;
import com.baoyz.widget.PullRefreshLayout;
import java.util.ArrayList;
import cc.winboll.studio.libaes.interfaces.IWinBoLLActivity;
public abstract class DrawerFragmentActivity extends AppCompatActivity implements IWinBoLLActivity, AdapterView.OnItemClickListener {
public static final String TAG = "DrawerFragmentActivity";
static final String SHAREDPREFERENCES_NAME = "SHAREDPREFERENCES_NAME";
static final String DRAWER_THEME_TYPE = "DRAWER_THEME_TYPE";
//protected Context mContext;
ActivityType mActivityType;
ActionBarDrawerToggle mActionBarDrawerToggle;
DrawerLayout mDrawerLayout;
PullRefreshLayout mPullRefreshLayout;
ADrawerMenuListView mADrawerMenuListView;
DrawerMenuDataAdapter mDrawerMenuDataAdapter;
boolean mIsDrawerOpened = false;
boolean mIsDrawerOpening = false;
boolean mIsDrawerClosing = false;
protected Toolbar mToolbar;
public enum ActivityType { Main, Secondary }
protected volatile AESThemeBean.ThemeType mThemeType;
protected ArrayList<DrawerMenuBean> malDrawerMenuItem;
abstract protected ActivityType initActivityType();
//abstract protected View initContentView(LayoutInflater inflater, ViewGroup rootView);
@Override
protected void onCreate(Bundle savedInstanceState) {
mThemeType = AESThemeBean.getThemeStyleType(AESThemeUtil.getThemeTypeID(getApplicationContext()));
setTheme(AESThemeUtil.getThemeTypeID(getApplicationContext()));
super.onCreate(savedInstanceState);
WinBoLLActivityManager.getInstance().add(this);
mActivityType = initActivityType();
initRootView();
LogUtils.d(TAG, "onCreate end.");
}
@Override
public Activity getActivity() {
return this;
}
@Override
public String getTag() {
return TAG;
}
@Override
protected void onDestroy() {
WinBoLLActivityManager.getInstance().registeRemove(this);
super.onDestroy();
// 修复:释放广告资源,避免内存泄漏
ADsBannerView adsBannerView = findViewById(R.id.adsbanner);
if (adsBannerView != null) {
adsBannerView.releaseAdResources();
}
}
/*@Override
public Intent getIntent() {
// TODO: Implement this method
return super.getIntent();
}
public Context getContext() {
return this.mContext;
}*/
@Override
public MenuInflater getMenuInflater() {
// TODO: Implement this method
return super.getMenuInflater();
}
/*public void setSubtitle(CharSequence context) {
// TODO: Implement this method
getSupportActionBar().setSubtitle(context);
}*/
@Override
public void recreate() {
super.recreate();
}
/*@Override
public boolean moveTaskToBack(boolean nonRoot) {
return super.moveTaskToBack(nonRoot);
}*/
@Override
public void startActivity(Intent intent) {
super.startActivity(intent);
}
@Override
public void startActivityForResult(Intent intent, int requestCode, Bundle options) {
super.startActivityForResult(intent, requestCode, options);
}
/*@Override
public FragmentManager getSupportFragmentManager() {
return super.getSupportFragmentManager();
}
public void setSubtitle(int resId) {
// TODO: Implement this method
getSupportActionBar().setSubtitle(resId);
}
public void setTitle(CharSequence context) {
// TODO: Implement this method
getSupportActionBar().setTitle(context);
}
public void setTitle(int resId) {
// TODO: Implement this method
getSupportActionBar().setTitle(resId);
}*/
@Override
public SharedPreferences getSharedPreferences(String name, int mode) {
return super.getSharedPreferences(name, mode);
}
@Override
public Context getApplicationContext() {
// TODO: Implement this method
return super.getApplicationContext();
}
@Override
public void onBackPressed() {
super.onBackPressed();
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
if (AESThemeUtil.onAppThemeItemSelected(this, item)) {
recreate();
} if (DevelopUtils.onDevelopItemSelected(this, item)) {
LogUtils.d(TAG, String.format("onOptionsItemSelected item.getItemId() %d ", item.getItemId()));
} else {
return super.onOptionsItemSelected(item);
}
return true;
}
@Override
protected void onResume() {
super.onResume();
ADsBannerView adsBannerView = findViewById(R.id.adsbanner);
if (adsBannerView != null) {
adsBannerView.resumeADs(DrawerFragmentActivity.this);
}
}
void initRootView() {
setContentView(R.layout.activity_drawerfragment);
mToolbar = findViewById(R.id.activitydrawerfragmentASupportToolbar1);
setSupportActionBar(mToolbar);
if (mActivityType == ActivityType.Main) {
initMainRootView();
} else if (mActivityType == ActivityType.Secondary) {
initSecondaryRootView();
}
}
void initMainRootView() {
mDrawerLayout = findViewById(R.id.activitydrawerfragmentDrawerLayout1);
mADrawerMenuListView = findViewById(R.id.activitydrawerfragmentDrawerMenuListView1);
mPullRefreshLayout = findViewById(R.id.activitydrawerfragmentPullRefreshLayout1);
mPullRefreshLayout.setOnRefreshListener(new PullRefreshLayout.OnRefreshListener() {
@Override
public void onRefresh() {
//LogUtils.d(TAG, "onRefresh");
reinitDrawerMenuItemList(malDrawerMenuItem);
mDrawerMenuDataAdapter.notifyDataSetChanged();
mPullRefreshLayout.setRefreshing(false);
}
});
malDrawerMenuItem = new ArrayList<DrawerMenuBean>();
mDrawerMenuDataAdapter = new DrawerMenuDataAdapter<DrawerMenuBean>(malDrawerMenuItem, R.layout.listview_drawermenu) {
@Override
public void bindView(ViewHolder holder, DrawerMenuBean obj) {
holder.setImageResource(R.id.listviewdrawermenuImageView1, obj.getIconId());
holder.setText(R.id.listviewdrawermenuTextView1, obj.getIconName());
}
};
mADrawerMenuListView.setAdapter(mDrawerMenuDataAdapter);
mADrawerMenuListView.setOnItemClickListener(this);
mActionBarDrawerToggle = new ActionBarDrawerToggle(this, mDrawerLayout, mToolbar, R.string.lib_name, R.string.lib_name) {
@Override
public void onDrawerOpened(View drawerView) {//完全打开时触发
super.onDrawerOpened(drawerView);
mIsDrawerOpened = true;
mIsDrawerOpening = false;
//Toast.makeText(MainActivity.this,"onDrawerOpened",Toast.LENGTH_SHORT).show();
}
@Override
public void onDrawerClosed(View drawerView) {//完全关闭时触发
super.onDrawerClosed(drawerView);
mIsDrawerOpened = false;
mIsDrawerClosing = false;
//Toast.makeText(MainActivity.this,"onDrawerClosed",Toast.LENGTH_SHORT).show();
}
/**
* 当抽屉被滑动的时候调用此方法
* slideOffset表示 滑动的幅度0-1
*/
@Override
public void onDrawerSlide(View drawerView, float slideOffset) {
super.onDrawerSlide(drawerView, slideOffset);
}
/**
* 当抽屉滑动状态改变的时候被调用
* 状态值是STATE_IDLE闲置--0, STATE_DRAGGING拖拽的--1, STATE_SETTLING固定--2中之一。
*具体状态可以慢慢调试
*/
@Override
public void onDrawerStateChanged(int newState) {
super.onDrawerStateChanged(newState);
}
};
//设置显示旋转菜单
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
//通过下面这句实现toolbar和Drawer的联动如果没有这行代码箭头是不会随着侧滑菜单的开关而变换的或者没有箭头
// 可以尝试一下,不影响正常侧滑
mActionBarDrawerToggle.syncState();
mDrawerLayout.setDrawerListener(mActionBarDrawerToggle);
//去掉侧滑的默认图标(动画箭头图标),也可以选择不去,
//不去的话把这一行注释掉或者改成true然后把toolbar.setNavigationIcon注释掉就行了
//mActionBarDrawerToggle.setDrawerIndicatorEnabled(false);
mToolbar.setNavigationOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (mIsDrawerOpened || mIsDrawerOpening) {
mIsDrawerClosing = true;
mIsDrawerOpening = false;
mDrawerLayout.closeDrawer(mPullRefreshLayout);
return;
}
if (!mIsDrawerOpened || mIsDrawerClosing) {
mIsDrawerOpening = true;
mIsDrawerClosing = false;
mDrawerLayout.openDrawer(mPullRefreshLayout);
return;
}
}
});
initDrawerMenuItemList(malDrawerMenuItem);
}
void initSecondaryRootView() {
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
mToolbar.setNavigationOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//LogUtils.d(TAG, "onClick " + Integer.toString(v.getId()));
finish();
}
});
}
public <T extends Fragment> int removeFragment(T fragment) {
FragmentManager fragmentManager = getSupportFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
fragmentTransaction.remove(fragment);
fragmentTransaction.commit();
return fragmentManager.getFragments().size() - 1;
}
public <T extends Fragment> int addFragment(T fragment) {
FragmentManager fragmentManager = getSupportFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
fragmentTransaction.add(R.id.activitydrawerfragmentFrameLayout1, fragment);
fragmentTransaction.commit();
return fragmentManager.getFragments().size() - 1;
}
public <T extends Fragment> void showFragment(T fragment) {
FragmentManager fragmentManager = getSupportFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
for (int i = 0; i < fragmentManager.getFragments().size(); i++) {
if (fragmentManager.getFragments().get(i).equals(fragment)) {
fragmentTransaction.show(fragmentManager.getFragments().get(i));
} else {
fragmentTransaction.hide(fragmentManager.getFragments().get(i));
}
}
fragmentTransaction.commit();
}
public void showFragment(int nPosition) {
FragmentManager fragmentManager = getSupportFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
for (int i = 0; i < fragmentManager.getFragments().size(); i++) {
if (i == nPosition) {
fragmentTransaction.show(fragmentManager.getFragments().get(i));
} else {
fragmentTransaction.hide(fragmentManager.getFragments().get(i));
}
}
fragmentTransaction.commit();
}
protected void initDrawerMenuItemList(ArrayList<DrawerMenuBean> listDrawerMenu) {
}
protected void reinitDrawerMenuItemList(ArrayList<DrawerMenuBean> listDrawerMenu) {
}
public void notifyDrawerMenuDataChanged() {
mDrawerMenuDataAdapter.notifyDataSetChanged();
}
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
mDrawerLayout.closeDrawer(mPullRefreshLayout);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
if (mActivityType == ActivityType.Main) {
// 主题菜单
AESThemeUtil.inflateMenu(this, menu);
// 调试工具菜单
if (GlobalApplication.isDebugging()) {
DevelopUtils.inflateMenu(this, menu);
}
// 应用信息菜单
getMenuInflater().inflate(R.menu.toolbar_drawerbase, menu);
}
return super.onCreateOptionsMenu(menu);
}
@Override
protected void onActivityResult(int who, int targetFragment, Intent requestCode) {
super.onActivityResult(who, targetFragment, requestCode);
}
}

View File

@@ -0,0 +1,204 @@
package cc.winboll.studio.libaes.dialogs;
import android.content.Context;
import android.content.DialogInterface;
import android.widget.TextView;
import android.widget.Toast;
import androidx.appcompat.app.AlertDialog;
import cc.winboll.studio.libappbase.LogUtils;
import java.io.File;
import java.lang.reflect.Field;
import java.text.Collator;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
public class LocalFileSelectDialog {
public static final String TAG = LocalFileSelectDialog.class.getSimpleName();
File mfCurrentPath = new File("/storage/emulated/0");
String mszResultPath = "/storage/emulated/0";
OKClickListener mOKClickListener;
Context mContext;
public LocalFileSelectDialog(Context context) {
mContext = context;
}
public void open() {
LogUtils.d(TAG, "call open()");
String[] szlist = getChildFileList(mfCurrentPath);
if (szlist != null) {
showSingleChoiceDialog(szlist, 0);
}
}
int yourChoice;
protected void showSingleChoiceDialog(final String[] szItems, final int nChoice) {
LogUtils.d(TAG, "call showSingleChoiceDialog(...)");
yourChoice = nChoice;
AlertDialog.Builder builder = new AlertDialog.Builder(mContext);
String sz = mfCurrentPath.getPath();
builder.setTitle(sz);
//builder.setCancelable(false);
builder.setSingleChoiceItems(szItems, nChoice,
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
yourChoice = which;
}
});
// 确定按钮
builder.setPositiveButton("确定",
new DialogInterface.OnClickListener() {
@Override
public void onClick(
DialogInterface dialog,
int which) {
mszResultPath = mfCurrentPath.getPath() + File.separator + szItems[yourChoice];
//Toast.makeText(mContext, mszResultPath, Toast.LENGTH_SHORT).show();
mOKClickListener.onOKClick(mszResultPath);
}
});
// 下一层文件按钮
builder.setNegativeButton(">>>", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
File file = new File(mfCurrentPath.getPath() + File.separator + szItems[yourChoice]);
String[] szlist = getChildFileList(file);
if (szlist != null) {
mfCurrentPath = new File(mfCurrentPath.getPath() + File.separator + szItems[yourChoice]);
showSingleChoiceDialog(szlist, 0);
} else {
Toast.makeText(mContext, "这是一个最低的目录", Toast.LENGTH_SHORT).show();
String[] szlistOld = getChildFileList(mfCurrentPath);
showSingleChoiceDialog(szlistOld, yourChoice);
}
}
});
// 上一层文件按钮
builder.setNeutralButton("<<<",
new DialogInterface.OnClickListener() {// 添加返回按钮
@Override
public void onClick(
DialogInterface dialog,
int which) {// 响应事件
String[] szlist = getChildFileList(mfCurrentPath.getParentFile());
if (szlist != null) {
mfCurrentPath = mfCurrentPath.getParentFile();
showSingleChoiceDialog(szlist, 0);
} else {
Toast.makeText(mContext, "这是一个最高的目录", Toast.LENGTH_SHORT).show();
String[] szlistOld = getChildFileList(mfCurrentPath);
showSingleChoiceDialog(szlistOld, yourChoice);
}
}
});
AlertDialog dialog = builder.create();
dialog.show();
// 反射原理修改对话框元素
//
//需要在show()方法之后才能修改
//修改“确认”、“取消”按钮的字体大小
//dialog.getButton(AlertDialog.BUTTON_POSITIVE).setTextSize(16);
//dialog.getButton(DialogInterface.BUTTON_NEGATIVE).setTextSize(16);
//dialog.getButton(DialogInterface.BUTTON_NEUTRAL).setTextSize(16);
try {
Field mAlert = AlertDialog.class.getDeclaredField("mAlert");
mAlert.setAccessible(true);
Object mAlertController = mAlert.get(dialog);
//通过反射修改title字体大小和颜色
Field mTitle = mAlertController.getClass().getDeclaredField("mTitleView");
mTitle.setAccessible(true);
TextView mTitleView = (TextView) mTitle.get(mAlertController);
//mTitleView.setTextSize(16);
//mTitleView.setTextColor(Color.RED);
mTitleView.setSingleLine(false);
//通过反射修改message字体大小和颜色
//Field mMessage = mAlertController.getClass().getDeclaredField("mMessageView");
//mMessage.setAccessible(true);
//TextView mMessageView = (TextView) mMessage.get(mAlertController);
//mMessageView.setTextSize(16);
//mMessageView.setTextColor(Color.GREEN);
} catch (IllegalAccessException e) {
LogUtils.d(TAG, "IllegalAccessException : " + e.getMessage());
} catch (NoSuchFieldException e) {
LogUtils.d(TAG, "NoSuchFieldException : " + e.getMessage());
}
}
public void setOnOKClickListener(OKClickListener listener) {
mOKClickListener = listener;
}
public interface OKClickListener {
void onOKClick(String szResultPath);
}
// 读取文件夹子目录
//
// "/storage/emulated/0"以上
// 和没有子目录的f参数返回空列表
protected String[] getChildFileList(File file) {
ArrayList<String> szlistFiles = new ArrayList<String>();
if (!file.getPath().equals("/storage/emulated")) {
File[] fileList = file.listFiles();
if (fileList != null) {
for (File fileItem : fileList) {
if (fileItem.getName().charAt(0) != '.') {
if (fileItem.isDirectory()) {
szlistFiles.add(fileItem.getName());
}
}
}
}
}
Collections.sort(szlistFiles, new SortChineseName(true));
if (szlistFiles.size() > 0) {
return szlistFiles.toArray(new String[szlistFiles.size()]);
} else {
return null;
}
}
private class SortChineseName implements Comparator<String> {
private boolean mIsA2Z = true;
public SortChineseName(boolean isA2Z) {
mIsA2Z = isA2Z;
}
Collator cmp = Collator.getInstance(java.util.Locale.CHINA);
@Override
public int compare(String o1, String o2) {
if (mIsA2Z) {
if (cmp.compare(o1, o2) > 0) {
return 1;
} else if (cmp.compare(o1, o2) < 0) {
return -1;
}
} else {
if (cmp.compare(o1, o2) > 0) {
return -1;
} else if (cmp.compare(o1, o2) < 0) {
return 1;
}
}
return 0;
}
}
}

View File

@@ -0,0 +1,57 @@
package cc.winboll.studio.libaes.dialogs;
import android.app.Dialog;
import android.view.Gravity;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
import cc.winboll.studio.libaes.R;
import cc.winboll.studio.libaes.utils.ScreenUtil;
public class StoragePathDialog extends Dialog {
public static final String TAG = "StoragePathDialog";
View.OnClickListener mOnOKClickListener;
public StoragePathDialog(android.content.Context context) {
super(context);
}
public StoragePathDialog(android.content.Context context, int themeResId) {
super(context, (themeResId == 0) ? cc.winboll.studio.libaes.R.style.NormalDialogStyle: themeResId);
// 加载默认布局
View view = View.inflate(context, R.layout.dialog_storagepath, null);
setContentView(view);
// 添加按键点击监听
view.findViewById(R.id.dialogstoragepathButton1).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
if (mOnOKClickListener != null) {
mOnOKClickListener.onClick(view);
}
}
});
// 使得点击对话框外部不消失对话框
setCanceledOnTouchOutside(false);
// 设置对话框大小
ScreenUtil.ScreenSize ss = ScreenUtil.getScreenSize(context);
view.setMinimumHeight((int) (ss.getHeightPixels() * 0.23f));
Window dialogWindow = getWindow();
WindowManager.LayoutParams lp = dialogWindow.getAttributes();
lp.width = (int) (ss.getWidthPixels() * 0.75f);
lp.height = WindowManager.LayoutParams.WRAP_CONTENT;
lp.gravity = Gravity.CENTER;
dialogWindow.setAttributes(lp);
}
protected StoragePathDialog(android.content.Context context, boolean cancelable, android.content.DialogInterface.OnCancelListener cancelListener) {
super(context, cancelable, cancelListener);
}
public void setOnOKClickListener(View.OnClickListener listener) {
mOnOKClickListener = listener;
}
}

View File

@@ -0,0 +1,60 @@
package cc.winboll.studio.libaes.dialogs;
/**
* @Author ZhanGSKen<zhangsken@qq.com>
* @Date 2025/03/28 17:40:47
* @Date 2024/08/12 14:46:25
* @Describe 询问用户确定与否的选择框
*/
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
public class YesNoAlertDialog {
public static final String TAG = "YesNoAlertDialog";
public static void show(Context context, String szTitle, String szMessage, final OnDialogResultListener listener) {
AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(
context);
// set title
alertDialogBuilder.setTitle(szTitle);
// set dialog message
alertDialogBuilder
.setMessage(szMessage)
.setCancelable(true)
.setOnCancelListener(new DialogInterface.OnCancelListener(){
@Override
public void onCancel(DialogInterface dialog) {
listener.onNo();
}
})
.setPositiveButton("YES", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
// if this button is clicked, close
// current activity
listener.onYes();
}
})
.setNegativeButton("NO", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
// if this button is clicked, just close
// the dialog box and do nothing
dialog.cancel();
}
});
// create alert dialog
AlertDialog alertDialog = alertDialogBuilder.create();
// show it
alertDialog.show();
}
public interface OnDialogResultListener {
abstract void onYes();
abstract void onNo();
}
}

View File

@@ -0,0 +1,32 @@
package cc.winboll.studio.libaes.enums;
/**
* @Author ZhanGSKen&豆包大模型<zhangsken@qq.com>
* @Date 2025/11/26 17:49
* @Describe 广告控制模式枚举
*/
public enum ADsMode {
STANDALONE("单机模式"), // 单机模式(默认)
MIMO_SDK("米盟广告SDK支持模式"), // 米盟广告SDK模式
STORE_QRCODE("云宝物语模式"); // 米盟广告SDK模式
private final String modeName;
ADsMode(String modeName) {
this.modeName = modeName;
}
public String getModeName() {
return modeName;
}
// 根据保存的字符串值解析枚举SP读取时使用
public static ADsMode fromValue(String value) {
if (value == null) return STANDALONE;
try {
return ADsMode.valueOf(value);
} catch (IllegalArgumentException e) {
return STANDALONE;
}
}
}

View File

@@ -0,0 +1,67 @@
package cc.winboll.studio.libaes.enums;
/**
* @Author ZhanGSKen&豆包大模型<zhangsken@qq.com>
* @Date 2025/11/27 12:35
* @Describe 隐私协议签约状态枚举
* 对应值0-拒绝1-赞同2-未签约(默认)
*/
public enum PrivacyAgreeStatus {
REJECTED(0, "拒绝"), // 0: 拒绝隐私协议
AGREED(1, "赞同"), // 1: 赞同隐私协议
UN_SIGNED(2, "未签约"); // 2: 未签约(初始默认状态)
private final int statusCode; // 对应存储的int值
private final String statusDesc; // 状态描述(可选,便于日志/UI显示
// Java 7 枚举构造方法必须private
private PrivacyAgreeStatus(int statusCode, String statusDesc) {
this.statusCode = statusCode;
this.statusDesc = statusDesc;
}
/**
* 根据int值获取枚举SP读取时使用兼容Java 7
* @param code 存储的int值0/1/2
* @return 对应枚举默认返回UN_SIGNED未签约
*/
public static PrivacyAgreeStatus fromCode(int code) {
// Java 7 不支持switch(String)用if-else兼容
if (code == REJECTED.statusCode) {
return REJECTED;
} else if (code == AGREED.statusCode) {
return AGREED;
} else {
return UN_SIGNED; // 默认未签约
}
}
/**
* 根据SP存储的字符串值获取枚举兼容原逻辑中String类型存储
* @param codeStr 存储的字符串值("0"/"1"/"2"
* @return 对应枚举默认返回UN_SIGNED未签约
*/
public static PrivacyAgreeStatus fromString(String codeStr) {
if (codeStr == null) {
return UN_SIGNED;
}
try {
int code = Integer.parseInt(codeStr);
return fromCode(code);
} catch (NumberFormatException e) {
// 字符串格式异常时,默认返回未签约
return UN_SIGNED;
}
}
// 获取状态码用于存储到SP
public int getStatusCode() {
return statusCode;
}
// 获取状态描述(用于日志/UI显示可选
public String getStatusDesc() {
return statusDesc;
}
}

View File

@@ -0,0 +1,24 @@
package cc.winboll.studio.libaes.interfaces;
import android.app.Activity;
/**
* @Author ZhanGSKen<zhangsken@qq.com>
* @Date 2025/05/10 09:34
* @Describe WinBoll 窗口操作接口(规范定义,职责单一)
*/
public interface IWinBoLLActivity {
String TAG = "IWinBoLLActivity";
String ACTION_BIND = IWinBoLLActivity.class.getName() + ".ACTION_BIND";
/**
* 获取当前Activity实例
*/
Activity getActivity();
/**
* 获取Activity唯一标识建议使用类名+UUID或固定唯一字符串
*/
String getTag();
}

View File

@@ -0,0 +1,143 @@
package cc.winboll.studio.libaes.models;
/**
* @Author ZhanGSKen<zhangsken@qq.com>
* @Date 2024/06/14 02:42:57
* @Describe 主题元素项目类
*/
import android.util.JsonReader;
import android.util.JsonWriter;
import cc.winboll.studio.libaes.R;
import cc.winboll.studio.libappbase.BaseBean;
import java.io.IOException;
public class AESThemeBean extends BaseBean {
public static final String TAG = "AESThemeBean";
public enum ThemeType {
AES("默认主题"),
DEPTH("深奥主题"),
SKY("天空主题"),
GOLDEN("辉煌主题"),
BEARING("智芋主题"),
MEMOR("梦箩主题"),
TAO("黑白主题");
private String name;
// 枚举构造函数
ThemeType(String name) {
this.name = name;
}
// 将字符串转换为枚举
public static ThemeType fromString(String themeTypeStr) {
return ThemeType.valueOf(themeTypeStr.toUpperCase()); // 注意这里用了toUpperCase(),确保匹配时不区分大小写
}
// 获取枚举的名称
public String getName() {
return name;
}
}
// 保存当前主题
int currentThemeStyleID = getThemeStyleID(ThemeType.AES);
public AESThemeBean() {
}
public AESThemeBean(int currentThemeStyleID) {
this.currentThemeStyleID = currentThemeStyleID;
}
public void setCurrentThemeTypeID(int currentThemeTypeID) {
this.currentThemeStyleID = currentThemeTypeID;
}
public int getCurrentThemeTypeID() {
return this.currentThemeStyleID;
}
@Override
public String getName() {
return AESThemeBean.class.getName();
}
@Override
public void writeThisToJsonWriter(JsonWriter jsonWriter) throws IOException {
super.writeThisToJsonWriter(jsonWriter);
AESThemeBean bean = this;
jsonWriter.name("currentThemeTypeID").value(bean.getCurrentThemeTypeID());
}
@Override
public boolean initObjectsFromJsonReader(JsonReader jsonReader, String name) throws IOException {
if(super.initObjectsFromJsonReader(jsonReader, name)) { return true; }
else{
if (name.equals("currentThemeTypeID")) {
setCurrentThemeTypeID(jsonReader.nextInt());
} else {
return false;
}
}
return true;
}
@Override
public BaseBean readBeanFromJsonReader(JsonReader jsonReader) throws IOException {
jsonReader.beginObject();
while (jsonReader.hasNext()) {
String name = jsonReader.nextName();
if(!initObjectsFromJsonReader(jsonReader, name)) {
jsonReader.skipValue();
}
}
// 结束 JSON 对象
jsonReader.endObject();
return this;
}
public static int getThemeStyleID(ThemeType themeType) {
int themeStyleID = R.style.AESTheme;
if (AESThemeBean.ThemeType.DEPTH == themeType) {
themeStyleID = R.style.DepthAESTheme;
} else if (AESThemeBean.ThemeType.SKY == themeType) {
themeStyleID = R.style.SkyAESTheme;
} else if (AESThemeBean.ThemeType.GOLDEN == themeType) {
themeStyleID = R.style.GoldenAESTheme;
} else if (AESThemeBean.ThemeType.BEARING == themeType) {
themeStyleID = R.style.BearingAESTheme;
} else if (AESThemeBean.ThemeType.MEMOR == themeType) {
themeStyleID = R.style.MemorAESTheme;
} else if (AESThemeBean.ThemeType.TAO == themeType) {
themeStyleID = R.style.TaoAESTheme;
} else if (AESThemeBean.ThemeType.AES == themeType) {
themeStyleID = R.style.AESTheme;
}
//LogUtils.d(TAG, "themeStyleID " + Integer.toString(themeStyleID));
return themeStyleID;
}
public static AESThemeBean.ThemeType getThemeStyleType(int nThemeStyleID) {
AESThemeBean.ThemeType themeStyle = AESThemeBean.ThemeType.AES;
if (R.style.DepthAESTheme == nThemeStyleID) {
themeStyle = AESThemeBean.ThemeType.DEPTH ;
} else if (R.style.SkyAESTheme == nThemeStyleID) {
themeStyle = AESThemeBean.ThemeType.SKY ;
} else if (R.style.GoldenAESTheme == nThemeStyleID) {
themeStyle = AESThemeBean.ThemeType.GOLDEN ;
} else if (R.style.BearingAESTheme == nThemeStyleID) {
themeStyle = AESThemeBean.ThemeType.BEARING ;
} else if (R.style.MemorAESTheme == nThemeStyleID) {
themeStyle = AESThemeBean.ThemeType.MEMOR ;
} else if (R.style.TaoAESTheme == nThemeStyleID) {
themeStyle = AESThemeBean.ThemeType.TAO ;
} else if (R.style.AESTheme == nThemeStyleID) {
themeStyle = AESThemeBean.ThemeType.AES;
}
//LogUtils.d(TAG, "themeStyle " + Integer.toString(themeStyle.ordinal()));
return themeStyle;
}
}

View File

@@ -0,0 +1,169 @@
package cc.winboll.studio.libaes.models;
/**
* @Author ZhanGSKen<zhangsken@qq.com>
* @Date 2025/01/20 14:19:02
* @Describe 应用信息类
*/
import cc.winboll.studio.libaes.R;
import java.io.Serializable;
public class APPInfo implements Serializable {
public static final String TAG = "APPInfo";
// 应用名称
String appName;
// 应用图标
int appIcon;
// 应用描述
String appDescription;
// 应用Git仓库地址
String appGitName;
// 应用Git仓库拥有者
String appGitOwner;
// 应用Git仓库分支
String appGitAPPBranch;
// 应用Git仓库子项目文件夹
String appGitAPPSubProjectFolder;
// 应用主页
String appHomePage;
// 应用包名称
String appAPKName;
// 应用包存储文件夹名称
String appAPKFolderName;
// 是否添加调试工具
boolean isAddDebugTools;
public APPInfo(String appName, int appIcon, String appDescription, String appGitName, String appGitOwner, String appGitAPPBranch, String appGitAPPSubProjectFolder, String appHomePage, String appAPKName, String appAPKFolderName) {
this.appName = appName;
this.appIcon = appIcon;
this.appDescription = appDescription;
this.appGitName = appGitName;
this.appGitOwner = appGitOwner;
this.appGitAPPBranch = appGitAPPBranch;
this.appGitAPPSubProjectFolder = appGitAPPSubProjectFolder;
this.appHomePage = appHomePage;
this.appAPKName = appAPKName;
this.appAPKFolderName = appAPKFolderName;
this.isAddDebugTools = false;
}
public APPInfo(String appName, int appIcon, String appDescription, String appGitName, String appGitOwner, String appGitAPPBranch, String appGitAPPSubProjectFolder, String appHomePage, String appAPKName, String appAPKFolderName, boolean isAddDebugTools) {
this.appName = appName;
this.appIcon = appIcon;
this.appDescription = appDescription;
this.appGitName = appGitName;
this.appGitOwner = appGitOwner;
this.appGitAPPBranch = appGitAPPBranch;
this.appGitAPPSubProjectFolder = appGitAPPSubProjectFolder;
this.appHomePage = appHomePage;
this.appAPKName = appAPKName;
this.appAPKFolderName = appAPKFolderName;
this.isAddDebugTools = isAddDebugTools;
}
public APPInfo() {
String szBranchName = "app";
this.appName = "APP";
this.appIcon = R.drawable.ic_launcher;
this.appDescription = "APP Description";
this.appGitName = "APP";
this.appGitOwner = "Studio";
this.appGitAPPBranch = szBranchName;
this.appGitAPPSubProjectFolder = szBranchName;
this.appHomePage = "https://www.winboll.cc/studio/details.php?app=APP";
this.appAPKName = "APP";
this.appAPKFolderName = "APP";
this.isAddDebugTools = false;
}
public void setIsAddDebugTools(boolean isAddDebugTools) {
this.isAddDebugTools = isAddDebugTools;
}
public boolean isAddDebugTools() {
return isAddDebugTools;
}
public void setAppGitOwner(String appGitOwner) {
this.appGitOwner = appGitOwner;
}
public String getAppGitOwner() {
return appGitOwner;
}
public void setAppGitAPPBranch(String appGitAPPBranch) {
this.appGitAPPBranch = appGitAPPBranch;
}
public String getAppGitAPPBranch() {
return appGitAPPBranch;
}
public void setAppGitAPPSubProjectFolder(String appGitAPPSubProjectFolder) {
this.appGitAPPSubProjectFolder = appGitAPPSubProjectFolder;
}
public String getAppGitAPPSubProjectFolder() {
return appGitAPPSubProjectFolder;
}
public void setAppIcon(int appIcon) {
this.appIcon = appIcon;
}
public int getAppIcon() {
return appIcon;
}
public void setAppDescription(String appDescription) {
this.appDescription = appDescription;
}
public String getAppDescription() {
return appDescription;
}
public void setAppAPKFolderName(String appAPKFolderName) {
this.appAPKFolderName = appAPKFolderName;
}
public String getAppAPKFolderName() {
return appAPKFolderName;
}
public void setAppName(String appName) {
this.appName = appName;
}
public String getAppName() {
return appName;
}
public void setAppGitName(String appGitName) {
this.appGitName = appGitName;
}
public String getAppGitName() {
return appGitName;
}
public void setAppHomePage(String appHomePage) {
this.appHomePage = appHomePage;
}
public String getAppHomePage() {
return appHomePage;
}
public void setAppAPKName(String appAPKName) {
this.appAPKName = appAPKName;
}
public String getAppAPKName() {
return appAPKName;
}
}

View File

@@ -0,0 +1,35 @@
package cc.winboll.studio.libaes.models;
/**
* @Author ZhanGSKen<zhangsken@qq.com>
* @Date 2024/06/14 01:53:34
* @Describe 抽屉菜单项目类
*/
public class DrawerMenuBean {
public static final String TAG = "DrawerMenuBean";
private int iconId;
private String iconName;
public DrawerMenuBean(int iconId, String iconName) {
this.iconId = iconId;
this.iconName = iconName;
}
public int getIconId() {
return iconId;
}
public String getIconName() {
return iconName;
}
public void setIconId(int iconId) {
this.iconId = iconId;
}
public void setIconName(String iconName) {
this.iconName = iconName;
}
}

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