Compare commits
60 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 3fde2551f9 | |||
| 3a8d509f4e | |||
| da529c737b | |||
| b1be995092 | |||
| 4247934a20 | |||
| bf5fe296ff | |||
| f6bc48d5d2 | |||
| e2c9f362fc | |||
| 7f53355005 | |||
| f09ab4036f | |||
| 405ebe41db | |||
| 54497d8adb | |||
| b50de904f2 | |||
| 18e41a7a8d | |||
| 8713872d62 | |||
| b16f29a43f | |||
| ebaf487d0a | |||
| d5d230c1bb | |||
| 1d0322252e | |||
| 22f9eff429 | |||
| c8e55730f3 | |||
| 5d73960a54 | |||
| 2661bd1d5f | |||
| 3aaa7ab4fb | |||
| c75344c238 | |||
| 927dfc47ff | |||
| 7587fba89b | |||
| ee73bc3b78 | |||
| fa95bcd873 | |||
| e136045d1f | |||
| bd635425a1 | |||
| 49414a3938 | |||
| 08a47768a1 | |||
| 79f161b559 | |||
| b14538db47 | |||
| c9e16a9a1e | |||
| 42dbfb4238 | |||
| b274503187 | |||
| b50e4e02ea | |||
| 84f1c820cb | |||
| 0e60d75ac6 | |||
| 773cc9f6c1 | |||
| ca33dab4da | |||
| eee8809485 | |||
| 69f79549e9 | |||
| f204cd2608 | |||
| 2d3592690a | |||
| 576f8bb5e5 | |||
| e787697d6b | |||
| 3db3edcf2c | |||
| 643d08bf22 | |||
| beefb16c32 | |||
| 878009ec9f | |||
| 2d8b5d515b | |||
| 1453287667 | |||
| 5fc59f3305 | |||
| b6ba73126f | |||
| bdd8d1e2b6 | |||
| 138ca80e35 | |||
| 7a3b54b78b |
@@ -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')
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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}"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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.
|
||||
@@ -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 生成)
|
||||
@@ -39,10 +39,6 @@ android {
|
||||
}
|
||||
}
|
||||
|
||||
// 米盟 SDK
|
||||
packagingOptions {
|
||||
doNotStrip "*/*/libmimo_1011.so"
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
#Created by .winboll/winboll_app_build.gradle
|
||||
#Tue Jun 02 08:54:20 HKT 2026
|
||||
stageCount=13
|
||||
#Sat Jul 11 20:07:00 HKT 2026
|
||||
stageCount=19
|
||||
libraryProject=libaes
|
||||
baseVersion=15.20
|
||||
publishVersion=15.20.12
|
||||
publishVersion=15.20.18
|
||||
buildCount=0
|
||||
baseBetaVersion=15.20.13
|
||||
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);
|
||||
|
||||
@@ -7,9 +7,12 @@ package cc.winboll.studio.aes;
|
||||
*/
|
||||
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 java.util.ArrayList;
|
||||
import cc.winboll.studio.libappbase.utils.CrashHandleNotifyUtils;
|
||||
import java.io.PrintWriter;
|
||||
import java.io.StringWriter;
|
||||
|
||||
|
||||
public class App extends GlobalApplication {
|
||||
@@ -18,12 +21,25 @@ public class App extends GlobalApplication {
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
AESThemeUtil.init(null);
|
||||
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
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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,8 +1,8 @@
|
||||
#Created by .winboll/winboll_app_build.gradle
|
||||
#Wed May 27 14:51:29 HKT 2026
|
||||
stageCount=23
|
||||
#Sun Jun 28 20:55:33 HKT 2026
|
||||
stageCount=35
|
||||
libraryProject=libappbase
|
||||
baseVersion=15.20
|
||||
publishVersion=15.20.22
|
||||
publishVersion=15.20.34
|
||||
buildCount=0
|
||||
baseBetaVersion=15.20.23
|
||||
baseBetaVersion=15.20.35
|
||||
|
||||
@@ -37,7 +37,7 @@ public class AboutActivity extends Activity {
|
||||
appInfo.setAppName("APPBase");
|
||||
appInfo.setAppIcon(R.drawable.ic_winboll);
|
||||
appInfo.setAppDescription(getString(R.string.app_description));
|
||||
appInfo.setAppGitName("APPBase");
|
||||
appInfo.setAppGitName("WinBoLL");
|
||||
appInfo.setAppGitOwner("Studio");
|
||||
appInfo.setAppGitAPPBranch(branchName);
|
||||
appInfo.setAppGitAPPSubProjectFolder(branchName);
|
||||
|
||||
@@ -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,10 +24,24 @@ public class App extends GlobalApplication {
|
||||
*/
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
|
||||
// 初始化 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
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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/)
|
||||
|
||||
#### 参考文档
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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,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>
|
||||
|
||||
@@ -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>
|
||||
@@ -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">AutoNFC</string>
|
||||
|
||||
</resources>
|
||||
@@ -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>
|
||||
@@ -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.21"
|
||||
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,27 @@ 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 'cc.winboll.studio:libaes:15.21.+'
|
||||
api 'cc.winboll.studio:libappbase:15.21.+'
|
||||
|
||||
api fileTree(dir: 'libs', include: ['*.jar'])
|
||||
}
|
||||
|
||||
@@ -1,8 +1,18 @@
|
||||
#Created by .winboll/winboll_app_build.gradle
|
||||
#Sat Apr 18 21:14:59 HKT 2026
|
||||
stageCount=13
|
||||
<<<<<<< HEAD
|
||||
#Tue Jul 21 11:13:12 CST 2026
|
||||
stageCount=12
|
||||
libraryProject=
|
||||
baseVersion=15.14
|
||||
publishVersion=15.14.12
|
||||
buildCount=0
|
||||
baseBetaVersion=15.14.13
|
||||
baseVersion=15.21
|
||||
publishVersion=15.20.11
|
||||
buildCount=22
|
||||
baseBetaVersion=15.20.12
|
||||
=======
|
||||
#Tue Jul 21 02:54:31 GMT 2026
|
||||
stageCount=0
|
||||
libraryProject=
|
||||
baseVersion=15.21
|
||||
publishVersion=15.21.0
|
||||
buildCount=1
|
||||
baseBetaVersion=15.21.1
|
||||
>>>>>>> origin/contacts
|
||||
|
||||
@@ -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"/>
|
||||
|
||||
@@ -251,8 +254,10 @@
|
||||
|
||||
<activity android:name="cc.winboll.studio.contacts.activities.AboutActivity"/>
|
||||
|
||||
<service android:name="cc.winboll.studio.contacts.services.LimitedTimeSpecialChannelService"/>
|
||||
<service android:name="cc.winboll.studio.contacts.services.LimitedTimeGreenChannelService"/>
|
||||
|
||||
<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,9 +5,9 @@ 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.services.LimitedTimeGreenChannelService;
|
||||
import cc.winboll.studio.contacts.utils.IntUtils;
|
||||
import cc.winboll.studio.libaes.interfaces.IWinBoLLActivity;
|
||||
import cc.winboll.studio.libappbase.LogUtils;
|
||||
@@ -103,7 +103,7 @@ public class UnitTestActivity extends WinBollActivity implements IWinBoLLActivit
|
||||
// 初始化规则实例
|
||||
Rules rules = Rules.getInstance(this);
|
||||
// 无规则时添加测试规则集
|
||||
initTestRulesIfEmpty(rules);
|
||||
SettingsActivity.initTestRulesIfEmpty(rules);
|
||||
|
||||
// 预设测试号码列表
|
||||
String[] testPhones = {
|
||||
@@ -122,33 +122,9 @@ public class UnitTestActivity extends WinBollActivity implements IWinBoLLActivit
|
||||
new Thread(new Runnable(){
|
||||
@Override
|
||||
public void run() {
|
||||
LimitedTimeSpecialChannelService.unitTest(UnitTestActivity.this);
|
||||
LimitedTimeGreenChannelService.unitTest(UnitTestActivity.this);
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
// ====================== 私有工具函数区 ======================
|
||||
/**
|
||||
* 规则集为空时初始化测试规则
|
||||
*/
|
||||
private void initTestRulesIfEmpty(Rules rules) {
|
||||
if (rules.getPhoneBlacRuleBeanList().size() == 0) {
|
||||
LogUtils.d(TAG, "initTestRulesIfEmpty: 当前无规则,添加测试规则集");
|
||||
// 规则1:中国手机号允许
|
||||
rules.add("^1[3-9]\\d{9}$", true, true);
|
||||
// 规则2:0660区号号码允许
|
||||
rules.add("^0660\\d+$", true, true);
|
||||
// 规则3:020区号号码允许
|
||||
rules.add("^020\\d+$", true, true);
|
||||
// 规则4:默认拒接所有号码
|
||||
rules.add(".*", false, true);
|
||||
|
||||
// 保存规则到本地
|
||||
rules.saveRules();
|
||||
LogUtils.d(TAG, "initTestRulesIfEmpty: 测试规则集已保存");
|
||||
} else {
|
||||
LogUtils.d(TAG, "initTestRulesIfEmpty: 当前已有规则,跳过初始化");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +1,16 @@
|
||||
package cc.winboll.studio.contacts.activities;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.view.MenuItem;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
|
||||
import cc.winboll.studio.contacts.dun.Rules;
|
||||
import cc.winboll.studio.contacts.model.SettingsBean;
|
||||
import cc.winboll.studio.contacts.services.LimitedTimeGreenChannelService;
|
||||
import cc.winboll.studio.libaes.interfaces.IWinBoLLActivity;
|
||||
import cc.winboll.studio.libaes.models.AESThemeBean;
|
||||
import cc.winboll.studio.libaes.utils.AESThemeUtil;
|
||||
@@ -21,6 +28,8 @@ public class WinBollActivity extends AppCompatActivity implements IWinBoLLActivi
|
||||
|
||||
// ====================== 成员变量区 ======================
|
||||
protected volatile AESThemeBean.ThemeType mThemeType;
|
||||
private android.content.BroadcastReceiver mGreenChannelReceiver;
|
||||
private int mDefaultStatusBarColor = 0; // 默认状态栏颜色
|
||||
|
||||
// ====================== 接口实现区 ======================
|
||||
@Override
|
||||
@@ -42,6 +51,114 @@ public class WinBollActivity extends AppCompatActivity implements IWinBoLLActivi
|
||||
// setThemeStyle();
|
||||
super.onCreate(savedInstanceState);
|
||||
//LogUtils.d(TAG, "onCreate: 基类主题设置完成,当前主题类型=" + mThemeType);
|
||||
|
||||
// 保存默认状态栏颜色
|
||||
saveDefaultStatusBarColor();
|
||||
|
||||
// 检测绿色通道状态,设置顶部状态栏背景色
|
||||
updateStatusBarForGreenChannel();
|
||||
|
||||
// 注册绿色通道状态变更广播接收器
|
||||
registerGreenChannelReceiver();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
super.onDestroy();
|
||||
// 反注册绿色通道状态变更广播接收器
|
||||
unregisterGreenChannelReceiver();
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据绿色通道状态更新顶部状态栏背景色
|
||||
* 绿色通道开启时状态栏背景为绿色,其他情况不改变
|
||||
*/
|
||||
private void updateStatusBarForGreenChannel() {
|
||||
// API 21+ 才支持设置状态栏颜色
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
boolean isGreenChannelEnabled = false;
|
||||
|
||||
Rules rules = Rules.getInstance(getApplicationContext());
|
||||
if (rules != null) {
|
||||
SettingsBean settings = rules.getSettingsModel();
|
||||
if (settings != null) {
|
||||
isGreenChannelEnabled = settings.isEnableGreenChannel();
|
||||
}
|
||||
}
|
||||
|
||||
if (isGreenChannelEnabled) {
|
||||
// 绿色通道开启,设置状态栏为绿色
|
||||
getWindow().setStatusBarColor(android.graphics.Color.GREEN);
|
||||
LogUtils.d(TAG, "updateStatusBarForGreenChannel: 绿色通道已开启,状态栏背景设为绿色");
|
||||
} else {
|
||||
// 绿色通道关闭,恢复默认状态栏颜色
|
||||
// 如果默认颜色为0(透明),则获取主题默认颜色
|
||||
int restoreColor = mDefaultStatusBarColor;
|
||||
if (restoreColor == 0) {
|
||||
// 从主题中获取默认状态栏颜色
|
||||
android.util.TypedValue typedValue = new android.util.TypedValue();
|
||||
if (getTheme().resolveAttribute(android.R.attr.statusBarColor, typedValue, true)) {
|
||||
restoreColor = typedValue.data;
|
||||
}
|
||||
}
|
||||
getWindow().setStatusBarColor(restoreColor);
|
||||
LogUtils.d(TAG, "updateStatusBarForGreenChannel: 绿色通道已关闭,状态栏背景已恢复, color=0x" + Integer.toHexString(restoreColor));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存默认状态栏颜色(在设置绿色通道颜色之前调用)
|
||||
*/
|
||||
private void saveDefaultStatusBarColor() {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
mDefaultStatusBarColor = getWindow().getStatusBarColor();
|
||||
|
||||
// 如果获取到的颜色为0(透明),尝试从主题获取默认颜色
|
||||
if (mDefaultStatusBarColor == 0) {
|
||||
android.util.TypedValue typedValue = new android.util.TypedValue();
|
||||
if (getTheme().resolveAttribute(android.R.attr.statusBarColor, typedValue, true)) {
|
||||
mDefaultStatusBarColor = typedValue.data;
|
||||
}
|
||||
}
|
||||
|
||||
LogUtils.d(TAG, "saveDefaultStatusBarColor: 保存默认状态栏颜色=0x" + Integer.toHexString(mDefaultStatusBarColor));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 注册绿色通道状态变更本地广播接收器
|
||||
*/
|
||||
private void registerGreenChannelReceiver() {
|
||||
LogUtils.d(TAG, "registerGreenChannelReceiver: 注册绿色通道状态变更广播接收器");
|
||||
mGreenChannelReceiver = new android.content.BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(android.content.Context context, Intent intent) {
|
||||
if (LimitedTimeGreenChannelService.ACTION_GREEN_CHANNEL_STATE_CHANGED.equals(intent.getAction())) {
|
||||
boolean isEnabled = intent.getBooleanExtra(LimitedTimeGreenChannelService.EXTRA_GREEN_CHANNEL_ENABLED, false);
|
||||
LogUtils.d(TAG, "registerGreenChannelReceiver: 收到绿色通道状态变更广播, isEnabled=" + isEnabled);
|
||||
|
||||
// 调用统一方法更新状态栏颜色
|
||||
updateStatusBarForGreenChannel();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
IntentFilter filter = new IntentFilter(LimitedTimeGreenChannelService.ACTION_GREEN_CHANNEL_STATE_CHANGED);
|
||||
LocalBroadcastManager.getInstance(this).registerReceiver(mGreenChannelReceiver, filter);
|
||||
LogUtils.d(TAG, "registerGreenChannelReceiver: 广播接收器注册完成");
|
||||
}
|
||||
|
||||
/**
|
||||
* 反注册绿色通道状态变更本地广播接收器
|
||||
*/
|
||||
private void unregisterGreenChannelReceiver() {
|
||||
if (mGreenChannelReceiver != null) {
|
||||
LogUtils.d(TAG, "unregisterGreenChannelReceiver: 反注册绿色通道状态变更广播接收器");
|
||||
LocalBroadcastManager.getInstance(this).unregisterReceiver(mGreenChannelReceiver);
|
||||
mGreenChannelReceiver = null;
|
||||
LogUtils.d(TAG, "unregisterGreenChannelReceiver: 反注册完成");
|
||||
}
|
||||
}
|
||||
|
||||
// ====================== 主题相关函数区 ======================
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ import cc.winboll.studio.contacts.activities.SettingsActivity;
|
||||
import cc.winboll.studio.contacts.bobulltoon.TomCat;
|
||||
import cc.winboll.studio.contacts.model.PhoneConnectRuleBean;
|
||||
import cc.winboll.studio.contacts.model.SettingsBean;
|
||||
import cc.winboll.studio.contacts.services.LimitedTimeSpecialChannelService;
|
||||
import cc.winboll.studio.contacts.services.LimitedTimeGreenChannelService;
|
||||
import cc.winboll.studio.contacts.services.MainService;
|
||||
import cc.winboll.studio.contacts.utils.ContactUtils;
|
||||
import cc.winboll.studio.contacts.utils.IntUtils;
|
||||
@@ -172,9 +172,9 @@ public class Rules {
|
||||
LogUtils.d(TAG, String.format("isDefend == %s\nisConnect == %s", isDefend, isConnect));
|
||||
}
|
||||
|
||||
// 限时特殊通道打开时返回连接
|
||||
if (!isDefend && LimitedTimeSpecialChannelService.isServiceRunning()) {
|
||||
LogUtils.d(TAG, String.format("PhoneNumber %s\n and Limited Time Special Channel Service Is Running.", phoneNumber));
|
||||
// 限时绿色通道打开时返回连接
|
||||
if (!isDefend && LimitedTimeGreenChannelService.isServiceRunning()) {
|
||||
LogUtils.d(TAG, String.format("PhoneNumber %s\n and Limited Time Green Channel Service Is Running.", phoneNumber));
|
||||
isDefend = true;
|
||||
isConnect = true;
|
||||
LogUtils.d(TAG, String.format("isDefend == %s\nisConnect == %s", isDefend, isConnect));
|
||||
|
||||
@@ -0,0 +1,284 @@
|
||||
package cc.winboll.studio.contacts.floatingwindow;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.graphics.PixelFormat;
|
||||
import android.os.Build;
|
||||
import android.view.Gravity;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.WindowManager;
|
||||
import android.widget.FrameLayout;
|
||||
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
|
||||
import cc.winboll.studio.contacts.R;
|
||||
import cc.winboll.studio.contacts.services.LimitedTimeGreenChannelService;
|
||||
import cc.winboll.studio.contacts.utils.PermissionUtils;
|
||||
import cc.winboll.studio.libappbase.LogUtils;
|
||||
|
||||
/**
|
||||
* @Author ZhanGSKen&豆包大模型<zhangsken@qq.com>
|
||||
* @Date 2026/07/20 15:00:00
|
||||
* @Describe 绿色通道悬浮窗管理类
|
||||
* 负责悬浮窗的创建、显示、隐藏和销毁
|
||||
* 适配 Android API 26+ Java 7 语法规范
|
||||
*/
|
||||
public class GreenChannelFloatingWindow {
|
||||
|
||||
// ====================== 常量定义区 ======================
|
||||
public static final String TAG = "GreenChannelFloatingWindow";
|
||||
|
||||
// Android版本常量(适配Java7)
|
||||
private static final int ANDROID_8_API = 26;
|
||||
private static final int ANDROID_10_API = 29;
|
||||
private static final int ANDROID_19_API = 19;
|
||||
|
||||
// ====================== 成员变量区 ======================
|
||||
private Context mContext;
|
||||
private WindowManager mWindowManager;
|
||||
private WindowManager.LayoutParams mWindowParams;
|
||||
private View mFloatingView;
|
||||
private boolean mIsShowing;
|
||||
private android.content.BroadcastReceiver mGreenChannelReceiver;
|
||||
|
||||
// ====================== 构造函数区 ======================
|
||||
/**
|
||||
* 构造函数
|
||||
* @param context 上下文
|
||||
*/
|
||||
public GreenChannelFloatingWindow(Context context) {
|
||||
this.mContext = context;
|
||||
this.mIsShowing = false;
|
||||
LogUtils.d(TAG, "GreenChannelFloatingWindow: 构造函数调用完成");
|
||||
}
|
||||
|
||||
// ====================== 公共方法区 ======================
|
||||
/**
|
||||
* 显示悬浮窗
|
||||
* @return true=显示成功,false=显示失败(权限不足或已显示)
|
||||
*/
|
||||
public boolean show() {
|
||||
LogUtils.d(TAG, "show: 尝试显示悬浮窗");
|
||||
|
||||
// 检查是否已显示
|
||||
if (mIsShowing) {
|
||||
LogUtils.w(TAG, "show: 悬浮窗已显示,跳过重复显示");
|
||||
return true;
|
||||
}
|
||||
|
||||
// 检查悬浮窗权限
|
||||
if (!PermissionUtils.isOverlayPermissionGranted(mContext)) {
|
||||
LogUtils.e(TAG, "show: 悬浮窗权限未授予,无法显示");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 初始化窗口管理器
|
||||
if (!initWindowManager()) {
|
||||
LogUtils.e(TAG, "show: 窗口管理器初始化失败");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 初始化悬浮窗视图
|
||||
if (!initFloatingView()) {
|
||||
LogUtils.e(TAG, "show: 悬浮窗视图初始化失败");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 显示悬浮窗
|
||||
try {
|
||||
mWindowManager.addView(mFloatingView, mWindowParams);
|
||||
mIsShowing = true;
|
||||
// 注册绿色通道状态变更广播接收器
|
||||
registerGreenChannelReceiver();
|
||||
LogUtils.d(TAG, "show: 悬浮窗显示成功");
|
||||
return true;
|
||||
} catch (SecurityException e) {
|
||||
LogUtils.e(TAG, "show: 悬浮窗显示失败(安全异常)", e);
|
||||
return false;
|
||||
} catch (Exception e) {
|
||||
LogUtils.e(TAG, "show: 悬浮窗显示异常", e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 隐藏悬浮窗
|
||||
*/
|
||||
public void dismiss() {
|
||||
LogUtils.d(TAG, "dismiss: 尝试隐藏悬浮窗");
|
||||
|
||||
if (!mIsShowing) {
|
||||
LogUtils.d(TAG, "dismiss: 悬浮窗未显示,跳过隐藏操作");
|
||||
return;
|
||||
}
|
||||
|
||||
// 反注册绿色通道状态变更广播接收器
|
||||
unregisterGreenChannelReceiver();
|
||||
|
||||
if (mFloatingView != null && mWindowManager != null) {
|
||||
try {
|
||||
mWindowManager.removeView(mFloatingView);
|
||||
mIsShowing = false;
|
||||
LogUtils.d(TAG, "dismiss: 悬浮窗隐藏成功");
|
||||
} catch (Exception e) {
|
||||
LogUtils.e(TAG, "dismiss: 悬浮窗隐藏异常", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查悬浮窗是否正在显示
|
||||
* @return true=正在显示,false=未显示
|
||||
*/
|
||||
public boolean isShowing() {
|
||||
return mIsShowing;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取悬浮窗视图
|
||||
* @return 悬浮窗视图,未初始化时返回null
|
||||
*/
|
||||
public View getFloatingView() {
|
||||
return mFloatingView;
|
||||
}
|
||||
|
||||
// ====================== 私有方法区 ======================
|
||||
/**
|
||||
* 初始化窗口管理器
|
||||
* @return true=初始化成功,false=初始化失败
|
||||
*/
|
||||
private boolean initWindowManager() {
|
||||
if (mWindowManager == null) {
|
||||
mWindowManager = (WindowManager) mContext.getApplicationContext()
|
||||
.getSystemService(Context.WINDOW_SERVICE);
|
||||
if (mWindowManager == null) {
|
||||
LogUtils.e(TAG, "initWindowManager: WindowManager获取失败");
|
||||
return false;
|
||||
}
|
||||
LogUtils.d(TAG, "initWindowManager: WindowManager初始化成功");
|
||||
}
|
||||
|
||||
// 初始化窗口参数
|
||||
initWindowParams();
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化窗口参数(适配API26+窗口类型)
|
||||
*/
|
||||
private void initWindowParams() {
|
||||
mWindowParams = new WindowManager.LayoutParams();
|
||||
|
||||
// 窗口位置:顶部居中
|
||||
mWindowParams.gravity = Gravity.CENTER_HORIZONTAL | Gravity.TOP;
|
||||
|
||||
// 窗口大小:宽度全屏,高度自适应
|
||||
mWindowParams.width = WindowManager.LayoutParams.MATCH_PARENT;
|
||||
mWindowParams.height = WindowManager.LayoutParams.WRAP_CONTENT;
|
||||
|
||||
// 窗口格式:半透明
|
||||
mWindowParams.format = PixelFormat.TRANSLUCENT;
|
||||
|
||||
// 窗口类型(适配API26+)
|
||||
if (Build.VERSION.SDK_INT >= ANDROID_10_API) {
|
||||
mWindowParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
|
||||
LogUtils.d(TAG, "initWindowParams: API29+ 悬浮窗类型=TYPE_APPLICATION_OVERLAY");
|
||||
} else if (Build.VERSION.SDK_INT >= ANDROID_8_API) {
|
||||
mWindowParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
|
||||
LogUtils.d(TAG, "initWindowParams: API26-28 悬浮窗类型=TYPE_APPLICATION_OVERLAY");
|
||||
} else {
|
||||
mWindowParams.type = WindowManager.LayoutParams.TYPE_PHONE;
|
||||
LogUtils.d(TAG, "initWindowParams: API26以下 悬浮窗类型=TYPE_PHONE");
|
||||
}
|
||||
|
||||
// 窗口标志:无焦点(不抢占输入)、全屏布局
|
||||
mWindowParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
|
||||
| WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN;
|
||||
|
||||
// API19+ 添加透明状态栏/导航栏标志
|
||||
if (Build.VERSION.SDK_INT >= ANDROID_19_API) {
|
||||
mWindowParams.flags |= WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS
|
||||
| WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION;
|
||||
}
|
||||
|
||||
LogUtils.d(TAG, "initWindowParams: 窗口参数初始化完成");
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化悬浮窗视图
|
||||
* @return true=初始化成功,false=初始化失败
|
||||
*/
|
||||
private boolean initFloatingView() {
|
||||
if (mFloatingView != null) {
|
||||
LogUtils.d(TAG, "initFloatingView: 视图已初始化,跳过重复创建");
|
||||
return true;
|
||||
}
|
||||
|
||||
try {
|
||||
// 创建FrameLayout拦截返回键
|
||||
FrameLayout keyInterceptorLayout = new FrameLayout(mContext) {
|
||||
@Override
|
||||
public boolean dispatchKeyEvent(KeyEvent event) {
|
||||
// 拦截返回键,避免误关闭悬浮窗
|
||||
if (event.getAction() == KeyEvent.ACTION_DOWN
|
||||
&& event.getKeyCode() == KeyEvent.KEYCODE_BACK) {
|
||||
LogUtils.d(TAG, "dispatchKeyEvent: 拦截悬浮窗返回键事件");
|
||||
return true;
|
||||
}
|
||||
return super.dispatchKeyEvent(event);
|
||||
}
|
||||
};
|
||||
|
||||
// 加载悬浮窗布局
|
||||
mFloatingView = LayoutInflater.from(mContext)
|
||||
.inflate(R.layout.view_green_channel_floating, keyInterceptorLayout);
|
||||
|
||||
LogUtils.d(TAG, "initFloatingView: 悬浮窗视图初始化完成");
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
LogUtils.e(TAG, "initFloatingView: 悬浮窗视图初始化异常", e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 注册绿色通道状态变更广播接收器
|
||||
* 接收 LimitedTimeGreenChannelView 发出的通道开启与关闭消息
|
||||
*/
|
||||
private void registerGreenChannelReceiver() {
|
||||
LogUtils.d(TAG, "registerGreenChannelReceiver: 注册绿色通道状态变更广播接收器");
|
||||
mGreenChannelReceiver = new android.content.BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
if (LimitedTimeGreenChannelService.ACTION_GREEN_CHANNEL_STATE_CHANGED.equals(intent.getAction())) {
|
||||
boolean isEnabled = intent.getBooleanExtra(
|
||||
LimitedTimeGreenChannelService.EXTRA_GREEN_CHANNEL_ENABLED, false);
|
||||
LogUtils.d(TAG, "registerGreenChannelReceiver: 收到绿色通道状态变更广播, isEnabled=" + isEnabled);
|
||||
|
||||
// 如果绿色通道关闭,关闭悬浮窗
|
||||
if (!isEnabled) {
|
||||
LogUtils.d(TAG, "registerGreenChannelReceiver: 绿色通道已关闭,准备关闭悬浮窗");
|
||||
dismiss();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
IntentFilter filter = new IntentFilter(LimitedTimeGreenChannelService.ACTION_GREEN_CHANNEL_STATE_CHANGED);
|
||||
LocalBroadcastManager.getInstance(mContext).registerReceiver(mGreenChannelReceiver, filter);
|
||||
LogUtils.d(TAG, "registerGreenChannelReceiver: 广播接收器注册完成");
|
||||
}
|
||||
|
||||
/**
|
||||
* 反注册绿色通道状态变更广播接收器
|
||||
*/
|
||||
private void unregisterGreenChannelReceiver() {
|
||||
if (mGreenChannelReceiver != null) {
|
||||
LogUtils.d(TAG, "unregisterGreenChannelReceiver: 反注册绿色通道状态变更广播接收器");
|
||||
LocalBroadcastManager.getInstance(mContext).unregisterReceiver(mGreenChannelReceiver);
|
||||
mGreenChannelReceiver = null;
|
||||
LogUtils.d(TAG, "unregisterGreenChannelReceiver: 反注册完成");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
/**
|
||||
@@ -28,6 +28,7 @@ public class SettingsBean extends BaseBean {
|
||||
private static final String JSON_KEY_DUN_RESUME_COUNT = "dunResumeCount";
|
||||
private static final String JSON_KEY_DUN_ENABLE = "isEnableDun";
|
||||
private static final String JSON_KEY_URL = "szBoBullToon_URL";
|
||||
private static final String JSON_KEY_GREEN_CHANNEL_ENABLE = "isEnableGreenChannel";
|
||||
|
||||
// ====================== 成员变量区 ======================
|
||||
// 云盾防御层数量
|
||||
@@ -40,6 +41,8 @@ public class SettingsBean extends BaseBean {
|
||||
private int dunResumeCount;
|
||||
// 是否启用云盾
|
||||
private boolean isEnableDun;
|
||||
// 是否启用绿色通道
|
||||
private boolean isEnableGreenChannel;
|
||||
// BoBullToon 应用模块数据请求地址
|
||||
private String szBoBullToon_URL;
|
||||
|
||||
@@ -53,6 +56,7 @@ public class SettingsBean extends BaseBean {
|
||||
this.dunResumeSecondCount = 60;
|
||||
this.dunResumeCount = 1;
|
||||
this.isEnableDun = false;
|
||||
this.isEnableGreenChannel = false;
|
||||
this.szBoBullToon_URL = "";
|
||||
LogUtils.d(TAG, "SettingsModel: 默认构造初始化完成 | 云盾默认配置加载完毕");
|
||||
}
|
||||
@@ -61,17 +65,20 @@ public class SettingsBean extends BaseBean {
|
||||
* 带参构造,初始化自定义配置并校验数值范围
|
||||
*/
|
||||
public SettingsBean(int dunTotalCount, int dunCurrentCount, int dunResumeSecondCount,
|
||||
int dunResumeCount, boolean isEnableDun, String szBoBullToon_URL) {
|
||||
int dunResumeCount, boolean isEnableDun, boolean isEnableGreenChannel,
|
||||
String szBoBullToon_URL) {
|
||||
this.dunTotalCount = getSettingsModelRangeInt(dunTotalCount);
|
||||
this.dunCurrentCount = getSettingsModelRangeInt(dunCurrentCount);
|
||||
this.dunResumeSecondCount = getSettingsModelRangeInt(dunResumeSecondCount);
|
||||
this.dunResumeCount = getSettingsModelRangeInt(dunResumeCount);
|
||||
this.isEnableDun = isEnableDun;
|
||||
this.isEnableGreenChannel = isEnableGreenChannel;
|
||||
this.szBoBullToon_URL = szBoBullToon_URL == null ? "" : szBoBullToon_URL;
|
||||
|
||||
LogUtils.d(TAG, "SettingsModel: 带参构造初始化完成 | 总层数=" + this.dunTotalCount
|
||||
+ " | 当前层数=" + this.dunCurrentCount + " | 恢复间隔=" + this.dunResumeSecondCount
|
||||
+ " | 恢复层数=" + this.dunResumeCount + " | 云盾启用=" + this.isEnableDun);
|
||||
+ " | 恢复层数=" + this.dunResumeCount + " | 云盾启用=" + this.isEnableDun
|
||||
+ " | 绿色通道启用=" + this.isEnableGreenChannel);
|
||||
}
|
||||
|
||||
// ====================== 私有工具方法区 ======================
|
||||
@@ -136,6 +143,15 @@ public class SettingsBean extends BaseBean {
|
||||
this.isEnableDun = isEnableDun;
|
||||
}
|
||||
|
||||
public boolean isEnableGreenChannel() {
|
||||
return isEnableGreenChannel;
|
||||
}
|
||||
|
||||
public void setIsEnableGreenChannel(boolean isEnableGreenChannel) {
|
||||
LogUtils.d(TAG, "setIsEnableGreenChannel: 绿色通道启用状态更新为" + isEnableGreenChannel);
|
||||
this.isEnableGreenChannel = isEnableGreenChannel;
|
||||
}
|
||||
|
||||
public String getBoBullToon_URL() {
|
||||
return szBoBullToon_URL;
|
||||
}
|
||||
@@ -164,6 +180,7 @@ public class SettingsBean extends BaseBean {
|
||||
jsonWriter.name(JSON_KEY_DUN_RESUME_SECOND).value(getDunResumeSecondCount());
|
||||
jsonWriter.name(JSON_KEY_DUN_RESUME_COUNT).value(getDunResumeCount());
|
||||
jsonWriter.name(JSON_KEY_DUN_ENABLE).value(isEnableDun());
|
||||
jsonWriter.name(JSON_KEY_GREEN_CHANNEL_ENABLE).value(isEnableGreenChannel());
|
||||
jsonWriter.name(JSON_KEY_URL).value(getBoBullToon_URL());
|
||||
LogUtils.d(TAG, "writeThisToJsonWriter: JSON序列化完成");
|
||||
}
|
||||
@@ -187,6 +204,8 @@ public class SettingsBean extends BaseBean {
|
||||
setDunResumeCount(getSettingsModelRangeInt(jsonReader.nextInt()));
|
||||
} else if (JSON_KEY_DUN_ENABLE.equals(name)) {
|
||||
setIsEnableDun(jsonReader.nextBoolean());
|
||||
} else if (JSON_KEY_GREEN_CHANNEL_ENABLE.equals(name)) {
|
||||
setIsEnableGreenChannel(jsonReader.nextBoolean());
|
||||
} else if (JSON_KEY_URL.equals(name)) {
|
||||
setBoBullToon_URL(jsonReader.nextString());
|
||||
} else {
|
||||
|
||||
@@ -13,17 +13,17 @@ import cc.winboll.studio.libappbase.LogUtils;
|
||||
* @Author 豆包&ZhanGSKen<zhangsken@qq.com>
|
||||
* @Date 2026/04/18 15:00:00 (GMT+8)
|
||||
* @LastEditTime 2026/04/22 17:20:00 (GMT+8)
|
||||
* @Describe 限时特殊通道服务
|
||||
* @Describe 限时绿色通道服务
|
||||
* 提供安全的服务启动、令牌校验及循环任务管理
|
||||
* 新增功能:
|
||||
* 1. 计时过程中实时输出剩余秒数
|
||||
* 2. 服务销毁前,发送本地广播通知应用内其他组件,并携带剩余秒数信息
|
||||
* 3. 每秒发送一次倒计时状态的本地广播
|
||||
*/
|
||||
public class LimitedTimeSpecialChannelService extends Service {
|
||||
public class LimitedTimeGreenChannelService extends Service {
|
||||
|
||||
// ========================= 常量定义 =========================
|
||||
public static final String TAG = "LimitedTimeSpecialChannelService";
|
||||
public static final String TAG = "LimitedTimeGreenChannelService";
|
||||
public static final String EXTRA_DELAY_MILLIS = "EXTRA_DELAY_MILLIS";
|
||||
private static final String EXTRA_SECURITY_TOKEN = "EXTRA_SECURITY_TOKEN";
|
||||
|
||||
@@ -32,6 +32,10 @@ public class LimitedTimeSpecialChannelService extends Service {
|
||||
|
||||
// 倒计时心跳广播 Action
|
||||
public static final String ACTION_COUNTDOWN_TICK = "cc.winboll.studio.contacts.services.ACTION_COUNTDOWN_TICK";
|
||||
// 绿色通道状态变更广播 Action
|
||||
public static final String ACTION_GREEN_CHANNEL_STATE_CHANGED = "cc.winboll.studio.contacts.services.ACTION_GREEN_CHANNEL_STATE_CHANGED";
|
||||
// 广播携带的额外参数:绿色通道是否开启
|
||||
public static final String EXTRA_GREEN_CHANNEL_ENABLED = "EXTRA_GREEN_CHANNEL_ENABLED";
|
||||
// 广播携带的额外参数:剩余秒数
|
||||
public static final String EXTRA_REMAINING_SECONDS = "EXTRA_REMAINING_SECONDS";
|
||||
// 广播携带的额外参数:总定时秒数
|
||||
@@ -44,7 +48,7 @@ public class LimitedTimeSpecialChannelService extends Service {
|
||||
*/
|
||||
public static final String mValidToken = "VALID_TOKEN_" + System.currentTimeMillis();
|
||||
|
||||
private static volatile LimitedTimeSpecialChannelService sInstance = null;
|
||||
private static volatile LimitedTimeGreenChannelService sInstance = null;
|
||||
private static volatile boolean sIsServiceRunning = false;
|
||||
|
||||
// ========================= 成员变量 =========================
|
||||
@@ -73,7 +77,7 @@ public class LimitedTimeSpecialChannelService extends Service {
|
||||
}
|
||||
|
||||
// 构建Intent传递参数
|
||||
Intent intent = new Intent(context, LimitedTimeSpecialChannelService.class);
|
||||
Intent intent = new Intent(context, LimitedTimeGreenChannelService.class);
|
||||
intent.putExtra(EXTRA_SECURITY_TOKEN, mValidToken);
|
||||
intent.putExtra(EXTRA_DELAY_MILLIS, delayMillis);
|
||||
|
||||
@@ -93,7 +97,7 @@ public class LimitedTimeSpecialChannelService extends Service {
|
||||
LogUtils.w(TAG, "停止失败,上下文为null");
|
||||
return;
|
||||
}
|
||||
Intent intent = new Intent(context, LimitedTimeSpecialChannelService.class);
|
||||
Intent intent = new Intent(context, LimitedTimeGreenChannelService.class);
|
||||
context.stopService(intent);
|
||||
LogUtils.i(TAG, "服务停止命令已发出");
|
||||
}
|
||||
@@ -106,6 +110,18 @@ public class LimitedTimeSpecialChannelService extends Service {
|
||||
return sIsServiceRunning;
|
||||
}
|
||||
|
||||
/**
|
||||
* 公共静态方法:发送绿色通道状态变更广播
|
||||
* @param context 上下文
|
||||
* @param isEnabled 绿色通道是否开启
|
||||
*/
|
||||
public static void sendGreenChannelStateChangedBroadcast(Context context, boolean isEnabled) {
|
||||
LogUtils.i(TAG, "sendGreenChannelStateChangedBroadcast: 发送绿色通道状态变更广播, isEnabled=" + isEnabled);
|
||||
Intent intent = new Intent(ACTION_GREEN_CHANNEL_STATE_CHANGED);
|
||||
intent.putExtra(EXTRA_GREEN_CHANNEL_ENABLED, isEnabled);
|
||||
LocalBroadcastManager.getInstance(context).sendBroadcast(intent);
|
||||
}
|
||||
|
||||
/**
|
||||
* 【核心单元测试方法】
|
||||
* 执行完整的单元测试流程
|
||||
@@ -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,154 @@
|
||||
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.contacts.floatingwindow.GreenChannelFloatingWindow;
|
||||
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;
|
||||
private GreenChannelFloatingWindow mGreenChannelFloatingWindow;
|
||||
|
||||
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);
|
||||
Button btnGreenChannel = (Button) findViewById(R.id.btn_debug_green_channel);
|
||||
|
||||
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();
|
||||
}
|
||||
});
|
||||
|
||||
btnGreenChannel.setOnClickListener(new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
onOpenGreenChannel();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 打开绿色通道悬浮窗
|
||||
*/
|
||||
private void onOpenGreenChannel() {
|
||||
LogUtils.d(TAG, "onOpenGreenChannel: 打开绿色通道悬浮窗");
|
||||
if (mGreenChannelFloatingWindow == null) {
|
||||
mGreenChannelFloatingWindow = new GreenChannelFloatingWindow(mContext);
|
||||
}
|
||||
if (mGreenChannelFloatingWindow.isShowing()) {
|
||||
LogUtils.d(TAG, "onOpenGreenChannel: 悬浮窗已显示,跳过");
|
||||
return;
|
||||
}
|
||||
boolean result = mGreenChannelFloatingWindow.show();
|
||||
if (result) {
|
||||
LogUtils.d(TAG, "onOpenGreenChannel: 悬浮窗显示成功");
|
||||
} else {
|
||||
LogUtils.e(TAG, "onOpenGreenChannel: 悬浮窗显示失败,可能缺少悬浮窗权限");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 + " 被拦截");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -13,79 +13,79 @@ import android.os.Message;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.TypedValue;
|
||||
import android.view.View;
|
||||
|
||||
import cc.winboll.studio.libappbase.LogUtils;
|
||||
import java.util.WeakHashMap;
|
||||
|
||||
/**
|
||||
* @Author ZhanGSKen&豆包大模型<zhangsken@qq.com>
|
||||
* @Date 2025/03/19 14:04:20
|
||||
* @Author 豆包&Big Pickle&MiMo&ZhanGSKen<zhangsken@qq.com>
|
||||
* @CreateDate 2025/03/19 14:04:20
|
||||
* @LastEdit 2026/07/21 11:40:00
|
||||
* @Describe 云盾华氏度热力视图,垂直盾值温度视图控件(带颜色渐变+静态Handler更新)
|
||||
* 采用绘图方式展示盾值温度,填充色随盾值比例渐变,支持设置文本在温度条左侧/右侧,底部对齐竖排显示
|
||||
* 温度条宽度=5dp,文本区宽度固定=5dp,整体左右边距=0,无任何多余空白间距
|
||||
* 1. 原生Canvas绘图渲染盾值温度条,填充色随盾值比例动态渐变
|
||||
* 2. 支持竖排数值文本切换至温度条左侧/右侧,文本底部对齐展示
|
||||
* 3. 固定尺寸规范:温度条宽度5dp、文本区域宽度5dp,控件左右无外边距
|
||||
* 4. 全局静态Handler统一分发盾值更新,WeakHashMap弱引用缓存控件防止内存泄漏
|
||||
* 5. 代码完全兼容Java7语法,无Lambda、无try-with-resources、无高版本API
|
||||
*/
|
||||
public class DunTemperatureView extends View {
|
||||
// ====================== 常量定义区 ======================
|
||||
public static final String TAG = "DunTemperatureView";
|
||||
// 控件默认高度
|
||||
private static final int DEFAULT_HEIGHT = 200;
|
||||
// 温度条宽度(5dp)、文本区宽度(固定5dp)
|
||||
private static final int THERMOMETER_WIDTH_DP = 5;
|
||||
private static final int TEXT_AREA_WIDTH_DP = 5;
|
||||
// 填充区域内边距(左右=0,上下=2避免贴边)
|
||||
private static final int FILL_PADDING_HORIZONTAL = 0;
|
||||
private static final int FILL_PADDING_VERTICAL = 2;
|
||||
// 竖排文本字间距
|
||||
private static final float TEXT_CHAR_SPACING = 8f;
|
||||
// Handler消息标识
|
||||
public static final int MSG_UPDATE_DUN_VALUE = 0x01;
|
||||
// 消息参数Key
|
||||
public static final String KEY_MAX_VALUE = "max_value";
|
||||
public static final String KEY_CURRENT_VALUE = "current_value";
|
||||
|
||||
private static final int DEFAULT_HEIGHT = 200;
|
||||
private static final int THERMOMETER_WIDTH_DP = 5;
|
||||
private static final int TEXT_AREA_WIDTH_DP = 5;
|
||||
private static final int FILL_PADDING_HORIZONTAL = 0;
|
||||
private static final int FILL_PADDING_VERTICAL = 2;
|
||||
private static final float TEXT_CHAR_SPACING = 8f;
|
||||
|
||||
private static final int COLOR_GRADIENT_LOW = Color.GREEN;
|
||||
private static final int COLOR_GRADIENT_MID = Color.YELLOW;
|
||||
private static final int COLOR_GRADIENT_HIGH = Color.RED;
|
||||
private static final int COLOR_BORDER = Color.parseColor("#FF444444");
|
||||
private static final int COLOR_TEXT = Color.parseColor("#FF000000");
|
||||
|
||||
// ====================== 静态成员区 ======================
|
||||
// 弱引用缓存控件实例,避免内存泄漏
|
||||
private static WeakHashMap<DunTemperatureView, Object> sViewCache = new WeakHashMap<>();
|
||||
// 静态Handler,处理跨线程更新消息
|
||||
private static final WeakHashMap<DunTemperatureView, Object> sViewCache = new WeakHashMap<DunTemperatureView, Object>();
|
||||
private static Handler sStaticHandler;
|
||||
|
||||
// ====================== 成员变量区 ======================
|
||||
// 画笔相关
|
||||
// ====================== 实例成员变量区 ======================
|
||||
private Paint mThermometerPaint;
|
||||
private Paint mFillPaint;
|
||||
private Paint mTextPaint;
|
||||
// 尺寸参数(dp转px后的值)
|
||||
|
||||
private int mThermometerWidth;
|
||||
private int mTextAreaWidth;
|
||||
private int mMaxValue = 100; // 最高盾值
|
||||
private int mCurrentValue = 0; // 当前盾值
|
||||
private RectF mThermometerRect; // 温度条矩形区域
|
||||
// 渐变颜色配置(低→中→高 对应绿→黄→红)
|
||||
private int[] mGradientColors = {Color.GREEN, Color.YELLOW, Color.RED};
|
||||
private float[] mGradientPositions = {0.0f, 0.5f, 1.0f};
|
||||
// 布局配置:true=文本在温度条右侧(默认),false=文本在温度条左侧
|
||||
private int mMaxValue = 100;
|
||||
private int mCurrentValue = 0;
|
||||
|
||||
private RectF mThermometerRect;
|
||||
private int[] mGradientColors = new int[]{COLOR_GRADIENT_LOW, COLOR_GRADIENT_MID, COLOR_GRADIENT_HIGH};
|
||||
private float[] mGradientPositions = new float[]{0.0f, 0.5f, 1.0f};
|
||||
private boolean isTextOnRight = true;
|
||||
// 其他颜色配置
|
||||
private int mBorderColor = Color.parseColor("#FF444444");
|
||||
private int mTextColor = Color.parseColor("#FF000000");
|
||||
private int mBorderColor = COLOR_BORDER;
|
||||
private int mTextColor = COLOR_TEXT;
|
||||
|
||||
// ====================== 静态代码块 ======================
|
||||
static {
|
||||
// 初始化静态Handler,绑定主线程Looper
|
||||
sStaticHandler = new Handler(Looper.getMainLooper()) {
|
||||
@Override
|
||||
public void handleMessage(Message msg) {
|
||||
public void handleMessage(final Message msg) {
|
||||
super.handleMessage(msg);
|
||||
if (msg.what == MSG_UPDATE_DUN_VALUE) {
|
||||
// 获取消息中的盾值参数
|
||||
int maxValue = msg.getData().getInt(KEY_MAX_VALUE, 100);
|
||||
int currentValue = msg.getData().getInt(KEY_CURRENT_VALUE, 0);
|
||||
LogUtils.d(TAG, "sStaticHandler: 收到更新消息,max=" + maxValue + ", current=" + currentValue);
|
||||
final int targetMsgWhat = msg.what;
|
||||
if (targetMsgWhat == MSG_UPDATE_DUN_VALUE) {
|
||||
final int maxValue = msg.getData().getInt(KEY_MAX_VALUE, 100);
|
||||
final int currentValue = msg.getData().getInt(KEY_CURRENT_VALUE, 0);
|
||||
LogUtils.d(TAG, "handleMessage receive update params, max=" + maxValue + ", current=" + currentValue);
|
||||
|
||||
// 遍历缓存的控件实例,更新所有实例
|
||||
for (DunTemperatureView view : sViewCache.keySet()) {
|
||||
if (view != null && view.isShown()) {
|
||||
view.setMaxValue(maxValue);
|
||||
view.setCurrentValue(currentValue);
|
||||
final DunTemperatureView targetView = view;
|
||||
if (targetView != null && targetView.isShown()) {
|
||||
targetView.setMaxValue(maxValue);
|
||||
targetView.setCurrentValue(currentValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -94,203 +94,194 @@ public class DunTemperatureView extends View {
|
||||
}
|
||||
|
||||
// ====================== 构造函数区 ======================
|
||||
public DunTemperatureView(Context context) {
|
||||
public DunTemperatureView(final Context context) {
|
||||
super(context);
|
||||
init();
|
||||
}
|
||||
|
||||
public DunTemperatureView(Context context, AttributeSet attrs) {
|
||||
public DunTemperatureView(final Context context, final AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
init();
|
||||
}
|
||||
|
||||
public DunTemperatureView(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
public DunTemperatureView(final Context context, final AttributeSet attrs, final int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
init();
|
||||
}
|
||||
|
||||
// ====================== 初始化方法区 ======================
|
||||
// ====================== 初始化私有方法 ======================
|
||||
/**
|
||||
* 初始化画笔和参数
|
||||
* 初始化画笔、尺寸参数并将实例存入全局缓存
|
||||
*/
|
||||
private void init() {
|
||||
LogUtils.d(TAG, "init: 开始初始化云盾温度视图控件");
|
||||
// dp转px(适配不同分辨率)
|
||||
mThermometerWidth = dp2px(getContext(), THERMOMETER_WIDTH_DP);
|
||||
mTextAreaWidth = dp2px(getContext(), TEXT_AREA_WIDTH_DP);
|
||||
LogUtils.d(TAG, "init: 温度条宽度5dp转px=" + mThermometerWidth + ",文本区宽度5dp转px=" + mTextAreaWidth);
|
||||
final Context context = getContext();
|
||||
LogUtils.d(TAG, "init start");
|
||||
|
||||
mThermometerWidth = dp2px(context, THERMOMETER_WIDTH_DP);
|
||||
mTextAreaWidth = dp2px(context, TEXT_AREA_WIDTH_DP);
|
||||
LogUtils.d(TAG, "init dp convert result, thermometerWidth=" + mThermometerWidth + ", textAreaWidth=" + mTextAreaWidth);
|
||||
|
||||
// 温度条边框画笔(宽度1px,适配5dp宽度)
|
||||
mThermometerPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||
mThermometerPaint.setColor(mBorderColor);
|
||||
mThermometerPaint.setStyle(Paint.Style.STROKE);
|
||||
mThermometerPaint.setStrokeWidth(1);
|
||||
|
||||
// 温度条填充画笔(支持渐变)
|
||||
mFillPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||
mFillPaint.setStyle(Paint.Style.FILL);
|
||||
|
||||
// 文本画笔(适配5dp窄文本区,文字居中绘制)
|
||||
mTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||
mTextPaint.setColor(mTextColor);
|
||||
mTextPaint.setTextSize(18); // 缩小字号适配5dp窄文本区(避免文字超出)
|
||||
mTextPaint.setTextSize(18);
|
||||
mTextPaint.setTextAlign(Paint.Align.CENTER);
|
||||
mTextPaint.setFakeBoldText(true); // 文字加粗,提升窄区域可读性
|
||||
mTextPaint.setFakeBoldText(true);
|
||||
|
||||
// 初始化温度条矩形
|
||||
mThermometerRect = new RectF();
|
||||
|
||||
// 将当前实例加入静态缓存
|
||||
sViewCache.put(this, null);
|
||||
LogUtils.d(TAG, "init: 云盾温度视图控件初始化完成,实例已加入缓存");
|
||||
LogUtils.d(TAG, "init complete, add view to static cache");
|
||||
}
|
||||
|
||||
// ====================== 工具方法区 ======================
|
||||
/**
|
||||
* dp 转 px(适配不同屏幕分辨率)
|
||||
* dp单位转换px,多分辨率屏幕适配
|
||||
* @param context 上下文
|
||||
* @param dpValue dp数值
|
||||
* @return 对应px像素值
|
||||
*/
|
||||
private int dp2px(Context context, float dpValue) {
|
||||
return (int) TypedValue.applyDimension(
|
||||
TypedValue.COMPLEX_UNIT_DIP,
|
||||
dpValue,
|
||||
context.getResources().getDisplayMetrics()
|
||||
);
|
||||
private int dp2px(final Context context, final float dpValue) {
|
||||
final float density = context.getResources().getDisplayMetrics().density;
|
||||
return (int) (dpValue * density + 0.5f);
|
||||
}
|
||||
|
||||
// ====================== 对外控制方法区 ======================
|
||||
// ====================== UI布局控制方法 ======================
|
||||
/**
|
||||
* 设置文本相对于温度条的位置
|
||||
* @param isOnRight true=文本在温度条右侧(默认),false=文本在温度条左侧
|
||||
* 设置文本在温度条的左右位置
|
||||
* @param isOnRight true=右侧,false=左侧
|
||||
*/
|
||||
public void setTextPosition(boolean isOnRight) {
|
||||
public void setTextPosition(final boolean isOnRight) {
|
||||
this.isTextOnRight = isOnRight;
|
||||
invalidate(); // 刷新布局绘制
|
||||
LogUtils.d(TAG, "setTextPosition input param=" + isOnRight);
|
||||
invalidate();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前文本位置配置
|
||||
* @return true=右侧,false=左侧
|
||||
*/
|
||||
public boolean isTextOnRight() {
|
||||
return isTextOnRight;
|
||||
}
|
||||
|
||||
// ====================== 对外静态方法区 ======================
|
||||
// ====================== 全局静态更新入口 ======================
|
||||
/**
|
||||
* 静态外部方法:发送消息更新所有 DunTemperatureView 实例的盾值
|
||||
* 可在子线程中调用
|
||||
* @param maxValue 最高盾值
|
||||
* 全局静态方法,统一更新所有控件盾值,支持子线程调用
|
||||
* @param maxValue 最大盾值
|
||||
* @param currentValue 当前盾值
|
||||
*/
|
||||
public static void updateDunValue(int maxValue, int currentValue) {
|
||||
public static void updateDunValue(final int maxValue, final int currentValue) {
|
||||
if (sStaticHandler == null) {
|
||||
LogUtils.w(TAG, "updateDunValue: 静态Handler未初始化");
|
||||
LogUtils.w(TAG, "updateDunValue static handler uninitialized");
|
||||
return;
|
||||
}
|
||||
// 封装参数到消息
|
||||
Message msg = sStaticHandler.obtainMessage(MSG_UPDATE_DUN_VALUE);
|
||||
LogUtils.d(TAG, "static updateDunValue call, input max=" + maxValue + ", current=" + currentValue);
|
||||
final Message msg = sStaticHandler.obtainMessage(MSG_UPDATE_DUN_VALUE);
|
||||
msg.getData().putInt(KEY_MAX_VALUE, maxValue);
|
||||
msg.getData().putInt(KEY_CURRENT_VALUE, currentValue);
|
||||
// 发送消息
|
||||
sStaticHandler.sendMessage(msg);
|
||||
}
|
||||
|
||||
// ====================== 对外实例方法区 ======================
|
||||
// ====================== 盾值读写实例方法 ======================
|
||||
/**
|
||||
* 设置最高盾值
|
||||
* @param maxValue 最高盾值(需大于0)
|
||||
* 设置最大盾值,非法值会拦截并打印日志
|
||||
* @param maxValue 目标最大值,必须大于0
|
||||
*/
|
||||
public void setMaxValue(int maxValue) {
|
||||
public void setMaxValue(final int maxValue) {
|
||||
if (maxValue <= 0) {
|
||||
LogUtils.w(TAG, "setMaxValue: 最高盾值必须大于0,当前值=" + maxValue);
|
||||
LogUtils.w(TAG, "setMaxValue invalid input value=" + maxValue);
|
||||
return;
|
||||
}
|
||||
this.mMaxValue = maxValue;
|
||||
// 限制当前值不超过最大值
|
||||
mCurrentValue = Math.min(mCurrentValue, maxValue);
|
||||
invalidate(); // 重绘控件
|
||||
LogUtils.d(TAG, "setMaxValue update max=" + maxValue + ", clamp current=" + mCurrentValue);
|
||||
invalidate();
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置当前盾值
|
||||
* @param currentValue 当前盾值(范围 0~maxValue)
|
||||
* 设置当前盾值,自动限制0~maxValue区间
|
||||
* @param currentValue 目标当前盾值
|
||||
*/
|
||||
public void setCurrentValue(int currentValue) {
|
||||
int oldValue = this.mCurrentValue;
|
||||
public void setCurrentValue(final int currentValue) {
|
||||
final int oldVal = this.mCurrentValue;
|
||||
this.mCurrentValue = Math.max(0, Math.min(currentValue, mMaxValue));
|
||||
if (oldValue != this.mCurrentValue) {
|
||||
LogUtils.d(TAG, "setCurrentValue: 当前盾值从" + oldValue + "更新为" + mCurrentValue);
|
||||
invalidate(); // 重绘控件
|
||||
if (oldVal != this.mCurrentValue) {
|
||||
LogUtils.d(TAG, "setCurrentValue change value from " + oldVal + " to " + mCurrentValue);
|
||||
invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前盾值
|
||||
*/
|
||||
public int getCurrentValue() {
|
||||
return mCurrentValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取最高盾值
|
||||
*/
|
||||
public int getMaxValue() {
|
||||
return mMaxValue;
|
||||
}
|
||||
|
||||
// ====================== 渐变、颜色自定义配置 ======================
|
||||
/**
|
||||
* 设置自定义渐变颜色
|
||||
* @param colors 渐变颜色数组(至少2种颜色)
|
||||
* @param positions 颜色位置数组(与colors长度一致,0.0~1.0)
|
||||
* 自定义渐变颜色数组与位置节点
|
||||
* @param colors 颜色数组,长度≥2
|
||||
* @param positions 渐变节点数组,长度与colors一致
|
||||
*/
|
||||
public void setGradientColors(int[] colors, float[] positions) {
|
||||
public void setGradientColors(final int[] colors, final float[] positions) {
|
||||
if (colors == null || colors.length < 2 || positions == null || positions.length != colors.length) {
|
||||
LogUtils.w(TAG, "setGradientColors: 渐变颜色参数不合法,颜色数组长度=" + (colors == null ? "null" : colors.length));
|
||||
LogUtils.w(TAG, "setGradientColors invalid param, color array length=" + (colors == null ? "null" : colors.length));
|
||||
return;
|
||||
}
|
||||
this.mGradientColors = colors;
|
||||
this.mGradientPositions = positions;
|
||||
LogUtils.d(TAG, "setGradientColors: 自定义渐变颜色已设置");
|
||||
LogUtils.d(TAG, "setGradientColors custom gradient config applied");
|
||||
invalidate();
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置温度条边框颜色
|
||||
* 更新温度条边框颜色
|
||||
* @param color 目标色值
|
||||
*/
|
||||
public void setBorderColor(int color) {
|
||||
public void setBorderColor(final int color) {
|
||||
this.mBorderColor = color;
|
||||
mThermometerPaint.setColor(color);
|
||||
LogUtils.d(TAG, "setBorderColor: 边框颜色已更新");
|
||||
LogUtils.d(TAG, "setBorderColor update border color");
|
||||
invalidate();
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置文本颜色
|
||||
* 更新竖排文本颜色
|
||||
* @param color 目标色值
|
||||
*/
|
||||
public void setTextColor(int color) {
|
||||
public void setTextColor(final int color) {
|
||||
this.mTextColor = color;
|
||||
mTextPaint.setColor(color);
|
||||
LogUtils.d(TAG, "setTextColor: 文本颜色已更新");
|
||||
LogUtils.d(TAG, "setTextColor update text color");
|
||||
invalidate();
|
||||
}
|
||||
|
||||
// ====================== 生命周期方法 ======================
|
||||
// ====================== 视图生命周期回调 ======================
|
||||
@Override
|
||||
protected void onDetachedFromWindow() {
|
||||
super.onDetachedFromWindow();
|
||||
// 控件从窗口移除时,从缓存中清除,避免内存泄漏
|
||||
sViewCache.remove(this);
|
||||
LogUtils.d(TAG, "onDetachedFromWindow: 控件实例已从缓存移除");
|
||||
LogUtils.d(TAG, "onDetachedFromWindow remove view from static cache");
|
||||
}
|
||||
|
||||
// ====================== 测量与绘制区 ======================
|
||||
// ====================== 测量绘制工具私有方法 ======================
|
||||
/**
|
||||
* 测量辅助函数
|
||||
* 测量尺寸计算工具
|
||||
* @param defaultSize 默认尺寸
|
||||
* @param measureSpec 测量规格
|
||||
* @return 最终计算尺寸
|
||||
*/
|
||||
private int measureSize(int defaultSize, int measureSpec) {
|
||||
private int measureSize(final int defaultSize, final int measureSpec) {
|
||||
int result = defaultSize;
|
||||
int specMode = MeasureSpec.getMode(measureSpec);
|
||||
int specSize = MeasureSpec.getSize(measureSpec);
|
||||
final int specMode = MeasureSpec.getMode(measureSpec);
|
||||
final int specSize = MeasureSpec.getSize(measureSpec);
|
||||
if (specMode == MeasureSpec.EXACTLY) {
|
||||
result = specSize;
|
||||
} else if (specMode == MeasureSpec.AT_MOST) {
|
||||
@@ -302,90 +293,83 @@ public class DunTemperatureView extends View {
|
||||
@Override
|
||||
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
||||
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
|
||||
// 强制控件整体左右内边距=0,彻底消除外部边距
|
||||
setPadding(0, getPaddingTop(), 0, getPaddingBottom());
|
||||
|
||||
// 控件宽度=温度条宽度 + 文本区宽度(均为5dp转px,无额外空白)
|
||||
int defaultWidth = mThermometerWidth + mTextAreaWidth;
|
||||
int width = measureSize(defaultWidth, widthMeasureSpec);
|
||||
int height = measureSize(DEFAULT_HEIGHT, heightMeasureSpec);
|
||||
final int defaultWidth = mThermometerWidth + mTextAreaWidth;
|
||||
final int width = measureSize(defaultWidth, widthMeasureSpec);
|
||||
final int height = measureSize(DEFAULT_HEIGHT, heightMeasureSpec);
|
||||
setMeasuredDimension(width, height);
|
||||
|
||||
// 根据文本位置配置,计算温度条矩形坐标
|
||||
float thermometerLeft, thermometerRight;
|
||||
if (isTextOnRight) {
|
||||
// 文本在右侧:温度条靠左,右接文本区
|
||||
float thermometerLeft;
|
||||
float thermometerRight;
|
||||
final boolean textRightFlag = isTextOnRight;
|
||||
if (textRightFlag) {
|
||||
thermometerLeft = 0;
|
||||
thermometerRight = thermometerLeft + mThermometerWidth;
|
||||
} else {
|
||||
// 文本在左侧:温度条靠右,左接文本区
|
||||
thermometerLeft = width - mThermometerWidth;
|
||||
thermometerRight = width;
|
||||
}
|
||||
float thermometerTop = getPaddingTop();
|
||||
float thermometerBottom = height - getPaddingBottom();
|
||||
final float thermometerTop = getPaddingTop();
|
||||
final float thermometerBottom = height - getPaddingBottom();
|
||||
mThermometerRect.set(thermometerLeft, thermometerTop, thermometerRight, thermometerBottom);
|
||||
|
||||
LogUtils.v(TAG, "onMeasure: 文本位置=" + (isTextOnRight ? "右侧" : "左侧") + ",控件尺寸=" + width + "x" + height + ",温度条区域=" + mThermometerRect.toShortString());
|
||||
LogUtils.v(TAG, "onMeasure textPos=" + (textRightFlag ? "RIGHT" : "LEFT") + ", view size=" + width + "x" + height);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDraw(Canvas canvas) {
|
||||
protected void onDraw(final Canvas canvas) {
|
||||
super.onDraw(canvas);
|
||||
// 1. 绘制温度条边框(5dp宽度,小圆角适配)
|
||||
// 绘制温度条外边框
|
||||
canvas.drawRoundRect(mThermometerRect, 3, 3, mThermometerPaint);
|
||||
|
||||
// 2. 计算填充高度(根据当前值占最大值的比例)
|
||||
float fillRatio = (float) mCurrentValue / mMaxValue;
|
||||
float fillHeight = mThermometerRect.height() * fillRatio;
|
||||
float fillTop = mThermometerRect.bottom - fillHeight;
|
||||
// 计算渐变填充区域高度比例
|
||||
final float fillRatio = (float) mCurrentValue / mMaxValue;
|
||||
final float fillHeight = mThermometerRect.height() * fillRatio;
|
||||
final float fillTop = mThermometerRect.bottom - fillHeight;
|
||||
|
||||
// 3. 绘制渐变填充部分(左右贴边框,无间距)
|
||||
// 绘制渐变填充色块
|
||||
if (fillHeight > 0) {
|
||||
RectF fillRect = new RectF(
|
||||
mThermometerRect.left + FILL_PADDING_HORIZONTAL,
|
||||
fillTop + FILL_PADDING_VERTICAL,
|
||||
mThermometerRect.right - FILL_PADDING_HORIZONTAL,
|
||||
mThermometerRect.bottom - FILL_PADDING_VERTICAL
|
||||
final RectF fillRect = new RectF(
|
||||
mThermometerRect.left + FILL_PADDING_HORIZONTAL,
|
||||
fillTop + FILL_PADDING_VERTICAL,
|
||||
mThermometerRect.right - FILL_PADDING_HORIZONTAL,
|
||||
mThermometerRect.bottom - FILL_PADDING_VERTICAL
|
||||
);
|
||||
LinearGradient gradient = new LinearGradient(
|
||||
fillRect.centerX(), fillRect.bottom,
|
||||
fillRect.centerX(), fillRect.top,
|
||||
mGradientColors,
|
||||
mGradientPositions,
|
||||
Shader.TileMode.CLAMP
|
||||
final LinearGradient gradient = new LinearGradient(
|
||||
fillRect.centerX(), fillRect.bottom,
|
||||
fillRect.centerX(), fillRect.top,
|
||||
mGradientColors,
|
||||
mGradientPositions,
|
||||
Shader.TileMode.CLAMP
|
||||
);
|
||||
mFillPaint.setShader(gradient);
|
||||
canvas.drawRoundRect(fillRect, 2, 2, mFillPaint);
|
||||
mFillPaint.setShader(null);
|
||||
}
|
||||
|
||||
// 4. 绘制文本(5dp固定宽度文本区,底部对齐竖排,文字居中)
|
||||
String text = String.format("%d/%d", mCurrentValue, mMaxValue);
|
||||
if (text.isEmpty()) return;
|
||||
// 竖排文本绘制逻辑
|
||||
final String text = String.format("%d/%d", mCurrentValue, mMaxValue);
|
||||
if (text.length() == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
float textBaseX;
|
||||
if (isTextOnRight) {
|
||||
// 文本在右侧:X=温度条右边缘 + 文本区宽度的一半(紧贴温度条)
|
||||
final boolean textRightFlag = isTextOnRight;
|
||||
if (textRightFlag) {
|
||||
textBaseX = mThermometerRect.right + (mTextAreaWidth / 2f);
|
||||
} else {
|
||||
// 文本在左侧:X=文本区宽度的一半(紧贴控件左边缘)
|
||||
textBaseX = mTextAreaWidth / 2f;
|
||||
}
|
||||
|
||||
// 文本绘制参数(适配5dp窄区域,缩小字间距提升紧凑度)
|
||||
float singleCharHeight = mTextPaint.getTextSize() + (TEXT_CHAR_SPACING - 2f); // 字间距减为6f
|
||||
float totalTextHeight = (singleCharHeight * text.length()) - (TEXT_CHAR_SPACING - 2f);
|
||||
Paint.FontMetrics fontMetrics = mTextPaint.getFontMetrics();
|
||||
float charBottomOffset = fontMetrics.bottom;
|
||||
final float singleCharHeight = mTextPaint.getTextSize() + (TEXT_CHAR_SPACING - 2f);
|
||||
final Paint.FontMetrics fontMetrics = mTextPaint.getFontMetrics();
|
||||
final float charBottomOffset = fontMetrics.bottom;
|
||||
final float startTextY = getHeight() - getPaddingBottom() - charBottomOffset;
|
||||
|
||||
// 文本起始Y(底部对齐控件底部,无间距)
|
||||
float startTextY = getHeight() - getPaddingBottom() - charBottomOffset;
|
||||
|
||||
// 逐字竖排绘制(文字居中于5dp文本区,无超出)
|
||||
for (int i = 0; i < text.length(); i++) {
|
||||
char singleChar = text.charAt(i);
|
||||
float currentTextY = startTextY - (i * singleCharHeight);
|
||||
final char singleChar = text.charAt(i);
|
||||
final float currentTextY = startTextY - (i * singleCharHeight);
|
||||
canvas.drawText(String.valueOf(singleChar), textBaseX, currentTextY, mTextPaint);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,7 +13,9 @@ import android.widget.LinearLayout;
|
||||
import android.widget.Switch;
|
||||
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
|
||||
import cc.winboll.studio.contacts.R;
|
||||
import cc.winboll.studio.contacts.services.LimitedTimeSpecialChannelService;
|
||||
import cc.winboll.studio.contacts.dun.Rules;
|
||||
import cc.winboll.studio.contacts.model.SettingsBean;
|
||||
import cc.winboll.studio.contacts.services.LimitedTimeGreenChannelService;
|
||||
import cc.winboll.studio.libappbase.LogUtils;
|
||||
import cc.winboll.studio.libappbase.ToastUtils;
|
||||
|
||||
@@ -21,7 +23,7 @@ import cc.winboll.studio.libappbase.ToastUtils;
|
||||
* @Author 豆包&ZhanGSKen<zhangsken@qq.com>
|
||||
* @Date 2026/04/18 15:05:00 (GMT+8)
|
||||
* @LastEditTime 2026/04/22 17:30:00 (GMT+8)
|
||||
* @Describe 限时特殊通道视图
|
||||
* @Describe 限时绿色通道视图
|
||||
* 功能说明:
|
||||
* 1. Switch 打开时,EditText 不可编辑,布局背景变为绿色,加减按钮不可用
|
||||
* 2. Switch 关闭时,EditText 可编辑,布局背景恢复为白色,加减按钮恢复可用
|
||||
@@ -31,10 +33,10 @@ import cc.winboll.studio.libappbase.ToastUtils;
|
||||
* 6. 新增快速设置按钮(btn_thisSeconds),点击直接设置为当前量级3600秒
|
||||
* 7. [新增] 服务销毁时,恢复EditText显示为倒计时剩余秒数
|
||||
*/
|
||||
public class LimitedTimeSpecialChannelView extends LinearLayout {
|
||||
public class LimitedTimeGreenChannelView extends LinearLayout {
|
||||
|
||||
// ====================== 常量定义 =========================
|
||||
public static final String TAG = "LimitedTimeSpecialChannelView";
|
||||
public static final String TAG = "LimitedTimeGreenChannelView";
|
||||
// 定义秒数增量的量级
|
||||
private static final long SECONDS_INCREMENT = 3600;
|
||||
|
||||
@@ -50,17 +52,17 @@ public class LimitedTimeSpecialChannelView extends LinearLayout {
|
||||
private android.content.BroadcastReceiver mCountdownReceiver;
|
||||
|
||||
// ====================== 构造函数 =========================
|
||||
public LimitedTimeSpecialChannelView(Context context) {
|
||||
public LimitedTimeGreenChannelView(Context context) {
|
||||
super(context);
|
||||
initView(context);
|
||||
}
|
||||
|
||||
public LimitedTimeSpecialChannelView(Context context, AttributeSet attrs) {
|
||||
public LimitedTimeGreenChannelView(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
initView(context);
|
||||
}
|
||||
|
||||
public LimitedTimeSpecialChannelView(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
public LimitedTimeGreenChannelView(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
initView(context);
|
||||
}
|
||||
@@ -71,19 +73,22 @@ public class LimitedTimeSpecialChannelView extends LinearLayout {
|
||||
this.mContext = context;
|
||||
|
||||
// 加载布局文件
|
||||
LayoutInflater.from(context).inflate(R.layout.view_limitedtimespecialchannel, this, true);
|
||||
LayoutInflater.from(context).inflate(R.layout.view_limitedtimegreenchannel, this, true);
|
||||
LogUtils.i(TAG, "布局文件加载完成");
|
||||
|
||||
// 绑定控件ID
|
||||
bindViews();
|
||||
// 初始化所有按钮点击事件
|
||||
initButtons();
|
||||
// 初始化开关状态
|
||||
// 初始化开关状态(从持久化存储加载)
|
||||
initSwitchState();
|
||||
// 注册广播监听
|
||||
registerServiceDestroyedListener();
|
||||
registerCountdownTickListener();
|
||||
|
||||
// 加载保存的绿色通道启用状态并设置Switch
|
||||
loadSavedGreenChannelState();
|
||||
|
||||
LogUtils.i(TAG, "视图初始化流程结束");
|
||||
}
|
||||
|
||||
@@ -191,15 +196,75 @@ public class LimitedTimeSpecialChannelView extends LinearLayout {
|
||||
// 根据开关状态更新UI可操作状态
|
||||
updateUIState(!isChecked);
|
||||
|
||||
// 保存绿色通道启用状态到持久化存储
|
||||
saveGreenChannelState(isChecked);
|
||||
|
||||
// 发送绿色通道状态变更广播
|
||||
LimitedTimeGreenChannelService.sendGreenChannelStateChangedBroadcast(mContext, isChecked);
|
||||
|
||||
if (isChecked) {
|
||||
startLimitedTimeSpecialChannelService();
|
||||
startLimitedTimeGreenChannelService();
|
||||
} else {
|
||||
LimitedTimeSpecialChannelService.stopService(mContext);
|
||||
LimitedTimeGreenChannelService.stopService(mContext);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 从持久化存储加载绿色通道启用状态并设置Switch
|
||||
*/
|
||||
private void loadSavedGreenChannelState() {
|
||||
Rules rules = Rules.getInstance(mContext);
|
||||
if (rules == null) {
|
||||
LogUtils.e(TAG, "loadSavedGreenChannelState: Rules实例获取失败");
|
||||
return;
|
||||
}
|
||||
SettingsBean settings = rules.getSettingsModel();
|
||||
if (settings == null) {
|
||||
LogUtils.e(TAG, "loadSavedGreenChannelState: SettingsBean获取失败");
|
||||
return;
|
||||
}
|
||||
|
||||
boolean savedState = settings.isEnableGreenChannel();
|
||||
LogUtils.i(TAG, "loadSavedGreenChannelState: 加载保存的绿色通道状态=" + savedState);
|
||||
|
||||
// 设置Switch状态(不会触发监听器,因为监听器在initSwitchState中已设置)
|
||||
if (mSwEnable != null && mSwEnable.isChecked() != savedState) {
|
||||
mSwEnable.setChecked(savedState);
|
||||
// 手动更新UI状态
|
||||
updateUIState(!savedState);
|
||||
LogUtils.i(TAG, "loadSavedGreenChannelState: Switch状态已恢复为" + (savedState ? "开启" : "关闭"));
|
||||
}
|
||||
|
||||
// 如果保存的状态是开启,启动服务
|
||||
if (savedState && !LimitedTimeGreenChannelService.isServiceRunning()) {
|
||||
LogUtils.i(TAG, "loadSavedGreenChannelState: 启动绿色通道服务");
|
||||
startLimitedTimeGreenChannelService();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存绿色通道启用状态到持久化存储
|
||||
* @param isEnabled 是否启用
|
||||
*/
|
||||
private void saveGreenChannelState(boolean isEnabled) {
|
||||
Rules rules = Rules.getInstance(mContext);
|
||||
if (rules == null) {
|
||||
LogUtils.e(TAG, "saveGreenChannelState: Rules实例获取失败,保存失败");
|
||||
return;
|
||||
}
|
||||
SettingsBean settings = rules.getSettingsModel();
|
||||
if (settings == null) {
|
||||
LogUtils.e(TAG, "saveGreenChannelState: SettingsBean获取失败,保存失败");
|
||||
return;
|
||||
}
|
||||
|
||||
settings.setIsEnableGreenChannel(isEnabled);
|
||||
rules.saveDun();
|
||||
LogUtils.i(TAG, "saveGreenChannelState: 绿色通道启用状态已保存为" + isEnabled);
|
||||
}
|
||||
|
||||
/**
|
||||
* 统一更新UI可操作状态
|
||||
* @param enabled 可用状态
|
||||
@@ -222,11 +287,11 @@ public class LimitedTimeSpecialChannelView extends LinearLayout {
|
||||
*/
|
||||
private void registerServiceDestroyedListener() {
|
||||
LogUtils.i(TAG, "注册服务销毁广播监听");
|
||||
IntentFilter filter = new IntentFilter(LimitedTimeSpecialChannelService.ACTION_SERVICE_DESTROYED);
|
||||
IntentFilter filter = new IntentFilter(LimitedTimeGreenChannelService.ACTION_SERVICE_DESTROYED);
|
||||
LocalBroadcastManager.getInstance(mContext).registerReceiver(new android.content.BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
if (LimitedTimeSpecialChannelService.ACTION_SERVICE_DESTROYED.equals(intent.getAction())) {
|
||||
if (LimitedTimeGreenChannelService.ACTION_SERVICE_DESTROYED.equals(intent.getAction())) {
|
||||
LogUtils.i(TAG, "收到服务销毁广播,执行恢复操作");
|
||||
|
||||
// 1. 恢复开关状态
|
||||
@@ -236,7 +301,7 @@ public class LimitedTimeSpecialChannelView extends LinearLayout {
|
||||
|
||||
// 2. [核心修改] 检索并设置倒计时剩余秒数参数
|
||||
// 从广播附加数据中获取之前服务发送的剩余秒数
|
||||
long remainingSeconds = intent.getLongExtra(LimitedTimeSpecialChannelService.EXTRA_REMAINING_SECONDS, 0);
|
||||
long remainingSeconds = intent.getLongExtra(LimitedTimeGreenChannelService.EXTRA_REMAINING_SECONDS, 0);
|
||||
// 实际倒计时消耗冲正
|
||||
remainingSeconds -= 1;
|
||||
// 倒计时秒数0值保底
|
||||
@@ -252,6 +317,10 @@ public class LimitedTimeSpecialChannelView extends LinearLayout {
|
||||
LogUtils.i(TAG, "已将EditText设置为剩余秒数: " + remainingSeconds);
|
||||
}
|
||||
|
||||
// 5. 保存绿色通道关闭状态并发送广播
|
||||
saveGreenChannelState(false);
|
||||
LimitedTimeGreenChannelService.sendGreenChannelStateChangedBroadcast(mContext, false);
|
||||
|
||||
//ToastUtils.show("服务已销毁,恢复倒计时剩余状态");
|
||||
}
|
||||
}
|
||||
@@ -266,8 +335,8 @@ public class LimitedTimeSpecialChannelView extends LinearLayout {
|
||||
mCountdownReceiver = new android.content.BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
if (LimitedTimeSpecialChannelService.ACTION_COUNTDOWN_TICK.equals(intent.getAction())) {
|
||||
long remainingSeconds = intent.getLongExtra(LimitedTimeSpecialChannelService.EXTRA_REMAINING_SECONDS, 0);
|
||||
if (LimitedTimeGreenChannelService.ACTION_COUNTDOWN_TICK.equals(intent.getAction())) {
|
||||
long remainingSeconds = intent.getLongExtra(LimitedTimeGreenChannelService.EXTRA_REMAINING_SECONDS, 0);
|
||||
if (mEtSeconds != null) {
|
||||
mEtSeconds.setText(String.valueOf(remainingSeconds));
|
||||
}
|
||||
@@ -278,7 +347,7 @@ public class LimitedTimeSpecialChannelView extends LinearLayout {
|
||||
}
|
||||
};
|
||||
|
||||
IntentFilter filter = new IntentFilter(LimitedTimeSpecialChannelService.ACTION_COUNTDOWN_TICK);
|
||||
IntentFilter filter = new IntentFilter(LimitedTimeGreenChannelService.ACTION_COUNTDOWN_TICK);
|
||||
LocalBroadcastManager.getInstance(mContext).registerReceiver(mCountdownReceiver, filter);
|
||||
LogUtils.i(TAG, "倒计时广播注册成功");
|
||||
}
|
||||
@@ -286,8 +355,8 @@ public class LimitedTimeSpecialChannelView extends LinearLayout {
|
||||
/**
|
||||
* 启动限时特殊通道服务
|
||||
*/
|
||||
private void startLimitedTimeSpecialChannelService() {
|
||||
LogUtils.i(TAG, "准备启动限时特殊通道服务");
|
||||
private void startLimitedTimeGreenChannelService() {
|
||||
LogUtils.i(TAG, "准备启动限时绿色通道服务");
|
||||
String secondsStr = mEtSeconds.getText().toString().trim();
|
||||
if (secondsStr.isEmpty()) {
|
||||
LogUtils.w(TAG, "输入框为空,使用默认值 3600 秒");
|
||||
@@ -299,8 +368,8 @@ public class LimitedTimeSpecialChannelView extends LinearLayout {
|
||||
long delayMillis = seconds * 1000;
|
||||
|
||||
LogUtils.i(TAG, "启动参数 - 输入秒数: " + seconds + ", 转换毫秒: " + delayMillis);
|
||||
//ToastUtils.show("调用 LimitedTimeSpecialChannelService 服务");
|
||||
LimitedTimeSpecialChannelService.startService(mContext, delayMillis);
|
||||
//ToastUtils.show("调用 LimitedTimeGreenChannelService 服务");
|
||||
LimitedTimeGreenChannelService.startService(mContext, delayMillis);
|
||||
} catch (NumberFormatException e) {
|
||||
LogUtils.e(TAG, "输入的秒数格式错误: " + secondsStr, e);
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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"
|
||||
@@ -67,12 +67,12 @@
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="特殊通道设置:"/>
|
||||
android:text="绿色通道设置:"/>
|
||||
|
||||
<cc.winboll.studio.contacts.views.LimitedTimeSpecialChannelView
|
||||
<cc.winboll.studio.contacts.views.LimitedTimeGreenChannelView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:id="@+id/view_LimitedTimeSpecialChannel"/>
|
||||
android:id="@+id/view_LimitedTimeGreenChannel"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
@@ -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,76 @@
|
||||
<?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_green_channel"
|
||||
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_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>
|
||||
@@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/ll_green_channel_floating"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:background="@android:color/white"
|
||||
android:elevation="4dp">
|
||||
|
||||
<cc.winboll.studio.contacts.views.LimitedTimeGreenChannelView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:id="@+id/view_LimitedTimeGreenChannel"/>
|
||||
|
||||
</LinearLayout>
|
||||
@@ -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,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>
|
||||
|
||||
@@ -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 +0,0 @@
|
||||
|
||||
@@ -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'])
|
||||
}
|
||||
@@ -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
|
||||
@@ -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,340 +0,0 @@
|
||||
package cc.winboll.studio.debugtemp;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.ClipData;
|
||||
import android.content.ClipboardManager;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageInfo;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Typeface;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
import android.view.Gravity;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.HorizontalScrollView;
|
||||
import android.widget.ScrollView;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
import cc.winboll.studio.libappbase.GlobalApplication;
|
||||
import cc.winboll.studio.libappbase.ToastUtils;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.Closeable;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.lang.Thread.UncaughtExceptionHandler;
|
||||
import java.text.DateFormat;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Arrays;
|
||||
import java.util.Date;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
public class App extends GlobalApplication {
|
||||
|
||||
private static Handler MAIN_HANDLER = new Handler(Looper.getMainLooper());
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
|
||||
// 初始化 Toast 框架
|
||||
ToastUtils.init(this);
|
||||
|
||||
//CrashHandler.getInstance().registerGlobal(this);
|
||||
//CrashHandler.getInstance().registerPart(this);
|
||||
}
|
||||
|
||||
public static void write(InputStream input, OutputStream output) throws IOException {
|
||||
byte[] buf = new byte[1024 * 8];
|
||||
int len;
|
||||
while ((len = input.read(buf)) != -1) {
|
||||
output.write(buf, 0, len);
|
||||
}
|
||||
}
|
||||
|
||||
public static void write(File file, byte[] data) throws IOException {
|
||||
File parent = file.getParentFile();
|
||||
if (parent != null && !parent.exists()) parent.mkdirs();
|
||||
|
||||
ByteArrayInputStream input = new ByteArrayInputStream(data);
|
||||
FileOutputStream output = new FileOutputStream(file);
|
||||
try {
|
||||
write(input, output);
|
||||
} finally {
|
||||
closeIO(input, output);
|
||||
}
|
||||
}
|
||||
|
||||
public static String toString(InputStream input) throws IOException {
|
||||
ByteArrayOutputStream output = new ByteArrayOutputStream();
|
||||
write(input, output);
|
||||
try {
|
||||
return output.toString("UTF-8");
|
||||
} finally {
|
||||
closeIO(input, output);
|
||||
}
|
||||
}
|
||||
|
||||
public static void closeIO(Closeable... closeables) {
|
||||
for (Closeable closeable : closeables) {
|
||||
try {
|
||||
if (closeable != null) closeable.close();
|
||||
} catch (IOException ignored) {}
|
||||
}
|
||||
}
|
||||
|
||||
public static class CrashHandler {
|
||||
|
||||
public static final UncaughtExceptionHandler DEFAULT_UNCAUGHT_EXCEPTION_HANDLER = Thread.getDefaultUncaughtExceptionHandler();
|
||||
|
||||
private static CrashHandler sInstance;
|
||||
|
||||
private PartCrashHandler mPartCrashHandler;
|
||||
|
||||
public static CrashHandler getInstance() {
|
||||
if (sInstance == null) {
|
||||
sInstance = new CrashHandler();
|
||||
}
|
||||
return sInstance;
|
||||
}
|
||||
|
||||
public void registerGlobal(Context context) {
|
||||
registerGlobal(context, null);
|
||||
}
|
||||
|
||||
public void registerGlobal(Context context, String crashDir) {
|
||||
Thread.setDefaultUncaughtExceptionHandler(new UncaughtExceptionHandlerImpl(context.getApplicationContext(), crashDir));
|
||||
}
|
||||
|
||||
public void unregister() {
|
||||
Thread.setDefaultUncaughtExceptionHandler(DEFAULT_UNCAUGHT_EXCEPTION_HANDLER);
|
||||
}
|
||||
|
||||
public void registerPart(Context context) {
|
||||
unregisterPart(context);
|
||||
mPartCrashHandler = new PartCrashHandler(context.getApplicationContext());
|
||||
MAIN_HANDLER.postAtFrontOfQueue(mPartCrashHandler);
|
||||
}
|
||||
|
||||
public void unregisterPart(Context context) {
|
||||
if (mPartCrashHandler != null) {
|
||||
mPartCrashHandler.isRunning.set(false);
|
||||
mPartCrashHandler = null;
|
||||
}
|
||||
}
|
||||
|
||||
private static class PartCrashHandler implements Runnable {
|
||||
|
||||
private final Context mContext;
|
||||
|
||||
public AtomicBoolean isRunning = new AtomicBoolean(true);
|
||||
|
||||
public PartCrashHandler(Context context) {
|
||||
this.mContext = context;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
while (isRunning.get()) {
|
||||
try {
|
||||
Looper.loop();
|
||||
} catch (final Throwable e) {
|
||||
e.printStackTrace();
|
||||
if (isRunning.get()) {
|
||||
MAIN_HANDLER.post(new Runnable(){
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
Toast.makeText(mContext, e.toString(), Toast.LENGTH_LONG).show();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
if (e instanceof RuntimeException) {
|
||||
throw (RuntimeException)e;
|
||||
} else {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static class UncaughtExceptionHandlerImpl implements UncaughtExceptionHandler {
|
||||
|
||||
private static DateFormat DATE_FORMAT = new SimpleDateFormat("yyyy_MM_dd-HH_mm_ss");
|
||||
|
||||
private final Context mContext;
|
||||
|
||||
private final File mCrashDir;
|
||||
|
||||
public UncaughtExceptionHandlerImpl(Context context, String crashDir) {
|
||||
this.mContext = context;
|
||||
this.mCrashDir = TextUtils.isEmpty(crashDir) ? new File(mContext.getExternalCacheDir(), "crash") : new File(crashDir);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void uncaughtException(Thread thread, Throwable throwable) {
|
||||
try {
|
||||
|
||||
String log = buildLog(throwable);
|
||||
writeLog(log);
|
||||
|
||||
try {
|
||||
Intent intent = new Intent(mContext, CrashActivity.class);
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
intent.putExtra(Intent.EXTRA_TEXT, log);
|
||||
mContext.startActivity(intent);
|
||||
} catch (Throwable e) {
|
||||
e.printStackTrace();
|
||||
writeLog(e.toString());
|
||||
}
|
||||
|
||||
throwable.printStackTrace();
|
||||
android.os.Process.killProcess(android.os.Process.myPid());
|
||||
System.exit(0);
|
||||
|
||||
} catch (Throwable e) {
|
||||
if (DEFAULT_UNCAUGHT_EXCEPTION_HANDLER != null) DEFAULT_UNCAUGHT_EXCEPTION_HANDLER.uncaughtException(thread, throwable);
|
||||
}
|
||||
}
|
||||
|
||||
private String buildLog(Throwable throwable) {
|
||||
String time = DATE_FORMAT.format(new Date());
|
||||
|
||||
String versionName = "unknown";
|
||||
long versionCode = 0;
|
||||
try {
|
||||
PackageInfo packageInfo = mContext.getPackageManager().getPackageInfo(mContext.getPackageName(), 0);
|
||||
versionName = packageInfo.versionName;
|
||||
versionCode = Build.VERSION.SDK_INT >= 28 ? packageInfo.getLongVersionCode() : packageInfo.versionCode;
|
||||
} catch (Throwable ignored) {}
|
||||
|
||||
LinkedHashMap<String, String> head = new LinkedHashMap<String, String>();
|
||||
head.put("Time Of Crash", time);
|
||||
head.put("Device", String.format("%s, %s", Build.MANUFACTURER, Build.MODEL));
|
||||
head.put("Android Version", String.format("%s (%d)", Build.VERSION.RELEASE, Build.VERSION.SDK_INT));
|
||||
head.put("App Version", String.format("%s (%d)", versionName, versionCode));
|
||||
head.put("Kernel", getKernel());
|
||||
head.put("Support Abis", Build.VERSION.SDK_INT >= 21 && Build.SUPPORTED_ABIS != null ? Arrays.toString(Build.SUPPORTED_ABIS): "unknown");
|
||||
head.put("Fingerprint", Build.FINGERPRINT);
|
||||
|
||||
StringBuilder builder = new StringBuilder();
|
||||
|
||||
for (String key : head.keySet()) {
|
||||
if (builder.length() != 0) builder.append("\n");
|
||||
builder.append(key);
|
||||
builder.append(" : ");
|
||||
builder.append(head.get(key));
|
||||
}
|
||||
|
||||
builder.append("\n\n");
|
||||
builder.append(Log.getStackTraceString(throwable));
|
||||
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
private void writeLog(String log) {
|
||||
String time = DATE_FORMAT.format(new Date());
|
||||
File file = new File(mCrashDir, "crash_" + time + ".txt");
|
||||
try {
|
||||
write(file, log.getBytes("UTF-8"));
|
||||
} catch (Throwable e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
private static String getKernel() {
|
||||
try {
|
||||
return App.toString(new FileInputStream("/proc/version")).trim();
|
||||
} catch (Throwable e) {
|
||||
return e.getMessage();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static final class CrashActivity extends Activity {
|
||||
|
||||
private String mLog;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
setTheme(android.R.style.Theme_DeviceDefault);
|
||||
setTitle("App Crash");
|
||||
|
||||
mLog = getIntent().getStringExtra(Intent.EXTRA_TEXT);
|
||||
|
||||
ScrollView contentView = new ScrollView(this);
|
||||
contentView.setFillViewport(true);
|
||||
|
||||
HorizontalScrollView horizontalScrollView = new HorizontalScrollView(this);
|
||||
|
||||
TextView textView = new TextView(this);
|
||||
int padding = dp2px(16);
|
||||
textView.setPadding(padding, padding, padding, padding);
|
||||
textView.setText(mLog);
|
||||
textView.setTextIsSelectable(true);
|
||||
textView.setTypeface(Typeface.DEFAULT);
|
||||
textView.setLinksClickable(true);
|
||||
|
||||
horizontalScrollView.addView(textView);
|
||||
contentView.addView(horizontalScrollView, ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
|
||||
|
||||
setContentView(contentView);
|
||||
}
|
||||
|
||||
private void restart() {
|
||||
Intent intent = getPackageManager().getLaunchIntentForPackage(getPackageName());
|
||||
if (intent != null) {
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
startActivity(intent);
|
||||
}
|
||||
finish();
|
||||
android.os.Process.killProcess(android.os.Process.myPid());
|
||||
System.exit(0);
|
||||
}
|
||||
|
||||
private static int dp2px(float dpValue) {
|
||||
final float scale = Resources.getSystem().getDisplayMetrics().density;
|
||||
return (int) (dpValue * scale + 0.5f);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
menu.add(0, android.R.id.copy, 0, android.R.string.copy)
|
||||
.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
|
||||
return super.onCreateOptionsMenu(menu);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
case android.R.id.copy:
|
||||
ClipboardManager cm = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);
|
||||
cm.setPrimaryClip(ClipData.newPlainText(getPackageName(), mLog));
|
||||
return true;
|
||||
}
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
restart();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
|
Before Width: | Height: | Size: 3.0 KiB |
|
Before Width: | Height: | Size: 4.9 KiB |
|
Before Width: | Height: | Size: 2.0 KiB |
|
Before Width: | Height: | Size: 2.8 KiB |
|
Before Width: | Height: | Size: 4.5 KiB |
|
Before Width: | Height: | Size: 6.9 KiB |
|
Before Width: | Height: | Size: 6.3 KiB |
|
Before Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 9.0 KiB |
|
Before Width: | Height: | Size: 15 KiB |
@@ -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>
|
||||
@@ -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>
|
||||