Compare commits

...

69 Commits

Author SHA1 Message Date
1c267682bd <colorpickerdialogex>APK 15.0.0 release Publish. 2026-05-02 21:13:04 +08:00
9f18ba65ab feat(MainActivity): 重构菜单创建方式为标准实现\n\n1. 修复资源文件\n - colorpickerdialogex/src/main/res/menu/main_menu.xml: 添加 app 命名空间,使用系统图标 @android:drawable/ic_dialog_info\n\n2. 重构菜单逻辑\n - 移除 onCreate() 中直接操作菜单的代码\n - 添加标准菜单方法 onCreateOptionsMenu() 和 onOptionsItemSelected()\n\n修复问题: 菜单图标缺失、非标准菜单创建方式 2026-05-02 21:00:34 +08:00
eb3b4d42d6 添加应用介绍窗口,设置调试框架。 2026-05-02 20:25:12 +08:00
8608f8be78 初始化项目框架 2026-05-02 16:01:00 +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
6c8867e15c 更正maven库引用顺序 2026-04-09 01:38:28 +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
94ac2d9f9c 更新编译说明 2026-02-04 13:13:58 +08:00
11d7846cd2 更新项目说明书 2026-02-04 12:38:17 +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
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
fb9dd93162 正在调整工具栏与应用介绍窗口。。。 2026-01-13 12:26:11 +08:00
92 changed files with 3739 additions and 1146 deletions

236
README.md
View File

@@ -1,175 +1,103 @@
## 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 文件
☆ "/sdcard/AppProjects/app.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 

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

View File

@@ -0,0 +1,36 @@
# ColorPickerDialogEx
[![](https://jitpack.io/v/ZhanGSKen/ColorPickerDialogEx.svg)](https://jitpack.io/#ZhanGSKen/ColorPickerDialogEx)
#### 介绍
WinBoLL ColorPickerDialogEx 调试版类库。
#### 软件架构
适配安卓应用 [AIDE Pro] 的 Gradle 编译结构。
也适配安卓应用 [AndroidIDE] 的 Gradle 编译结构。
#### Gradle 编译说明
调试版编译命令 gradle assembleBetaDebug
阶段版编译命令 bash .winboll/bashPublishAPKAddTag.sh colorpickerdialogex
阶段版类库发布命令 git pull &&bash .winboll/bashPublishLIBAddTag.sh libcolorpickerdialogex
#### 使用说明
#### 参与贡献
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 @@

View File

@@ -0,0 +1,118 @@
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.colorpickerdialogex"
minSdkVersion 23
// 适配MIUI12
targetSdkVersion 30
versionCode 1
// versionName 更新后需要手动设置
// .winboll/winbollBuildProps.properties 文件的 stageCount=0
// Gradle编译环境下合起来的 versionName 就是 "${versionName}.0"
versionName "15.0"
if(true) {
versionName = genVersionName("${versionName}")
}
}
// 米盟 SDK
packagingOptions {
doNotStrip "*/*/libmimo_1011.so"
}
sourceSets {
main {
jniLibs.srcDirs = ['libs'] // 若SO库放在libs目录下
}
}
}
dependencies {
api project(':libcolorpickerdialogex')
// 下拉控件
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
#Sat May 02 21:13:04 HKT 2026
stageCount=1
libraryProject=libcolorpickerdialogex
baseVersion=15.0
publishVersion=15.0.0
buildCount=0
baseBetaVersion=15.0.1

137
colorpickerdialogex/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">ColorPickerDialogEx ©</string>
</resources>

View File

@@ -0,0 +1,40 @@
<?xml version='1.0' encoding='utf-8'?>
<manifest
xmlns:android="http://schemas.android.com/apk/res/android"
package="cc.winboll.studio.colorpickerdialogex">
<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"/>
<activity android:name=".AboutActivity"
android:exported="true"/>
</application>
</manifest>

View File

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

View File

@@ -0,0 +1,344 @@
package cc.winboll.studio.colorpickerdialogex;
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();
// 如果应用不在调试状态,就根据编译类型设置调试状态
if (isDebugging() != true) {
setIsDebugging(BuildConfig.DEBUG);
}
// 初始化 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,85 @@
package cc.winboll.studio.colorpickerdialogex;
import android.content.Intent;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.LinearLayout;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
import cc.winboll.studio.LibraryActivity;
import cc.winboll.studio.libappbase.LogView;
import cc.winboll.studio.libappbase.ToastUtils;
import cc.winboll.studio.libcolorpickerdialogex.ColorPickerDialogEx;
public class MainActivity extends AppCompatActivity {
LogView mLogView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Toolbar toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
mLogView = findViewById(R.id.logview);
ToastUtils.show("onCreate");
}
@Override
protected void onResume() {
super.onResume();
if (App.isDebugging() == true) {
mLogView.setVisibility(View.VISIBLE);
mLogView.start();
} else {
mLogView.setVisibility(View.GONE);
}
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.main_menu, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
if (item.getItemId() == R.id.menu_about) {
startActivity(new Intent(this, AboutActivity.class));
return true;
}
return super.onOptionsItemSelected(item);
}
public void onSettingBackgroundWithColorPickerDialogEx(View view) {
ColorPickerDialogEx dlg = new ColorPickerDialogEx(this, getResources().getColor(R.color.colorPrimary));
dlg.setOnColorChangedListener(new com.a4455jkjh.colorpicker.view.OnColorChangedListener() {
@Override
public void beforeColorChanged() {
}
@Override
public void onColorChanged(int color) {
LinearLayout llMain=(LinearLayout)findViewById(R.id.ll_main);
llMain.setBackgroundColor(color);
}
@Override
public void afterColorChanged() {
}
});
dlg.show();
}
public void onLibraryActivity(View view) {
startActivity(new Intent(this, LibraryActivity.class));
}
}

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,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,21 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.widget.Toolbar
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,58 @@
<?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:id="@+id/ll_main">
<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:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1.0"
android:gravity="center_vertical|center_horizontal">
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Setting Background With ColorPickerDialogEx"
android:onClick="onSettingBackgroundWithColorPickerDialogEx"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Open LibraryActivity"
android:onClick="onLibraryActivity"/>
</LinearLayout>
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1.0">
<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,8 @@
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/menu_about"
android:title="About"
android:icon="@android:drawable/ic_dialog_info"
app:showAsAction="ifRoom" />
</menu>

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,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,5 @@
<resources>
<string name="app_name">ColorPickerDialogEx</string>
<string name="app_description">WinBoLL ColorPickerDialogEx 调试版类库调试应用。</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>

0
gradlew vendored Normal file → Executable file
View File

1
libcolorpickerdialogex/.gitignore vendored Normal file
View File

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

View File

@@ -0,0 +1,89 @@
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'
}
}
}
dependencies {
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.2'
api 'cc.winboll.studio:libappbase:15.15.11'
// 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
#Sat May 02 21:13:04 HKT 2026
stageCount=1
libraryProject=libcolorpickerdialogex
baseVersion=15.0
publishVersion=15.0.0
buildCount=0
baseBetaVersion=15.0.1

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,13 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="cc.winboll.studio.libcolorpickerdialogex" >
<application>
<activity
android:name="cc.winboll.studio.LibraryActivity"
android:label="@string/lib_name" >
</activity>
</application>
</manifest>

View File

@@ -0,0 +1,13 @@
package cc.winboll.studio;
import android.app.Activity;
import android.os.Bundle;
import cc.winboll.studio.libcolorpickerdialogex.R;
public class LibraryActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.library);
}
}

View File

@@ -0,0 +1,48 @@
package cc.winboll.studio.libcolorpickerdialogex;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import com.a4455jkjh.colorpicker.ColorPickerDialog;
import com.a4455jkjh.colorpicker.view.ColorPickerLayout;
import com.a4455jkjh.colorpicker.view.OnColorChangedListener;
import java.lang.CharSequence;
import java.lang.Object;
/**
* @Author 豆包&ZhanGSKen<zhangsken@qq.com>
* @Date 2026/05/02 15:14
*/
public class ColorPickerDialogEx extends com.a4455jkjh.colorpicker.ColorPickerDialog {
public static final String TAG = "ColorPickerDialog";
public ColorPickerDialogEx(Context context, int p) {
super(context, p);
}
public void dismiss() {
super.dismiss();
}
public void onClick(DialogInterface dialogInterface, int p) {
super.onClick(dialogInterface, p);
}
public ColorPickerDialog setOnColorChangedListener(OnColorChangedListener onColorChangedListener) {
return super.setOnColorChangedListener(onColorChangedListener);
}
public ColorPickerDialog setTitle(int p) {
return super.setTitle(p);
}
public ColorPickerDialog setTitle(CharSequence charSequence) {
return super.setTitle(charSequence);
}
public void show() {
super.show();
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

View File

@@ -0,0 +1,11 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center">
<TextView
android:text="@string/text_libraryactivity"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>

View File

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="AppTheme" parent="@android:style/Theme.Material.Light">
</style>
</resources>

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="lib_name">libdebugtemp</string>
<string name="hello_world">Hello world!</string>
<string name="text_libraryactivity">LibraryActivity</string>
</resources>

View File

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="AppTheme" parent="@android:style/Theme.Holo.Light">
</style>
</resources>

View File

@@ -30,6 +30,11 @@
//include ':libaes'
//rootProject.name = "aes"
// ColorPickerDialogEx 项目编译设置
//include ':colorpickerdialogex'
//include ':libcolorpickerdialogex'
//rootProject.name = "colorpickerdialogex"
// Contacts 项目编译设置
//include ':contacts'
//rootProject.name = "contacts"

View File

@@ -13,6 +13,9 @@ WinBoLL 网站浏览器。
阶段版编译命令 bash .winboll/bashPublishAPKAddTag.sh winboll
#### 使用说明
3. Termux应用配置
- 已安装Termux包名 com.termux 
- 执行  echo "allow-external-apps = true" > ~/.termux/termux.properties
#### 参与贡献

View File

@@ -73,12 +73,13 @@ dependencies {
implementation 'com.alibaba:fastjson:1.2.76'
// AndroidX 类库
api 'androidx.appcompat:appcompat:1.1.0'
/*api 'androidx.appcompat:appcompat:1.1.0'
//api 'com.google.android.material:material:1.4.0'
//api 'androidx.viewpager:viewpager:1.0.0'
//api 'androidx.vectordrawable:vectordrawable:1.1.0'
//api 'androidx.vectordrawable:vectordrawable-animated:1.1.0'
//api 'androidx.fragment:fragment:1.1.0'
//api 'androidx.fragment:fragment:1.1.0'*/
// 米盟
api 'com.miui.zeus:mimo-ad-sdk:5.3.+'//请使用最新版sdk
@@ -88,14 +89,31 @@ dependencies {
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.12.13'
api 'cc.winboll.studio:libappbase:15.14.2'
api 'cc.winboll.studio:libaes:15.15.2'
api 'cc.winboll.studio:libappbase:15.15.11'
// WinBoLL备用库 jitpack.io 地址
//api 'com.github.ZhanGSKen:AES:aes-v15.12.9'
//api 'com.github.ZhanGSKen:APPBase:appbase-v15.14.1'
//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

@@ -1,8 +1,8 @@
#Created by .winboll/winboll_app_build.gradle
#Tue Jan 13 03:11:40 HKT 2026
stageCount=10
#Thu Apr 30 14:58:25 CST 2026
stageCount=27
libraryProject=
baseVersion=15.11
publishVersion=15.11.9
buildCount=0
baseBetaVersion=15.11.10
publishVersion=15.11.26
buildCount=11
baseBetaVersion=15.11.27

View File

@@ -1,7 +1,9 @@
<?xml version='1.0' encoding='utf-8'?>
<manifest
xmlns:android="http://schemas.android.com/apk/res/android"
package="cc.winboll.studio.winboll">
xmlns:tools="http://schemas.android.com/tools"
package="cc.winboll.studio.winboll"
android:sharedUserId="com.termux">
<!-- 拥有完全的网络访问权限 -->
<uses-permission android:name="android.permission.INTERNET"/>
@@ -12,6 +14,15 @@
<!-- 对正在运行的应用重新排序 -->
<uses-permission android:name="android.permission.REORDER_TASKS"/>
<!-- 计算应用存储空间 -->
<uses-permission android:name="android.permission.GET_PACKAGE_SIZE"/>
<uses-permission
android:name="android.permission.QUERY_ALL_PACKAGES"
tools:ignore="QueryAllPackagesPermission"/>
<uses-permission android:name="android.permission.GET_PACKAGE_SIZE"/>
<application
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
@@ -282,6 +293,39 @@
<activity android:name="cc.winboll.studio.winboll.activities.WXPayActivity"/>
<activity android:name="cc.winboll.studio.winboll.activities.PatternLockActivity"
android:exported="true">
<intent-filter>
<action android:name="cc.winboll.studio.winboll.activities.PatternLockActivity.ACTION_OPEN_PATTERN"/>
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
</activity>
<activity android:name="cc.winboll.studio.winboll.unittest.TermuxEnvTestActivity"/>
<activity
android:name=".termux.NfcTermuxBridgeActivity"
android:exported="true">
<intent-filter>
<action android:name="cc.winboll.studio.winboll.termux.NfcTermuxBridgeActivity.ACTION_BUILD"/>
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
</activity>
<activity android:name="cc.winboll.studio.winboll.applications.MyTermuxActivity"
android:label="@string/my_termux_activity"
android:exported="true"/>
</application>
</manifest>

View File

@@ -1,53 +0,0 @@
package cc.winboll.studio.winboll;
/**
* @Author ZhanGSKen<zhangsken@188.com>
* @Date 2025/05/22 13:08
*/
import android.content.Context;
import android.content.res.TypedArray;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.TextView;
import androidx.annotation.Nullable;
import androidx.appcompat.widget.Toolbar;
public class CustomToolbar extends Toolbar {
private View viewMain;
public CustomToolbar(Context context) {
this(context, null);
}
public CustomToolbar(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public CustomToolbar(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initView(context, attrs);
}
private void initView(Context context, AttributeSet attrs) {
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.CustomToolbar);
// 获取属性值
String toolbarTitle = typedArray.getString(R.styleable.CustomToolbar_toolbarTitle);
int toolbarTitleColor = typedArray.getColor(R.styleable.CustomToolbar_toolbarTitleColor, android.graphics.Color.WHITE);
int toolbarBackgroundColor = typedArray.getColor(R.styleable.CustomToolbar_toolbarBackgroundColor, android.graphics.Color.BLUE);
// 加载布局
viewMain = LayoutInflater.from(context).inflate(R.layout.view_toolbar, this, true);
// 应用属性值
TextView toolbarTitleTextView = viewMain.findViewById(R.id.toolbar_title);
toolbarTitleTextView.setText(toolbarTitle);
toolbarTitleTextView.setTextColor(toolbarTitleColor);
viewMain.setBackgroundColor(toolbarBackgroundColor);
// 释放 TypedArray
typedArray.recycle();
}
}

View File

@@ -8,16 +8,16 @@ import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.AdapterView;
import cc.winboll.studio.libaes.activitys.AboutActivity;
import cc.winboll.studio.libaes.activitys.DrawerFragmentActivity;
import cc.winboll.studio.libaes.models.APPInfo;
import cc.winboll.studio.libaes.models.DrawerMenuBean;
import cc.winboll.studio.libaes.utils.WinBoLLActivityManager;
import cc.winboll.studio.libappbase.LogUtils;
import cc.winboll.studio.winboll.R;
import cc.winboll.studio.winboll.activities.AboutActivity;
import cc.winboll.studio.winboll.activities.SettingsActivity;
import cc.winboll.studio.winboll.activities.WXPayActivity;
import cc.winboll.studio.winboll.applications.MyTermuxActivity;
import cc.winboll.studio.winboll.fragments.BrowserFragment;
import cc.winboll.studio.winboll.unittest.TermuxEnvTestActivity;
import java.util.ArrayList;
public class MainActivity extends DrawerFragmentActivity {
@@ -123,6 +123,9 @@ public class MainActivity extends DrawerFragmentActivity {
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.toolbar_main, menu);
if (App.isDebugging()) {
getMenuInflater().inflate(R.menu.toolbar_test, menu);
}
return super.onCreateOptionsMenu(menu);
}
@@ -149,12 +152,17 @@ public class MainActivity extends DrawerFragmentActivity {
}
} else if (nItemId == R.id.item_settings) {
WinBoLLActivityManager.getInstance().startWinBoLLActivity(getApplicationContext(), SettingsActivity.class);
} else if (nItemId == R.id.item_wxpayactivity) {
WinBoLLActivityManager.getInstance().startWinBoLLActivity(getApplicationContext(), WXPayActivity.class);
} else if (nItemId == cc.winboll.studio.libaes.R.id.item_about) {
} else if (nItemId == R.id.item_about) {
Intent intent = new Intent(getApplicationContext(), AboutActivity.class);
APPInfo appInfo = genDefaultAPPInfo();
intent.putExtra(AboutActivity.EXTRA_APPINFO, appInfo);
WinBoLLActivityManager.getInstance().startWinBoLLActivity(getApplicationContext(), intent, AboutActivity.class);
} else if (nItemId == R.id.item_mytermux) {
Intent intent = new Intent(getApplicationContext(), MyTermuxActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
} else if (nItemId == R.id.item_termux_env_test) {
Intent intent = new Intent(getApplicationContext(), TermuxEnvTestActivity.class);
WinBoLLActivityManager.getInstance().startWinBoLLActivity(getApplicationContext(), intent, AboutActivity.class);
} else {
return super.onOptionsItemSelected(item);
@@ -163,23 +171,6 @@ public class MainActivity extends DrawerFragmentActivity {
return true;
}
APPInfo genDefaultAPPInfo() {
String szBranchName = "winboll";
APPInfo appInfo = new APPInfo();
appInfo.setAppName("WinBoLL");
appInfo.setAppIcon(cc.winboll.studio.libaes.R.drawable.ic_winboll);
appInfo.setAppDescription("WinBoLL 网站浏览器。");
appInfo.setAppGitName("WinBoLL");
appInfo.setAppGitOwner("Studio");
appInfo.setAppGitAPPBranch(szBranchName);
appInfo.setAppGitAPPSubProjectFolder(szBranchName);
appInfo.setAppHomePage("https://www.winboll.cc/apks/index.php?project=WinBoLL");
appInfo.setAppAPKName("WinBoLL");
appInfo.setAppAPKFolderName("WinBoLL");
return appInfo;
}
// ------------------- 新增对外提供Handler供其他组件发送消息如BrowserFragment -------------------
public Handler getMainHandler() {
return _mMainHandler;

View File

@@ -0,0 +1,86 @@
package cc.winboll.studio.winboll.activities;
import android.os.Bundle;
import android.view.View;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
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;
import cc.winboll.studio.winboll.MainActivity;
import cc.winboll.studio.winboll.R;
import android.app.Activity;
/**
* @Author 豆包&ZhanGSKen<zhangsken@qq.com>
* @Date 2026/01/13 11:54
* @Describe 应用介绍窗口
*/
public class AboutActivity extends BaseWinBoLLActivity {
@Override
public Activity getActivity() {
return this;
}
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 = getActivity().findViewById(R.id.aboutview);
aboutView.setAPPInfo(genDefaultAppInfo());
}
private void initToolbar() {
LogUtils.d(TAG, "initToolbar() 开始初始化");
mToolbar = (Toolbar) findViewById(R.id.toolbar);
if (mToolbar == null) {
LogUtils.e(TAG, "initToolbar() | Toolbar未找到");
return;
}
setSupportActionBar(mToolbar);
mToolbar.setSubtitle(getTag());
((AppCompatActivity)getActivity()).getSupportActionBar().setDisplayHomeAsUpEnabled(true);
mToolbar.setNavigationOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
LogUtils.d(TAG, "导航栏 点击返回按钮");
getActivity().finish();
}
});
LogUtils.d(TAG, "initToolbar() 配置完成");
}
private APPInfo genDefaultAppInfo() {
LogUtils.d(TAG, "genDefaultAppInfo() 调用");
String branchName = "winboll";
APPInfo appInfo = new APPInfo();
appInfo.setAppName(getActivity().getString(R.string.app_name));
appInfo.setAppIcon(R.drawable.ic_winboll);
appInfo.setAppDescription(getActivity().getString(R.string.app_description));
appInfo.setAppGitName("WinBoLL");
appInfo.setAppGitOwner("Studio");
appInfo.setAppGitAPPBranch(branchName);
appInfo.setAppGitAPPSubProjectFolder(branchName);
appInfo.setAppHomePage("https://www.winboll.cc/apks/index.php?project=WinBoLL");
appInfo.setAppAPKName("WinBoLL");
appInfo.setAppAPKFolderName("WinBoLL");
LogUtils.d(TAG, "genDefaultAppInfo: 应用信息已生成");
return appInfo;
}
}

View File

@@ -0,0 +1,56 @@
package cc.winboll.studio.winboll.activities;
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:33
* 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 = getThemeType();
setThemeStyle();
super.onCreate(savedInstanceState);
WinBoLLActivityManager.getInstance().add(this);
//ToastUtils.show(getTag() + ": onCreate");
}
AESThemeBean.ThemeType getThemeType() {
/*SharedPreferences sharedPreferences = getSharedPreferences(
SHAREDPREFERENCES_NAME, MODE_PRIVATE);
return AESThemeBean.ThemeType.values()[((sharedPreferences.getInt(DRAWER_THEME_TYPE, AESThemeBean.ThemeType.DEFAULT.ordinal())))];
*/
return AESThemeBean.getThemeStyleType(AESThemeUtil.getThemeTypeID(getApplicationContext()));
}
void setThemeStyle() {
//setTheme(AESThemeBean.getThemeStyle(getThemeType()));
setTheme(AESThemeUtil.getThemeTypeID(getApplicationContext()));
}
@Override
protected void onDestroy() {
WinBoLLActivityManager.getInstance().registeRemove(this);
super.onDestroy();
}
// 子类必须实现getTag(),确保唯一标识
@Override
public abstract String getTag();
public abstract Activity getActivity();
}

View File

@@ -5,20 +5,32 @@ package cc.winboll.studio.winboll.activities;
* @Date 2025/03/25 11:46:40
* @Describe 测试窗口2
*/
import android.app.Activity;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.Toolbar;
import androidx.appcompat.widget.Toolbar;
import cc.winboll.studio.winboll.R;
import android.app.Activity;
public class New2Activity extends BaseWinBoLLActivity {
@Override
public Activity getActivity() {
return this;
}
public class New2Activity extends WinBoLLActivity {
public static final String TAG = "New2Activity";
Toolbar mToolbar;
@Override
public String getTag() {
return TAG;
}
//LogView mLogView;
@Override
protected void onCreate(Bundle savedInstanceState) {
@@ -28,15 +40,10 @@ public class New2Activity extends WinBoLLActivity {
// mLogView = findViewById(R.id.logview);
// mLogView.start();
mToolbar = findViewById(R.id.toolbar);
setActionBar(mToolbar);
setSupportActionBar(mToolbar);
}
@Override
protected void onResume() {
super.onResume();
//mLogView.start();
}
public void onCloseThisActivity(View view) {
//WinBoLLActivityManager.getInstance().finish(this);
@@ -49,17 +56,4 @@ public class New2Activity extends WinBoLLActivity {
public void onNewActivity(View view) {
//WinBoLLActivityManager.getInstance().startWinBoLLActivity(this, NewActivity.class);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
//getMenuInflater().inflate(R.menu.toolbar_main, menu);
return super.onCreateOptionsMenu(menu);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
// 在switch语句中处理每个ID并在处理完后返回true未处理的情况返回false。
return super.onOptionsItemSelected(item);
}
}

View File

@@ -5,29 +5,26 @@ package cc.winboll.studio.winboll.activities;
* @Date 2025/03/25 05:04:22
* @Describe
*/
import android.app.Activity;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.Toolbar;
import cc.winboll.studio.libaes.interfaces.IWinBoLLActivity;
import androidx.appcompat.widget.Toolbar;
import cc.winboll.studio.libaes.utils.WinBoLLActivityManager;
import cc.winboll.studio.winboll.R;
import cc.winboll.studio.winboll.App;
import android.app.Activity;
public class NewActivity extends BaseWinBoLLActivity {
@Override
public Activity getActivity() {
return this;
}
public class NewActivity extends WinBoLLActivity implements IWinBoLLActivity {
public static final String TAG = "NewActivity";
Toolbar mToolbar;
//LogView mLogView;
@Override
public Activity getActivity() {
return this;
}
@Override
public String getTag() {
return TAG;
@@ -40,15 +37,10 @@ public class NewActivity extends WinBoLLActivity implements IWinBoLLActivity {
// mLogView = findViewById(R.id.logview);
// mLogView.start();
mToolbar = findViewById(R.id.toolbar);
setActionBar(mToolbar);
setSupportActionBar(mToolbar);
}
@Override
protected void onResume() {
super.onResume();
//mLogView.start();
}
public void onCloseThisActivity(View view) {
WinBoLLActivityManager.getInstance().finish(this);
@@ -61,17 +53,4 @@ public class NewActivity extends WinBoLLActivity implements IWinBoLLActivity {
public void onNew2Activity(View view) {
// WinBoLLActivityManager.getInstance().startWinBoLLActivity(App.getInstance(), New2Activity.class);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
//getMenuInflater().inflate(R.menu.toolbar_main, menu);
return super.onCreateOptionsMenu(menu);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
// 在switch语句中处理每个ID并在处理完后返回true未处理的情况返回false。
return super.onOptionsItemSelected(item);
}
}

View File

@@ -0,0 +1,295 @@
package cc.winboll.studio.winboll.activities;
import android.app.Activity;
import android.content.Context;
import android.content.SharedPreferences;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.view.MotionEvent;
import android.widget.FrameLayout;
import androidx.appcompat.widget.Toolbar;
import cc.winboll.studio.libaes.utils.AESThemeUtil;
import cc.winboll.studio.libaes.utils.WinBoLLActivityManager;
import cc.winboll.studio.winboll.R;
public class PatternLockActivity extends BaseWinBoLLActivity {
public static final String TAG = "PatternLockActivity";
private static final int DOT_RADIUS = 8;
private static final int PATTERN_ERROR_DURATION = 1500;
private static final String PREFS_NAME = "pattern_lock_prefs";
static final String KEY_LOCK_PATTERN = "lock_pattern";
static final String KEY_ERROR_STATE = "error_state";
private static final String KEY_ERROR_REPEAT_PATTERN = "error_repeat_pattern";
private boolean mIsInErrorState;
private boolean mNeedRestart;
private Handler mHandler;
private FrameLayout mContainer;
private PatternView mPatternView;
public PatternLockActivity() {
mHandler = new Handler(Looper.getMainLooper());
}
PatternLockActivity(Context context) {
mHandler = new Handler(Looper.getMainLooper());
}
@Override
public Activity getActivity() {
return this;
}
@Override
public String getTag() {
return TAG;
}
@Override
protected void onCreate(Bundle savedInstanceState) {
AESThemeUtil.applyAppTheme(this);
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_pattern_lock);
Toolbar toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
getSupportActionBar().setSubtitle(TAG);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
toolbar.setNavigationOnClickListener(v -> finish());
mContainer = findViewById(R.id.container);
mPatternView = new PatternView(this);
mContainer.addView(mPatternView);
mPatternView.invalidate();
mNeedRestart = false;
boolean isEnoughPoints = savedInstanceIsEnoughPoints();
if (savedInstanceState != null) {
mIsInErrorState = savedInstanceState.getBoolean(KEY_ERROR_STATE, false);
mNeedRestart = savedInstanceState.getBoolean(KEY_ERROR_REPEAT_PATTERN, false);
}
if (mIsInErrorState) {
mPatternView.invalidate();
}
}
boolean savedInstanceIsEnoughPoints() {
int count = 0;
if (mPatternView != null) {
for (int i = 0; i < 9; i++) {
if (mPatternView.mDotState[i] == 1) {
count++;
}
}
}
return count >= 4 || count == 0;
}
@Override
protected void onSaveInstanceState(Bundle outState) {
if (mIsInErrorState) {
outState.putBoolean(KEY_ERROR_STATE, mIsInErrorState);
}
if (mNeedRestart) {
outState.putBoolean(KEY_ERROR_REPEAT_PATTERN, mNeedRestart);
}
super.onSaveInstanceState(outState);
}
private void showErrorState() {
mIsInErrorState = true;
invalidatePattern();
mHandler.postDelayed(() -> {
mIsInErrorState = false;
SharedPreferences prefs = getSharedPreferences(PREFS_NAME, MODE_PRIVATE);
prefs.edit().putBoolean(KEY_ERROR_STATE, false).apply();
invalidatePattern();
if (mPatternView != null) mPatternView.invalidate();
}, PATTERN_ERROR_DURATION);
}
private void clearErrorState() {
mIsInErrorState = false;
SharedPreferences prefs = getSharedPreferences(PREFS_NAME, MODE_PRIVATE);
prefs.edit().putBoolean(KEY_ERROR_STATE, false).apply();
invalidatePattern();
if (mPatternView != null) mPatternView.invalidate();
}
private void showErrorToast() {
android.widget.Toast.makeText(this, "图案点数不足,请重新绘制",
android.widget.Toast.LENGTH_SHORT).show();
mNeedRestart = true;
}
private void showSuccessDialog() {
android.app.AlertDialog alertDialog = new android.app.AlertDialog.Builder(this)
.setTitle("设置成功")
.setMessage("图案密码已设置成功")
.setPositiveButton("确定", (dialog, which) -> finish())
.setCancelable(false)
.create();
alertDialog.show();
}
void finishWithRestart() {
finish();
}
private void invalidatePattern() {
if (mPatternView != null) {
mPatternView.invalidate();
}
}
class PatternView extends FrameLayout {
int mPatternSize = 0;
int MAX_DOT_COUNT = 9;
int[] mDotX = new int[MAX_DOT_COUNT];
int[] mDotY = new int[MAX_DOT_COUNT];
int[] mDotState = new int[MAX_DOT_COUNT];
Bitmap mDotBitmap;
Paint mPaintConnector;
Paint mPaintErrorBackground;
int mDotCount = 0;
PatternView(Context context) {
super(context);
setBackgroundColor(Color.WHITE);
for (int i = 0; i < MAX_DOT_COUNT; i++) {
mDotX[i] = -1;
mDotY[i] = -1;
mDotState[i] = 0;
}
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
if (w == 0 || h == 0) return;
mPatternSize = w > h ? h : w;
int grid = 3;
int cell = mPatternSize / grid;
for (int i = 0; i < MAX_DOT_COUNT; i++) {
mDotX[i] = (i % grid) * cell + cell / 2 - cell / 24;
mDotY[i] = (i / grid) * cell + cell / 2 - cell / 24;
mDotState[i] = 0;
}
if (mDotBitmap == null) {
mDotBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.dot_darkgreen_dark);
}
if (mPaintConnector == null) {
mPaintConnector = new Paint(Paint.FILTER_BITMAP_FLAG);
mPaintConnector.setColor(-0xFF006400);
}
if (mPaintErrorBackground == null) {
mPaintErrorBackground = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaintErrorBackground.setColor(Color.RED);
}
}
@Override
public boolean onTouchEvent(MotionEvent event) {
if (mDotCount > 0) return false;
switch (event.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
invalidate();
return true;
case MotionEvent.ACTION_MOVE:
float x = event.getX();
float y = event.getY();
for (int i = 0; i < MAX_DOT_COUNT; i++) {
int dx = (int) Math.abs(x - mDotX[i]);
int dy = (int) Math.abs(y - mDotY[i]);
if (dx <= DOT_RADIUS && dy <= DOT_RADIUS && mDotState[i] == 0) {
mDotState[i] = 1;
mDotCount++;
}
}
for (int i = 0; i < mDotCount - 1; i++) {
int a = -1, b = -1;
for (int k = 0; k < MAX_DOT_COUNT; k++) {
if (mDotState[k] == 1) {
if (a < 0) a = k;
else b = k;
}
}
if (a >= 0 && b >= 0) {
a = Math.min(a, b);
b = Math.max(a, b);
}
if (mDotState[a] == 1 && mDotState[b] == 1) {
int dx = mDotX[b] - mDotX[a];
int dy = mDotY[b] - mDotY[a];
if ((Math.abs(dx) <= 1 && Math.abs(dy) <= 1) ||
(Math.abs(dx) <= 2 && Math.abs(dy) <= 1)) {
if (mDotState[b] == 1) {
for (int k = a + 1; k < b; k++) {
if (mDotState[k] == 0) {
mDotState[k] = 1;
}
}
mDotCount += (b - a - 1);
}
}
}
}
invalidate();
return true;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
if (mDotCount < 4) {
showErrorState();
showErrorToast();
}
break;
}
return true;
}
@Override
public void onDraw(Canvas canvas) {
if (mPatternSize == 0) return;
int activeCount = 0;
for (int i = 0; i < MAX_DOT_COUNT; i++) {
if (mDotState[i] == 1) activeCount++;
}
if (activeCount == 0) {
mPaintErrorBackground.setAlpha(50);
} else {
mPaintErrorBackground.setAlpha(mIsInErrorState ? 80 : 60);
}
canvas.clipRect(0, 0, mPatternSize * 80 / 100, mPatternSize * 80 / 100);
canvas.drawRect(0, 0, mPatternSize, mPatternSize, mPaintErrorBackground);
if (mDotBitmap != null) {
for (int i = 0; i < MAX_DOT_COUNT; i++) {
if (mDotState[i] == 1) {
canvas.drawBitmap(mDotBitmap, mDotX[i], mDotY[i], mPaintConnector);
}
}
}
}
}
}

View File

@@ -1,27 +1,27 @@
package cc.winboll.studio.winboll.activities;
import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import androidx.appcompat.widget.Toolbar;
import cc.winboll.studio.libaes.interfaces.IWinBoLLActivity;
import cc.winboll.studio.libaes.utils.AESThemeUtil;
import cc.winboll.studio.winboll.R;
import android.app.Activity;
/**
* @Author ZhanGSKen&豆包大模型<zhangsken@qq.com>
* @Date 2025/12/05 18:48
* @Describe Settings Activity
*/
public class SettingsActivity extends WinBoLLActivity implements IWinBoLLActivity {
public static final String TAG = "SettingsActivity";
public class SettingsActivity extends BaseWinBoLLActivity {
@Override
public Activity getActivity() {
return this;
}
public static final String TAG = "SettingsActivity";
@Override
public String getTag() {
return TAG;

View File

@@ -1,211 +0,0 @@
package cc.winboll.studio.winboll.activities;
import android.app.Activity;
import android.graphics.Bitmap;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.view.View;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;
import cc.winboll.studio.libaes.interfaces.IWinBoLLActivity;
import cc.winboll.studio.winboll.R;
import cc.winboll.studio.winboll.WxPayConfig;
import cc.winboll.studio.winboll.utils.SpecUtil;
import cc.winboll.studio.winboll.utils.WxPayApi;
import cc.winboll.studio.winboll.utils.ZXingUtils;
import java.util.Timer;
import java.util.TimerTask;
/**
* 主界面:生成二维码 + 轮询查询支付结果
* @Author ZhanGSKen<zhangsken@qq.com>
* @Date 2026/01/07
*/
public class WXPayActivity extends WinBoLLActivity implements IWinBoLLActivity {
private static final String TAG = "WXPayActivity";
// Handler消息标识
private static final int MSG_POLL_TIMEOUT = 1001;
private static final int MSG_POLL_SUCCESS = 1002;
private static final int MSG_POLL_FAILED = 1003;
private ImageView mIvQrCode;
private TextView mTvOrderNo;
private TextView mTvPayStatus;
private Button mBtnCreateOrder;
private String mOutTradeNo; // 商户订单号
private Timer mPollTimer; // 轮询定时器
private long mPollStartTime; // 轮询开始时间
@Override
public Activity getActivity() {
return this;
}
@Override
public String getTag() {
return TAG;
}
private Handler mPollHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
switch (msg.what) {
case MSG_POLL_TIMEOUT:
stopPoll();
mTvPayStatus.setText("支付状态:轮询超时");
mTvPayStatus.setTextColor(getResources().getColor(android.R.color.darker_gray));
Toast.makeText(WXPayActivity.this, "轮询超时,请手动查询", Toast.LENGTH_SHORT).show();
break;
case MSG_POLL_SUCCESS:
boolean isPaySuccess = (boolean) msg.obj;
String tradeState = (String) msg.getData().getString("tradeState");
stopPoll();
if (isPaySuccess) {
mTvPayStatus.setText("支付状态:支付成功 ✅");
mTvPayStatus.setTextColor(getResources().getColor(android.R.color.holo_green_dark));
Toast.makeText(WXPayActivity.this, "支付成功!", Toast.LENGTH_SHORT).show();
} else {
mTvPayStatus.setText("支付状态:" + tradeState);
mTvPayStatus.setTextColor(getResources().getColor(android.R.color.holo_red_dark));
}
break;
case MSG_POLL_FAILED:
String errorMsg = (String) msg.obj;
mTvPayStatus.setText("查询失败:" + errorMsg);
break;
}
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_wxpay);
initView();
initListener();
}
private void initView() {
mIvQrCode = findViewById(R.id.iv_qrcode);
mTvOrderNo = findViewById(R.id.tv_order_no);
mTvPayStatus = findViewById(R.id.tv_pay_status);
mBtnCreateOrder = findViewById(R.id.btn_create_order);
}
private void initListener() {
mBtnCreateOrder.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
createOrder();
}
});
}
/**
* 统一下单,生成二维码
*/
private void createOrder() {
mBtnCreateOrder.setEnabled(false);
mTvPayStatus.setText("支付状态:生成订单中...");
mIvQrCode.setImageBitmap(null);
WxPayApi.createOrder(new WxPayApi.OnCreateOrderCallback() {
@Override
public void onSuccess(String outTradeNo, String codeUrl) {
mOutTradeNo = outTradeNo;
mTvOrderNo.setText("商户订单号:" + outTradeNo);
mTvPayStatus.setText("支付状态:未支付,请扫码");
// 生成二维码
Bitmap qrCodeBitmap = ZXingUtils.createQRCodeBitmap(codeUrl, 250, 250);
if (qrCodeBitmap != null) {
mIvQrCode.setImageBitmap(qrCodeBitmap);
// 开始轮询查询支付结果
startPoll();
} else {
mTvPayStatus.setText("支付状态:生成二维码失败");
mBtnCreateOrder.setEnabled(true);
}
}
@Override
public void onFailure(String errorMsg) {
SpecUtil.WWSpecLogError(TAG, "统一下单失败:" + errorMsg);
mTvPayStatus.setText("生成订单失败:" + errorMsg);
mBtnCreateOrder.setEnabled(true);
Toast.makeText(WXPayActivity.this, errorMsg, Toast.LENGTH_SHORT).show();
}
});
}
/**
* 开始轮询查询支付结果
*/
private void startPoll() {
stopPoll(); // 先停止之前的轮询
mPollStartTime = System.currentTimeMillis();
mPollTimer = new Timer();
mPollTimer.scheduleAtFixedRate(new TimerTask() {
@Override
public void run() {
// 检查是否超时
long elapsedTime = System.currentTimeMillis() - mPollStartTime;
if (elapsedTime >= WxPayConfig.POLL_TIMEOUT) {
mPollHandler.sendEmptyMessage(MSG_POLL_TIMEOUT);
return;
}
// 查询订单状态
WxPayApi.queryOrder(mOutTradeNo, new WxPayApi.OnQueryOrderCallback() {
@Override
public void onSuccess(boolean isPaySuccess, String tradeState) {
Message msg = Message.obtain();
msg.what = MSG_POLL_SUCCESS;
msg.obj = isPaySuccess;
Bundle bundle = new Bundle();
bundle.putString("tradeState", tradeState);
msg.setData(bundle);
mPollHandler.sendMessage(msg);
}
@Override
public void onFailure(String errorMsg) {
Message msg = Message.obtain();
msg.what = MSG_POLL_FAILED;
msg.obj = errorMsg;
mPollHandler.sendMessage(msg);
}
});
}
}, 0, WxPayConfig.POLL_INTERVAL);
}
/**
* 停止轮询
*/
private void stopPoll() {
if (mPollTimer != null) {
mPollTimer.cancel();
mPollTimer = null;
}
}
@Override
protected void onDestroy() {
super.onDestroy();
stopPoll();
}
}

View File

@@ -1,47 +0,0 @@
package cc.winboll.studio.winboll.activities;
/**
* @Author ZhanGSKen<zhangsken@qq.com>
* @Date 2025/05/10 09:48
* @Describe WinBoLL 窗口基础类
*/
import android.app.Activity;
import android.os.Bundle;
import android.view.MenuItem;
import androidx.appcompat.app.AppCompatActivity;
import cc.winboll.studio.libappbase.LogUtils;
public class WinBoLLActivity extends AppCompatActivity {
public static final String TAG = "WinBoLLActivity";
@Override
protected void onResume() {
super.onResume();
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
/*if (item.getItemId() == R.id.item_log) {
WinBoLLActivityManager.getInstance().startLogActivity(this);
return true;
} else if (item.getItemId() == R.id.item_home) {
startActivity(new Intent(this, MainActivity.class));
return true;
}*/
// 在switch语句中处理每个ID并在处理完后返回true未处理的情况返回false。
return super.onOptionsItemSelected(item);
}
@Override
protected void onPostCreate(Bundle savedInstanceState) {
super.onPostCreate(savedInstanceState);
//WinBoLLActivityManager.getInstance().add(this);
}
@Override
protected void onDestroy() {
super.onDestroy();
//WinBoLLActivityManager.getInstance().registeRemove(this);
}
}

View File

@@ -0,0 +1,77 @@
package cc.winboll.studio.winboll.applications;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
import cc.winboll.studio.libappbase.LogUtils;
import cc.winboll.studio.winboll.R;
import cc.winboll.studio.winboll.termux.TermuxCommandExecutor;
public class MyTermuxActivity extends AppCompatActivity {
public static final String TAG = "MyTermuxActivity";
private Toolbar mToolbar;
private Button mTermuxButton;
private Button mTermuxWorkSpacesButton;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_my_termux);
// 初始化工具栏
initToolbar();
// 初始化按钮
initView();
}
private void initToolbar() {
mToolbar = findViewById(R.id.toolbar);
if (mToolbar != null) {
setSupportActionBar(mToolbar);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
mToolbar.setNavigationOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
LogUtils.d(TAG, "点击返回按钮");
finish();
}
});
LogUtils.d(TAG, "工具栏初始化完成");
}
}
private void initView() {
mTermuxButton = findViewById(R.id.btn_termux);
if (mTermuxButton != null) {
mTermuxButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
LogUtils.d(TAG, "点击 Termux 按钮");
TermuxCommandExecutor.openTermuxBash(MyTermuxActivity.this, "cd ~");
//TermuxCommandExecutor.openTermuxBash(MyTermuxActivity.this, "cd ~/TermuxWorkSpaces", "./TermuxWorkSpaces");
}
});
LogUtils.d(TAG, "Termux 按钮初始化完成");
}
mTermuxWorkSpacesButton = findViewById(R.id.btn_termuxworkspaces);
if (mTermuxWorkSpacesButton != null) {
mTermuxWorkSpacesButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
LogUtils.d(TAG, "点击 TermuxWorkSpaces 按钮");
TermuxCommandExecutor.openTermuxBash(MyTermuxActivity.this, "cd ~/TermuxWorkSpaces", "./TermuxWorkSpaces");
}
});
LogUtils.d(TAG, "TermuxWorkSpaces 按钮初始化完成");
}
}
private boolean isTermuxAvailable() {
return TermuxCommandExecutor.isTermuxInstalled(this);
}
}

View File

@@ -0,0 +1,14 @@
package cc.winboll.studio.winboll.models;
/**
* @Author 豆包&ZhanGSKen<zhangsken@qq.com>
* @Date 2026/03/15 08:46
*/
public class NfcTermuxCmd {
public String script; // 要执行的预制脚本名(如 auth.sh
public String[] args; // 脚本参数
public String workDir; // 工作目录
public boolean background; // 是否后台执行
public String resultDir; // 结果输出目录(可为 null
}

View File

@@ -0,0 +1,142 @@
package cc.winboll.studio.winboll.models;
import android.util.JsonReader;
import android.util.JsonWriter;
import cc.winboll.studio.libappbase.BaseBean;
import java.io.IOException;
/**
* @Author 豆包&ZhanGSKen<zhangsken@qq.com>
* @Date 2026/04/30 10:47
*/
public class TermuxButtonModel extends BaseBean {
public static final String TAG = "TermuxButtonModel";
String buttonName;
String exeCommand;
String workDir;
// 已修改isCommit 改为规范过去式命名 isCommitted
boolean isCommitted;
String commitTitle;
String commitInfo;
public TermuxButtonModel() {
this.buttonName = "";
this.exeCommand = "";
this.workDir = "";
// 默认初始化
this.isCommitted = false;
this.commitTitle = "";
this.commitInfo = "";
}
public void setButtonName(String buttonName) {
this.buttonName = buttonName;
}
public String getButtonName() {
return buttonName;
}
public void setExeCommand(String exeCommand) {
this.exeCommand = exeCommand;
}
public String getExeCommand() {
return exeCommand;
}
public void setWorkDir(String workDir) {
this.workDir = workDir;
}
public String getWorkDir() {
return workDir;
}
// ========== 已修改 对应 isCommitted 完整 Get & Set ==========
public boolean isCommitted() {
return isCommitted;
}
public void setCommitted(boolean committed) {
isCommitted = committed;
}
public String getCommitTitle() {
return commitTitle;
}
public void setCommitTitle(String commitTitle) {
this.commitTitle = commitTitle;
}
public String getCommitInfo() {
return commitInfo;
}
public void setCommitInfo(String commitInfo) {
this.commitInfo = commitInfo;
}
@Override
public String getName() {
return TermuxButtonModel.class.getName();
}
@Override
public void writeThisToJsonWriter(JsonWriter jsonWriter) throws IOException {
super.writeThisToJsonWriter(jsonWriter);
jsonWriter.name("buttonName").value(getButtonName());
jsonWriter.name("exeCommand").value(getExeCommand());
jsonWriter.name("workDir").value(getWorkDir());
// JSON写入同步修改
jsonWriter.name("isCommitted").value(isCommitted());
jsonWriter.name("commitTitle").value(getCommitTitle());
jsonWriter.name("commitInfo").value(getCommitInfo());
}
@Override
public boolean initObjectsFromJsonReader(JsonReader jsonReader, String name) throws IOException {
if (super.initObjectsFromJsonReader(jsonReader, name)) {
return true;
} else {
if (name.equals("buttonName")) {
setButtonName(jsonReader.nextString());
} else if (name.equals("exeCommand")) {
setExeCommand(jsonReader.nextString());
} else if (name.equals("workDir")) {
setWorkDir(jsonReader.nextString());
}
// JSON解析字段同步修改
else if (name.equals("isCommitted")) {
setCommitted(jsonReader.nextBoolean());
} else if (name.equals("commitTitle")) {
setCommitTitle(jsonReader.nextString());
} else if (name.equals("commitInfo")) {
setCommitInfo(jsonReader.nextString());
} 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();
}
}
jsonReader.endObject();
return this;
}
}

View File

@@ -0,0 +1,241 @@
/*
* 源码说明与描述:
* NFC 与 Termux 桥接活动,用于接收外部应用(包调用)传递的 JSON 指令并执行 Termux 脚本命令。
* 支持 ACTION_BUILD后台执行与 ACTION_BUILD_VIEW终端窗口唤起两种动作。
*
* 作者:豆包&ZhanGSKen<zhangsken@qq.com>
* 创建时间2025-03-15 14:00:00
* 最后编辑时间2026-03-16 10:00:00
*/
package cc.winboll.studio.winboll.termux;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.widget.Toast;
import cc.winboll.studio.libappbase.LogUtils;
import cc.winboll.studio.libappbase.ToastUtils;
import cc.winboll.studio.winboll.models.NfcTermuxCmd;
import com.google.gson.Gson;
public class NfcTermuxBridgeActivity extends Activity {
// ========================= 常量与静态属性 =========================
public static final String TAG = "NfcTermuxBridgeActivity";
// 外部应用调用时使用的 Action 常量
public static final String ACTION_BUILD = "cc.winboll.studio.winboll.termux.NfcTermuxBridgeActivity.ACTION_BUILD";
private static final Gson GSON = new Gson();
// ========================= 生命周期方法 =========================
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
LogUtils.d(TAG, "onCreate() 调用savedInstanceState: " + (savedInstanceState != null ? "非空" : ""));
dispatchIntent(getIntent());
}
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
LogUtils.d(TAG, "onNewIntent() 调用intent: " + (intent != null ? intent.toString() : "null"));
if (intent != null) {
LogUtils.d(TAG, "onNewIntent() action: " + intent.getAction());
LogUtils.d(TAG, "onNewIntent() data: " + intent.getDataString());
LogUtils.d(TAG, "onNewIntent() extras: " + intent.getExtras());
LogUtils.d(TAG, "onNewIntent() flags: " + intent.getFlags());
LogUtils.d(TAG, "onNewIntent() component: " + intent.getComponent());
} else {
LogUtils.w(TAG, "onNewIntent() intent is null");
}
dispatchIntent(intent);
}
// ========================= 统一 Intent 分发(合并去重) =========================
/**
* 统一分发 Intent根据 Action 路由到不同业务逻辑
* @param intent 外部传入的 Intent
*/
private void dispatchIntent(Intent intent) {
LogUtils.d(TAG, "dispatchIntent() 分发 intent");
if (intent == null) {
LogUtils.w(TAG, "dispatchIntent() intent is null");
return;
}
String action = intent.getAction();
if (ACTION_BUILD.equals(action)) {
ToastUtils.show("ACTION_BUILD 命中");
onOpenTermuxProjectBuild(intent);
} else {
LogUtils.w(TAG, "dispatchIntent() 未知 Action: " + action);
finish();
}
}
// ========================= 核心业务方法 =========================
/**
* 处理 ACTION_BUILD 动作:后台执行 NFC 指令
*/
// private void handleNfcIntent(Intent intent) {
// LogUtils.d(TAG, "handleNfcIntent() 调用");
// if (intent == null) {
// LogUtils.w(TAG, "handleNfcIntent() intent 为空");
// return;
// }
//
// try {
// String json = intent.getStringExtra(Intent.EXTRA_TEXT);
// LogUtils.d(TAG, "handleNfcIntent() json: " + json);
//
// if (json == null || json.isEmpty()) {
// LogUtils.e(TAG, "handleNfcIntent() 指令为空");
// showToast("指令为空");
// finish();
// return;
// }
//
// NfcTermuxCmd cmd = GSON.fromJson(json, NfcTermuxCmd.class);
// LogUtils.d(TAG, "handleNfcIntent() cmd: " + cmd);
//
// if (cmd.script == null || cmd.script.isEmpty()) {
// LogUtils.e(TAG, "handleNfcIntent() script 为空");
// showToast("script 不能为空");
// finish();
// return;
// }
//
// //String scriptPath = "/data/data/com.termux/files/home/TermuxWorkSpaces/BashShells/AutoNFC/" + cmd.script;
// String scriptPath = "/data/data/com.termux/files/home/TermuxWorkSpaces/BashShells/AutoNFC/" + "BuildWinBoLLProject.sh";
// LogUtils.d(TAG, "handleNfcIntent() 脚本路径: " + scriptPath);
//
// boolean success = TermuxCommandExecutor.executeCommand(
// this, scriptPath, cmd.args, cmd.workDir, cmd.background, cmd.resultDir
// );
// LogUtils.d(TAG, "handleNfcIntent() 执行结果: " + success);
//
// if (success) {
// showToast("指令已发送: " + cmd.script);
// LogUtils.i(TAG, "执行成功: " + scriptPath);
// } else {
// showToast("指令发送失败");
// LogUtils.e(TAG, "执行失败");
// }
//
// } catch (Exception e) {
// LogUtils.e(TAG, "handleNfcIntent() 异常: " + e.getMessage(), e);
// showToast("解析失败");
// } finally {
// finish();
// }
// }
/**
* 处理 ACTION_BUILD_VIEW 动作:唤起 Termux 窗口执行命令
*/
public void onOpenTermuxProjectBuild(Intent intent) {
LogUtils.d(TAG, "onOpenTermuxProjectBuildView() 调用");
if (intent == null) {
LogUtils.w(TAG, "onOpenTermuxProjectBuildView() intent 为空");
return;
}
try {
String json = intent.getStringExtra(Intent.EXTRA_TEXT);
LogUtils.d(TAG, "onOpenTermuxProjectBuildView() json: " + json);
if (json == null || json.isEmpty()) {
LogUtils.e(TAG, "onOpenTermuxProjectBuildView() 指令为空");
showToast("指令为空");
finish();
return;
}
NfcTermuxCmd cmd = GSON.fromJson(json, NfcTermuxCmd.class);
LogUtils.d(TAG, "onOpenTermuxProjectBuildView() cmd: " + cmd);
if (cmd.script == null || cmd.script.isEmpty()) {
LogUtils.e(TAG, "onOpenTermuxProjectBuildView() script 为空");
showToast("script 不能为空");
finish();
return;
}
StringBuilder targetCmd = new StringBuilder();
String nfcScriptFolder = "/data/data/com.termux/files/home/TermuxWorkSpaces/BashShells/AutoNFC/";
targetCmd.append("cd " + nfcScriptFolder + " && ");
//targetCmd.append("stdbuf -o0 -e0 -i0 bash ").append(cmd.script).append(" ");
targetCmd.append("stdbuf -o0 -e0 -i0 bash ").append("BuildWinBoLLProject.sh").append(" ");
if (cmd.args != null) {
for (String arg : cmd.args) {
targetCmd.append(arg).append(" ");
}
}
LogUtils.d(TAG, "onOpenTermuxProjectBuildView() 命令: " + targetCmd);
boolean cmdSuccess = TermuxCommandExecutor.executeTerminalCommand(this, targetCmd.toString());
LogUtils.d(TAG, "onOpenTermuxProjectBuildView() 执行结果: " + cmdSuccess);
if (cmdSuccess) {
showToast("指令已发送: " + cmd.script);
} else {
showToast("指令发送失败");
}
} catch (Exception e) {
LogUtils.e(TAG, "onOpenTermuxProjectBuildView() 异常: " + e.getMessage(), e);
showToast("解析失败");
} finally {
finish();
}
}
// ========================= 公共静态测试方法 =========================
/**
* 内部测试方法:发送 ACTION_BUILD 指令
*/
public static void testCommand(Context context) {
LogUtils.d(TAG, "testCommand()");
try {
String testJson = "{\"script\":\"BuildWinBoLLProject.sh\",\"args\":[\"DebugTemp\"],\"workDir\":null,\"background\":true,\"resultDir\":null}";
Intent intent = new Intent(context, NfcTermuxBridgeActivity.class);
intent.setAction(ACTION_BUILD); // 指定 Action
intent.putExtra(Intent.EXTRA_TEXT, testJson);
context.startActivity(intent);
} catch (Exception e) {
LogUtils.e(TAG, "testCommand() 失败: " + e.getMessage());
}
}
/**
* 内部测试方法:发送 ACTION_BUILD_VIEW 指令
*/
// public static void testViewCommand(Context context) {
// LogUtils.d(TAG, "testViewCommand()");
// try {
// String testJson = "{\"script\":\"BuildWinBoLLProjectView.sh\",\"args\":[\"DebugTemp\"],\"workDir\":null,\"background\":true,\"resultDir\":null}";
// Intent intent = new Intent(context, NfcTermuxBridgeActivity.class);
// intent.setAction(ACTION_BUILD_VIEW); // 指定 Action
// intent.putExtra(Intent.EXTRA_TEXT, testJson);
// context.startActivity(intent);
// } catch (Exception e) {
// LogUtils.e(TAG, "testViewCommand() 失败: " + e.getMessage());
// }
// }
// ========================= 工具方法 =========================
/**
* 统一显示 Toast确保在主线程调用
*/
private void showToast(final String message) {
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(NfcTermuxBridgeActivity.this, message, Toast.LENGTH_SHORT).show();
}
});
}
}

View File

@@ -0,0 +1,222 @@
package cc.winboll.studio.winboll.termux;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.Build;
import cc.winboll.studio.libappbase.LogUtils; // 替换 Log 为 LogUtils与 Activity 一致)
import com.termux.shared.termux.TermuxConstants;
import com.termux.shared.shell.command.ExecutionCommand.Runner;
/**
* Termux 命令调用工具类(基于 RunCommandService 原型封装)
* 用于向 Termux 发送命令执行请求,兼容 Termux RUN_COMMAND Intent 规范
* @Author 豆包&ZhanGSKen<zhangsken@qq.com>
* @Date 2026/01/19 16:30:00
* @LastEditTime 2026/01/20 10:15:00
*/
public class TermuxCommandExecutor {
private static final String TAG = "TermuxCommandExecutor";
// 核心修复Termux 官方包名(无 .app 后缀)
private static final String TERMUX_PACKAGE_NAME = "com.termux";
// Termux RunCommandService 完整类名(包名+类名)
private static final String TERMUX_RUN_CMD_SERVICE_CLASS = "com.termux.app.RunCommandService";
private static final String TERMUX_RUN_CMD_ACTION = TermuxConstants.TERMUX_APP.RUN_COMMAND_SERVICE.ACTION_RUN_COMMAND;
private static final String TERMUX_HOME_PATH = "/data/data/com.termux/files/home";
/**
* 执行 Termux 命令(核心方法)
* @param context 上下文(如 Activity、Service
* @param command 要执行的命令路径(如 "/bin/ls"、"/data/data/com.termux/files/usr/bin/bash"
* @param args 命令参数(如 ["-l", "/data/data/com.termux/files/home"]
* @param workDir 工作目录(可为 null默认 Termux 主目录)
* @param isBackground 是否后台执行true=后台false=终端会话执行)
* @param resultDir 命令结果输出目录(可为 null不输出到文件
* @return 是否成功发送命令请求
*/
public static boolean executeCommand(Context context, String command, String[] args, String workDir, boolean isBackground, String resultDir) {
// 1. 校验上下文和命令合法性
if (context == null || command == null || command.isEmpty()) {
LogUtils.e(TAG, "执行命令失败:上下文或命令为空");
return false;
}
// 2. 校验 Termux 是否安装(新增:提前校验,避免白跑流程)
if (!isTermuxInstalled(context)) {
LogUtils.e(TAG, "执行命令失败Termux 未安装");
return false;
}
// 3. 创建 Intent 并设置目标 Service
Intent intent = new Intent(TERMUX_RUN_CMD_ACTION);
intent.setClassName(TERMUX_PACKAGE_NAME, TERMUX_RUN_CMD_SERVICE_CLASS); // 用正确包名
intent.setPackage(TERMUX_PACKAGE_NAME); // 明确包名,避免歧义
// 4. 设置核心命令参数(遵循 Termux RunCommandService 规范)
intent.putExtra(TermuxConstants.TERMUX_APP.RUN_COMMAND_SERVICE.EXTRA_COMMAND_PATH, command);
if (args != null && args.length > 0) {
intent.putExtra(TermuxConstants.TERMUX_APP.RUN_COMMAND_SERVICE.EXTRA_ARGUMENTS, args);
LogUtils.d(TAG, "命令参数:" + String.join(",", args));
}
if (workDir != null && !workDir.isEmpty()) {
intent.putExtra(TermuxConstants.TERMUX_APP.RUN_COMMAND_SERVICE.EXTRA_WORKDIR, workDir);
LogUtils.d(TAG, "工作目录:" + workDir);
}
// 5. 设置执行模式(后台/终端会话)
intent.putExtra(TermuxConstants.TERMUX_APP.RUN_COMMAND_SERVICE.EXTRA_BACKGROUND, isBackground);
String runner = isBackground ? Runner.APP_SHELL.getName() : Runner.TERMINAL_SESSION.getName();
intent.putExtra(TermuxConstants.TERMUX_APP.RUN_COMMAND_SERVICE.EXTRA_RUNNER, runner);
LogUtils.d(TAG, "执行模式:" + (isBackground ? "后台" : "终端会话") + "Runner" + runner);
// 6. 设置命令结果输出(可选,输出到文件)
if (resultDir != null && !resultDir.isEmpty()) {
intent.putExtra(TermuxConstants.TERMUX_APP.RUN_COMMAND_SERVICE.EXTRA_RESULT_DIRECTORY, resultDir);
intent.putExtra(TermuxConstants.TERMUX_APP.RUN_COMMAND_SERVICE.EXTRA_RESULT_SINGLE_FILE, true);
intent.putExtra(TermuxConstants.TERMUX_APP.RUN_COMMAND_SERVICE.EXTRA_RESULT_FILE_BASENAME, "authcenter_cmd_result");
LogUtils.d(TAG, "结果输出目录:" + resultDir);
}
// 7. 允许替换参数中的逗号替代字符
intent.putExtra(TermuxConstants.TERMUX_APP.RUN_COMMAND_SERVICE.EXTRA_REPLACE_COMMA_ALTERNATIVE_CHARS_IN_ARGUMENTS, true);
// 8. 发送请求(区分 Android O 及以上的前台服务)
try {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
context.startForegroundService(intent);
LogUtils.d(TAG, "Android O+ 启动前台服务发送命令");
} else {
context.startService(intent);
LogUtils.d(TAG, "启动普通服务发送命令");
}
LogUtils.i(TAG, "命令发送成功command=" + command);
return true;
} catch (Exception e) {
LogUtils.e(TAG, "命令发送失败:" + e.getMessage(), e);
return false;
}
}
/**
* 简化方法:执行 Termux 终端命令(默认工作目录,终端会话执行)
* @param context 上下文
* @param command 命令(如 "ls -l /home"、"echo 'hello termux'"
* @return 是否成功发送
*/
public static boolean executeTerminalCommand(Context context, String command) {
LogUtils.d(TAG, "调用 executeTerminalCommand命令" + command);
if (command == null || command.isEmpty()) {
LogUtils.e(TAG, "命令为空,执行失败");
return false;
}
// 通过 bash 执行任意终端命令
String[] args = {"-c", command};
return executeCommand(
context,
"/data/data/com.termux/files/usr/bin/bash", // Termux 默认 bash 路径(正确)
args,
TERMUX_HOME_PATH, // 默认工作目录
false, // 终端会话执行(可见)
null // 不输出到文件
);
}
/**
* 简化方法:后台执行 Termux 命令(无输出文件)
* @param context 上下文
* @param command 命令路径
* @param args 命令参数
* @return 是否成功发送
*/
public static boolean executeBackgroundCommand(Context context, String command, String[] args) {
LogUtils.d(TAG, "调用 executeBackgroundCommandcommand=" + command);
return executeCommand(
context,
command,
args,
null,
true, // 后台执行
null
);
}
/**
* 校验 Termux 是否安装(修复核心错误)
* @param context 上下文
* @return Termux 是否已安装
*/
public static boolean isTermuxInstalled(Context context) {
LogUtils.d(TAG, "校验 Termux 是否安装,包名:" + TERMUX_PACKAGE_NAME);
if (context == null) {
LogUtils.e(TAG, "校验失败:上下文为空");
return false;
}
try {
// 用正确的 Termux 包名查询com.termux
context.getPackageManager().getPackageInfo(TERMUX_PACKAGE_NAME, PackageManager.GET_ACTIVITIES);
LogUtils.d(TAG, "Termux 已安装");
return true;
} catch (PackageManager.NameNotFoundException e) {
LogUtils.w(TAG, "Termux 未安装:" + e.getMessage());
return false;
} catch (Exception e) {
LogUtils.e(TAG, "校验 Termux 安装状态异常:" + e.getMessage(), e);
return false;
}
}
/**
* 校验 Termux 是否允许外部应用调用
* @return 校验提示信息
*/
public static String checkTermuxExternalAppPermission() {
String tip = "请确保 Termux 已开启「允许外部应用调用」权限:\n1. 打开 Termux 输入termux-setup-storage\n2. 编辑配置文件echo \"allow-external-apps = true\" > ~/.termux/termux.properties\n3. 重启 Termux 生效";
LogUtils.d(TAG, "外部应用调用权限提示:" + tip);
return tip;
}
public static boolean openTermuxBash(Context context, String command) {
return openTermuxBash(context, command, "~");
}
public static boolean openTermuxBash(Context context, String command, String workDir) {
LogUtils.d(TAG, "openTermuxBash() 按钮点击执行Gradle命令实时输出");
// 1. 校验Termux是否安装
if (!TermuxCommandExecutor.isTermuxInstalled(context)) {
LogUtils.e(TAG, "openTermuxBash() 错误未安装Termux应用");
return false;
}
// 2. 定义核心路径确保路径与Termux中一致
String projectPath = TERMUX_HOME_PATH;
if (workDir.startsWith("~") || workDir.startsWith(".")) {
projectPath = TERMUX_HOME_PATH + "/" + workDir.substring(1);
}
// 3. 构造命令核心用stdbuf禁用缓冲实现实时输出
String targetCmd = "";
// 步骤1进入项目目录不存在则创建
targetCmd += "cd " + projectPath + " && ";
// 步骤2加载环境变量
targetCmd += "source ~/.bashrc && ";
// 步骤3显式配置PATH
targetCmd += "export PATH=/data/data/com.termux/files/usr/bin:$PATH && ";
// 步骤4用stdbuf禁用stdout/stderr缓冲关键执行Gradle命令
// -o0stdout无缓冲-e0stderr无缓冲-i0stdin无缓冲
//targetCmd += "stdbuf -o0 -e0 -i0 " + gradleFullPath + " task --all | grep assemble && ";
//targetCmd += "stdbuf -o0 -e0 -i0 " + gradleFullPath + " -Pandroid.aapt2FromMavenOverride=/data/data/com.termux/files/home/android-sdk/build-tools/34.0.4/aapt2 assembleBetaDebug && ";
targetCmd += "stdbuf -o0 -e0 -i0 bash && ";
// 步骤5执行成功提示
targetCmd += "echo '\n✅ 命令执行完成!' && echo '\n📌 当前目录:" + projectPath + "' && read -p '按回车键关闭终端...'";
// 4. 执行命令终端会话模式唤起Termux窗口
boolean cmdSuccess = TermuxCommandExecutor.executeTerminalCommand(context, targetCmd);
if (!cmdSuccess) {
return true;
}
return false;
}
}

View File

@@ -0,0 +1,454 @@
package cc.winboll.studio.winboll.unittest;
import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.view.View;
import android.widget.TextView;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
import cc.winboll.studio.libappbase.LogUtils;
import cc.winboll.studio.libappbase.ToastUtils;
import cc.winboll.studio.winboll.R;
import cc.winboll.studio.winboll.activities.BaseWinBoLLActivity;
import cc.winboll.studio.winboll.termux.NfcTermuxBridgeActivity;
import cc.winboll.studio.winboll.termux.TermuxCommandExecutor;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
/**
* @Author 豆包&ZhanGSKen<zhangsken@qq.com>
* @Date 2026/01/19 11:11:00
* @LastEditTime 2026/01/21 17:45:00
* @Describe Termux环境测试工具跨包+sharedUserId模式
* 适配不同应用包名当前包cc.winboll.studio.winboll.beta / Termux包com.termux
* 支持Termux目录读取、Gradle命令实时输出执行唤起窗口基于sharedUserId实现跨包权限适配
*/
public class TermuxEnvTestActivity extends BaseWinBoLLActivity {
@Override
public Activity getActivity() {
return this;
}
// 常量属性(置顶排列)
public static final String TAG = "TermuxEnvTestActivity";
private static final String TERMUX_HOME_PATH = "/data/data/com.termux/files/home/TermuxWorkSpaces";
private static final String CMD_RESULT_FILE = TERMUX_HOME_PATH + "/CMD_RESULT_FILE.log";
// 成员属性(常量后排列)
private Toolbar mToolbar;
private TextView tvMessage;
private Handler mainHandler;
@Override
public String getTag() {
return TAG;
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
LogUtils.d(TAG, "onCreate() 调用初始化Activity");
setContentView(R.layout.activity_termux_env_test);
initView();
initToolbar();
initTermuxDirectory();
LogUtils.d(TAG, "onCreate() 执行完成");
}
/**
* 初始化视图组件
*/
private void initView() {
LogUtils.d(TAG, "initView() 开始初始化视图");
tvMessage = (TextView) findViewById(R.id.tv_message);
mainHandler = new Handler(Looper.getMainLooper());
// 初始化提示信息
StringBuilder initMsg = new StringBuilder();
initMsg.append("Termux 测试工具(跨包+sharedUserId模式\n");
initMsg.append("-------------------------\n");
initMsg.append("当前应用包名:");
initMsg.append(getPackageName());
initMsg.append("\n");
initMsg.append("Termux应用包名com.termux\n");
initMsg.append("sharedUserIdcom.termux需一致\n");
initMsg.append("支持功能目录读取、Gradle命令实时输出唤起Termux窗口\n");
initMsg.append("-------------------------\n");
tvMessage.setText(initMsg.toString());
LogUtils.d(TAG, "initView() 初始化完成tvMessage初始值" + initMsg.toString().trim());
}
/**
* 初始化Toolbar组件
*/
private void initToolbar() {
LogUtils.d(TAG, "initToolbar() 开始初始化Toolbar");
mToolbar = (Toolbar) findViewById(R.id.toolbar);
if (mToolbar == null) {
LogUtils.e(TAG, "initToolbar() 错误未找到Toolbar组件");
return;
}
setSupportActionBar(mToolbar);
mToolbar.setSubtitle(getTag());
((AppCompatActivity) getActivity()).getSupportActionBar().setDisplayHomeAsUpEnabled(true);
mToolbar.setNavigationOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
LogUtils.d(TAG, "initToolbar() 导航栏返回按钮点击");
getActivity().finish();
}
});
LogUtils.d(TAG, "initToolbar() 初始化完成");
}
/**
* 初始化Termux目标目录跨包+sharedUserId模式
*/
private void initTermuxDirectory() {
LogUtils.d(TAG, "initTermuxDirectory() 开始初始化Termux目录路径" + TERMUX_HOME_PATH);
File termuxDir = new File(TERMUX_HOME_PATH);
if (termuxDir.exists()) {
LogUtils.d(TAG, "initTermuxDirectory() Termux目录已存在无需创建");
return;
}
tvMessage.append("正在创建Termux目标目录...\n");
boolean createSuccess = termuxDir.mkdirs();
if (createSuccess) {
LogUtils.d(TAG, "initTermuxDirectory() Termux目录创建成功" + TERMUX_HOME_PATH);
tvMessage.append("Termux目录创建成功");
tvMessage.append(TERMUX_HOME_PATH);
tvMessage.append("\n");
} else {
LogUtils.e(TAG, "initTermuxDirectory() 错误Termux目录创建失败");
tvMessage.append("警告Termux目录创建失败\n");
tvMessage.append("请检查:\n");
tvMessage.append("1.AndroidManifest.xml中sharedUserId是否为com.termux\n");
tvMessage.append("2.设备是否已root部分机型需root才能跨包写私有目录\n");
}
}
/**
* 测试读取Termux目录按钮点击事件
*/
public void onTestTermuxEnv(View view) {
LogUtils.d(TAG, "onTestTermuxEnv() 按钮点击开始读取Termux目录");
tvMessage.append("\n【测试读取Termux目录】\n");
String fileListStr = readTermuxHomeFileList();
tvMessage.append(fileListStr);
tvMessage.append("\n-------------------------\n");
LogUtils.d(TAG, "onTestTermuxEnv() 执行完成,读取结果长度:" + fileListStr.length() + "字符");
}
/**
* 测试执行Gradle命令实时输出版唤起Termux窗口
*/
public void onTestTermuxGradleBuildCMD(View view) {
LogUtils.d(TAG, "onTestTermuxCMD() 按钮点击执行Gradle命令实时输出");
tvMessage.append("\n【测试执行Gradle命令实时输出\n");
// 1. 校验Termux是否安装
if (!TermuxCommandExecutor.isTermuxInstalled(this)) {
LogUtils.e(TAG, "onTestTermuxCMD() 错误未安装Termux应用");
tvMessage.append("错误未安装Termux应用包名com.termux\n");
return;
}
// 2. 定义核心路径确保路径与Termux中一致
String gradleFullPath = "/data/data/com.termux/files/home/gradle/gradle-7.5.1/bin/gradle";
String projectPath = TERMUX_HOME_PATH + "/Sources/DebugTemp"; // 项目目录
// 3. 构造命令核心用stdbuf禁用缓冲实现实时输出
String targetCmd = "";
// 步骤1进入项目目录不存在则创建
//targetCmd += "cd " + projectPath + " || (mkdir -p " + projectPath + " && cd " + projectPath + ") && ";
targetCmd += "cd " + projectPath + " && ";
// 步骤2加载环境变量
targetCmd += "source ~/.bashrc && ";
// 步骤3显式配置PATH
targetCmd += "export PATH=/data/data/com.termux/files/usr/bin:/data/data/com.termux/files/home/gradle/gradle-7.5.1/bin:$PATH && ";
// 步骤4用stdbuf禁用stdout/stderr缓冲关键执行Gradle命令
// -o0stdout无缓冲-e0stderr无缓冲-i0stdin无缓冲
//targetCmd += "stdbuf -o0 -e0 -i0 " + gradleFullPath + " task --all | grep assemble && ";
targetCmd += "stdbuf -o0 -e0 -i0 " + gradleFullPath + " -Pandroid.aapt2FromMavenOverride=/data/data/com.termux/files/home/android-sdk/build-tools/34.0.4/aapt2 assembleBetaDebug && ";
// 步骤5执行成功提示
targetCmd += "echo '\n✅ 命令执行完成!' && echo '\n📌 项目目录:" + projectPath + "' && read -p '按回车键关闭终端...'";
LogUtils.d(TAG, "onTestTermuxCMD() 执行命令:" + targetCmd);
// 4. 执行命令终端会话模式唤起Termux窗口
boolean cmdSuccess = TermuxCommandExecutor.executeTerminalCommand(this, targetCmd);
if (!cmdSuccess) {
LogUtils.e(TAG, "onTestTermuxCMD() 错误:命令发送失败");
tvMessage.append("命令发送失败!\n");
tvMessage.append("可能原因:\n");
tvMessage.append("1.Termux未开启外部应用调用权限执行termux-setup-storage后配置\n");
tvMessage.append("2.sharedUserId配置不一致\n");
tvMessage.append("3.Termux未安装stdbuf执行pkg install coreutils\n");
return;
}
// 5. 应用内提示(说明实时输出特性)
tvMessage.append("已唤起Termux窗口执行说明\n");
tvMessage.append("1. 自动创建/进入项目目录:");
tvMessage.append(projectPath);
tvMessage.append("\n");
tvMessage.append("2. 禁用输出缓冲stdbuf实现Gradle实时输出\n");
tvMessage.append("3. 筛选assemble相关任务grep assemble\n");
tvMessage.append("⚙️ Gradle路径");
tvMessage.append(gradleFullPath);
tvMessage.append("\n");
tvMessage.append("💡 若未实时输出请在Termux中执行pkg install coreutils安装stdbuf\n");
tvMessage.append("-------------------------\n");
}
public void onTestWinBoLLProjectBuild(View view) {
ToastUtils.show("onTestWinBoLLProjectBuild");
NfcTermuxBridgeActivity.testCommand(this);
}
// public void onTestWinBoLLProjectBuildView(View view) {
// ToastUtils.show("onTestWinBoLLProjectBuildView");
// NfcTermuxBridgeActivity.testViewCommand(this);
// }
public void onOpenTermuxBash(View view) {
LogUtils.d(TAG, "onTestTermuxCMD() 按钮点击执行Gradle命令实时输出");
tvMessage.append("\n【测试执行Gradle命令实时输出\n");
// 1. 校验Termux是否安装
if (!TermuxCommandExecutor.isTermuxInstalled(this)) {
LogUtils.e(TAG, "onTestTermuxCMD() 错误未安装Termux应用");
tvMessage.append("错误未安装Termux应用包名com.termux\n");
return;
}
// 2. 定义核心路径确保路径与Termux中一致
String gradleFullPath = "/data/data/com.termux/files/home/gradle/gradle-7.5.1/bin/gradle";
String projectPath = TERMUX_HOME_PATH + "/"; // 项目目录
// 3. 构造命令核心用stdbuf禁用缓冲实现实时输出
String targetCmd = "";
// 步骤1进入项目目录不存在则创建
targetCmd += "cd " + projectPath + " && ";
// 步骤2加载环境变量
targetCmd += "source ~/.bashrc && ";
// 步骤3显式配置PATH
targetCmd += "export PATH=/data/data/com.termux/files/usr/bin:/data/data/com.termux/files/home/gradle/gradle-7.5.1/bin:$PATH && ";
// 步骤4用stdbuf禁用stdout/stderr缓冲关键执行Gradle命令
// -o0stdout无缓冲-e0stderr无缓冲-i0stdin无缓冲
//targetCmd += "stdbuf -o0 -e0 -i0 " + gradleFullPath + " task --all | grep assemble && ";
//targetCmd += "stdbuf -o0 -e0 -i0 " + gradleFullPath + " -Pandroid.aapt2FromMavenOverride=/data/data/com.termux/files/home/android-sdk/build-tools/34.0.4/aapt2 assembleBetaDebug && ";
targetCmd += "stdbuf -o0 -e0 -i0 bash && ";
// 步骤5执行成功提示
targetCmd += "echo '\n✅ 命令执行完成!' && echo '\n📌 项目目录:" + projectPath + "' && read -p '按回车键关闭终端...'";
LogUtils.d(TAG, "onTestTermuxCMD() 执行命令:" + targetCmd);
// 4. 执行命令终端会话模式唤起Termux窗口
boolean cmdSuccess = TermuxCommandExecutor.executeTerminalCommand(this, targetCmd);
if (!cmdSuccess) {
LogUtils.e(TAG, "onTestTermuxCMD() 错误:命令发送失败");
tvMessage.append("命令发送失败!\n");
tvMessage.append("可能原因:\n");
tvMessage.append("1.Termux未开启外部应用调用权限执行termux-setup-storage后配置\n");
tvMessage.append("2.sharedUserId配置不一致\n");
tvMessage.append("3.Termux未安装stdbuf执行pkg install coreutils\n");
return;
}
// 5. 应用内提示(说明实时输出特性)
tvMessage.append("已唤起Termux窗口执行说明\n");
tvMessage.append("1. 自动创建/进入项目目录:");
tvMessage.append(projectPath);
tvMessage.append("\n");
tvMessage.append("2. 禁用输出缓冲stdbuf实现Gradle实时输出\n");
tvMessage.append("3. 筛选assemble相关任务grep assemble\n");
tvMessage.append("⚙️ Gradle路径");
tvMessage.append(gradleFullPath);
tvMessage.append("\n");
tvMessage.append("💡 若未实时输出请在Termux中执行pkg install coreutils安装stdbuf\n");
tvMessage.append("-------------------------\n");
}
/**
* 跨包读取Termux命令结果文件保留原功能兼容其他场景
*/
private String readCmdResultDirectly() {
LogUtils.d(TAG, "readCmdResultDirectly() 开始读取命令结果,文件路径:" + CMD_RESULT_FILE);
File resultFile = new File(CMD_RESULT_FILE);
// 校验文件存在性
if (!resultFile.exists()) {
LogUtils.e(TAG, "readCmdResultDirectly() 错误:结果文件不存在");
return "错误:结果文件不存在\n可能原因\n1.命令执行失败\n2.延迟时间不足\n3.跨包写权限被拒绝需root";
}
// 校验文件可读性
if (!resultFile.canRead()) {
LogUtils.e(TAG, "readCmdResultDirectly() 错误:无结果文件读取权限");
return "错误无结果文件读取权限sharedUserId配置错误或未root";
}
// 读取文件内容
StringBuilder result = new StringBuilder();
BufferedReader br = null;
try {
br = new BufferedReader(new InputStreamReader(new FileInputStream(resultFile), StandardCharsets.UTF_8));
String line;
while ((line = br.readLine()) != null) {
result.append(line);
result.append("\n");
}
LogUtils.d(TAG, "readCmdResultDirectly() 文件读取完成,结果长度:" + result.length() + "字符");
} catch (IOException e) {
LogUtils.e(TAG, "readCmdResultDirectly() 错误:文件读取异常", e);
return "错误:读取文件异常 → " + e.getMessage();
} finally {
if (br != null) {
try {
br.close();
LogUtils.d(TAG, "readCmdResultDirectly() BufferedReader资源已关闭");
} catch (IOException e) {
LogUtils.e(TAG, "readCmdResultDirectly() 错误BufferedReader关闭异常", e);
}
}
}
String resultStr = result.toString().trim();
return resultStr.isEmpty() ? "命令执行成功,但无输出" : resultStr;
}
/**
* 删除命令结果临时文件(保留原功能)
*/
private void deleteCmdResultFile() {
LogUtils.d(TAG, "deleteCmdResultFile() 开始删除临时文件,路径:" + CMD_RESULT_FILE);
File resultFile = new File(CMD_RESULT_FILE);
if (!resultFile.exists()) {
LogUtils.d(TAG, "deleteCmdResultFile() 临时文件不存在,无需删除");
return;
}
if (resultFile.canWrite()) {
boolean deleteSuccess = resultFile.delete();
if (deleteSuccess) {
LogUtils.d(TAG, "deleteCmdResultFile() 临时文件删除成功");
} else {
LogUtils.w(TAG, "deleteCmdResultFile() 警告:临时文件删除失败");
tvMessage.append("警告:临时结果文件删除失败\n");
}
} else {
LogUtils.w(TAG, "deleteCmdResultFile() 警告:无临时文件删除权限");
}
}
/**
* 跨包读取Termux目录文件列表保留原功能
*/
private String readTermuxHomeFileList() {
LogUtils.d(TAG, "readTermuxHomeFileList() 开始读取目录,路径:" + TERMUX_HOME_PATH);
File termuxHomeDir = new File(TERMUX_HOME_PATH);
StringBuilder result = new StringBuilder();
// 基础校验
if (!termuxHomeDir.exists()) {
LogUtils.e(TAG, "readTermuxHomeFileList() 错误:目录不存在");
return "错误Termux目录不存在 → " + TERMUX_HOME_PATH;
}
if (!termuxHomeDir.isDirectory()) {
LogUtils.e(TAG, "readTermuxHomeFileList() 错误:指定路径不是目录");
return "错误:指定路径不是目录 → " + TERMUX_HOME_PATH;
}
if (!termuxHomeDir.canRead()) {
LogUtils.e(TAG, "readTermuxHomeFileList() 错误:无目录读取权限");
return "错误无目录读取权限需满足1.sharedUserId=com.termux 2.设备root";
}
// 获取文件列表
File[] files = termuxHomeDir.listFiles();
if (files == null || files.length == 0) {
LogUtils.d(TAG, "readTermuxHomeFileList() 目录为空");
return "Termux目录为空 → " + TERMUX_HOME_PATH;
}
// 拼接结果字符串
result.append("Termux主目录");
result.append(TERMUX_HOME_PATH);
result.append("\n");
result.append("文件/子目录总数:");
result.append(files.length);
result.append("\n");
result.append("目录权限:");
result.append("读:");
result.append(termuxHomeDir.canRead());
result.append(" | 写:");
result.append(termuxHomeDir.canWrite());
result.append("\n");
result.append("-------------------------\n");
for (File file : files) {
String fileType = file.isDirectory() ? "[目录]" : "[文件]";
String fileName = file.getName();
String filePath = file.getAbsolutePath();
String fileSize = file.isFile() ? " | 大小:" + formatFileSize(file.length()) : "";
String filePerm = " | 权限r:" + file.canRead() + "/w:" + file.canWrite();
result.append(fileType);
result.append(" ");
result.append(fileName);
result.append(fileSize);
result.append(filePerm);
result.append("");
result.append(filePath);
result.append("\n");
}
LogUtils.d(TAG, "readTermuxHomeFileList() 读取完成,结果长度:" + result.length() + "字符");
return result.toString().trim();
}
/**
* 格式化文件大小B→KB→MB
*/
private String formatFileSize(long length) {
LogUtils.d(TAG, "formatFileSize() 调用,文件长度:" + length + "B");
String sizeStr;
if (length < 1024) {
sizeStr = length + "B";
} else if (length < 1024 * 1024) {
sizeStr = String.format("%.1fKB", length / 1024.0);
} else {
sizeStr = String.format("%.1fMB", length / (1024.0 * 1024));
}
LogUtils.d(TAG, "formatFileSize() 格式化结果:" + sizeStr);
return sizeStr;
}
@Override
protected void onDestroy() {
super.onDestroy();
LogUtils.d(TAG, "onDestroy() 调用,清理资源");
deleteCmdResultFile();
if (mainHandler != null) {
mainHandler.removeCallbacksAndMessages(null);
LogUtils.d(TAG, "onDestroy() Handler资源已释放");
}
LogUtils.d(TAG, "onDestroy() 执行完成");
}
}

View File

@@ -7,10 +7,10 @@ import android.os.Message;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
import cc.winboll.studio.libaes.interfaces.IWinBoLLActivity;
import cc.winboll.studio.libappbase.LogUtils;
import cc.winboll.studio.winboll.R;
import cc.winboll.studio.winboll.activities.WinBoLLActivity;
/**
* @Author 豆包&ZhanGSKen<zhangsken@qq.com>
@@ -18,7 +18,7 @@ import cc.winboll.studio.winboll.activities.WinBoLLActivity;
* @Describe 企业微信SDK接口测试基础调试版
* 包含SDK初始化、基础接口调用、日志输出、主线程回调处理
*/
public class TestWeWorkSpecSDK extends WinBoLLActivity implements IWinBoLLActivity, View.OnClickListener {
public class TestWeWorkSpecSDK extends AppCompatActivity implements IWinBoLLActivity, View.OnClickListener {
public static final String TAG = "TestWeWorkSpecSDK";

View File

@@ -0,0 +1,264 @@
package cc.winboll.studio.winboll.views;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.util.AttributeSet;
import android.view.View;
import android.widget.Button;
import cc.winboll.studio.libappbase.LogUtils;
import cc.winboll.studio.winboll.models.TermuxButtonModel;
/**
* 自定义Termux功能按钮控件
* 绑定TermuxButtonModel实体数据拦截点击事件做确认弹窗逻辑判断
* isCommitted为true直接执行点击事件为false弹出确认对话框二次确认
* @Author 豆包&ZhanGSKen<zhangsken@qq.com>
* @CreateTime 2026/04/30 10:57:00
* @EditTime 2026/04/30 13:52:15
*/
public class TermuxButton extends Button {
public static final String TAG = "TermuxButton";
/** 绑定按钮对应数据实体 */
private TermuxButtonModel buttonModel;
/** 保存外部设置的原始点击监听 */
private OnClickListener originClickListener;
//==================== 构造方法 ====================
/**
* 代码动态创建控件构造
* @param context 上下文
*/
public TermuxButton(Context context) {
super(context);
LogUtils.d(TAG, "TermuxButton 无参构造执行,上下文:" + context);
initView(null, null);
}
/**
* XML布局引用控件基础构造
* @param context 上下文
* @param attrs XML属性集
*/
public TermuxButton(Context context, AttributeSet attrs) {
super(context, attrs);
LogUtils.d(TAG, "TermuxButton XML构造执行");
initView(attrs, null);
}
/**
* XML布局带自定义属性构造
* @param context 上下文
* @param attrs XML属性集
* @param defStyleAttr 默认样式属性
*/
public TermuxButton(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
LogUtils.d(TAG, "TermuxButton 带样式属性构造执行");
initView(attrs, null);
}
/**
* 高版本Android完整全参构造
* @param context 上下文
* @param attrs XML属性集
* @param defStyleAttr 默认样式属性
* @param defStyleRes 默认样式资源
*/
public TermuxButton(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
LogUtils.d(TAG, "TermuxButton 全参构造执行");
initView(attrs, null);
}
/**
* 直接传入Model初始化控件构造
* @param context 上下文
* @param model 按钮数据实体
*/
public TermuxButton(Context context, TermuxButtonModel model) {
super(context);
LogUtils.d(TAG, "TermuxButton Model入参构造执行");
initView(null, model);
}
//==================== 核心初始化 ====================
/**
* 控件统一初始化方法
* @param attrs XML属性集合
* @param model 绑定数据实体
*/
private void initView(AttributeSet attrs, TermuxButtonModel model) {
this.buttonModel = model;
// 按钮基础默认配置
setClickable(true);
setFocusable(true);
// 解析XML布局自定义属性
if (attrs != null) {
parseXmlCustomAttr(attrs);
}
// 同步Model内按钮名称到控件展示文本
refreshButtonText();
// 绑定自定义拦截点击事件
setCustomClickEvent();
}
/**
* 解析XML布局属性读取原生android:text与自定义属性赋值到Model
* @param attrs XML属性集
*/
private void parseXmlCustomAttr(AttributeSet attrs) {
if (buttonModel == null) {
buttonModel = new TermuxButtonModel();
LogUtils.d(TAG, "自动初始化空的TermuxButtonModel实体");
}
// 读取原生android:text作为按钮名称
String androidText = attrs.getAttributeValue("http://schemas.android.com/apk/res/android", "text");
// 读取自定义扩展属性
String exeCommand = attrs.getAttributeValue("http://schemas.android.com/apk/res-auto", "exeCommand");
String workDir = attrs.getAttributeValue("http://schemas.android.com/apk/res-auto", "workDir");
String isCommittedStr = attrs.getAttributeValue("http://schemas.android.com/apk/res-auto", "isCommitted");
String commitTitle = attrs.getAttributeValue("http://schemas.android.com/apk/res-auto", "commitTitle");
String commitInfo = attrs.getAttributeValue("http://schemas.android.com/apk/res-auto", "commitInfo");
// 属性赋值绑定
if (androidText != null) {
buttonModel.setButtonName(androidText);
}
if (exeCommand != null) {
buttonModel.setExeCommand(exeCommand);
}
if (workDir != null) {
buttonModel.setWorkDir(workDir);
}
if (isCommittedStr != null) {
buttonModel.setCommitted(Boolean.parseBoolean(isCommittedStr));
}
if (commitTitle != null) {
buttonModel.setCommitTitle(commitTitle);
}
if (commitInfo != null) {
buttonModel.setCommitInfo(commitInfo);
}
LogUtils.d(TAG, "XML属性解析完成按钮名称" + androidText);
}
/**
* 同步Model中buttonName更新按钮展示文字
*/
private void refreshButtonText() {
if (buttonModel != null) {
setText(buttonModel.getButtonName());
}
}
//==================== 点击事件相关 ====================
/**
* 重写点击监听设置,保存外部原始点击事件
* @param l 外部传入点击监听
*/
@Override
public void setOnClickListener(OnClickListener l) {
this.originClickListener = l;
LogUtils.d(TAG, "保存外部原始按钮点击监听");
}
/**
* 自定义拦截按钮点击逻辑
* isCommitted=true 直接执行原始点击事件
* isCommitted=false 弹出确认二次弹窗
*/
private void setCustomClickEvent() {
super.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View view) {
if (buttonModel == null) {
LogUtils.d(TAG, "无绑定Model直接执行原始点击事件");
if (originClickListener != null) {
originClickListener.onClick(view);
}
return;
}
boolean commitState = buttonModel.isCommitted();
LogUtils.d(TAG, "按钮点击触发isCommitted状态" + commitState);
if (commitState) {
// 无需确认,直接执行原有点击任务
if (originClickListener != null) {
originClickListener.onClick(view);
}
} else {
// 需要二次确认,弹出提示对话框
showCommitDialog();
}
}
});
}
/**
* 弹出操作确认对话框
* 标题commitTitle 内容commitInfo
* 取消:关闭弹窗无操作 确定:执行原始点击事件
*/
private void showCommitDialog() {
Context context = getContext();
String dialogTitle = buttonModel.getCommitTitle();
String dialogMsg = buttonModel.getCommitInfo();
// 空值默认兜底处理
if (dialogTitle == null || "".equals(dialogTitle)) {
dialogTitle = "温馨提示";
}
if (dialogMsg == null || "".equals(dialogMsg)) {
dialogMsg = "确定要执行该操作吗?";
}
LogUtils.d(TAG, "弹出确认对话框,标题:" + dialogTitle);
new AlertDialog.Builder(context)
.setTitle(dialogTitle)
.setMessage(dialogMsg)
.setNegativeButton("取消", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
LogUtils.d(TAG, "对话框点击取消,终止操作");
}
})
.setPositiveButton("确定", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
LogUtils.d(TAG, "对话框点击确定,继续执行操作");
if (originClickListener != null) {
originClickListener.onClick(TermuxButton.this);
}
}
})
.setCancelable(false)
.show();
}
//==================== Getter & Setter ====================
public TermuxButtonModel getButtonModel() {
return buttonModel;
}
/**
* 设置绑定按钮数据实体,自动刷新按钮展示文字
* @param buttonModel 数据实体类
*/
public void setButtonModel(TermuxButtonModel buttonModel) {
this.buttonModel = buttonModel;
LogUtils.d(TAG, "外部设置ButtonModel自动刷新按钮文本");
refreshButtonText();
}
}

View File

@@ -1,210 +0,0 @@
package com.tencent.wework;
import java.util.Map;
import java.util.HashMap;;
/**
* @warning: 1. 不要修改成员变量名native方法内有反射调用
* 2. 调用本地方法需保持包结构本工具需放在包com.tencent.wework内
* 3. 不允许继承类名和函数名均不可修改会影响本地方法的引用详见javah生成本地方法头文件
*/
public final class SpecCallbackSDK {
/**
* @description 调用本地方法后实例化的对象指针
*/
private long specCallbackSDKptr = 0;
public long GetPtr() { return specCallbackSDKptr; }
/**
* @description: 回包的headers
*/
private Map<String, String> responseHeaders;
public Map<String, String> GetResponseHeaders() { return responseHeaders; }
/**
* @description: 回包的加密后的body
*/
private String responseBody;
public String GetResponseBody() { return responseBody; }
/**
* @description: 每个请求构造一个SpecCallbackSDK示例,
* SpecCallbackSDK仅持有headers和body的引用,
* 因此需保证headers和body的生存期比SpecCallbackSDK长
* @param method: 请求方法GET/POST
* @param headers: 请求header
* @param body: 请求body
* @example:
* SpecCallbackSDK sdk = new SpecCallbackSDK(method, headers, body);
* if (sdk.IsOk()) {
* String corpid = sdk.GetCorpId();
* String agentid = sdk.GetAgentId();
* String call_type = sdk.GetCallType();
* String data = sdk.GetData();
* //do something...
* }
* String response = ...;
* sdk.BuildResponseHeaderBody(response);
* Map<String, String> responseHeaders = sdk.GetResponseHeaders();
* String body = sdk.GetResponseBody();
* //do response
*
* @return errorcode 示例如下:
* -920001: 未设置请求方法
* -920002: 未设置请求header
* -920003: 未设置请求body
* */
public SpecCallbackSDK(String method, Map<String, String> headers, String body) {
try {
specCallbackSDKptr = NewCallbackSDK(method, headers, body);
} catch (Exception e) {
SpecUtil.WWSpecLogError("SpecCallbackSDK exception caught", e.getMessage());
}
}
private native long NewCallbackSDK(String method, Map<String, String> headers, String body);
/**
* @usage 在Java对象的内存回收前析构C++对象
*/
@Override
protected void finalize() throws Throwable {
DeleteCPPInstance(specCallbackSDKptr);
super.finalize();
}
private native void DeleteCPPInstance(long specCallbackSDKptr);
/**
* @description: 判断构造函数中传入的请求是否解析成功
* @return: 成功与否
* */
public boolean IsOk() {
return IsOk(specCallbackSDKptr);
}
private native boolean IsOk(long specCallbackSDKptr);
/**
* @description: 获取请求的企业
* @require: 仅当IsOk() == true可调用
* @return: corpid
* */
public String GetCorpId() {
return GetCorpId(specCallbackSDKptr);
}
private native String GetCorpId(long specCallbackSDKptr);
/**
* @description: 获取请求的应用
* @require: 仅当IsOk() == true可调用
* @return: agentid
* */
public long GetAgentId() {
return GetAgentId(specCallbackSDKptr);
}
private native long GetAgentId(long specCallbackSDKptr);
/**
* @description: 获取请求的类型
* @require: 仅当IsOk() == true可调用
* @return: 1 - 来自[应用调用专区]的请求
* 2 - 来自企业微信的回调事件
* */
public long GetCallType() {
return GetCallType(specCallbackSDKptr);
}
private native long GetCallType(long specCallbackSDKptr);
/**
* @description: 获取请求数据
* @require: 仅当IsOk() == true可调用
* @return: 请求数据,根据call_type可能是:
* - 企业微信回调事件
* - [应用调用专区]接口中的request_data
* */
public String GetData() {
return GetData(specCallbackSDKptr);
}
private native String GetData(long specCallbackSDKptr);
/**
* @description: 是否异步请求
* @require: 仅当IsOk() == true可调用
* @return: 是否异步请求
* */
public boolean GetIsAsync() {
return GetIsAsync(specCallbackSDKptr);
}
private native boolean GetIsAsync(long specCallbackSDKptr);
/**
* @description: 获取请求的job_info,
* @require: 仅当IsOk() == true可调用
* @return: job_info,无需理解内容,
* 在同一个请求上下文中使用SpecSDK的时候传入
* */
public String GetJobInfo() {
return GetJobInfo(specCallbackSDKptr);
}
private native String GetJobInfo(long specCallbackSDKptr);
/**
* @description: 获取请求的ability_id,[应用调用专区]接口时指定
* @require: 仅当IsOk() == true可调用
* @return: ability_id
* */
public String GetAbilityId() {
return GetAbilityId(specCallbackSDKptr);
}
private native String GetAbilityId(long specCallbackSDKptr);
/**
* @description: 获取请求的notify_id,用于[应用同步调用专区程序]接口
* @require: 仅当IsOk() == true可调用
* @return: notify_id
* */
public String GetNotifyId() {
return GetNotifyId(specCallbackSDKptr);
}
private native String GetNotifyId(long specCallbackSDKptr);
/**
* @description: 对返回包计算签名&加密
* @param response: 待加密的回包明文.如果IsOk()==false,传入空串即可
* @note 本接口的执行问题可查看日志
* */
public void BuildResponseHeaderBody(String response) {
try {
responseHeaders = new HashMap<String, String>();
responseBody = "";
BuildResponseHeaderBody(specCallbackSDKptr, response);
} catch (Exception e) {
SpecUtil.WWSpecLogError("SpecCallbackSDK exception caught", e.getMessage());
}
}
private native void BuildResponseHeaderBody(long specCallbackSDKptr, String response);
// 静态代码块内还无法调用native日志函数这里的日志在管理系统无法查询
static {
try {
Class.forName("com.tencent.wework.SpecUtil");
} catch (ClassNotFoundException e) {
e.printStackTrace();
System.exit(1);
}
}
}

View File

@@ -1,163 +0,0 @@
package com.tencent.wework;
/**
* @warning: 1. 不要修改成员变量名native方法内有反射调用
* 2. 调用本地方法需保持包结构本工具需放在包com.tencent.wework内
* 3. 不允许继承类名和函数名均不可修改会影响本地方法的引用详见javah生成本地方法头文件
*/
public final class SpecSDK {
/**
* @description 调用本地方法后实例化的对象指针
*/
private long specSDKptr = 0;
/**
* @usage invoke的请求
* @example "{\"limit\":1}
*/
private String request;
public void SetRequest(String request) {
this.request = request;
}
/**
* @usage 访问上一次invoke的结果
*/
private String response;
public String GetResponse() {
return response;
}
/**
* @param corpid: 企业corpid必选参数
* @param agentid: 应用id必选参数
* @param ability_id: 能力ID可选参数
* @param job_info: job_info可选参数
* */
public SpecSDK(String corpId, long agentId) {
specSDKptr = NewSDK1(corpId, agentId);
}
private native long NewSDK1(String corpId, long agentId);
public SpecSDK(String corpId, long agentId, String abilityId) {
specSDKptr = NewSDK2(corpId, agentId, abilityId);
}
private native long NewSDK2(String corpId, long agentId, String abilityId);
public SpecSDK(String corpId, long agentId, String abilityId, String jobInfo) {
specSDKptr = NewSDK3(corpId, agentId, abilityId, jobInfo);
}
private native long NewSDK3(String corpId, long agentId, String abilityId, String jobInfo);
/**
* @description 使用callback的请求来初始化
* @param callback_sdk: 要求IsOk()==true
* @return C++内部指针创建失败时指针仍为0并输出错误日志
* */
public SpecSDK(SpecCallbackSDK callbackSDK) {
specSDKptr = NewSDK4(callbackSDK.GetPtr());
}
private native long NewSDK4(long callbackSDK);
/**
* @usage 在Java对象的内存回收前析构C++对象
*/
@Override
protected void finalize() throws Throwable {
DeleteCPPInstance(specSDKptr);
super.finalize();
}
private native void DeleteCPPInstance(long specSDKptr);
/**
* @description 用于在专区内调用企业微信接口
* @param api_name 接口名
* @param request json格式的请求数据
* @param response json格式的返回数据
* @return errorcode 参考如下:
* 0: 成功
* -910001: SDK没有初始化
* -910002: 没有设置请求体
* -910003: 没有设置请求的API
* -910004: 在SDK成员内找不到成员"response",注意lib内有反射机制,不要修改成员变量名
* -910005: 使用未初始化的callback初始化SDK
* -910006: invoke调用失败,应检查日志查看具体原因
* -910007: 响应体为空
* @note 当返回0时,表示没有网络或请求协议层面或调用方法的失败,
* 调用方需继续检查response中的errcode字段确保业务层面的成功
*
* @usage 当前版本sdk支持的接口列表,每个接口的具体协议请查看企业微信文档:
* https://developer.work.weixin.qq.com/document/path/91201
*
* +--------------------------------+--------------------------------+
* |接口名 |描述 |
* |--------------------------------|--------------------------------|
* |program_async_job_call_back |上报异步任务结果 |
* |sync_msg |获取会话记录 |
* |get_group_chat |获取内部群信息 |
* |get_agree_status_single |获取单聊会话同意情况 |
* |get_agree_status_room |获取群聊会话同意情况 |
* |set_hide_sensitiveinfo_config |设置成员会话组件敏感信息隐藏配置|
* |get_hide_sensitiveinfo_config |获取成员会话组件敏感信息隐藏配置|
* |search_chat |会话名称搜索 |
* |search_msg |会话消息搜索 |
* |create_rule |新增关键词规则 |
* |get_rule_list |获取关键词列表 |
* |get_rule_detail |获取关键词规则详情 |
* |update_rule |修改关键词规则 |
* |delete_rule |删除关键词规则 |
* |get_hit_msg_list |获取命中关键词规则的会话记录 |
* |create_sentiment_task |创建情感分析任务 |
* |get_sentiment_result |获取情感分析结果 |
* |create_summary_task |创建摘要提取任务 |
* |get_summary_result |获取摘要提取结果 |
* |create_customer_tag_task |创建标签匹配任务 |
* |get_customer_tag_result |获取标签任务结果 |
* |create_recommend_dialog_task |创建话术推荐任务 |
* |get_recommend_dialog_result |获取话术推荐结果 |
* |create_private_task |创建自定义模型任务 |
* |get_private_task_result |获取自定义模型结果 |
* |(废弃)document_list |获取知识集列表 |
* |create_spam_task |会话反垃圾创建分析任务 |
* |get_spam_result |会话反垃圾获取任务结果 |
* |create_chatdata_export_job |创建会话内容导出任务 |
* |get_chatdata_export_job_status |获取会话内容导出任务结果 |
* |spec_notify_app |专区通知应用 |
* |create_program_task |创建自定义程序任务 |
* |get_program_task_result |获取自定义程序结果 |
* |knowledge_base_list |获取企业授权给应用的知识集列表 |
* |knowledge_base_create |创建知识集 |
* |knowledge_base_detail |获取知识集详情 |
* |knowledge_base_add_doc |添加知识集內容 |
* |knowledge_base_remove_doc |删除知识集內容 |
* |knowledge_base_modify_name |修改知识集名称 |
* |knowledge_base_delete |删除知识集 |
* |search_contact_or_customer |员工或者客户名称搜索 |
* |create_ww_model_task |创建企微通用模型任务 |
* |get_ww_model_result |获取企微通用模型结果 |
* |get_msg_list_by_page_id |page_id获取消息列表 |
* +-----------------------------------------------------------------+
* */
public int Invoke(String apiName) {
return Invoke(specSDKptr, apiName, request);
}
private native int Invoke(long sdk, String apiName, String request);
// 静态代码块内还无法调用native日志函数这里的日志在管理系统无法查询
static {
try {
Class.forName("com.tencent.wework.SpecUtil");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}

View File

@@ -1,171 +0,0 @@
package com.tencent.wework;
//import java.lang.management.ManagementFactory;
//import java.lang.management.RuntimeMXBean;
/**
* @warning: 1. 不要修改成员变量名native方法内有反射调用
* 2. 调用本地方法需保持包结构本工具需放在包com.tencent.wework内
* 3. 不允许继承类名和函数名均不可修改会影响本地方法的引用详见javah生成本地方法头文件
* 4. 使用其他工具打印的日志将无法被查询如需使用SLF4j风格的日志或性能更好的日志框架
* 请自行封装SpecUtil.SpecLog或SpecUtil.SpecLogNative方法
*
* @usage: 1. 获取SDK的版本号
* 2. 打印三个级别的日志
* 3. 开启调试模式
*/
public final class SpecUtil {
/**
* @description SDK版本号
* @usage 可用于校对不同SDK版本或后续针对不同的SDK版本添加业务逻辑
*/
private static final String SDK_VERSION = "1.4.0";
public static String GetSDKVersion() {
return SDK_VERSION;
}
/**
* @description 正确的包名SDK必须存放在"com.tencent.wework"下,否则会影响本地方法的调用
*/
private static final String EXPECTED_PACKAGE_NAME = "com.tencent.wework";
public static String GetExpectedPackageName() {
return EXPECTED_PACKAGE_NAME;
}
private static final String LINE_SEPERATOR = System.getProperty("line.separator");
public static void WWSpecLogInfo(String... args) {
SpecLog('I', args);
}
public static void WWSpecLogError(String... args) {
SpecLog('E', args);
}
public static void WWSpecLogDebug(String... args) {
SpecLog('D', args);
}
public static void WWSpecLogInfoWithReqId(String reqId, String... args) {
SpecLogWithReqId(reqId, 'I', args);
}
public static void WWSpecLogErrorWithReqId(String reqId, String... args) {
SpecLogWithReqId(reqId, 'E', args);
}
public static void WWSpecLogDebugWithReqId(String reqId, String... args) {
SpecLogWithReqId(reqId, 'D', args);
}
/**
* @usage 打印标准日志
* @note 只有使用SpecLog和SpecLogNative函数打印的日志能被调试平台查询其他框架的日志仅能本地查看
* @param logLevel 日志级别使用char传递目前支持I——INFO、E——ERROR、D——DEBUG
* @param args 自定义参数
*/
public static void SpecLog(char logLevel, String... args) {
StackTraceElement element = Thread.currentThread().getStackTrace()[3];
SpecLogNative(
logLevel,
element.getFileName(),
element.getLineNumber(),
String.join(",", args).replace(LINE_SEPERATOR, " ")
);
}
/**
* @usage 打印标准日志
* @note 只有使用SpecLog和SpecLogNative函数打印的日志能被调试平台查询其他框架的日志仅能本地查看
* @param reqid 请求id
* @param logLevel 日志级别使用char传递目前支持I——INFO、E——ERROR、D——DEBUG
* @param args 自定义参数
*/
public static void SpecLogWithReqId(String reqId, char logLevel, String... args) {
StackTraceElement element = Thread.currentThread().getStackTrace()[3];
SpecLogNativeWithReqId(
reqId,
logLevel,
element.getFileName(),
element.getLineNumber(),
String.join(",", args).replace(LINE_SEPERATOR, " ")
);
}
/**
* @usage 打印标准日志
* @note 只有使用SpecLog和SpecLogNative函数打印的日志能被调试平台查询其他框架的日志仅能本地查看
* 如需SLF4J风格的接口或对日志性能有进一步需求开发者可以自行封装该函数
* @param logLevel 日志级别使用char传递目前支持I——INFO、E——ERROR、D——DEBUG
* @param fileName 文件名(类名)
* @param lineNumber 行号
* @param argsString 自定义参数
*/
public static native void SpecLogNative(char logLevel, String fileName, int lineNumber, String argsString);
/**
* @usage 打印标准日志
* @note 只有使用SpecLog和SpecLogNative函数打印的日志能被调试平台查询其他框架的日志仅能本地查看
* 如需SLF4J风格的接口或对日志性能有进一步需求开发者可以自行封装该函数
* @param reqid 请求id
* @param logLevel 日志级别使用char传递目前支持I——INFO、E——ERROR、D——DEBUG
* @param fileName 文件名(类名)
* @param lineNumber 行号
* @param argsString 自定义参数
*/
public static native void SpecLogNativeWithReqId(String reqId, char logLevel, String fileName, int lineNumber, String argsString);
/**
* @usage 开启调试模式,进程级别开关
* @param debugToken 调试凭证,在管理端获取
* @param accessToken 应用access token
* @return 是否开启成功
*/
public static boolean SpecOpenDebugMode(String debugToken, String accessToken) {
return SpecOpenDebugModeNative(debugToken, accessToken);
}
private static native boolean SpecOpenDebugModeNative(String debugToken, String accessToken);
/**
* @usage 生成notify id。用户可调用本接口生成notify id也可完全自定义生成
* @return 新的notify id支持纳秒级隔离内部异常时会输出日志并返回空串
* @note 1. 用户可先生成notify id将其与回调数据关联存储后再使用该notify id通知应用
* 从而保证回调数据被请求时已存储完毕
*/
public static String GenerateNotifyId() {
return GenerateNotifyIdNative();
}
private static native String GenerateNotifyIdNative();
static {
// 检查包名
String packageName = SpecUtil.class.getPackage().getName();
if (!EXPECTED_PACKAGE_NAME.equals(packageName)) {
// 静态代码块内还无法调用native日志函数这里的日志在管理系统无法查询
System.out.println("SpecUtil class must be in package com.tencent.wework");
System.exit(1);
}
// 加载so库
try {
System.loadLibrary("WeWorkSpecSDK");
} catch (UnsatisfiedLinkError e) {
System.out.println("libWeWorkSpecSDK.so not found in java.library.path");
e.printStackTrace();
System.exit(1);
} catch (Exception e) {
System.out.println("unexpected exception: " + e.getMessage());
e.printStackTrace();
System.exit(1);
}
SpecUtil.WWSpecLogInfo("SDK init done", "packageName=" + packageName, "SDK_VERSION=" + SDK_VERSION);
}
}

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="ring"
android:innerRadiusRatio="3"
android:thicknessRatio="8"
android:useLevel="false">
<solid android:color="#00000000"/>
</shape>

View File

@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<inset xmlns:android="http://schemas.android.com/apk/res/android"
android:insetLeft="2dp"
android:insetTop="2dp"
android:insetRight="2dp"
android:insetBottom="2dp">
<shape android:shape="ring"
android:innerRadiusRatio="2"
android:thicknessRatio="2.5"
android:useLevel="false">
<solid android:color="#006400"/>
<stroke android:width="1dp" android:color="#006400"/>
</shape>
</inset>

View File

@@ -0,0 +1,20 @@
<?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

@@ -6,14 +6,6 @@
android:layout_height="match_parent"
android:orientation="vertical">
<cc.winboll.studio.winboll.CustomToolbar
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:toolbarTitle="@string/app_name"
app:toolbarTitleColor="@color/colorAccent"
app:toolbarBackgroundColor="@color/colorPrimary"
android:id="@+id/toolbar"/>
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"

View File

@@ -0,0 +1,90 @@
<?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="wrap_content"
android:gravity="top">
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
app:title="@string/app_name"
app:subtitle="@string/my_termux_activity"
app:titleTextColor="@android:color/white"
app:subtitleTextColor="@android:color/white"/>
<ScrollView
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1.0">
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<HorizontalScrollView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1.0">
<LinearLayout
android:orientation="horizontal"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<cc.winboll.studio.winboll.views.TermuxButton
android:id="@+id/btn_termux"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Termux"
android:textSize="18sp"
android:padding="16dp"
android:backgroundTint="@android:color/holo_blue_dark"
app:exeCommand="cd ~"
app:workDir="~"
app:isCommitted="true"
app:commitTitle="打开 Termux"
app:commitInfo="打开 Termux 应用"/>
<cc.winboll.studio.winboll.views.TermuxButton
android:id="@+id/btn_termuxworkspaces"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="TermuxWorkSpaces"
android:textSize="18sp"
android:padding="16dp"
android:backgroundTint="@android:color/holo_blue_dark"
app:exeCommand="cd ~/TermuxWorkSpaces"
app:workDir="~"
app:isCommitted="false"
app:commitTitle="打开 TermuxWorkSpaces"
app:commitInfo="打开 Termux 应用,进入 TermuxWorkSpaces 目录。"/>
</LinearLayout>
</HorizontalScrollView>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="+"
android:id="@+id/btn_addtermuxbutton"/>
</LinearLayout>
</LinearLayout>
</ScrollView>
</LinearLayout>

View File

@@ -6,7 +6,7 @@
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.widget.Toolbar
<androidx.appcompat.widget.Toolbar
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/toolbar"/>

View File

@@ -6,7 +6,7 @@
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.widget.Toolbar
<androidx.appcompat.widget.Toolbar
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/toolbar"/>

View File

@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/black">
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
android:gravity="center"
app:titleTextColor="@android:color/white"
app:subtitleTextColor="@android:color/white"/>
<FrameLayout
android:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#FFFFFF"/>
</LinearLayout>

View File

@@ -0,0 +1,65 @@
<?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"/>
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="right">
<Button
android:textAllCaps="false"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Open Termux Bash"
android:onClick="onOpenTermuxBash"/>
<Button
android:textAllCaps="false"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Test Termux Gradle Build CMD"
android:onClick="onTestTermuxGradleBuildCMD"/>
<Button
android:textAllCaps="false"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Test Termux Env"
android:onClick="onTestTermuxEnv"/>
<Button
android:textAllCaps="false"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Test WinBoLL Project Build"
android:onClick="onTestWinBoLLProjectBuild"/>
</LinearLayout>
<ScrollView
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1.0">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Text"
android:id="@+id/tv_message"
android:textIsSelectable="true"/>
</ScrollView>
</LinearLayout>

View File

@@ -5,9 +5,12 @@
android:id="@+id/item_home"
android:title="HOME"/>
<item
android:id="@+id/item_mytermux"
android:title="MyTermuxActivity"/>
<item
android:id="@+id/item_settings"
android:title="Settings"/>
<item
android:id="@+id/item_wxpayactivity"
android:title="WXPayActivity"/>
android:id="@+id/item_about"
android:title="About"/>
</menu>

View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item android:title="UnitTest">
<menu >
<item
android:id="@+id/item_termux_env_test"
android:title="TermuxEnvTestActivity"/>
</menu>
</item>
</menu>

View File

@@ -5,5 +5,13 @@
<attr name="toolbarTitleColor" format="color" />
<attr name="toolbarBackgroundColor" format="color" />
</declare-styleable>
<declare-styleable name="TermuxButton">
<attr name="exeCommand" format="string" />
<attr name="workDir" format="string" />
<attr name="isCommitted" format="boolean" />
<attr name="commitTitle" format="string" />
<attr name="commitInfo" format="string" />
</declare-styleable>
</resources>

View File

@@ -3,4 +3,5 @@
<color name="colorPrimary">#009688</color>
<color name="colorPrimaryDark">#00796B</color>
<color name="colorAccent">#FF9800</color>
<color name="pattern_lock_black">#000000</color>
</resources>

View File

@@ -1,5 +1,6 @@
<resources>
<string name="app_name">WinBoLL</string>
<string name="app_description">WinBoLL 网站浏览器。</string>
<string name="app_name_cn1">筋斗云</string>
<string name="app_name_cn2">金抖云</string>
<string name="switchto_en1">WinBoLL</string>
@@ -10,4 +11,6 @@
<string name="cn2_switch_disabled">金抖云 X</string>
<string name="tileservice_name">WinBoLL</string>
<string name="toolbar_icon_description">WinBoLL APP</string>
<string name="my_termux_activity">MyTermuxActivity</string>
<string name="pattern_lock_title">图案密码设置</string>
</resources>

View File

@@ -11,5 +11,11 @@
<certificates src="system" />
</trust-anchors>
</base-config>
<!-- 允许特定域名的明文请求仅本地127.0.0.1 -->
<domain-config cleartextTrafficPermitted="true">
<domain includeSubdomains="true">127.0.0.1</domain>
<domain includeSubdomains="true">localhost</domain>
</domain-config>
</network-security-config>