Compare commits

...

67 Commits

Author SHA1 Message Date
ZhanGSKen b50de904f2 <contacts>APK 15.20.11 release Publish. 2026-07-19 06:16:39 +08:00
ZhanGSKen 18e41a7a8d 云盾Buff加成。 2026-07-19 06:12:57 +08:00
ZhanGSKen 8713872d62 UI调整 2026-07-19 05:59:34 +08:00
ZhanGSKen b16f29a43f 把应用日志窗口调整为应用调试资源 2026-07-19 05:51:13 +08:00
MiMo ebaf487d0a 调试类型控件与应用类型控件进行分类管理。 2026-07-19 05:29:41 +08:00
MiMo d5d230c1bb feat: 新增云盾查询Fragment替换日志Fragment
- 新增 DunQueryFragment 和 DunQueryView 控件
- 主界面第三个标签页从应用日志改为云盾查询
- 移除 LogFragment 及相关布局文件
- 移除 SettingsActivity 中的 DunQueryView 控件
2026-07-19 05:07:03 +08:00
MiMo 1d0322252e refactor: 精简调试工具按钮组
移除冗余按钮,仅保留测试崩溃和布局草稿两个功能:
- 移除:清除数据、查看SP、查看日志、重启服务、应用信息
- 测试崩溃改为getString循环触发崩溃
2026-07-19 04:17:39 +08:00
MiMo 22f9eff429 feat: LayoutDraftPreViewActivity添加工具栏
仿照MainActivity为布局草稿预览窗口添加工具栏:
- 继承改为AppCompatActivity以支持Toolbar
- 添加返回按钮和页面标题
- 点击返回按钮关闭页面
2026-07-18 20:12:34 +08:00
MiMo c8e55730f3 feat: 调试工具按钮组添加布局草稿预览入口
在DebugToolButtonsView中新增布局草稿按钮,
点击后跳转LayoutDraftPreViewActivity窗口。
2026-07-18 20:05:42 +08:00
MiMo 5d73960a54 feat: 添加SettingsActivity调试工具按钮组
新增调试工具按钮组控件,仅在调试模式下动态加载:
- view_debug_tool_buttons.xml: 按钮组布局文件
- DebugToolButtonsView.java: 自定义View控件
- SettingsActivity: isDebugging()时加载调试面板

功能按钮:清除数据、查看SP、测试崩溃、查看日志、重启服务、应用信息
2026-07-18 19:58:25 +08:00
ZhanGSKen 2661bd1d5f 添加Http登录验证按钮图片资源 2026-07-18 19:38:33 +08:00
ZhanGSKen 3aaa7ab4fb 把原草稿布局文件方式改为调试预览窗口方式的类源码与资源 2026-07-18 19:37:38 +08:00
ZhanGSKen c75344c238 添加项目开发时,xml布局文件设计的辅助草稿文件。方便快捷调整视图控件属性。 2026-07-18 19:23:38 +08:00
ZhanGSKen 927dfc47ff <contacts>APK 15.20.10 release Publish. 2026-07-18 18:56:11 +08:00
MiMo 7587fba89b fix: 更新contacts模块Maven依赖版本号
libaes 15.20.16 → 15.20.21
libappbase 15.20.33 → 15.20.34

原版本号在Nexus仓库中不存在,导致编译失败。
更新为仓库中可用的最新15.20.x版本。
2026-07-18 18:42:04 +08:00
ZhanGSKen ee73bc3b78 更新源码仓库地址 2026-07-18 16:52:46 +08:00
ZhanGSKen fa95bcd873 恢复原WinBoLL项目保持的Contacts分支模块源码 2026-07-18 16:49:15 +08:00
ZhanGSKen e136045d1f Merge remote-tracking branch 'winboll_https/projects_keeper_tag' into mywinboll 2026-07-11 20:21:13 +08:00
qinglong bd635425a1 合并模块AES 同步最新时间标签aes-v15.20.18 2026-07-11 20:17:35 +08:00
BigPickle 49414a3938 App/MainActivity: 修复主题切换 crash 及工具栏主题不刷新问题
- App.onCreate 添加 AESThemeUtil.init(null) 初始化 themeStyleIDList,
  修复点击主题菜单时 IndexOutOfBoundsException 崩溃
- MainActivity.onCreate 添加 AESThemeUtil.applyAppCompatTheme(this)
  置于 super.onCreate 之前,使 recreate() 后应用已保存的主题样式
2026-07-02 01:34:58 +08:00
BigPickle 08a47768a1 MainActivity: 接入 AESThemeUtil/DevelopUtils 工具类,替换 Toolbar 为 ASupportToolbar
- MainActivity 新增 onCreateOptionsMenu/onOptionsItemSelected,
  使用 AESThemeUtil.inflateCompatThemeMenu 加载主题菜单、
  DevelopUtils.inflateMenu 加载调试菜单
- 布局文件 Toolbar 替换为 cc.winboll.studio.libaes.views.ASupportToolbar,
  移除 AppBarLayout 包裹层
- MyAppTheme 日间/夜间主题增加 aSupportToolbar 属性引用 AESASupportToolbar 样式
2026-07-02 01:26:50 +08:00
ZhanGSKen 79f161b559 fix: 修复 MyAppTheme 主题继承链,适配 Android 16(SDK 36)
- 修复 view_log.xml 自定义属性解析失败崩溃(需要 APPBaseTheme 的属性集)
- 修复 AppCompatActivity 需要 Theme.AppCompat 子主题的 IllegalStateException
- 父主题改为 Theme.AppCompat.Light.NoActionBar / Theme.AppCompat.NoActionBar
- 新增 values-night/ 日夜模式自动切换支持
- 主色调从 Teal/Orange 更换为 Blue/Amber 专业配色方案
2026-07-01 17:06:20 +08:00
LaizyBoy b14538db47 20260701_163734_738 2026-07-01 16:37:41 +08:00
ZhanGSKen c9e16a9a1e 编译配置完成 2026-07-01 15:53:23 +08:00
ZhanGSKen 42dbfb4238 加入WinBoLL框架文件 2026-07-01 15:47:18 +08:00
ZhanGSKen b274503187 20260701_154438_390 2026-07-01 15:45:11 +08:00
ZhanGSKen b50e4e02ea 修复git管理脚本 2026-07-01 04:49:24 +08:00
ZhanGSKen 84f1c820cb 修复git管理脚本 2026-07-01 04:48:30 +08:00
ZhanGSKen 0e60d75ac6 修复git管理脚本 2026-07-01 04:44:47 +08:00
ZhanGSKen 773cc9f6c1 修复git管理脚本 2026-07-01 04:42:45 +08:00
ZhanGSKen ca33dab4da 修复git管理脚本 2026-07-01 04:33:36 +08:00
ZhanGSKen eee8809485 修复git管理脚本 2026-07-01 04:11:36 +08:00
ZhanGSKen 69f79549e9 合并模块WinBoLL 同步最新时间标签winboll-v15.20.8 2026-07-01 04:10:40 +08:00
ZhanGSKen f204cd2608 修复git管理脚本 2026-07-01 04:08:42 +08:00
ZhanGSKen 2d3592690a 更新新版winboll架构 2026-07-01 03:49:09 +08:00
ZhanGSKen 576f8bb5e5 更新新版winboll架构 2026-07-01 03:48:29 +08:00
qinglong e787697d6b 合并模块AES 同步最新时间标签aes-v15.20.17 2026-06-30 19:00:05 +08:00
qinglong 3db3edcf2c 合并模块APPBase 同步最新时间标签appbase-v15.20.34 2026-06-30 19:00:05 +08:00
qinglong 643d08bf22 合并模块AES 同步最新时间标签aes-v15.20.16 2026-06-26 02:00:28 +08:00
qinglong beefb16c32 合并模块APPBase 同步最新时间标签appbase-v15.20.33 2026-06-26 02:00:28 +08:00
qinglong 878009ec9f 合并模块WinBoLL 同步最新时间标签winboll-v15.20.8 2026-06-26 02:00:28 +08:00
qinglong 2d8b5d515b 合并模块AES 同步最新时间标签aes-v15.20.15 2026-06-03 20:30:22 +08:00
qinglong 1453287667 合并模块APPBase 同步最新时间标签appbase-v15.20.26 2026-06-03 20:30:22 +08:00
qinglong 5fc59f3305 合并模块AES 同步最新时间标签aes-v15.20.14 2026-06-03 08:00:01 +08:00
qinglong b6ba73126f 合并模块WinBoLL 同步最新时间标签winboll-v15.20.7 2026-06-03 08:00:01 +08:00
qinglong bdd8d1e2b6 合并模块APPBase 同步最新时间标签appbase-v15.20.25 2026-06-03 07:00:01 +08:00
qinglong 138ca80e35 合并模块APPBase 同步最新时间标签appbase-v15.20.23 2026-06-03 06:06:41 +08:00
qinglong 7a3b54b78b 合并模块WinBoLL 同步最新时间标签winboll-v15.20.6 2026-06-02 21:00:01 +08:00
qinglong db804d1897 合并模块AES 同步最新时间标签aes-v15.20.12 2026-06-02 08:55:05 +08:00
qinglong 039c8fcd98 合并模块WinBoLL 同步最新时间标签winboll-v15.20.5 2026-06-02 04:00:01 +08:00
qinglong ae63d1ec0a 合并模块AES 同步最新时间标签aes-v15.20.11 2026-06-02 03:00:01 +08:00
qinglong 9e9486b488 合并模块WinBoLL 同步最新时间标签winboll-v15.20.4 2026-06-01 21:00:01 +08:00
qinglong 4b8967b253 合并模块WinBoLL 同步最新时间标签winboll-v15.20.3 2026-05-31 21:00:02 +08:00
qinglong b1059c3f46 合并模块AES 同步最新时间标签aes-v15.20.10 2026-05-27 20:26:41 +08:00
qinglong f3d723fbee 合并模块APPBase 同步最新时间标签appbase-v15.20.22 2026-05-27 15:00:01 +08:00
qinglong e3c30ea9a3 合并模块AES 同步最新时间标签aes-v15.20.9 2026-05-24 10:49:26 +08:00
qinglong ab4cef21f0 合并模块APPBase 同步最新时间标签appbase-v15.20.21 2026-05-24 10:00:01 +08:00
QingLong 2754a2ad7c 合并模块AES 同步最新时间标签aes-v15.20.8 2026-05-20 21:00:01 +08:00
QingLong 8876896cbc 合并模块APPBase 同步最新时间标签appbase-v15.20.20 2026-05-20 17:00:01 +08:00
QingLong 28e6a8ee78 合并模块APPBase 同步最新时间标签appbase-v15.20.19 2026-05-20 15:00:01 +08:00
QingLong 7d872fd14c 合并模块APPBase 同步最新时间标签appbase-v15.20.18 2026-05-20 04:00:01 +08:00
QingLong d6fab2133f 合并模块AES 同步最新时间标签aes-v15.20.7 2026-05-19 21:00:01 +08:00
QingLong 45821b8daa 合并模块APPBase 同步最新时间标签appbase-v15.20.17 2026-05-19 21:00:01 +08:00
QingLong edd312a525 合并模块APPBase 同步最新时间标签appbase-v15.20.16 2026-05-19 19:00:01 +08:00
QingLong aec680e36e 合并模块AES 同步最新时间标签aes-v15.20.6 2026-05-19 01:53:02 +08:00
QingLong 1c6f7783e5 合并模块AES 同步最新时间标签aes-v15.20.5 2026-05-19 01:49:06 +08:00
qinglong 193011a80d 合并模块AES 同步最新时间标签aes-v15.20.4 2026-05-17 21:00:01 +08:00
771 changed files with 3440 additions and 56124 deletions
+3 -3
View File
@@ -94,8 +94,8 @@ lint-results.html
## 忽略 AndroidIDE 临时文件夹
.androidide
## WinBoLL 基础应用(避免上传敏感配置)
## MyWinBoLL 基础应用(避免上传敏感配置)
/winboll.properties
/local.properties
/settings.gradle
/gradle.properties
#/settings.gradle
#/gradle.properties
@@ -1,5 +1,5 @@
#!/system/bin/sh
## 合并其他项目分支的模块源码到projects-keeper分支。
## 合并其他项目分支的模块源码到projects_keeper分支。
# ====================== 0. 进入目标目录 ======================
TARGET_DIR="/sdcard/AppProjects/Projects_Keeper"
@@ -36,7 +36,7 @@ fi
# ====================== 3. Git 分支检查 ======================
CUR_BRANCH=$(git symbolic-ref --short HEAD 2>/dev/null)
TARGET_BRANCH="projects-keeper"
TARGET_BRANCH="projects_keeper"
if [ "$CUR_BRANCH" != "$TARGET_BRANCH" ]; then
echo "错误:当前不在 $TARGET_BRANCH 分支!"
@@ -72,7 +72,6 @@ libaes
libappbase
libdebugtemp
libgpsrelaysentinel
libwinboll
local.properties-demo
mymessagemanager
positions
@@ -82,13 +81,14 @@ winboll
winboll.properties-demo
)
# ====================== 5. 获取当前目录真实文件列表 ======================
# ====================== 5. 获取当前目录真实文件列表(兼容过滤 . .. ======================
REAL_ITEMS=()
# 使用固定排序ls,自动过滤 . 和 ..,不会进入比对数组
while IFS= read -r line; do
if [[ "$line" != "." && "$line" != ".." ]]; then
REAL_ITEMS+=("$line")
fi
done < <(ls -a)
done < <(LC_COLLATE=C ls -a1 --color=none)
# ====================== 6. 差异比对函数 ======================
check_diff() {
@@ -158,7 +158,7 @@ echo -e "## 对象列表结束
## 合并 APP 项目
MERGE_APP_PROJECT_LIST=(
DemoAPP
WinBoLL
)
echo -e "#@@@ 开始合并应用型模块源码 @@@#
## 目标合并对象列表:"
@@ -166,14 +166,13 @@ echo -e "#@@@ 开始合并应用型模块源码 @@@#
for item in "${MERGE_APP_PROJECT_LIST[@]}"; do
echo "正在合并 $item 项目 ..."
item_lower=$(echo "$item" | tr 'A-Z' 'a-z')
git checkout origin/$item_lower $item_lower
git checkout origin/$item_lower $item_lower
git add .
git commit -m "合并 $item 项目"
done
## 合并 LIB 项目
MERGE_LIB_PROJECT_LIST=(
WinBoLL
APPBase
AES
)
@@ -183,10 +182,10 @@ echo -e "#@@@ 开始合并类库型模块源码 @@@#
for item in "${MERGE_LIB_PROJECT_LIST[@]}"; do
echo "正在合并 $item 项目 ..."
item_lower=$(echo "$item" | tr 'A-Z' 'a-z')
git checkout origin/$item_lower $item_lower lib$item_lower
git checkout origin/$item_lower $item_lower lib$item_lower
git add .
git commit -m "合并 $item 项目"
done
echo '正在推送 Projects_Keeper 项目'
git push
git push
@@ -38,7 +38,7 @@ if [ "${NOW_BRANCH}" != "${TARGET_BRANCH}" ];then
exit 1
fi
# 目录结构校验
# 目录结构校验白名单(不含 . ..
MERGE_OBJECTS_LIST=(
.git
.gitignore
@@ -65,7 +65,6 @@ libaes
libappbase
libdebugtemp
libgpsrelaysentinel
libwinboll
local.properties-demo
mymessagemanager
positions
@@ -76,9 +75,13 @@ winboll.properties-demo
)
REAL_ITEMS=()
# 标准排序ls输出,循环强制过滤 . 和 ..
while IFS= read -r line; do
[[ $line != "." && $line != ".." ]] && REAL_ITEMS+=("$line")
done < <(ls -a)
# 跳过虚拟目录 . 和 ..
if [[ "$line" != "." && "$line" != ".." ]]; then
REAL_ITEMS+=("$line")
fi
done < <(LC_COLLATE=C ls -a1 --color=none)
check_diff(){
local miss=() extra=()
@@ -89,7 +92,17 @@ check_diff(){
[[ ! " ${MERGE_OBJECTS_LIST[@]} " =~ " ${i} " ]] && extra+=("$i")
done
if [[ ${#miss[@]} -gt 0 || ${#extra[@]} -gt 0 ]];then
echo "========================================"
echo "本地目录结构不一致,终止运行"
if [[ ${#miss[@]} -gt 0 ]]; then
echo -e "\n缺失条目:"
for m in "${miss[@]}"; do echo " $m"; done
fi
if [[ ${#extra[@]} -gt 0 ]]; then
echo -e "\n多余条目:"
for e in "${extra[@]}"; do echo " $e"; done
fi
echo "========================================"
exit 1
fi
}
@@ -98,7 +111,7 @@ check_diff
echo -e "#@@@ 按时间获取最新标签合并模块源码 @@@#"
# 应用型模块
MERGE_APP_PROJECT_LIST=(DemoAPP)
MERGE_APP_PROJECT_LIST=(WinBoLL)
echo -e "---------- 应用型模块 ----------"
for name in "${MERGE_APP_PROJECT_LIST[@]}";do
low_name=$(echo "$name" | tr 'A-Z' 'a-z')
@@ -120,7 +133,7 @@ for name in "${MERGE_APP_PROJECT_LIST[@]}";do
done
# 类库模块
MERGE_LIB_PROJECT_LIST=(WinBoLL APPBase AES)
MERGE_LIB_PROJECT_LIST=(APPBase AES)
echo -e "---------- 类库模块 ----------"
for name in "${MERGE_LIB_PROJECT_LIST[@]}";do
low_name=$(echo "$name" | tr 'A-Z' 'a-z')
+228
View File
@@ -0,0 +1,228 @@
#!/usr/bin/bash
# ==============================================================================
# WinBoLL 应用发布脚本
# 功能:检查Git源码状态 → 编译Stage Release包 → 添加WinBoLL标签 → 提交并推送源码
# 依赖:build.properties、app_update_description.txt(项目根目录下)
# 使用:./script_name.sh <APP_NAME>
# 作者:豆包&ZhanGSKen<zhangsken@qq.com>
# ==============================================================================
# ==================== 常量定义 ====================
# 脚本退出码
EXIT_CODE_SUCCESS=0
EXIT_CODE_ERR_NO_APP_NAME=2
EXIT_CODE_ERR_WORK_DIR=1
EXIT_CODE_ERR_GIT_CHECK=1
EXIT_CODE_ERR_ADD_WINBOLL_TAG=1
# Gradle 任务(正式发布)
GRADLE_TASK_PUBLISH="assembleStageRelease"
# Gradle 任务(调试用,注释备用)
# GRADLE_TASK_DEBUG="assembleBetaDebug"
# aapt2本地覆盖参数
AAPT2_OVERRIDE_ARG="-Pandroid.aapt2FromMavenOverride=/data/data/com.termux/files/usr/bin/aapt2"
# 禁用Gradle守护进程
GRADLE_NO_DAEMON="--no-daemon"
# ==================== 函数定义 ====================
# 检查Git源码是否已完全提交(无未提交变更)
# 返回值:0=已完全提交,1=存在未提交变更
function checkGitSources() {
# 配置Git安全目录(解决权限问题)
git config --global --add safe.directory "$(pwd)"
# 检查是否有未提交的变更
if [[ -n $(git diff --stat) ]]; then
echo "[ERROR] Git源码存在未提交变更,请先提交所有修改!"
return 1
fi
echo "[INFO] Git源码检查通过:所有变更已提交。"
return 0
}
# 询问是否添加GitHub Workflows标签(当前逻辑注释,保留扩展能力)
# 返回值:1=用户选择是,0=用户选择否
function askAddWorkflowsTag() {
read -p "是否添加GitHub Workflows标签?(Y/n) " answer
if [[ $answer =~ ^[Yy]$ ]]; then
return 1
else
return 0
fi
}
# 添加WinBoLL正式标签
# 参数:$1=应用名称(项目根目录名)
# 返回值:0=标签添加成功,1=标签已存在/添加失败
function addWinBoLLTag() {
local app_name=$1
local build_prop_path="${app_name}/build.properties"
# 从build.properties中提取publishVersion
local publish_version=$(grep -o "publishVersion=.*" "${build_prop_path}" | awk -F '=' '{print $2}')
if [[ -z ${publish_version} ]]; then
echo "[ERROR] 未从${build_prop_path}中提取到publishVersion配置!"
return 1
fi
echo "[INFO] 从${build_prop_path}读取到publishVersion${publish_version}"
# 构造WinBoLL标签(格式:<APP_NAME>-v<publishVersion>
local tag="${app_name}-v${publish_version}"
echo "[INFO] 准备添加WinBoLL标签:${tag}"
# 检查标签是否已存在
if [[ "$(git tag -l ${tag})" == "${tag}" ]]; then
echo "[ERROR] WinBoLL标签${tag}已存在!"
return 1
fi
# 添加带注释的标签(注释来自app_update_description.txt
git tag -a "${tag}" -F "${app_name}/app_update_description.txt"
echo "[INFO] WinBoLL标签${tag}添加成功!"
return 0
}
# 添加GitHub Workflows Beta标签(当前逻辑注释,保留扩展能力)
# 参数:$1=应用名称(项目根目录名)
# 返回值:0=标签添加成功,1=标签已存在/添加失败
function addWorkflowsTag() {
local app_name=$1
local build_prop_path="${app_name}/build.properties"
# 从build.properties中提取baseBetaVersion
local base_beta_version=$(grep -o "baseBetaVersion=.*" "${build_prop_path}" | awk -F '=' '{print $2}')
if [[ -z ${base_beta_version} ]]; then
echo "[ERROR] 未从${build_prop_path}中提取到baseBetaVersion配置!"
return 1
fi
echo "[INFO] 从${build_prop_path}读取到baseBetaVersion${base_beta_version}"
# 构造Workflows标签(格式:<APP_NAME>-v<baseBetaVersion>-beta
local tag="${app_name}-v${base_beta_version}-beta"
echo "[INFO] 准备添加Workflows标签:${tag}"
# 检查标签是否已存在
if [[ "$(git tag -l ${tag})" == "${tag}" ]]; then
echo "[ERROR] Workflows标签${tag}已存在!"
return 1
fi
# 添加带注释的标签(注释来自app_update_description.txt
git tag -a "${tag}" -F "${app_name}/app_update_description.txt"
echo "[INFO] Workflows标签${tag}添加成功!"
return 0
}
# ==================== 主流程开始 ====================
echo "============================================="
echo " WinBoLL 应用发布脚本"
echo "============================================="
# 1. 检查应用名称参数是否指定
if [ -z "$1" ]; then
echo "[ERROR] 未指定应用名称!使用方式:${0} <APP_NAME>"
exit ${EXIT_CODE_ERR_NO_APP_NAME}
fi
APP_NAME=$1
echo "[INFO] 待发布应用名称:${APP_NAME}"
# 2. 检查并切换到项目根目录(确保build.properties存在)
echo "[INFO] 当前工作目录:$(pwd)"
if [[ ! -e "${APP_NAME}/build.properties" ]]; then
echo "[WARNING] 当前目录不存在${APP_NAME}/build.properties,尝试切换到上级目录..."
cd ..
echo "[INFO] 切换后工作目录:$(pwd)"
fi
# 验证最终工作目录是否正确
if [[ ! -e "${APP_NAME}/build.properties" ]]; then
echo "[ERROR] 工作目录错误!${APP_NAME}/build.properties 文件不存在。"
exit ${EXIT_CODE_ERR_WORK_DIR}
fi
echo "[INFO] 工作目录验证通过:${APP_NAME}/build.properties 存在。"
# 3. 检查Git源码状态
echo "---------------------------------------------"
echo " 步骤1:检查Git源码状态"
echo "---------------------------------------------"
checkGitSources
if [[ $? -ne ${EXIT_CODE_SUCCESS} ]]; then
echo "[ERROR] Git源码检查失败,脚本终止!"
exit ${EXIT_CODE_ERR_GIT_CHECK}
fi
# 4. 编译Stage Release版本APK(携带aapt2覆盖参数 + --no-daemon
echo "---------------------------------------------"
echo " 步骤2:编译Stage Release APK"
echo "---------------------------------------------"
echo "[INFO] 开始执行Gradle任务:${GRADLE_TASK_PUBLISH}"
# 调试用(注释正式任务,启用调试任务)
# bash gradlew ${AAPT2_OVERRIDE_ARG} ${GRADLE_NO_DAEMON} :${APP_NAME}:${GRADLE_TASK_DEBUG}
bash gradlew ${AAPT2_OVERRIDE_ARG} ${GRADLE_NO_DAEMON} :${APP_NAME}:${GRADLE_TASK_PUBLISH}
if [[ $? -ne ${EXIT_CODE_SUCCESS} ]]; then
echo "[ERROR] Gradle编译任务失败!"
exit 1
fi
echo "[INFO] Stage Release APK编译成功!"
# 5. 添加WinBoLL正式标签
echo "---------------------------------------------"
echo " 步骤3:添加WinBoLL标签"
echo "---------------------------------------------"
addWinBoLLTag ${APP_NAME}
if [[ $? -ne ${EXIT_CODE_SUCCESS} ]]; then
echo "[ERROR] WinBoLL标签添加失败,脚本终止!"
exit ${EXIT_CODE_ERR_ADD_WINBOLL_TAG}
fi
# 6. (可选)添加GitHub Workflows标签(当前逻辑注释,保留扩展能力)
# echo "---------------------------------------------"
# echo " 步骤4:添加Workflows标签(可选)"
# echo "---------------------------------------------"
# echo "是否添加GitHub Workflows Beta标签?(Y/n) "
# askAddWorkflowsTag
# nAskAddWorkflowsTag=$?
# if [[ ${nAskAddWorkflowsTag} -eq 1 ]]; then
# addWorkflowsTag ${APP_NAME}
# if [[ $? -ne ${EXIT_CODE_SUCCESS} ]]; then
# echo "[ERROR] Workflows标签添加失败,脚本终止!"
# exit 1
# fi
# fi
# 7. 清理更新描述文件
echo "---------------------------------------------"
echo " 步骤5:清理更新描述文件"
echo "---------------------------------------------"
echo "" > "${APP_NAME}/app_update_description.txt"
echo "[INFO] 已清空${APP_NAME}/app_update_description.txt"
# 8. 提交并推送源码与标签
echo "---------------------------------------------"
echo " 步骤6:提交并推送源码"
echo "---------------------------------------------"
git add .
git commit -m "<${APP_NAME}> 开始新的Stage版本开发。"
echo "[INFO] 源码提交成功,开始推送..."
# 推送源码到远程仓库
git push origin
# 推送标签到远程仓库
git push origin --tags
if [[ $? -eq ${EXIT_CODE_SUCCESS} ]]; then
echo "[INFO] 源码与标签推送成功!"
else
echo "[ERROR] 源码与标签推送失败!"
exit 1
fi
# ==================== 主流程结束 ====================
echo "============================================="
echo " WinBoLL 应用发布完成!"
echo "============================================="
exit ${EXIT_CODE_SUCCESS}
+20
View File
@@ -0,0 +1,20 @@
#!/usr/bin/bash
# aapt2本地覆盖参数
AAPT2_OVERRIDE_ARG="-Pandroid.aapt2FromMavenOverride=/data/data/com.termux/files/usr/bin/aapt2"
# Gradle禁用守护进程参数
GRADLE_NO_DAEMON="--no-daemon"
# 检查是否指定了将要发布的类库名称
# 使用 `-z` 命令检查变量是否为空
if [ -z "$1" ]; then
echo "No Library name specified : $0"
exit 2
fi
## 正式发布使用
git pull && bash gradlew ${AAPT2_OVERRIDE_ARG} ${GRADLE_NO_DAEMON} :$1:publishReleasePublicationToWinBoLLReleaseRepository && bash .winboll/bashCommitLibReleaseBuildFlagInfo.sh $1
## 调试使用
#bash gradlew ${AAPT2_OVERRIDE_ARG} ${GRADLE_NO_DAEMON} :$1:publishSnapshotWinBoLLPublicationToWinBoLLSnapshotRepository && bash .winboll/bashCommitLibReleaseBuildFlagInfo.sh $1
+56 -27
View File
@@ -122,7 +122,6 @@ android {
// 如果正在调试,就拷贝到 WinBoLL 备份管理文件夹
//
if(variant.flavorName == "beta"&&variant.buildType.name == "debug"){
//File outBuildBckDir = new File(fWinBoLLStudioDir, "/${rootProject.name}/${variant.buildType.name}")
File outBuildBckDir = new File(fWinBoLLStudioDir, "/" + project.rootDir.name + "/${variant.buildType.name}")
// 创建目标路径目录
if(!outBuildBckDir.exists()) {
@@ -130,6 +129,7 @@ android {
println "Output Folder Created.(WinBoLLStudio) : " + outBuildBckDir.getAbsolutePath()
}
if(outBuildBckDir.exists()) {
def targetApkFile = new File(outBuildBckDir, outputFileName)
copy{
from file.outputFile
into outBuildBckDir
@@ -138,6 +138,14 @@ android {
}
println "Output APK (WinBoLLStudio): " + outBuildBckDir.getAbsolutePath() + "/${outputFileName}"
}
// ========== 设置文件权限为775 ==========
if(targetApkFile.exists()){
exec {
commandLine 'chmod', '775', targetApkFile.absolutePath
}
println "Set file permission to 775 : ${targetApkFile.absolutePath}"
}
// 检查编译标志位配置
assert (winbollBuildProps['buildCount'] != null)
assert (winbollBuildProps['libraryProject'] != null)
@@ -160,8 +168,7 @@ android {
assert(libraryProjectBuildPropsFile.exists())
java.nio.file.Path sourceFilePath = winbollBuildPropsFile.toPath();
java.nio.file.Path targetFilePath = libraryProjectBuildPropsFile.toPath();
// 使用copyTo()方法复制文件,如果目标文件存在会被覆盖,可选参数可以选择不覆盖
java.nio.file.Files.copy(sourceFilePath, targetFilePath, java.nio.file.StandardCopyOption.REPLACE_EXISTING);
java.nio.file.Files.copy(sourceFilePath, targetFilePath, java.nio.file.StandardCopyOption.REPLACE_EXISTING);
println "\n\n>>> Library Project build.properties saved.\n\n";
}
@@ -172,16 +179,12 @@ android {
//
if(variant.flavorName == "stage"&&variant.buildType.name == "release"){
// 发布 APK 文件
//
// 截取版本号的版本字段为短版本名
String szVersionName = "${versionName}"
String[] szlistTemp = szVersionName.split("-")
String szShortVersionName = szlistTemp[0]
//String szCommonTagAPKName = "${rootProject.name}_" + szShortVersionName + ".apk"
String szCommonTagAPKName = project.rootDir.name + "_" + szShortVersionName + ".apk"
println "CommonTagAPKName is : " + szCommonTagAPKName
//File outTagDir = new File(fWinBoLLStudioDir, "/${rootProject.name}/tag/")
File outTagDir = new File(fWinBoLLStudioDir, "/" + project.rootDir.name + "/tag/")
// 创建目标路径目录
if(!outTagDir.exists()) {
@@ -192,12 +195,10 @@ android {
if(outTagDir.exists()) {
File targetAPK = new File(outTagDir, "${szCommonTagAPKName}")
if(targetAPK.exists()) {
// 标签版本APK文件已经存在,构建拷贝任务停止
assert (!targetAPK.exists())
// 可选择删除并继续输出APK文件
//delete targetAPK
}
// 复制一个备份
// 复制完整版APK
def fullApkFile = new File(outTagDir, outputFileName)
copy{
from file.outputFile
into outTagDir
@@ -206,7 +207,16 @@ android {
}
println "Output APK (Tags): "+ outTagDir.getAbsolutePath() + "/${outputFileName}"
}
// 复制一个并重命名为短版本名
// 设置权限775。
if(fullApkFile.exists()){
exec {
commandLine 'chmod', '775', fullApkFile.absolutePath
}
println "Set file permission to 775 : ${fullApkFile.absolutePath}"
}
// 复制短版本名APK
def shortApkFile = new File(outTagDir, szCommonTagAPKName)
copy{
from file.outputFile
into outTagDir
@@ -215,6 +225,14 @@ android {
}
println "Output APK (Tags): "+ outTagDir.getAbsolutePath() + "/${szCommonTagAPKName}"
}
// 设置权限775。
if(shortApkFile.exists()){
exec {
commandLine 'chmod', '775', shortApkFile.absolutePath
}
println "Set file permission to 775 : ${shortApkFile.absolutePath}"
}
// 检查编译标志位配置
assert (winbollBuildProps['stageCount'] != null)
assert (winbollBuildProps['publishVersion'] != null)
@@ -239,14 +257,11 @@ android {
fos.close();
if(winbollBuildProps['libraryProject'] != "") {
// 如果应用 build.properties 文件设置了类库模块项目文件名
// 就拷贝一份新的编译标志配置到类库项目文件夹
File libraryProjectBuildPropsFile = new File("$RootProjectDir/" + winbollBuildProps['libraryProject'] + "/build.properties")
assert(winbollBuildPropsFile.exists())
assert(libraryProjectBuildPropsFile.exists())
java.nio.file.Path sourceFilePath = winbollBuildPropsFile.toPath();
java.nio.file.Path targetFilePath = libraryProjectBuildPropsFile.toPath();
// 使用copyTo()方法复制文件,如果目标文件存在会被覆盖,可选参数可以选择不覆盖
java.nio.file.Files.copy(sourceFilePath, targetFilePath, java.nio.file.StandardCopyOption.REPLACE_EXISTING);
}
@@ -263,17 +278,12 @@ android {
// 如果正在调试发布版,就只生成和输出APK文件,不处理 Git 仓库提交与更新问题。
//
if(variant.flavorName == "stage"&&variant.buildType.name == "debug"){
// 发布 APK 文件
//
// 截取版本号的版本字段为短版本名
String szVersionName = "${versionName}"
String[] szlistTemp = szVersionName.split("-")
String szShortVersionName = szlistTemp[0]
//String szCommonTagAPKName = "${rootProject.name}_" + szShortVersionName + ".apk"
String szCommonTagAPKName = project.rootDir.name + "_" + szShortVersionName + ".apk"
println "CommonTagAPKName is : " + szCommonTagAPKName
//File outTagDir = new File(fWinBoLLStudioDir, "/${rootProject.name}/tag/")
File outTagDir = new File(fWinBoLLStudioDir, "/" + project.rootDir.name + "/${variant.buildType.name}/")
// 创建目标路径目录
if(!outTagDir.exists()) {
@@ -284,13 +294,11 @@ android {
if(outTagDir.exists()) {
File targetAPK = new File(outTagDir, "${szCommonTagAPKName}")
if(targetAPK.exists()) {
// 标签版本APK文件已经存在,构建拷贝任务停止
println '如果是在调试 Stage 版应用包构建,请删除(注:在debug目录)现有的 Stage 应用包('+targetAPK.getAbsolutePath()+')。再编译一次。'
assert (!targetAPK.exists())
// 可选择删除并继续输出APK文件
//delete targetAPK
}
// 复制一个备份
// 复制完整版APK
def debugFullApk = new File(outTagDir, outputFileName)
copy{
from file.outputFile
into outTagDir
@@ -299,7 +307,16 @@ android {
}
println "Output APK (Tags): "+ outTagDir.getAbsolutePath() + "/${outputFileName}"
}
// 复制一个并重命名为短版本名
// 权限设为775。
if(debugFullApk.exists()){
exec {
commandLine 'chmod', '775', debugFullApk.absolutePath
}
println "Set file permission to 775 : ${debugFullApk.absolutePath}"
}
// 复制短版本名APK
def debugShortApk = new File(outTagDir, szCommonTagAPKName)
copy{
from file.outputFile
into outTagDir
@@ -308,8 +325,13 @@ android {
}
println "Output APK (Tags): "+ outTagDir.getAbsolutePath() + "/${szCommonTagAPKName}"
}
//不保存编译标志配置
// 权限设为775
if(debugShortApk.exists()){
exec {
commandLine 'chmod', '775', debugShortApk.absolutePath
}
println "Set file permission to 775 : ${debugShortApk.absolutePath}"
}
}
}
@@ -328,6 +350,13 @@ android {
}
println "Output APK (Common): " + outCommonDir.getAbsolutePath() + "/${commandAPKName}"
}
// 额外输出文件设置775权限
if(apkFile.exists()){
exec {
commandLine 'chmod', '775', apkFile.absolutePath
}
println "Set file permission to 775 : ${apkFile.absolutePath}"
}
}
}
-201
View File
@@ -1,201 +0,0 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
+97
View File
@@ -0,0 +1,97 @@
# WinBoLL 源码 LICENSE-Private-Demo 规范说明书
# LICENSE-Private-Demo
# WinBoLL 源码公共转私有继承开发规范守则
## 核心声明
本文档**唯一核心设计目的**:通过文件标识、分支隔离、操作规范、责任界定四重约束,**从根源规避私有开发分支代码被人为合并、推送、提交至公共开源主流分支的风险**,明确人为操作失误、违规合并的全部责任归属,同时保证私有分支可正常同步、拉取公共主流分支的上游更新。
## 一、文件宗旨与风险防控说明
本文件为 WinBoLL 项目公共开源分支转为私有独立分支开发的**强制标准化操作手册与责任界定文件**,核心风控目标:
1. 严格隔离公共开源分支与私有开发分支,通过授权文件标记实现分支属性一眼可辨,杜绝人为操作混淆
2. **重点防控人为操作导致的私有分支代码违规合并、回合、推送至公共 ****`winboll`**** 主流分支**,从流程上封堵合并风险
3. 明确所有开发提交者的操作责任,违规合并公共分支的行为由操作人承担全部代码泄露、合规风险
4. 规范私有分支初始化全流程,保证私有分支仅可单向同步公共分支更新,禁止任何反向代码流入公共分支
## 二、公私分支授权标识文件定义(风控核心依据)
### 1. 公共开源分支唯一标识
**文件名:LICENSE**
- 仅允许存在于公共主流分支 `winboll` 及官方公共衍生分支
- 标识当前分支为**开源公开可贡献分支**,遵循原开源授权协议
- **严禁私有分支内保留、恢复此文件**,出现即判定分支属性异常
### 2. 私有开发分支唯一标识
**文件名:LICENSE-Private**
- 仅允许存在于私有开发分支,**绝对禁止出现在公共 ****`winboll`**** 分支**
- 标识当前分支为**私有闭源分支**,代码仅限内部使用,禁止公开、禁止对外贡献
- 为本分支私有属性的法定判定依据,也是禁止合并至公共分支的核心标记
## 三、分支管理与合并风控规则(强制遵守)
1. **公共主流分支**:固定为 `winboll`,为项目唯一开源主线,仅保留 `LICENSE` 文件,**禁止接收任何私有分支的合并、提交、推送请求**。
2. **私有开发分支**:统一从 `winboll` 分支检出,命名固定格式为 `private-demo-*`,与公共分支物理隔离。
3. **核心合并风控铁则**
- 私有分支 → 公共分支:**永久禁止任何形式的合并、推送、PR 提交、代码回合,人为操作也绝不允许**
- 公共分支 → 私有分支:允许正常拉取、同步上游更新,不影响私有开发迭代
4. 所有仓库提交者、合并操作者,均视为已阅读并完全认可本规则,**人为执行私有分支向公共分支的合并操作,由操作人承担全部代码泄露、合规违约、项目安全风险**。
## 四、公共转私有标准化操作步骤(锁死合并风险)
请严格按顺序执行,每一步均为风控必要环节,不可跳过、不可修改顺序。
1. 基于公共主流分支 `winboll`,新建私有开发分支,严格使用 `private-demo-*` 命名,从名称上明确分支私有属性,避免人为混淆。
2. 本地仓库切换至新建私有分支,确认当前分支名称、检出来源无误。
3. **永久删除项目根目录公共授权文件 ****`LICENSE`**,彻底移除公共分支标识,断绝误合并的标识漏洞。
4. 将本规范文件 `LICENSE-Private-Demo` 复制并重命名为 `LICENSE-Private`,作为私有分支生效授权文件。
5. 将以上所有变更执行一次性 Git 提交,**提交信息必须固定使用以下内容,不可修改**:
> 初始化私有开发分支,已切换私有授权文件,本分支禁止任何人为合并、推送至 winboll 公共分支
>
>
6. 提交完成后,本分支正式转为私有开发状态,后续所有代码提交、分支合并、版本迭代,均严禁指向公共 `winboll` 分支。
## 五、人为操作责任界定(核心补充条款)
1. 本分支所有开发者、代码提交者、分支合并操作者,均视为**完全知晓本分支的私有属性与合并禁令**,自愿遵守本规范全部约束。
2. **无论故意或过失,凡是人为执行私有分支向公共 ****`winboll`**** 分支的合并、推送、PR 提交、代码回合操作,全部责任由执行操作的本人独立承担**,项目方不承担任何因人为违规操作导致的代码泄露、开源合规、版本污染风险。
3. 仓库管理员需严格校验合并请求的分支标识与授权文件,发现带有 `LICENSE-Private` 标记的分支申请合并至公共分支,一律直接拒绝,并记录操作人信息。
4. 分支属性校验以根目录授权文件为唯一标准:只要分支内存在 `LICENSE-Private` 文件,就绝对禁止向公共分支发起任何合并操作。
## 六、分支状态校验与异常处理
- 合规公共分支:仅存在 `LICENSE`,无 `LICENSE-Private`
- 合规私有分支:仅存在 `LICENSE-Private`,无 `LICENSE`
- 异常状态:两个文件同时存在 / 均不存在 → 立即停止开发与提交,按本规范重置分支状态,严禁执行任何合并操作
> (注:文档部分内容可能由 AI 生成)
+1 -1
View File
@@ -20,7 +20,7 @@ WinBoLL AndroidX 可视化元素类库。
1. Fork 本仓库
2. 新建 Feat_xxx 分支
3. 提交代码 : ZhanGSKen(ZhanGSKen<zhangsken@188.com>)
3. 提交代码 : ZhanGSKen(ZhanGSKen<ZhanGSKen@QQ.COM>)
4. 新建 Pull Request
+4 -5
View File
@@ -26,7 +26,10 @@ android {
applicationId "cc.winboll.studio.aes"
minSdkVersion 26
targetSdkVersion 30
versionCode 1
//1. Android 官方规则
//-  versionCode  类型:int 整型
//- Java int 最大值:2147483647
versionCode 1520000
// versionName 更新后需要手动设置
// 项目模块目录的 build.gradle 文件的 stageCount=0
// Gradle编译环境下合起来的 versionName 就是 "${versionName}.0"
@@ -36,10 +39,6 @@ android {
}
}
// 米盟 SDK
packagingOptions {
doNotStrip "*/*/libmimo_1011.so"
}
}
dependencies {
+4 -4
View File
@@ -1,8 +1,8 @@
#Created by .winboll/winboll_app_build.gradle
#Tue May 12 13:11:28 HKT 2026
stageCount=4
#Sat Jul 11 20:07:00 HKT 2026
stageCount=19
libraryProject=libaes
baseVersion=15.20
publishVersion=15.20.3
publishVersion=15.20.18
buildCount=0
baseBetaVersion=15.20.4
baseBetaVersion=15.20.19
@@ -65,7 +65,7 @@ public class AboutActivity extends BaseWinBoLLActivity {
appInfo.setAppName(getString(R.string.app_name));
appInfo.setAppIcon(R.drawable.ic_winboll);
appInfo.setAppDescription(getString(R.string.app_description));
appInfo.setAppGitName("AES");
appInfo.setAppGitName("WinBoLL");
appInfo.setAppGitOwner("Studio");
appInfo.setAppGitAPPBranch(branchName);
appInfo.setAppGitAPPSubProjectFolder(branchName);
@@ -5,10 +5,14 @@ package cc.winboll.studio.aes;
* @Date 2024/06/13 19:03:58
* @Describe AES应用类
*/
import android.view.Gravity;
import cc.winboll.studio.libaes.utils.AESThemeUtil;
import cc.winboll.studio.libaes.utils.WinBoLLActivityManager;
import cc.winboll.studio.libappbase.CrashActivity;
import cc.winboll.studio.libappbase.GlobalApplication;
import cc.winboll.studio.libappbase.ToastUtils;
import cc.winboll.studio.libappbase.utils.CrashHandleNotifyUtils;
import java.io.PrintWriter;
import java.io.StringWriter;
public class App extends GlobalApplication {
@@ -17,13 +21,25 @@ public class App extends GlobalApplication {
@Override
public void onCreate() {
super.onCreate();
setIsDebugging(BuildConfig.DEBUG);
//setIsDebugging(false);
WinBoLLActivityManager.init(this);
try {
super.onCreate();
ToastUtils.init(this);
WinBoLLActivityManager.init(this);
AESThemeUtil.init(null);
} catch (Throwable e) {
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
e.printStackTrace(pw);
pw.close();
String stackTraceStr = sw.toString();
CrashHandleNotifyUtils.handleUncaughtException(
this,
getPackageName(),
stackTraceStr,
CrashActivity.class
);
}
// 初始化 Toast 框架
ToastUtils.init(this);
}
@Override
@@ -84,11 +84,12 @@ public class MainActivity extends DrawerFragmentActivity {
@Override
public boolean onCreateOptionsMenu(Menu menu) {
super.onCreateOptionsMenu(menu);
getMenuInflater().inflate(R.menu.toolbar_main, menu);
// if(App.isDebugging()) {
// getMenuInflater().inflate(cc.winboll.studio.libaes.R.menu.toolbar_studio_debug, menu);
// }
return super.onCreateOptionsMenu(menu);
return true;
}
@Override
@@ -1,11 +1,7 @@
package cc.winboll.studio.aes;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import cc.winboll.studio.libaes.views.ADsControlView;
/**
* @Author ZhanGSKen&豆包大模型<zhangsken@qq.com>
@@ -20,7 +16,7 @@ public class SettingsActivity extends Activity {
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_settings);
ADsControlView adsControlView = (ADsControlView) findViewById(R.id.ads_control_view);
//ADsControlView adsControlView = (ADsControlView) findViewById(R.id.ads_control_view);
// adsControlView.setOnAdsModeSelectedListener(new ADsControlView.OnAdsModeSelectedListener() {
// @Override
@@ -6,12 +6,5 @@
android:layout_width="match_parent"
android:layout_height="match_parent" android:background="@drawable/bg_container_border">
<cc.winboll.studio.libaes.views.ADsControlView
android:id="@+id/ads_control_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/bg_frame"
android:padding="10dp"/>
</LinearLayout>
+1 -1
View File
@@ -5,7 +5,7 @@
</style>
<style name="MyDebugActivityTheme" parent="Theme.AppCompat.NoActionBar">
<item name="android:statusBarColor">@color/toolbarBackgroundColor</item>
<item name="android:statusBarColor">?attr/colorPrimaryDark</item>
<item name="colorTittle">@color/mainWindowTextColor</item>
<item name="colorTittleBackgound">@color/toolbarBackgroundColor</item>
<item name="colorText">@color/debugTextColor</item>
+1 -1
View File
@@ -5,7 +5,7 @@
</style>
<style name="MyDebugActivityTheme" parent="Theme.AppCompat.Light.NoActionBar">
<item name="android:statusBarColor">@color/toolbarBackgroundColor</item>
<item name="android:statusBarColor">?attr/colorPrimaryDark</item>
<item name="colorTittle">@color/mainWindowTextColor</item>
<item name="colorTittleBackgound">@color/toolbarBackgroundColor</item>
<item name="colorText">@color/debugTextColor</item>
+1 -1
View File
@@ -20,7 +20,7 @@ WinBoLL 安卓手机端安卓应用开发基础类库。
1. Fork 本仓库
2. 新建 Feat_xxx 分支
3. 提交代码 : ZhanGSKen(ZhanGSKen<zhangsken@188.com>)
3. 提交代码 : ZhanGSKen(ZhanGSKen<ZhanGSKen@QQ.COM>)
4. 新建 Pull Request
+4 -1
View File
@@ -26,7 +26,10 @@ android {
applicationId "cc.winboll.studio.appbase"
minSdkVersion 26
targetSdkVersion 30
versionCode 1
//1. Android 官方规则
//-  versionCode  类型:int 整型
//- Java int 最大值:2147483647
versionCode 1520000
// versionName 更新后需要手动设置
// .winboll/winbollBuildProps.properties 文件的 stageCount=0
// Gradle编译环境下合起来的 versionName 就是 "${versionName}.0"
+4 -4
View File
@@ -1,8 +1,8 @@
#Created by .winboll/winboll_app_build.gradle
#Sun May 17 16:16:36 HKT 2026
stageCount=16
#Sun Jun 28 20:55:33 HKT 2026
stageCount=35
libraryProject=libappbase
baseVersion=15.20
publishVersion=15.20.15
publishVersion=15.20.34
buildCount=0
baseBetaVersion=15.20.16
baseBetaVersion=15.20.35
@@ -1,8 +1,11 @@
package cc.winboll.studio.appbase;
import cc.winboll.studio.libappbase.CrashActivity;
import cc.winboll.studio.libappbase.GlobalApplication;
import cc.winboll.studio.libappbase.ToastUtils;
import cc.winboll.studio.libappbase.BuildConfig;
import cc.winboll.studio.libappbase.utils.CrashHandleNotifyUtils;
import java.io.PrintWriter;
import java.io.StringWriter;
/**
* @Author ZhanGSKen<zhangsken@qq.com>
@@ -21,16 +24,24 @@ public class App extends GlobalApplication {
*/
@Override
public void onCreate() {
super.onCreate();
// 如果应用不在调试状态,就根据编译类型设置调试状态
if (isDebugging() != true) {
setIsDebugging(BuildConfig.DEBUG);
}
// release 版调试码
//setIsDebugging(!BuildConfig.DEBUG);
// 初始化 Toast 工具类(传入应用全局上下文,确保 Toast 可在任意地方调用)
ToastUtils.init(getApplicationContext());
try {
super.onCreate();
// 初始化 Toast 工具类(传入应用全局上下文,确保 Toast 可在任意地方调用)
ToastUtils.init(getApplicationContext());
} catch (Throwable e) {
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
e.printStackTrace(pw);
pw.close();
String stackTraceStr = sw.toString();
CrashHandleNotifyUtils.handleUncaughtException(
this,
getPackageName(),
stackTraceStr,
CrashActivity.class
);
}
}
/**
@@ -2,8 +2,8 @@ package cc.winboll.studio.appbase.model;
import android.util.JsonReader;
import android.util.JsonWriter;
import cc.winboll.studio.libappbase.BaseBean;
import cc.winboll.studio.libappbase.LogUtils;
import cc.winboll.studio.libappbase.models.libs1520000.BaseBean;
import java.io.IOException;
/**
-34
View File
@@ -1,34 +0,0 @@
# AutoNFC
#### 介绍
NFC 卡应用,主要管理 NFC 卡接触手机的动作响应,NFC 接触状态用于作为其他应用激活活动动作的启动令牌。
#### 软件架构
适配安卓应用 [AIDE Pro] 的 Gradle 编译结构。
也适配安卓应用 [AndroidIDE] 的 Gradle 编译结构。
#### Gradle 编译说明
调试版编译命令 gradle assembleBetaDebug
阶段版编译命令 bash .winboll/bashPublishAPKAddTag.sh autonfc
#### 使用说明
#### 参与贡献
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/)
#### 参考文档
-8
View File
@@ -1,8 +0,0 @@
#Created by .winboll/winboll_app_build.gradle
#Mon Mar 16 18:30:19 GMT 2026
stageCount=0
libraryProject=
baseVersion=15.11
publishVersion=15.0.0
buildCount=54
baseBetaVersion=15.0.1
-21
View File
@@ -1,21 +0,0 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# 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 *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
-51
View File
@@ -1,51 +0,0 @@
<?xml version='1.0' encoding='utf-8'?>
<manifest
xmlns:android="http://schemas.android.com/apk/res/android"
package="cc.winboll.studio.autonfc">
<uses-permission android:name="android.permission.NFC"/>
<uses-feature
android:name="android.hardware.nfc"
android:required="true"/>
<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>
<activity
android:name=".nfc.NFCInterfaceActivity"
android:launchMode="singleTop">
<intent-filter>
<action android:name="android.nfc.action.NDEF_DISCOVERED"/>
<category android:name="android.intent.category.DEFAULT"/>
<data android:mimeType="*/*"/>
</intent-filter>
</activity>
<!-- NFC 绑定服务 -->
<service
android:name=".nfc.AutoNFCService"
android:exported="false"/>
<meta-data
android:name="android.max_aspect"
android:value="4.0"/>
</application>
</manifest>
@@ -1,344 +0,0 @@
package cc.winboll.studio.autonfc;
import android.app.Activity;
import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageInfo;
import android.content.res.Resources;
import android.graphics.Typeface;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.text.TextUtils;
import android.util.Log;
import android.view.Gravity;
import android.view.Menu;
import android.view.MenuItem;
import android.view.ViewGroup;
import android.widget.HorizontalScrollView;
import android.widget.ScrollView;
import android.widget.TextView;
import android.widget.Toast;
import cc.winboll.studio.libappbase.GlobalApplication;
import cc.winboll.studio.libappbase.ToastUtils;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.Closeable;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.Thread.UncaughtExceptionHandler;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.concurrent.atomic.AtomicBoolean;
public class App extends GlobalApplication {
private static Handler MAIN_HANDLER = new Handler(Looper.getMainLooper());
@Override
public void onCreate() {
super.onCreate();
// 初始化 Toast 框架
// ToastUtils.init(this);
// // 设置 Toast 布局样式
// //ToastUtils.setView(R.layout.view_toast);
// ToastUtils.setStyle(new WhiteToastStyle());
// ToastUtils.setGravity(Gravity.BOTTOM, 0, 200);
//
//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();
}
}
}
@@ -1,180 +0,0 @@
package cc.winboll.studio.autonfc;
import android.app.PendingIntent;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.nfc.NfcAdapter;
import android.os.Bundle;
import android.os.IBinder;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
import cc.winboll.studio.autonfc.nfc.ActionDialog;
import cc.winboll.studio.autonfc.nfc.AutoNFCService;
import cc.winboll.studio.autonfc.nfc.NFCInterfaceActivity;
import cc.winboll.studio.libappbase.LogActivity;
import cc.winboll.studio.libappbase.LogUtils;
public class MainActivity extends AppCompatActivity {
public static final String TAG = "MainActivity";
private NfcAdapter mNfcAdapter;
private PendingIntent mPendingIntent;
private AutoNFCService mService;
private boolean mBound = false;
// 服务连接
private ServiceConnection mConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
AutoNFCService.LocalBinder binder = (AutoNFCService.LocalBinder) service;
mService = binder.getService();
mBound = true;
LogUtils.d(TAG, "onServiceConnected: 服务已绑定");
// 关键:把 Activity 传给 Service,用于回调
mService.attachActivity(MainActivity.this);
}
@Override
public void onServiceDisconnected(ComponentName name) {
mBound = false;
mService = null;
LogUtils.d(TAG, "onServiceDisconnected: 服务已断开");
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Toolbar toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
// 初始化 NFC
mNfcAdapter = NfcAdapter.getDefaultAdapter(this);
Intent nfcIntent = new Intent(this, getClass()).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
mPendingIntent = PendingIntent.getActivity(this, 0, nfcIntent, 0);
LogUtils.d(TAG, "onCreate() -> NFC 监听已绑定到 MainActivity");
}
@Override
protected void onStart() {
super.onStart();
// 绑定服务
Intent intent = new Intent(this, AutoNFCService.class);
bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
LogUtils.d(TAG, "onStart: 绑定服务");
}
@Override
protected void onStop() {
super.onStop();
// 解绑服务
if (mBound) {
unbindService(mConnection);
mBound = false;
LogUtils.d(TAG, "onStop: 解绑服务");
}
}
@Override
protected void onResume() {
super.onResume();
LogUtils.d(TAG, "onResume() -> 开启 NFC 前台分发");
if (mNfcAdapter != null) {
mNfcAdapter.enableForegroundDispatch(this, mPendingIntent, null, null);
}
}
@Override
protected void onPause() {
super.onPause();
LogUtils.d(TAG, "onPause() -> 关闭 NFC 前台分发");
if (mNfcAdapter != null) {
mNfcAdapter.disableForegroundDispatch(this);
}
}
// NFC 卡片靠近唯一入口
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
LogUtils.d(TAG, "onNewIntent() -> 检测到 NFC 卡片");
// 把 NFC 事件交给 Service 处理
if (mBound && mService != null) {
mService.handleNfcIntent(intent);
} else {
LogUtils.e(TAG, "服务未绑定,无法处理 NFC");
}
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.main_menu, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
int id = item.getItemId();
if (id == R.id.menu_log) {
LogActivity.startLogActivity(this);
return true;
}
return super.onOptionsItemSelected(item);
}
public void onNFCInterfaceActivity(View view) {
startActivity(new Intent(this, NFCInterfaceActivity.class));
}
// ========================= 【新增】关键方法:由 Service 回调来弹出对话框 =========================
/**
* Service 解析完 NFC 数据后,回调此方法在 Activity 中弹出对话框
*/
public void showNfcActionDialog(final String nfcData) {
LogUtils.d(TAG, "showNfcActionDialog() -> Activity 存活,安全弹出对话框");
// Activity 正在运行,直接弹框,绝对不会报 BadTokenException
final ActionDialog dialog = new ActionDialog(this);
dialog.setNfcData(nfcData);
dialog.setButtonClickListener(new ActionDialog.OnButtonClickListener() {
@Override
public void onBuildClick() {
LogUtils.d(TAG, "点击 Build");
if (mService != null) {
mService.executeTermuxCommand(AutoNFCService.ACTION_BUILD, nfcData);
}
dialog.dismiss();
}
@Override
public void onViewClick() {
LogUtils.d(TAG, "点击 View");
if (mService != null) {
mService.executeTermuxCommand(AutoNFCService.ACTION_BUILD_VIEW, nfcData);
}
dialog.dismiss();
}
@Override
public void onCancelClick() {
dialog.dismiss();
}
});
dialog.show();
}
}
@@ -1,66 +0,0 @@
package cc.winboll.studio.autonfc.models;
/**
* @Author 豆包&ZhanGSKen<zhangsken@qq.com>
* @Date 2026/03/16 09:38
*/
public class NfcTermuxCmd {
private String script; // 要执行的预制脚本名(如 auth.sh)
private String[] args; // 脚本参数
private String workDir; // 工作目录
private boolean background; // 是否后台执行
private String resultDir; // 结果输出目录(可为 null
public NfcTermuxCmd() {
}
public NfcTermuxCmd(String script, String[] args, String workDir, boolean background, String resultDir) {
this.script = script;
this.args = args;
this.workDir = workDir;
this.background = background;
this.resultDir = resultDir;
}
public String getScript() {
return script;
}
public void setScript(String script) {
this.script = script;
}
public String[] getArgs() {
return args;
}
public void setArgs(String[] args) {
this.args = args;
}
public String getWorkDir() {
return workDir;
}
public void setWorkDir(String workDir) {
this.workDir = workDir;
}
public boolean isBackground() {
return background;
}
public void setBackground(boolean background) {
this.background = background;
}
public String getResultDir() {
return resultDir;
}
public void setResultDir(String resultDir) {
this.resultDir = resultDir;
}
}
@@ -1,123 +0,0 @@
package cc.winboll.studio.autonfc.nfc;
import android.app.Dialog;
import android.content.Context;
import android.view.View;
import android.widget.Button;
import android.widget.LinearLayout;
import cc.winboll.studio.autonfc.R;
import cc.winboll.studio.libappbase.LogUtils;
/**
* 自定义对话框类,用于与用户交互,展示 NFC 相关操作选项
* 兼容 Java 7 语法
*
* @author 豆包&ZhanGSKen
* @create 2025-08-15
* @lastModify 2026-03-17
*/
public class ActionDialog extends Dialog {
private static final String TAG = "ActionDialog";
private String mNfcData;
private OnButtonClickListener mClickListener;
/**
* 构造函数
*/
public ActionDialog(Context context) {
super(context);
initDialog();
}
/**
* 设置 NFC 数据
*/
public void setNfcData(String nfcData) {
this.mNfcData = nfcData;
LogUtils.d(TAG, "setNfcData() -> " + nfcData);
}
/**
* 设置点击监听
*/
public void setButtonClickListener(OnButtonClickListener listener) {
this.mClickListener = listener;
}
/**
* 初始化布局
*/
private void initDialog() {
setTitle("请选择操作");
LinearLayout layout = new LinearLayout(getContext());
layout.setOrientation(LinearLayout.VERTICAL);
layout.setPadding(20, 20, 20, 20);
addButtons(layout);
setContentView(layout);
}
/**
* 添加按钮
*/
private void addButtons(LinearLayout layout) {
// Build 按钮
Button btnBuild = createButton("Build", new View.OnClickListener() {
@Override
public void onClick(View v) {
LogUtils.d(TAG, "点击 Build");
if (mClickListener != null) {
mClickListener.onBuildClick();
}
}
});
layout.addView(btnBuild);
// View 按钮
Button btnView = createButton("View", new View.OnClickListener() {
@Override
public void onClick(View v) {
LogUtils.d(TAG, "点击 View");
if (mClickListener != null) {
mClickListener.onViewClick();
}
}
});
layout.addView(btnView);
// 取消按钮
Button btnCancel = createButton("Cancel", new View.OnClickListener() {
@Override
public void onClick(View v) {
LogUtils.d(TAG, "点击 Cancel");
dismiss();
}
});
layout.addView(btnCancel);
}
/**
* 创建按钮
*/
private Button createButton(String text, View.OnClickListener listener) {
Button button = new Button(getContext());
button.setText(text);
button.setPadding(10, 10, 10, 10);
button.setOnClickListener(listener);
return button;
}
/**
* 回调接口
*/
public interface OnButtonClickListener {
void onBuildClick();
void onViewClick();
void onCancelClick();
}
}
@@ -1,202 +0,0 @@
package cc.winboll.studio.autonfc.nfc;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.nfc.NdefMessage;
import android.nfc.NdefRecord;
import android.nfc.NfcAdapter;
import android.nfc.Tag;
import android.nfc.tech.Ndef;
import android.os.Binder;
import android.os.IBinder;
import cc.winboll.studio.autonfc.MainActivity;
import cc.winboll.studio.libappbase.LogUtils;
import cc.winboll.studio.libappbase.ToastUtils;
import java.nio.charset.Charset;
import java.util.Arrays;
public class AutoNFCService extends Service {
public static final String TAG = "AutoNFCService";
// ================= 已修改:更新为 Beta 包名 =================
public static final String ACTION_BUILD = "cc.winboll.studio.winboll.termux.NfcTermuxBridgeActivity.ACTION_BUILD";
public static final String ACTION_BUILD_VIEW = "cc.winboll.studio.winboll.termux.NfcTermuxBridgeActivity.ACTION_BUILD_VIEW";
private final IBinder mBinder = new LocalBinder();
private String mNfcData;
private MainActivity mActivity; // 持有 Activity 引用,用于回调
// ========================= 生命周期 =========================
@Override
public void onCreate() {
super.onCreate();
LogUtils.d(TAG, "onCreate() -> 服务创建");
// 移除:startForeground(NOTIFICATION_ID, buildNotification());
}
@Override
public void onDestroy() {
super.onDestroy();
LogUtils.d(TAG, "onDestroy() -> 服务已停止");
mActivity = null; // 释放引用
}
// ========================= 服务绑定 =========================
@Override
public IBinder onBind(Intent intent) {
LogUtils.d(TAG, "onBind() -> 服务被绑定");
return mBinder;
}
@Override
public boolean onUnbind(Intent intent) {
LogUtils.d(TAG, "onUnbind() -> 服务解绑");
// 移除:stopForeground(true);
stopSelf();
return super.onUnbind(intent);
}
// ========================= 对外暴露方法 =========================
/**
* 绑定 Activity,用于回调显示对话框
*/
public void attachActivity(MainActivity activity) {
this.mActivity = activity;
}
/**
* 处理 NFC 意图
*/
public void handleNfcIntent(Intent intent) {
LogUtils.d(TAG, "handleNfcIntent() -> 开始处理");
if (intent == null) {
LogUtils.e(TAG, "handleNfcIntent() -> 参数 intent 为空");
return;
}
String action = intent.getAction();
LogUtils.d(TAG, "handleNfcIntent() -> Action = " + action);
if (NfcAdapter.ACTION_NDEF_DISCOVERED.equals(action)
|| NfcAdapter.ACTION_TECH_DISCOVERED.equals(action)
|| NfcAdapter.ACTION_TAG_DISCOVERED.equals(action)) {
LogUtils.d(TAG, "handleNfcIntent() -> 匹配 NFC 动作");
Tag tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
if (tag == null) {
LogUtils.e(TAG, "handleNfcIntent() -> Tag 为空");
return;
}
LogUtils.d(TAG, "handleNfcIntent() -> Tag ID = " + bytesToHexString(tag.getId()));
LogUtils.d(TAG, "handleNfcIntent() -> Tech List = " + Arrays.toString(tag.getTechList()));
parseNdefData(tag);
}
}
// ========================= 内部业务 =========================
private void parseNdefData(Tag tag) {
LogUtils.d(TAG, "parseNdefData() -> 开始解析");
if (tag == null) return;
Ndef ndef = Ndef.get(tag);
if (ndef == null) {
LogUtils.e(TAG, "parseNdefData() -> 不支持 NDEF 格式");
return;
}
try {
ndef.connect();
NdefMessage msg = ndef.getNdefMessage();
if (msg == null || msg.getRecords() == null || msg.getRecords().length == 0) {
LogUtils.w(TAG, "parseNdefData() -> 卡片无数据");
return;
}
NdefRecord record = msg.getRecords()[0];
byte[] payload = record.getPayload();
int langLen = payload[0] & 0x3F;
int start = 1 + langLen;
if (start < payload.length) {
mNfcData = new String(payload, start, payload.length - start, Charset.forName("UTF-8"));
LogUtils.d(TAG, "parseNdefData() -> 读卡成功: " + mNfcData);
// 关键:回调给 Activity 弹框,此时 Activity 一定是存活状态
if (mActivity != null) {
mActivity.showNfcActionDialog(mNfcData);
}
}
} catch (Exception e) {
LogUtils.e(TAG, "parseNdefData() -> 读取失败", e);
} finally {
try {
ndef.close();
} catch (Exception e) {
// 忽略关闭异常
}
}
}
/**
* 执行 Termux 命令
*/
public void executeTermuxCommand(String action, String nfcData) {
LogUtils.d(TAG, "executeTermuxCommand() -> 开始执行");
if (nfcData == null || nfcData.isEmpty()) {
ToastUtils.show("数据错误");
return;
}
try {
LogUtils.d(TAG, "executeTermuxCommand() -> 发送指令: " + nfcData);
Intent bridgeIntent = new Intent(action);
// ================= 已修改:使用 Beta 包名 =================
bridgeIntent.setClassName(
"cc.winboll.studio.winboll.beta",
"cc.winboll.studio.winboll.termux.NfcTermuxBridgeActivity"
);
bridgeIntent.putExtra(Intent.EXTRA_TEXT, nfcData);
bridgeIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(bridgeIntent);
ToastUtils.show("指令已发送");
} catch (Exception e) {
LogUtils.e(TAG, "executeTermuxCommand() -> 发送失败", e);
ToastUtils.show("发送失败");
}
}
// ========================= 工具方法 =========================
private String bytesToHexString(byte[] bytes) {
if (bytes == null || bytes.length == 0) return "";
StringBuilder sb = new StringBuilder();
for (byte b : bytes) {
sb.append(String.format("%02X", b));
}
return sb.toString();
}
// ========================= Binder =========================
public class LocalBinder extends Binder {
public AutoNFCService getService() {
return AutoNFCService.this;
}
}
}
@@ -1,230 +0,0 @@
package cc.winboll.studio.autonfc.nfc;
import android.app.Activity;
import android.app.PendingIntent;
import android.content.Intent;
import android.nfc.NfcAdapter;
import android.nfc.Tag;
import android.os.Bundle;
import android.view.View;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;
import cc.winboll.studio.autonfc.R;
import cc.winboll.studio.autonfc.models.NfcTermuxCmd;
import cc.winboll.studio.libappbase.LogUtils;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
public class NFCInterfaceActivity extends Activity {
public static final String TAG = "NFCInterfaceActivity";
private EditText et_script;
private EditText et_args;
private EditText et_workDir;
private EditText et_background;
private EditText et_resultDir;
private TextView tvResult;
private TextView tvStatus;
private NfcAdapter mNfcAdapter;
private PendingIntent mNfcPendingIntent;
private Tag mCurrentTag;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_nfc_interface);
initView();
initNfc();
}
private void initView() {
et_script = findViewById(R.id.et_script);
et_args = findViewById(R.id.et_args);
et_workDir = findViewById(R.id.et_workDir);
et_background = findViewById(R.id.et_background);
et_resultDir = findViewById(R.id.et_resultDir);
tvResult = findViewById(R.id.tv_result);
tvStatus = findViewById(R.id.tv_status);
}
private void initNfc() {
mNfcAdapter = NfcAdapter.getDefaultAdapter(this);
if (mNfcAdapter == null) {
tvStatus.setText("设备不支持NFC");
return;
}
if (!mNfcAdapter.isEnabled()) {
tvStatus.setText("请开启NFC");
return;
}
Intent nfcIntent = new Intent(this, getClass()).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
mNfcPendingIntent = PendingIntent.getActivity(this, 0, nfcIntent, PendingIntent.FLAG_UPDATE_CURRENT);
tvStatus.setText("NFC已启动,等待卡片靠近");
}
@Override
protected void onResume() {
super.onResume();
if (mNfcAdapter != null && mNfcAdapter.isEnabled()) {
mNfcAdapter.enableForegroundDispatch(this, mNfcPendingIntent, null, null);
}
}
@Override
protected void onPause() {
super.onPause();
if (mNfcAdapter != null) {
mNfcAdapter.disableForegroundDispatch(this);
}
mCurrentTag = null;
}
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
mCurrentTag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
if (mCurrentTag == null) return;
tvStatus.setText("卡片已连接,解析中...");
readNfc();
}
// -------------------------------------------------------------------------
// 读取 NFC(完全委托给工具类)
// -------------------------------------------------------------------------
private void readNfc() {
try {
NfcTermuxCmd cmd = NfcUtils.readTag(mCurrentTag);
if (cmd == null) {
tvStatus.setText("读取成功:标签为空");
tvResult.setText("");
// 清空窗体
clearUiFields();
return;
}
// 核心改动:读取成功后,同时更新详情显示 和 窗体输入框
updateUiWithCmd(cmd);
} catch (Exception e) {
LogUtils.e(TAG, "readNfc 失败", e);
tvStatus.setText("读取失败:" + e.getMessage());
// 出错时清空窗体
clearUiFields();
}
}
// -------------------------------------------------------------------------
// 新增:根据读取到的 Cmd 填充 UI(详情 + 窗体)
// -------------------------------------------------------------------------
private void updateUiWithCmd(NfcTermuxCmd cmd) {
if (cmd == null) return;
String time = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.CHINA).format(new Date());
String show = "【读取时间】 " + time + "\n\n"
+ "【解析结果】\n"
+ "script: " + cmd.getScript() + "\n"
+ "args: " + (cmd.getArgs() != null ? String.join(", ", cmd.getArgs()) : "[]") + "\n"
+ "workDir: " + cmd.getWorkDir() + "\n"
+ "background: " + cmd.isBackground() + "\n"
+ "resultDir: " + cmd.getResultDir();
tvResult.setText(show);
tvStatus.setText("读取成功!");
// 👇 关键逻辑:自动填入窗体(每次读取后都会覆盖输入框)
et_script.setText(cmd.getScript() != null ? cmd.getScript() : "");
et_args.setText(cmd.getArgs() != null ? String.join(",", cmd.getArgs()) : "");
et_workDir.setText(cmd.getWorkDir() != null ? cmd.getWorkDir() : "");
et_background.setText(String.valueOf(cmd.isBackground()));
et_resultDir.setText(cmd.getResultDir() != null ? cmd.getResultDir() : "");
}
// -------------------------------------------------------------------------
// 辅助:清空所有输入框
// -------------------------------------------------------------------------
private void clearUiFields() {
et_script.setText("");
et_args.setText("");
et_workDir.setText("");
et_background.setText("");
et_resultDir.setText("");
}
// -------------------------------------------------------------------------
// 写入按钮(委托给工具类)
// -------------------------------------------------------------------------
public void onWriteClick(View view) {
if (mCurrentTag == null) {
showToast("请先靠近卡片");
return;
}
try {
NfcTermuxCmd cmd = buildCmdFromUI();
NfcUtils.writeTag(mCurrentTag, cmd);
tvStatus.setText("写入成功!");
showToast("写入成功");
readNfc(); // 写入后重读,此时会自动填入窗体
} catch (Exception e) {
LogUtils.e(TAG, "写入失败", e);
tvStatus.setText("写入失败:" + e.getMessage());
showToast("写入失败");
}
}
// -------------------------------------------------------------------------
// 填充调试数据
// -------------------------------------------------------------------------
public void onFillTestDataClick(View view) {
String testJson = "{\"script\":\"BuildWinBoLLProject.sh\",\"args\":[\"DebugTemp\"],\"workDir\":null,\"background\":true,\"resultDir\":null}";
try {
NfcTermuxCmd cmd = NfcUtils.jsonToCmd(testJson);
et_script.setText(cmd.getScript());
et_args.setText(cmd.getArgs() != null ? String.join(",", cmd.getArgs()) : "");
et_workDir.setText(cmd.getWorkDir() != null ? cmd.getWorkDir() : "");
et_background.setText(String.valueOf(cmd.isBackground()));
et_resultDir.setText(cmd.getResultDir() != null ? cmd.getResultDir() : "");
showToast("调试数据已填入");
} catch (Exception e) {
showToast("解析失败");
}
}
// -------------------------------------------------------------------------
// 从 UI 构建 NfcTermuxCmd
// -------------------------------------------------------------------------
private NfcTermuxCmd buildCmdFromUI() {
String script = et_script.getText().toString().trim();
String argsStr = et_args.getText().toString().trim();
String workDir = et_workDir.getText().toString().trim();
String bgStr = et_background.getText().toString().trim();
String resultDir = et_resultDir.getText().toString().trim();
NfcTermuxCmd cmd = new NfcTermuxCmd();
cmd.setScript(script);
cmd.setArgs(argsStr.isEmpty() ? new String[0] : argsStr.split(","));
cmd.setWorkDir(workDir.isEmpty() ? null : workDir);
cmd.setBackground("true".equalsIgnoreCase(bgStr));
cmd.setResultDir(resultDir.isEmpty() ? null : resultDir);
return cmd;
}
private void showToast(String msg) {
Toast.makeText(this, msg, Toast.LENGTH_SHORT).show();
}
}
@@ -1,78 +0,0 @@
package cc.winboll.studio.autonfc.nfc;
import java.util.HashMap;
import java.util.Map;
public class NfcStateMonitor {
private static Map<String, OnNfcStateListener> sListenerMap = new HashMap<>();
private static boolean sIsRunning = false;
public static void startMonitor() {
if (sIsRunning) return;
sListenerMap = new HashMap<>();
sIsRunning = true;
}
public static void stopMonitor() {
if (!sIsRunning) return;
sIsRunning = false;
if (sListenerMap != null) {
sListenerMap.clear();
sListenerMap = null;
}
}
// 你原来的方法名:registerListener
public static void registerListener(String key, OnNfcStateListener listener) {
if (!sIsRunning || listener == null) return;
sListenerMap.put(key, listener);
}
public static void unregisterListener(String key) {
if (!sIsRunning || key == null) return;
sListenerMap.remove(key);
}
public static void notifyNfcConnected() {
if (!sIsRunning) return;
for (OnNfcStateListener l : sListenerMap.values()) {
l.onNfcConnected();
}
}
public static void notifyNfcDisconnected() {
if (!sIsRunning) return;
for (OnNfcStateListener l : sListenerMap.values()) {
l.onNfcDisconnected();
}
}
public static void notifyReadSuccess(String data) {
if (!sIsRunning) return;
for (OnNfcStateListener l : sListenerMap.values()) {
l.onNfcReadSuccess(data);
}
}
public static void notifyReadFail(String error) {
if (!sIsRunning) return;
for (OnNfcStateListener l : sListenerMap.values()) {
l.onNfcReadFail(error);
}
}
public static void notifyWriteSuccess() {
if (!sIsRunning) return;
for (OnNfcStateListener l : sListenerMap.values()) {
l.onNfcWriteSuccess();
}
}
public static void notifyWriteFail(String error) {
if (!sIsRunning) return;
for (OnNfcStateListener l : sListenerMap.values()) {
l.onNfcWriteFail(error);
}
}
}
@@ -1,136 +0,0 @@
package cc.winboll.studio.autonfc.nfc;
/**
* @Author 豆包&ZhanGSKen<zhangsken@qq.com>
* @Date 2026/03/16 14:26
*/
import android.nfc.NdefMessage;
import android.nfc.NdefRecord;
import android.nfc.Tag;
import android.nfc.tech.Ndef;
import com.google.gson.Gson;
import com.google.gson.JsonSyntaxException;
import cc.winboll.studio.autonfc.models.NfcTermuxCmd;
import cc.winboll.studio.libappbase.LogUtils;
import java.nio.charset.Charset;
import java.util.Locale;
public class NfcUtils {
public static final String TAG = "NfcUtils";
private static Gson sGson = new Gson();
// -------------------------------------------------------------------------
// 读取 NFC 标签并解析为 NfcTermuxCmd
// -------------------------------------------------------------------------
public static NfcTermuxCmd readTag(Tag tag) throws Exception {
if (tag == null) {
LogUtils.e(TAG, "readTag: tag is null");
return null;
}
Ndef ndef = Ndef.get(tag);
if (ndef == null) {
LogUtils.e(TAG, "readTag: 不支持 NDEF");
return null;
}
try {
ndef.connect();
NdefMessage msg = ndef.getNdefMessage();
if (msg == null) return null;
NdefRecord[] records = msg.getRecords();
if (records == null || records.length == 0) return null;
byte[] payload = records[0].getPayload();
int status = payload[0] & 0xFF;
int langLen = status & 0x3F;
int start = 1 + langLen;
if (start >= payload.length) return null;
String json = new String(payload, start, payload.length - start, Charset.forName("UTF-8"));
LogUtils.d(TAG, "readTag: 提取 JSON -> " + json);
return sGson.fromJson(json, NfcTermuxCmd.class);
} finally {
if (ndef != null && ndef.isConnected()) {
ndef.close();
}
}
}
// -------------------------------------------------------------------------
// 写入 NfcTermuxCmd 到 NFC 标签
// -------------------------------------------------------------------------
public static void writeTag(Tag tag, NfcTermuxCmd cmd) throws Exception {
if (tag == null) throw new Exception("tag is null");
String json = sGson.toJson(cmd);
writeJson(tag, json);
}
// -------------------------------------------------------------------------
// 写入原始 JSON 字符串到 NFC
// -------------------------------------------------------------------------
public static void writeJson(Tag tag, String json) throws Exception {
if (tag == null) throw new Exception("tag is null");
Ndef ndef = Ndef.get(tag);
if (ndef == null) throw new Exception("标签不支持 NDEF");
try {
ndef.connect();
int maxSize = ndef.getMaxSize();
int realSize = json.getBytes(Charset.forName("UTF-8")).length;
if (realSize > maxSize) {
throw new Exception("数据过大 (" + realSize + ") > 容量 (" + maxSize + ")");
}
NdefRecord record = createTextRecord(json, true);
NdefMessage msg = new NdefMessage(new NdefRecord[]{record});
ndef.writeNdefMessage(msg);
LogUtils.d(TAG, "writeJson: 写入成功");
} finally {
if (ndef != null && ndef.isConnected()) {
ndef.close();
}
}
}
// -------------------------------------------------------------------------
// 创建 NFC 文本记录
// -------------------------------------------------------------------------
public static NdefRecord createTextRecord(String text, boolean isUtf8) {
byte[] langBytes = "en".getBytes(Charset.forName("US-ASCII"));
byte[] textBytes = text.getBytes(Charset.forName(isUtf8 ? "UTF-8" : "UTF-16"));
int status = isUtf8 ? 0 : 0x80;
status |= langBytes.length & 0x3F;
byte[] data = new byte[1 + langBytes.length + textBytes.length];
data[0] = (byte) status;
System.arraycopy(langBytes, 0, data, 1, langBytes.length);
System.arraycopy(textBytes, 0, data, 1 + langBytes.length, textBytes.length);
return new NdefRecord(NdefRecord.TNF_WELL_KNOWN, NdefRecord.RTD_TEXT, new byte[0], data);
}
// -------------------------------------------------------------------------
// 辅助:JSON -> NfcTermuxCmd
// -------------------------------------------------------------------------
public static NfcTermuxCmd jsonToCmd(String json) throws JsonSyntaxException {
return sGson.fromJson(json, NfcTermuxCmd.class);
}
// -------------------------------------------------------------------------
// 辅助:NfcTermuxCmd -> JSON
// -------------------------------------------------------------------------
public static String cmdToJson(NfcTermuxCmd cmd) {
return sGson.toJson(cmd);
}
}
@@ -1,11 +0,0 @@
package cc.winboll.studio.autonfc.nfc;
public interface OnNfcStateListener {
void onNfcConnected(); // 无参数!
void onNfcDisconnected();
void onNfcReadSuccess(String data);
void onNfcReadFail(String error);
void onNfcWriteSuccess();
void onNfcWriteFail(String error);
}
@@ -1,38 +0,0 @@
<?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">
<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="NFC Interface Activity"
android:onClick="onNFCInterfaceActivity"/>
</LinearLayout>
</LinearLayout>
@@ -1,104 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<ScrollView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="脚本名称 script:"/>
<EditText
android:id="@+id/et_script"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="如 auth.sh"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="参数 args (逗号分隔):"/>
<EditText
android:id="@+id/et_args"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="user1,pass123"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="工作目录 workDir:"/>
<EditText
android:id="@+id/et_workDir"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="/data/data/com.termux/files/home"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="后台执行 background (true/false):"/>
<EditText
android:id="@+id/et_background"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="true"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="结果目录 resultDir:"/>
<EditText
android:id="@+id/et_resultDir"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="/data/data/com.termux/files/log"/>
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:onClick="onFillTestDataClick"
android:text="填入调试数据 (BuildWinBoLLProject.sh)"/>
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:onClick="onWriteClick"
android:text="写入 NFC (NfcTermuxCmd JSON)"/>
<TextView
android:id="@+id/tv_status"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="状态"/>
<TextView
android:id="@+id/tv_result"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:scrollbars="vertical"
android:textSize="14sp"/>
</LinearLayout>
</ScrollView>
-6
View File
@@ -1,6 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/menu_log"
android:title="启动日志"/>
</menu>
-6
View File
@@ -1,6 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="colorPrimary">#009688</color>
<color name="colorPrimaryDark">#00796B</color>
<color name="colorAccent">#FF9800</color>
</resources>
-4
View File
@@ -1,4 +0,0 @@
<resources>
<string name="app_name">AutoNFC</string>
</resources>
-11
View File
@@ -1,11 +0,0 @@
<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>
+21 -39
View File
@@ -18,44 +18,30 @@ def genVersionName(def versionName){
}
android {
// 适配MIUI12
compileSdkVersion 30
compileSdkVersion 30
buildToolsVersion "30.0.3"
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_7
targetCompatibility JavaVersion.VERSION_1_7
}
defaultConfig {
applicationId "cc.winboll.studio.contacts"
minSdkVersion 23
// 适配MIUI12
minSdkVersion 26
targetSdkVersion 30
versionCode 2
versionCode 3
// versionName 更新后需要手动设置
// 项目模块目录的 build.gradle 文件的 stageCount=0
// .winboll/winbollBuildProps.properties 文件的 stageCount=0
// Gradle编译环境下合起来的 versionName 就是 "${versionName}.0"
versionName "15.14"
versionName "15.20"
if(true) {
versionName = genVersionName("${versionName}")
}
}
// 米盟 SDK
packagingOptions {
doNotStrip "*/*/libmimo_1011.so"
}
}
dependencies {
// 米盟
api 'com.miui.zeus:mimo-ad-sdk:5.3.+'//请使用最新版sdk
//注意:以下5个库必须要引入
//api 'androidx.appcompat:appcompat:1.4.1'
api 'androidx.recyclerview:recyclerview:1.0.0'
api 'com.google.code.gson:gson:2.8.5'
api 'com.github.bumptech.glide:glide:4.9.0'
//annotationProcessor 'com.github.bumptech.glide:compiler:4.9.0'
// 权限请求框架:https://github.com/getActivity/XXPermissions
//api 'com.github.getActivity:XXPermissions:18.63'
// 下拉控件
api 'com.baoyz.pullrefreshlayout:library:1.2.0'
// 拼音搜索
@@ -72,29 +58,25 @@ dependencies {
api 'io.github.medyo:android-about-page:2.0.0'
// 网络连接类库
api 'com.squareup.okhttp3:okhttp:4.4.1'
// AndroidX 类库
/*implementation 'androidx.appcompat:appcompat:1.1.0'
implementation 'androidx.viewpager:viewpager:1.0.0'
implementation 'androidx.vectordrawable:vectordrawable:1.1.0'
implementation 'androidx.vectordrawable:vectordrawable-animated:1.1.0'
implementation 'androidx.fragment:fragment:1.1.0'
implementation 'com.google.android.material:material:1.4.0'
*/
api 'androidx.appcompat:appcompat:1.1.0'
api 'androidx.cardview:cardview:1.0.0'
api 'com.google.android.material:material:1.4.0'
//api 'androidx.viewpager:viewpager:1.0.0'
api 'androidx.viewpager:viewpager:1.0.0'
//api 'androidx.vectordrawable:vectordrawable:1.1.0'
//api 'androidx.vectordrawable:vectordrawable-animated:1.1.0'
//api 'androidx.fragment:fragment:1.1.0'
// WinBoLL库 nexus.winboll.cc 地址
api 'cc.winboll.studio:libaes:15.12.13'
api 'cc.winboll.studio:libappbase:15.14.2'
//注意:以下5个库必须要引入
//implementation 'androidx.appcompat:appcompat:1.4.1'
api 'androidx.recyclerview:recyclerview:1.0.0'
api 'com.google.code.gson:gson:2.8.5'
api 'com.github.bumptech.glide:glide:4.9.0'
//annotationProcessor 'com.github.bumptech.glide:compiler:4.9.0'
// WinBoLL备用库 jitpack.io 地址
//api 'com.github.ZhanGSKen:AES:aes-v15.12.9'
//api 'com.github.ZhanGSKen:APPBase:appbase-v15.14.1'
// WinBoLL库 nexus.winboll.cc 地址
api 'cc.winboll.studio:libaes:15.20.21'
api 'cc.winboll.studio:libappbase:15.20.34'
api fileTree(dir: 'libs', include: ['*.jar'])
}
+5 -5
View File
@@ -1,8 +1,8 @@
#Created by .winboll/winboll_app_build.gradle
#Sat Apr 18 21:14:59 HKT 2026
stageCount=13
#Sun Jul 19 06:16:39 HKT 2026
stageCount=12
libraryProject=
baseVersion=15.14
publishVersion=15.14.12
baseVersion=15.20
publishVersion=15.20.11
buildCount=0
baseBetaVersion=15.14.13
baseBetaVersion=15.20.12
+6 -1
View File
@@ -3,6 +3,9 @@
xmlns:android="http://schemas.android.com/apk/res/android"
package="cc.winboll.studio.contacts">
<!-- 拥有完全的网络访问权限 -->
<uses-permission android:name="android.permission.INTERNET"/>
<!-- BIND_AUTOFILL_SERVICE -->
<uses-permission android:name="android.permission.BIND_AUTOFILL_SERVICE"/>
@@ -253,6 +256,8 @@
<service android:name="cc.winboll.studio.contacts.services.LimitedTimeSpecialChannelService"/>
<activity android:name="cc.winboll.studio.contacts.unittest.LayoutDraftPreViewActivity"/>
</application>
</manifest>
</manifest>
@@ -16,9 +16,6 @@ public class App extends GlobalApplication {
@Override
public void onCreate() {
super.onCreate();
// 设置应用调试标志
setIsDebugging(BuildConfig.DEBUG);
// 初始化窗口管理类
WinBoLLActivityManager.init(this);
// 初始化 Toast 框架
@@ -31,15 +31,13 @@ import cc.winboll.studio.contacts.activities.WinBollActivity;
import cc.winboll.studio.contacts.dun.Rules;
import cc.winboll.studio.contacts.fragments.CallLogFragment;
import cc.winboll.studio.contacts.fragments.ContactsFragment;
import cc.winboll.studio.contacts.fragments.LogFragment;
import cc.winboll.studio.contacts.fragments.DunQueryFragment;
import cc.winboll.studio.contacts.model.MainServiceBean;
import cc.winboll.studio.contacts.services.MainService;
import cc.winboll.studio.contacts.utils.PermissionUtils;
import cc.winboll.studio.contacts.views.DunTemperatureView;
import cc.winboll.studio.libaes.interfaces.IWinBoLLActivity;
import cc.winboll.studio.libaes.views.ADsBannerView;
import cc.winboll.studio.libappbase.LogUtils;
import cc.winboll.studio.libappbase.LogView;
import com.google.android.material.tabs.TabLayout;
import java.util.ArrayList;
import java.util.List;
@@ -76,8 +74,6 @@ public final class MainActivity extends WinBollActivity implements IWinBoLLActiv
private final String[] REQUIRED_PERMISSIONS = PermissionUtils.BASE_PERMISSIONS;
// ====================== 4. UI控件成员区 ======================
private ADsBannerView mADsBannerView;
private LogView mLogView;
private Toolbar mToolbar;
private CheckBox cbMainService;
private TabLayout tabLayout;
@@ -136,10 +132,6 @@ public final class MainActivity extends WinBollActivity implements IWinBoLLActiv
@Override
protected void onResume() {
super.onResume();
if (mADsBannerView != null) {
mADsBannerView.resumeADs(MainActivity.this);
LogUtils.d(TAG, "onResume: 广告栏资源已恢复");
}
}
@Override
@@ -151,11 +143,6 @@ public final class MainActivity extends WinBollActivity implements IWinBoLLActiv
protected void onDestroy() {
super.onDestroy();
LogUtils.d(TAG, "===== onDestroy: 主Activity开始销毁 =====");
// 释放广告资源
if (mADsBannerView != null) {
mADsBannerView.releaseAdResources();
LogUtils.d(TAG, "onDestroy: 广告栏资源已释放");
}
// 清空Fragment相关引用,避免内存泄漏
if (fragmentList != null) {
fragmentList.clear();
@@ -293,10 +280,6 @@ public final class MainActivity extends WinBollActivity implements IWinBoLLActiv
tabLayout.setupWithViewPager(viewPager);
LogUtils.d(TAG, "initUIAndLogic: ViewPager与TabLayout初始化完成");
// 3. 广告栏初始化
mADsBannerView = (ADsBannerView) findViewById(R.id.adsbanner);
LogUtils.d(TAG, "initUIAndLogic: 广告栏控件初始化完成");
// 左边盾值视图初始化(Java7分步写法,禁止链式调用)
DunTemperatureView tempViewLeft = (DunTemperatureView) findViewById(R.id.dun_temp_view_left);
tempViewLeft.setMaxValue(Rules.getInstance(this).getSettingsModel().getDunTotalCount());
@@ -335,10 +318,10 @@ public final class MainActivity extends WinBollActivity implements IWinBoLLActiv
// 添加Fragment实例(仅创建对象,不初始化业务逻辑)
fragmentList.add(CallLogFragment.newInstance(0));
fragmentList.add(ContactsFragment.newInstance(1));
fragmentList.add(LogFragment.newInstance(2));
fragmentList.add(DunQueryFragment.newInstance(2));
tabTitleList.add("通话记录");
tabTitleList.add("联系人");
tabTitleList.add("应用日志");
tabTitleList.add("云盾查询");
// 初始化懒加载标记数组(默认均未初始化)
int fragmentCount = fragmentList.size();
@@ -382,8 +365,8 @@ public final class MainActivity extends WinBollActivity implements IWinBoLLActiv
((CallLogFragment) targetFragment).initData();
} else if (targetFragment instanceof ContactsFragment) {
((ContactsFragment) targetFragment).initData();
} else if (targetFragment instanceof LogFragment) {
((LogFragment) targetFragment).initData();
} else if (targetFragment instanceof DunQueryFragment) {
((DunQueryFragment) targetFragment).initData();
}
// 标记为已初始化
isFragmentInit[position] = true;
@@ -1,38 +1,22 @@
package cc.winboll.studio.contacts.activities;
import android.app.Activity;
import android.content.Context;
import android.os.Bundle;
import android.view.ViewGroup;
import android.widget.LinearLayout;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
import cc.winboll.studio.contacts.R;
import cc.winboll.studio.libaes.interfaces.IWinBoLLActivity;
import cc.winboll.studio.libaes.models.APPInfo;
import cc.winboll.studio.libaes.utils.WinBoLLActivityManager;
import cc.winboll.studio.libaes.views.AboutView;
import cc.winboll.studio.libappbase.LogUtils;
import cc.winboll.studio.libappbase.models.APPInfo;
import cc.winboll.studio.libappbase.views.AboutView;
import androidx.appcompat.widget.Toolbar;
/**
* @Author ZhanGSKen&豆包大模型<zhangsken@qq.com>
* @Date 2025/03/31 15:15:54
* @Describe 应用介绍窗口
* @Author 豆包&ZhanGSKen<zhangsken@qq.com>
* @Date 2026/01/11 12:55
* @Describe AboutActivity
*/
public class AboutActivity extends WinBollActivity implements IWinBoLLActivity {
// ====================== 常量定义区 ======================
public static final String TAG = "AboutActivity";
private static final String BRANCH_NAME = "contacts";
// ====================== 成员变量区 ======================
private Context mContext;
private Toolbar mToolbar;
// ====================== 接口实现区 ======================
@Override
@Override
public Activity getActivity() {
return this;
}
@@ -41,76 +25,32 @@ public class AboutActivity extends WinBollActivity implements IWinBoLLActivity {
public String getTag() {
return TAG;
}
// ====================== 生命周期函数区 ======================
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
LogUtils.d(TAG, "onCreate: 关于页面开始创建");
mContext = this;
setContentView(R.layout.activity_about);
// 初始化工具栏
initToolbar();
// 初始化关于页面视图
initAboutView();
// 注册Activity管理
WinBoLLActivityManager.getInstance().add(this);
Toolbar toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
LogUtils.d(TAG, "onCreate: 关于页面初始化完成");
AboutView aboutView = findViewById(R.id.aboutview);
aboutView.setAPPInfo(genDefaultAppInfo());
}
@Override
protected void onDestroy() {
super.onDestroy();
LogUtils.d(TAG, "onDestroy: 关于页面开始销毁");
WinBoLLActivityManager.getInstance().registeRemove(this);
LogUtils.d(TAG, "onDestroy: 关于页面销毁完成");
}
// ====================== 控件初始化函数区 ======================
private void initToolbar() {
LogUtils.d(TAG, "initToolbar: 初始化工具栏");
// Java7 适配:添加强制类型转换
mToolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(mToolbar);
mToolbar.setSubtitle(TAG);
// 非空判断,避免空指针异常
if (getSupportActionBar() != null) {
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
}
}
private void initAboutView() {
LogUtils.d(TAG, "initAboutView: 初始化关于页面内容视图");
AboutView aboutView = createAboutView();
LinearLayout layout = (LinearLayout) findViewById(R.id.aboutviewroot_ll);
ViewGroup.LayoutParams params = new ViewGroup.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT
);
layout.addView(aboutView, params);
LogUtils.d(TAG, "initAboutView: AboutView已添加到布局");
}
// ====================== 业务逻辑函数区 ======================
private AboutView createAboutView() {
LogUtils.d(TAG, "createAboutView: 构建APP信息并创建AboutView");
private APPInfo genDefaultAppInfo() {
String branchName = "contacts";
APPInfo appInfo = new APPInfo();
appInfo.setAppName("Contacts");
appInfo.setAppIcon(cc.winboll.studio.libaes.R.drawable.ic_winboll);
appInfo.setAppDescription("这是可以根据正则表达式匹配拦截骚扰电话的手机拨号应用。");
appInfo.setAppGitName("WinBoLL");
appInfo.setAppGitOwner("Studio");
appInfo.setAppGitAPPBranch(BRANCH_NAME);
appInfo.setAppGitAPPSubProjectFolder(BRANCH_NAME);
appInfo.setAppIcon(R.drawable.ic_winboll);
appInfo.setAppDescription(getString(R.string.app_description));
appInfo.setAppGitName("Contacts");
appInfo.setAppGitOwner("ZhanGSKen");
appInfo.setAppGitAPPBranch(branchName);
appInfo.setAppGitAPPSubProjectFolder(branchName);
appInfo.setAppHomePage("https://www.winboll.cc/apks/index.php?project=Contacts");
appInfo.setAppAPKName("Contacts");
appInfo.setAppAPKFolderName("Contacts");
return new AboutView(mContext, appInfo);
return appInfo;
}
}
@@ -29,6 +29,7 @@ import cc.winboll.studio.contacts.model.PhoneConnectRuleBean;
import cc.winboll.studio.contacts.model.RingTongBean;
import cc.winboll.studio.contacts.model.SettingsBean;
import cc.winboll.studio.contacts.services.MainService;
import cc.winboll.studio.contacts.views.DebugToolButtonsView;
import cc.winboll.studio.contacts.views.DuInfoTextView;
import cc.winboll.studio.libaes.interfaces.IWinBoLLActivity;
import cc.winboll.studio.libaes.utils.WinBoLLActivityManager;
@@ -98,6 +99,9 @@ public class SettingsActivity extends WinBollActivity implements IWinBoLLActivit
initDunSettings(); // 云盾设置初始化
initBoBullToonViews(); // BoBullToon功能初始化
// 调试模式下加载调试工具按钮组
initDebugTools();
LogUtils.d(TAG, "onCreate: 设置页面初始化完成");
}
@@ -300,6 +304,26 @@ public class SettingsActivity extends WinBollActivity implements IWinBoLLActivit
}
}
/**
* 初始化调试工具按钮组(仅在调试模式下加载)
*/
private void initDebugTools() {
LogUtils.d(TAG, "initDebugTools: 检查调试模式状态");
if (cc.winboll.studio.libappbase.GlobalApplication.isDebugging()) {
LogUtils.d(TAG, "initDebugTools: 调试模式已开启,加载调试工具按钮组");
android.widget.LinearLayout contentLayout = (android.widget.LinearLayout) findViewById(R.id.activity_settings_content);
if (contentLayout != null) {
DebugToolButtonsView debugView = new DebugToolButtonsView(this);
contentLayout.addView(debugView);
LogUtils.d(TAG, "initDebugTools: 调试工具按钮组加载完成");
} else {
LogUtils.e(TAG, "initDebugTools: 内容布局未找到,加载失败");
}
} else {
LogUtils.d(TAG, "initDebugTools: 调试模式未开启,跳过加载");
}
}
// ====================== 点击事件回调区(按功能模块归类) ======================
/**
* 云盾开关点击事件(联动参数编辑权限+配置保存)
@@ -382,6 +406,21 @@ public class SettingsActivity extends WinBollActivity implements IWinBoLLActivit
LogUtils.d(TAG, "onAddNewConnectionRule: 规则添加完成,当前共" + mRuleList.size() + "条规则");
}
/**
* 初始化测试规则
*/
public void onInitTestRules(View view) {
LogUtils.d(TAG, "onInitTestRules: 初始化测试规则");
Rules rules = Rules.getInstance(this);
if (rules == null) {
LogUtils.e(TAG, "onInitTestRules: Rules实例获取失败");
return;
}
initTestRulesIfEmpty(rules);
mRuleAdapter.notifyDataSetChanged();
ToastUtils.show("测试规则初始化完成");
}
/**
* 跳转默认电话应用设置
*/
@@ -528,15 +567,24 @@ public class SettingsActivity extends WinBollActivity implements IWinBoLLActivit
WinBoLLActivityManager.getInstance().startWinBoLLActivity(this, AboutActivity.class);
}
// ====================== 工具方法区(通用功能+权限相关) ======================
/**
* 跳转日志查看页面
* 规则集为空时初始化测试规则
*/
public void onLogView(View view) {
LogUtils.d(TAG, "onLogView: 跳转日志页面");
WinBoLLActivityManager.getInstance().startLogActivity(this);
public static void initTestRulesIfEmpty(Rules rules) {
if (rules.getPhoneBlacRuleBeanList().size() == 0) {
LogUtils.d(TAG, "initTestRulesIfEmpty: 当前无规则,添加测试规则集");
rules.add("^1[3-9]\\d{9}$", true, true);
rules.add("^0660\\d+$", true, true);
rules.add("^020\\d+$", true, true);
rules.add(".*", false, true);
rules.saveRules();
LogUtils.d(TAG, "initTestRulesIfEmpty: 测试规则集已保存");
} else {
LogUtils.d(TAG, "initTestRulesIfEmpty: 当前已有规则,跳过初始化");
}
}
// ====================== 工具方法区(通用功能+权限相关) ======================
/**
* 更新音量显示文本(当前音量/最大音量)
*/
@@ -5,7 +5,7 @@ import android.view.View;
import android.widget.EditText;
import androidx.appcompat.app.AppCompatActivity;
import cc.winboll.studio.contacts.R;
import cc.winboll.studio.contacts.activities.UnitTestActivity;
import cc.winboll.studio.contacts.activities.SettingsActivity;
import cc.winboll.studio.contacts.dun.Rules;
import cc.winboll.studio.contacts.services.LimitedTimeSpecialChannelService;
import cc.winboll.studio.contacts.utils.IntUtils;
@@ -103,7 +103,7 @@ public class UnitTestActivity extends WinBollActivity implements IWinBoLLActivit
// 初始化规则实例
Rules rules = Rules.getInstance(this);
// 无规则时添加测试规则集
initTestRulesIfEmpty(rules);
SettingsActivity.initTestRulesIfEmpty(rules);
// 预设测试号码列表
String[] testPhones = {
@@ -126,29 +126,5 @@ public class UnitTestActivity extends WinBollActivity implements IWinBoLLActivit
}
}).start();
}
// ====================== 私有工具函数区 ======================
/**
* 规则集为空时初始化测试规则
*/
private void initTestRulesIfEmpty(Rules rules) {
if (rules.getPhoneBlacRuleBeanList().size() == 0) {
LogUtils.d(TAG, "initTestRulesIfEmpty: 当前无规则,添加测试规则集");
// 规则1:中国手机号允许
rules.add("^1[3-9]\\d{9}$", true, true);
// 规则20660区号号码允许
rules.add("^0660\\d+$", true, true);
// 规则3020区号号码允许
rules.add("^020\\d+$", true, true);
// 规则4:默认拒接所有号码
rules.add(".*", false, true);
// 保存规则到本地
rules.saveRules();
LogUtils.d(TAG, "initTestRulesIfEmpty: 测试规则集已保存");
} else {
LogUtils.d(TAG, "initTestRulesIfEmpty: 当前已有规则,跳过初始化");
}
}
}
@@ -69,7 +69,7 @@ public class CallLogAdapter extends RecyclerView.Adapter<CallLogAdapter.CallLogV
}
@Override
public void onBindViewHolder(@NonNull CallLogViewHolder holder, int position) {
public void onBindViewHolder(@NonNull final CallLogViewHolder holder, int position) {
LogUtils.d(TAG, "onBindViewHolder: 绑定列表项数据,position=" + position);
final CallLogModel callLog = callLogList.get(position);
@@ -54,7 +54,7 @@ public class ContactAdapter extends RecyclerView.Adapter<ContactAdapter.ContactV
}
@Override
public void onBindViewHolder(@NonNull ContactViewHolder holder, int position) {
public void onBindViewHolder(@NonNull final ContactViewHolder holder, int position) {
LogUtils.d(TAG, "onBindViewHolder: 绑定联系人列表项数据,position=" + position);
final ContactModel contact = contactList.get(position);
@@ -0,0 +1,76 @@
package cc.winboll.studio.contacts.fragments;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import cc.winboll.studio.contacts.R;
import cc.winboll.studio.libappbase.LogUtils;
/**
* @Author ZhanGSKen<zhangsken@qq.com>
* @Date 2026/07/19
* @Describe 云盾查询区域视图(支持懒加载,仅切换到当前页才加载)
*/
public class DunQueryFragment extends Fragment {
public static final String TAG = "DunQueryFragment";
private static final String ARG_PAGE = "ARG_PAGE";
private int mPage;
private boolean isViewInitialized = false;
private boolean isLazyInitCompleted = false;
public static DunQueryFragment newInstance(int page) {
LogUtils.d(TAG, "newInstance: 创建云盾查询Fragment实例,页码=" + page);
Bundle args = new Bundle();
args.putInt(ARG_PAGE, page);
DunQueryFragment fragment = new DunQueryFragment();
fragment.setArguments(args);
return fragment;
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
LogUtils.d(TAG, "onCreate: Fragment创建开始");
if (getArguments() != null) {
mPage = getArguments().getInt(ARG_PAGE);
LogUtils.d(TAG, "onCreate: 读取页面参数,mPage=" + mPage);
}
LogUtils.d(TAG, "onCreate: Fragment创建完成");
}
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
LogUtils.d(TAG, "onCreateView: 加载Fragment布局");
View view = inflater.inflate(R.layout.fragment_dun_query, container, false);
isViewInitialized = true;
LogUtils.d(TAG, "onCreateView: Fragment布局加载完成");
return view;
}
@Override
public void onDestroy() {
super.onDestroy();
LogUtils.d(TAG, "onDestroy: Fragment开始销毁");
isViewInitialized = false;
isLazyInitCompleted = false;
LogUtils.d(TAG, "onDestroy: Fragment销毁完成");
}
public void initData() {
if (isLazyInitCompleted || !isViewInitialized || getContext() == null) {
LogUtils.d(TAG, "initData: 懒加载已完成/视图未就绪,跳过");
return;
}
LogUtils.d(TAG, "initData: 开始懒加载初始化");
isLazyInitCompleted = true;
LogUtils.d(TAG, "initData: 懒加载初始化完成");
}
}
@@ -1,118 +0,0 @@
package cc.winboll.studio.contacts.fragments;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import cc.winboll.studio.contacts.R;
import cc.winboll.studio.libappbase.LogUtils;
import cc.winboll.studio.libappbase.LogView;
/**
* @Author ZhanGSKen&豆包大模型<zhangsken@qq.com>
* @Date 2025/02/20 12:58:15
* @Describe 应用日志区域视图(支持懒加载,仅切换到当前页才启动日志)
*/
public class LogFragment extends Fragment {
// ====================== 常量定义区 ======================
public static final String TAG = "LogFragment";
private static final String ARG_PAGE = "ARG_PAGE";
// ====================== 页面参数区 ======================
private int mPage;
// ====================== UI控件区 ======================
private LogView mLogView;
// ====================== 懒加载标记区 ======================
private boolean isViewInitialized = false; // 视图控件绑定完成标记
private boolean isLazyInitCompleted = false; // 懒加载总流程完成标记
private boolean isLogViewStarted = false; // LogView启动状态标记
// ====================== 实例化函数区 ======================
public static LogFragment newInstance(int page) {
LogUtils.d(TAG, "newInstance: 创建日志Fragment实例,页码=" + page);
Bundle args = new Bundle();
args.putInt(ARG_PAGE, page);
LogFragment fragment = new LogFragment();
fragment.setArguments(args);
return fragment;
}
// ====================== 生命周期函数区 ======================
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
LogUtils.d(TAG, "onCreate: Fragment创建开始");
if (getArguments() != null) {
mPage = getArguments().getInt(ARG_PAGE);
LogUtils.d(TAG, "onCreate: 读取页面参数,mPage=" + mPage);
}
LogUtils.d(TAG, "onCreate: Fragment创建完成");
}
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
LogUtils.d(TAG, "onCreateView: 加载Fragment布局");
View view = inflater.inflate(R.layout.fragment_log, container, false);
// Java7 适配:添加强制类型转换,仅初始化LogView控件(不启动)
mLogView = (LogView) view.findViewById(R.id.logview);
LogUtils.d(TAG, "onCreateView: LogView控件初始化完成(未启动)");
// 标记视图控件绑定完成
isViewInitialized = true;
return view;
}
@Override
public void onResume() {
super.onResume();
LogUtils.d(TAG, "onResume: Fragment进入前台");
// 已完成懒加载 → 仅重启LogView(切回页面时恢复日志显示)
if (isLazyInitCompleted && mLogView != null && !isLogViewStarted) {
mLogView.start();
isLogViewStarted = true;
LogUtils.d(TAG, "onResume: LogView已重启,恢复日志显示");
}
// 未完成懒加载 → 不操作(等待MainActivity调用initData触发)
}
@Override
public void onDestroy() {
super.onDestroy();
LogUtils.d(TAG, "onDestroy: Fragment开始销毁");
if (mLogView != null) {
// 若LogView有停止方法,必须调用(避免后台持续占用资源,根据实际API调整)
// mLogView.stop(); // 关键:释放LogView资源,防止内存泄漏
LogUtils.d(TAG, "onDestroy: LogView资源已释放");
}
// 重置所有标记,避免重建时状态异常
mLogView = null;
isViewInitialized = false;
isLazyInitCompleted = false;
isLogViewStarted = false;
LogUtils.d(TAG, "onDestroy: Fragment销毁完成");
}
// ====================== 懒加载核心方法(供MainActivity调用) ======================
public void initData() {
// 双重防护:避免重复初始化(标记+视图就绪+控件非空)
if (isLazyInitCompleted || !isViewInitialized || mLogView == null || getContext() == null) {
LogUtils.d(TAG, "initData: 懒加载已完成/视图未就绪,跳过");
return;
}
LogUtils.d(TAG, "initData: 开始懒加载初始化,启动LogView");
// 核心:启动LogView(原onCreateView中的start逻辑迁移至此)
mLogView.start();
isLogViewStarted = true;
// 标记懒加载总流程完成(仅执行一次)
isLazyInitCompleted = true;
LogUtils.d(TAG, "initData: 懒加载初始化完成,LogView正常启动");
}
}
@@ -2,7 +2,7 @@ package cc.winboll.studio.contacts.model;
import android.util.JsonReader;
import android.util.JsonWriter;
import cc.winboll.studio.libappbase.BaseBean;
import cc.winboll.studio.libappbase.models.libs1520000.BaseBean;
import cc.winboll.studio.libappbase.LogUtils;
import java.io.IOException;
@@ -2,7 +2,7 @@ package cc.winboll.studio.contacts.model;
import android.util.JsonReader;
import android.util.JsonWriter;
import cc.winboll.studio.libappbase.BaseBean;
import cc.winboll.studio.libappbase.models.libs1520000.BaseBean;
import cc.winboll.studio.libappbase.LogUtils;
import java.io.IOException;
@@ -2,7 +2,7 @@ package cc.winboll.studio.contacts.model;
import android.util.JsonReader;
import android.util.JsonWriter;
import cc.winboll.studio.libappbase.BaseBean;
import cc.winboll.studio.libappbase.models.libs1520000.BaseBean;
import cc.winboll.studio.libappbase.LogUtils;
import java.io.IOException;
@@ -6,7 +6,7 @@ import android.util.JsonWriter;
import java.io.IOException;
import cc.winboll.studio.contacts.utils.IntUtils;
import cc.winboll.studio.libappbase.BaseBean;
import cc.winboll.studio.libappbase.models.libs1520000.BaseBean;
import cc.winboll.studio.libappbase.LogUtils;
/**
@@ -0,0 +1,47 @@
package cc.winboll.studio.contacts.unittest;
import android.os.Bundle;
import android.view.View;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
import cc.winboll.studio.contacts.R;
import cc.winboll.studio.libappbase.LogUtils;
/**
* @Author 豆包&BigPickle&ZhanGSKen<zhangsken@qq.com>
* @Date 2026/07/18 19:35
* @Describe 布局控件快速便捷辅助草稿
*/
public class LayoutDraftPreViewActivity extends AppCompatActivity {
public static final String TAG = "LayoutDraftPreViewActivity";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
LogUtils.d(TAG, "onCreate: 布局草稿预览窗口启动");
setContentView(R.layout.activity_layoutdraft_preview);
initToolbar();
LogUtils.d(TAG, "onCreate: 初始化完成");
}
private void initToolbar() {
LogUtils.d(TAG, "initToolbar: 初始化工具栏");
Toolbar toolbar = (Toolbar) findViewById(R.id.activity_layoutdraft_preview_toolbar);
setSupportActionBar(toolbar);
if (getSupportActionBar() != null) {
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
getSupportActionBar().setTitle(TAG);
}
toolbar.setNavigationOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
LogUtils.d(TAG, "initToolbar: 点击后退按钮,关闭页面");
finish();
}
});
LogUtils.d(TAG, "initToolbar: 工具栏初始化完成");
}
}
@@ -0,0 +1,124 @@
package cc.winboll.studio.contacts.views;
import android.content.Context;
import android.content.Intent;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.Button;
import android.widget.LinearLayout;
import cc.winboll.studio.contacts.R;
import cc.winboll.studio.libappbase.LogActivity;
import cc.winboll.studio.libappbase.LogUtils;
/**
* @Author ZhanGSKen<zhangsken@qq.com>
* @Date 2026/07/18
* @Describe 调试工具按钮组控件:提供常用调试功能快捷入口,仅在调试模式下显示
*/
public class DebugToolButtonsView extends LinearLayout {
private static final String TAG = "DebugToolButtonsView";
private Context mContext;
public DebugToolButtonsView(Context context) {
super(context);
initView(context);
}
public DebugToolButtonsView(Context context, AttributeSet attrs) {
super(context, attrs);
initView(context);
}
public DebugToolButtonsView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initView(context);
}
private void initView(Context context) {
LogUtils.d(TAG, "initView: 初始化调试工具按钮组");
this.mContext = context;
setOrientation(VERTICAL);
LayoutInflater.from(context).inflate(R.layout.view_debug_tool_buttons, this, true);
initButtons();
LogUtils.d(TAG, "initView: 调试工具按钮组初始化完成");
}
private void initButtons() {
Button btnCrash = (Button) findViewById(R.id.btn_debug_crash);
Button btnLayoutPreview = (Button) findViewById(R.id.btn_debug_layout_preview);
Button btnUnitTest = (Button) findViewById(R.id.btn_debug_unit_test);
Button btnLogView = (Button) findViewById(R.id.btn_debug_logview);
btnCrash.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
onTestCrash();
}
});
btnLayoutPreview.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
onOpenLayoutPreview();
}
});
btnUnitTest.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
onOpenUnitTest();
}
});
btnLogView.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
onLogView();
}
});
}
private void onTestCrash() {
LogUtils.d(TAG, "onTestCrash: 触发测试崩溃");
for (int i = Integer.MIN_VALUE; i < Integer.MAX_VALUE; i++) {
((android.app.Activity) mContext).getString(i);
}
}
private void onOpenLayoutPreview() {
LogUtils.d(TAG, "onOpenLayoutPreview: 打开布局草稿预览窗口");
try {
Intent intent = new Intent();
intent.setClassName(mContext, "cc.winboll.studio.contacts.unittest.LayoutDraftPreViewActivity");
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
mContext.startActivity(intent);
LogUtils.d(TAG, "onOpenLayoutPreview: 跳转成功");
} catch (Exception e) {
LogUtils.e(TAG, "onOpenLayoutPreview: 跳转失败", e);
}
}
private void onOpenUnitTest() {
LogUtils.d(TAG, "onOpenUnitTest: 打开单元测试窗口");
try {
Intent intent = new Intent();
intent.setClassName(mContext, "cc.winboll.studio.contacts.activities.UnitTestActivity");
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
mContext.startActivity(intent);
LogUtils.d(TAG, "onOpenUnitTest: 跳转成功");
} catch (Exception e) {
LogUtils.e(TAG, "onOpenUnitTest: 跳转失败", e);
}
}
/**
* 跳转日志查看页面
*/
private void onLogView() {
LogUtils.d(TAG, "onLogView: 打开日志窗口");
LogActivity.startLogActivity(getContext(), true);
}
}
@@ -0,0 +1,103 @@
package cc.winboll.studio.contacts.views;
import android.content.Context;
import android.text.Editable;
import android.text.TextWatcher;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.TextView;
import cc.winboll.studio.contacts.R;
import cc.winboll.studio.contacts.dun.Rules;
import cc.winboll.studio.libappbase.LogUtils;
/**
* @Author ZhanGSKen<zhangsken@qq.com>
* @Date 2026/07/19
* @Describe 云盾拦截查询视图控件:实时查询电话号码的云盾拦截状态
*/
public class DunQueryView extends LinearLayout {
private static final String TAG = "DunQueryView";
private Context mContext;
private EditText mEtPhone;
private TextView mTvResult;
public DunQueryView(Context context) {
super(context);
initView(context);
}
public DunQueryView(Context context, AttributeSet attrs) {
super(context, attrs);
initView(context);
}
public DunQueryView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initView(context);
}
private void initView(Context context) {
LogUtils.d(TAG, "initView: 初始化云盾查询视图");
this.mContext = context;
setOrientation(VERTICAL);
LayoutInflater.from(context).inflate(R.layout.view_dun_query, this, true);
initViews();
initListeners();
LogUtils.d(TAG, "initView: 云盾查询视图初始化完成");
}
private void initViews() {
mEtPhone = (EditText) findViewById(R.id.et_dun_query_phone);
mTvResult = (TextView) findViewById(R.id.tv_dun_query_result);
}
private void initListeners() {
mEtPhone.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
@Override
public void afterTextChanged(Editable s) {
queryDunStatus(s.toString().trim());
}
});
}
private void queryDunStatus(String phoneNumber) {
if (phoneNumber.isEmpty()) {
mTvResult.setText("请输入电话号码");
mTvResult.setTextColor(mContext.getResources().getColor(R.color.gray_500));
LogUtils.d(TAG, "queryDunStatus: 输入为空,显示默认提示");
return;
}
Rules rules = Rules.getInstance(mContext);
if (rules == null) {
mTvResult.setText("云盾规则未初始化");
mTvResult.setTextColor(mContext.getResources().getColor(R.color.gray_500));
LogUtils.e(TAG, "queryDunStatus: Rules实例获取失败");
return;
}
boolean isAllowed = rules.isAllowed(phoneNumber, true);
if (isAllowed) {
mTvResult.setText("这个号码很新,什么防御都不会启动。");
mTvResult.setTextColor(mContext.getResources().getColor(R.color.green_normal));
LogUtils.d(TAG, "queryDunStatus: 号码" + phoneNumber + " 允许接通");
} else {
mTvResult.setText("你说我像云像雾像雨又像风~~~ 怎么来来去去,怎么也又播不通~~~");
mTvResult.setTextColor(mContext.getResources().getColor(R.color.red_normal));
LogUtils.d(TAG, "queryDunStatus: 号码" + phoneNumber + " 被拦截");
}
}
}
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportHeight="24"
android:viewportWidth="24">
<path
android:fillColor="#ff000000"
android:pathData="M5.8,10C5.4,8.8 4.3,8 3,8C1.3,8 0,9.3 0,11S1.3,14 3,14C4.3,14 5.4,13.2 5.8,12H7V14H9V12H11V10H5.8M3,12C2.4,12 2,11.6 2,11S2.4,10 3,10 4,10.4 4,11 3.6,12 3,12M16,4C13.8,4 12,5.8 12,8S13.8,12 16,12 20,10.2 20,8 18.2,4 16,4M16,10.1C14.8,10.1 13.9,9.2 13.9,8C13.9,6.8 14.8,5.9 16,5.9C17.2,5.9 18.1,6.8 18.1,8S17.2,10.1 16,10.1M16,13C13.3,13 8,14.3 8,17V20H24V17C24,14.3 18.7,13 16,13M22.1,18.1H9.9V17C9.9,16.4 13,14.9 16,14.9C19,14.9 22.1,16.4 22.1,17V18.1Z"/>
</vector>
+14 -12
View File
@@ -1,21 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?attr/activityBackgroundColor">
<androidx.appcompat.widget.Toolbar
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="0dp"
android:layout_weight="1.0"
android:id="@+id/aboutviewroot_ll"/>
<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>
@@ -5,8 +5,10 @@
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<cc.winboll.studio.libappbase.LogView
android:layout_height="match_parent"
android:layout_width="match_parent"
android:id="@+id/logview"/>
<androidx.appcompat.widget.Toolbar
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/activity_layoutdraft_preview_toolbar"/>
</LinearLayout>
@@ -12,11 +12,6 @@
android:layout_height="wrap_content"
android:id="@+id/activitymainToolbar1"/>
<cc.winboll.studio.libaes.views.ADsBannerView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/adsbanner"/>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="0dp"
@@ -17,6 +17,7 @@
android:id="@+id/activitymainToolbar1"/>
<LinearLayout
android:id="@+id/activity_settings_content"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="0dp"
@@ -57,7 +58,6 @@
android:id="@+id/tv_DunInfo"/>
</LinearLayout>
<LinearLayout
android:orientation="vertical"
@@ -325,29 +325,18 @@
android:background="@drawable/recycler_view_border"
android:layout_margin="5dp"/>
<TextView
android:layout_width="wrap_content"
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="单元测试:"/>
android:text="初始化测试规则"
android:onClick="onInitTestRules"
android:layout_margin="5dp"/>
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="right">
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="LogView"
android:onClick="onLogView"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Unit Test"
android:onClick="onUnitTest"/>
</LinearLayout>
<TextView
@@ -369,11 +358,6 @@
</LinearLayout>
<cc.winboll.studio.libaes.views.ADsControlView
android:id="@+id/ads_control_view"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
</LinearLayout>
@@ -1,13 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="@+id/image"
<cc.winboll.studio.contacts.views.DunQueryView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="fitCenter"/>
android:layout_height="wrap_content"/>
</FrameLayout>
</LinearLayout>
@@ -0,0 +1,68 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:layout_marginBottom="8dp"
android:padding="12dp"
android:background="@drawable/recycler_view_border">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="调试工具"
android:textSize="16sp"
android:textStyle="bold"
android:textColor="@color/red_normal"
android:layout_marginBottom="8dp"/>
<HorizontalScrollView
android:layout_width="match_parent"
android:layout_height="wrap_content">
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="right">
<Button
android:id="@+id/btn_debug_logview"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="应用日志"
android:textSize="12sp"
android:layout_margin="2dp"/>
<Button
android:id="@+id/btn_debug_unit_test"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="单元测试"
android:textSize="12sp"
android:layout_margin="2dp"/>
<Button
android:id="@+id/btn_debug_layout_preview"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="布局草稿"
android:textSize="12sp"
android:layout_margin="2dp"/>
<Button
android:id="@+id/btn_debug_crash"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="测试崩溃"
android:textSize="12sp"
android:layout_margin="2dp"/>
</LinearLayout>
</HorizontalScrollView>
</LinearLayout>
@@ -0,0 +1,38 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:layout_marginBottom="8dp"
android:padding="12dp"
android:background="@drawable/recycler_view_border">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="云盾拦截查询"
android:textSize="16sp"
android:textStyle="bold"
android:textColor="@color/red_normal"
android:layout_marginBottom="8dp"/>
<EditText
android:id="@+id/et_dun_query_phone"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="输入电话号码查询拦截状态"
android:inputType="phone"
android:textSize="14sp"
android:layout_marginBottom="8dp"/>
<TextView
android:id="@+id/tv_dun_query_result"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="请输入电话号码"
android:textSize="14sp"
android:textColor="@color/gray_500"/>
</LinearLayout>
+64
View File
@@ -10,4 +10,68 @@
<color name="white">#FFFFFFFF</color>
<color name="lightgray">#FFE0E0E0</color>
<!-- 基础黑白(必含,适配文字/背景) -->
<color name="black">#000000</color>
<!-- 基础色系(按钮/强调色常用) -->
<color name="blue_light">#4A90E2</color>
<color name="blue_normal">#2196F3</color>
<color name="blue_dark">#1976D2</color>
<color name="green_light">#66BB6A</color>
<color name="green_normal">#4CAF50</color>
<color name="green_dark">#388E3C</color>
<color name="red_light">#EF5350</color>
<color name="red_normal">#F44336</color>
<color name="red_dark">#D32F2F</color>
<color name="yellow_light">#FFF59D</color>
<color name="yellow_normal">#FFC107</color>
<color name="yellow_dark">#FFA000</color>
<color name="orange_normal">#FF9800</color>
<color name="purple_normal">#9C27B0</color>
<!-- 透明色(遮罩/背景叠加) -->
<color name="transparent">#00000000</color>
<color name="black_transparent_50">#80000000</color>
<!-- 不透明灰色(常用深浅梯度) -->
<color name="gray_100">#F5F5F5</color>
<color name="gray_200">#EEEEEE</color>
<color name="gray_300">#E0E0E0</color>
<color name="gray_400">#BDBDBD</color>
<color name="gray_500">#9E9E9E</color>
<color name="gray_600">#757575</color>
<color name="gray_700">#616161</color>
<color name="gray_800">#424242</color>
<color name="gray_900">#212121</color>
<!-- 半透明灰色 -->
<color name="gray_transparent_30">#4D9E9E9E</color>
<color name="gray_transparent_50">#809E9E9E</color>
<color name="gray_transparent_70">#B39E9E9E</color>
<color name="gray_light">#EEE</color>
<color name="gray_mid">#999</color>
<color name="gray_dark">#666</color>
<color name="gray_black">#333</color>
<!-- 遮罩/蒙层 -->
<color name="mask_gray">#809E9E9E</color>
<color name="bg_overlay_gray">#4D424242</color>
<!-- 按钮灰色 -->
<color name="btn_gray_normal">#9E9E9E</color>
<color name="btn_gray_pressed">#757575</color>
<color name="btn_gray_disabled">#E0E0E0</color>
<!-- 主题颜色 (适配 libappbase) -->
<color name="colorText">#FF000000</color>
<color name="colorTextBackgound">#FFF5F5F5</color>
<color name="mainWindowBackgroundColor">#FFF5F5F5</color>
<color name="mainWindowTextColor">#FF000000</color>
<color name="mainWindowDarkBackgroundColor">#FFF5F5F5</color>
<color name="mainWindowDarkTextColor">#FF000000</color>
<color name="toolbarTextColor">#FFFFFFFF</color>
<color name="toolbarBackgroundColor">#FF196ABC</color>
<color name="debugTextColor">#FF808080</color>
</resources>
+2 -1
View File
@@ -2,6 +2,7 @@
<resources>
<string name="app_name">Contacts</string>
<string name="default_bobulltoon_url">https://gitea.winboll.cc/Studio/BoBullToon/archive/main.zip</string>
<string name="app_description">This is a mobile dialer app that can block nuisance calls by matching regular expressions.</string>
<string name="default_bobulltoon_url">https://gitea.winboll.cc/ZhanGSKen/BoBullToon/archive/main.zip</string>
</resources>
+34 -30
View File
@@ -7,42 +7,46 @@
<item name="colorAccent">@color/colorAccent</item>
<item name="android:windowFullscreen">false</item>
<item name="android:windowContentOverlay">@null</item>
<!-- libappbase自定义属性 -->
<item name="themeDebug">@style/DebugActivityTheme</item>
<item name="aboutViewBackgroundColor">?attr/mainWindowBackgroundColor</item>
<item name="aboutViewTextColor">?attr/mainWindowTextColor</item>
<item name="aboutViewTitleColor">?attr/mainWindowTextColor</item>
<item name="aboutViewDividerColor">?attr/mainWindowDarkTextColor</item>
<item name="dialogBackgroundColor">?attr/mainWindowBackgroundColor</item>
<item name="dialogTextColor">?attr/mainWindowTextColor</item>
<item name="toolbarBackgroundColor">@color/toolbarBackgroundColor</item>
<item name="toolbarTextColor">@color/toolbarTextColor</item>
<item name="textViewBackgroundColor">?attr/mainWindowBackgroundColor</item>
<item name="textViewTextColor">?attr/mainWindowTextColor</item>
<item name="editTextBackgroundColor">?attr/mainWindowBackgroundColor</item>
<item name="editTextTextColor">?attr/mainWindowTextColor</item>
<item name="scrollViewBackgroundColor">?attr/mainWindowBackgroundColor</item>
<item name="activityBackgroundColor">?attr/mainWindowBackgroundColor</item>
<item name="activityTextColor">?attr/mainWindowTextColor</item>
<item name="mainWindowBackgroundColor">@color/mainWindowBackgroundColor</item>
<item name="mainWindowTextColor">@color/mainWindowTextColor</item>
<item name="mainWindowDarkBackgroundColor">@color/mainWindowBackgroundColor</item>
<item name="mainWindowDarkTextColor">@color/mainWindowTextColor</item>
<item name="debugTextColor">@color/debugTextColor</item>
</style>
<style name="GlobalCrashActivityTheme" parent="Theme.MaterialComponents.Light.NoActionBar">
<style name="DebugActivityTheme" parent="Theme.AppCompat.Light.NoActionBar">
<item name="android:statusBarColor">@color/toolbarBackgroundColor</item>
<item name="colorTittle">?attr/mainWindowTextColor</item>
<item name="colorTittleBackgound">@color/toolbarBackgroundColor</item>
<item name="colorText">?attr/debugTextColor</item>
<item name="colorTextBackgound">?attr/mainWindowBackgroundColor</item>
<item name="debugTextColor">@color/debugTextColor</item>
<item name="toolbarTextColor">@color/toolbarTextColor</item>
</style>
<style name="GlobalCrashActivityTheme" parent="Theme.AppCompat.Light.NoActionBar">
<item name="colorTittle">@color/colorAccent</item>
<item name="colorTittleBackgound">@color/colorPrimary</item>
<item name="colorText">@color/colorAccent</item>
<item name="colorTextBackgound">@color/colorPrimaryDark</item>
</style>
<!-- 方案2:带 ActionBar 主题(如需系统默认标题栏,启用此方案) -->
<!--
<style name="MyAppTheme" parent="Theme.MaterialComponents.Light">
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item>
<item name="android:windowFullscreen">true</item>
<item name="android:windowContentOverlay">@null</item>
<item name="android:textSizeHeadline">18sp</item>
<item name="android:textSizeBody">16sp</item>
<item name="android:textSizeSubtitle">14sp</item>
<item name="android:textSizeCaption">12sp</item>
</style>
<style name="GlobalCrashActivityTheme" parent="Theme.MaterialComponents.Light">
<item name="colorTittle">@color/colorAccent</item>
<item name="colorTittleBackgound">@color/colorPrimary</item>
<item name="colorText">@color/colorAccent</item>
<item name="colorTextBackgound">@color/colorPrimaryDark</item>
<item name="android:textSizeHeadline">20sp</item>
<item name="android:textSizeBody">14sp</item>
<item name="android:textSizeButton">16sp</item>
</style>
-->
</resources>
@@ -6,4 +6,7 @@
<domain-config cleartextTrafficPermitted="true">
<domain includeSubdomains="false">10.8.0.12</domain>
</domain-config>
<domain-config cleartextTrafficPermitted="true">
<domain includeSubdomains="false">10.8.0.4</domain>
</domain-config>
</network-security-config>
-1
View File
@@ -1 +0,0 @@
-120
View File
@@ -1,120 +0,0 @@
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.debugtemp"
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(':libdebugtemp')
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'])
}
-8
View File
@@ -1,8 +0,0 @@
#Created by .winboll/winboll_app_build.gradle
#Fri May 01 17:09:11 HKT 2026
stageCount=57
libraryProject=libdebugtemp
baseVersion=15.0
publishVersion=15.0.56
buildCount=0
baseBetaVersion=15.0.57
-12
View File
@@ -1,12 +0,0 @@
<?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>
@@ -1,6 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">DebugTemp☆</string>
</resources>
@@ -1,38 +0,0 @@
package cc.winboll.studio.debugtemp;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
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;
public class MainActivity extends AppCompatActivity {
LogView mLogView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Toolbar toolbar=(Toolbar)findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
mLogView = findViewById(R.id.logview);
ToastUtils.show("onCreate");
}
@Override
protected void onResume() {
super.onResume();
mLogView.start();
}
public void onLibraryActivity(View view) {
startActivity(new Intent(this, LibraryActivity.class));
}
}
@@ -1,34 +0,0 @@
<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>
@@ -1,170 +0,0 @@
<?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>
@@ -1,5 +0,0 @@
<?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>
@@ -1,5 +0,0 @@
<?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.

Before

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

-6
View File
@@ -1,6 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="colorPrimary">#009688</color>
<color name="colorPrimaryDark">#00796B</color>
<color name="colorAccent">#FF9800</color>
</resources>
@@ -1,4 +0,0 @@
<resources>
<string name="app_name">DebugTemp</string>
</resources>
-11
View File
@@ -1,11 +0,0 @@
<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>
-12
View File
@@ -1,12 +0,0 @@
<?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>
@@ -1,6 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Put flavor specific strings here -->
</resources>
-34
View File
@@ -1,34 +0,0 @@
# Gallery
#### 介绍
云宝相册应用
#### 软件架构
适配安卓应用 [AIDE Pro] 的 Gradle 编译结构。
也适配安卓应用 [AndroidIDE] 的 Gradle 编译结构。
#### Gradle 编译说明
调试版编译命令 gradle assembleBetaDebug
阶段版编译命令 bash .winboll/bashPublishAPKAddTag.sh gallery
#### 使用说明
#### 参与贡献
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/)
#### 参考文档
-1
View File
@@ -1 +0,0 @@

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