diff --git a/.winboll/Readme.txt b/.winboll/Readme.txt index 56949ff..f618520 100644 --- a/.winboll/Readme.txt +++ b/.winboll/Readme.txt @@ -1,4 +1,4 @@ -## WinBoll 主机编译事项提醒 +## WinBoLL 主机编译事项提醒 ## 类库类型源码发布 # 类库发布使用以下面命令 diff --git a/.winboll/bashCommitLibReleaseBuildFlagInfo.sh b/.winboll/bashCommitLibReleaseBuildFlagInfo.sh index d424a3a..d92f320 100644 --- a/.winboll/bashCommitLibReleaseBuildFlagInfo.sh +++ b/.winboll/bashCommitLibReleaseBuildFlagInfo.sh @@ -37,7 +37,7 @@ fi # 使用grep找到包含"publishVersion="的那一行,然后用awk提取其后的值 PUBLISH_VERSION=$(grep -o "publishVersion=.*" $1/build.properties | awk -F '=' '{print $2}') echo "< $1/build.properties publishVersion : ${PUBLISH_VERSION} >" -## 设新的 WinBoll 标签 +## 设新的 WinBoLL 标签 # 脚本调试时使用 #tag="v7.6.4-test1" # 正式设置标签时使用 diff --git a/.winboll/bashPublishAPKAddTag.sh b/.winboll/bashPublishAPKAddTag.sh index ebdd056..0a51077 100644 --- a/.winboll/bashPublishAPKAddTag.sh +++ b/.winboll/bashPublishAPKAddTag.sh @@ -38,24 +38,24 @@ function askAddWorkflowsTag { fi } -function addWinBollTag { +function addWinBoLLTag { # 就读取脚本 .winboll/winboll_app_build.gradle 生成的 publishVersion。 # 如果文件中有 publishVersion 这一项, # 使用grep找到包含"publishVersion="的那一行,然后用awk提取其后的值 PUBLISH_VERSION=$(grep -o "publishVersion=.*" $1/build.properties | awk -F '=' '{print $2}') echo "< $1/build.properties publishVersion : ${PUBLISH_VERSION} >" - ## 设新的 WinBoll 标签 + ## 设新的 WinBoLL 标签 # 脚本调试时使用 #tag="projectname-v7.6.4-test1" # 正式设置标签时使用 tag=$1"-v"${PUBLISH_VERSION} - echo "< WinBoll Tag To: $tag >"; - # 检查是否已经添加了 WinBoll Tag + echo "< WinBoLL Tag To: $tag >"; + # 检查是否已经添加了 WinBoLL Tag if [ "$(git tag -l ${tag})" == "${tag}" ]; then - echo -e "< WinBoll Tag ${tag} exist! >" - return 1 # WinBoll标签重复 + echo -e "< WinBoLL Tag ${tag} exist! >" + return 1 # WinBoLL标签重复 fi - # 添加WinBoll标签 + # 添加WinBoLL标签 git tag -a ${tag} -F $1/app_update_description.txt return 0 } @@ -119,22 +119,22 @@ if [[ $? -eq 0 ]]; then echo $result # 发布应用 - echo "Publishing WinBoll APK ..." + echo "Publishing WinBoLL APK ..." # 脚本调试时使用 #bash gradlew :$1:assembleBetaDebug # 正式发布 bash gradlew :$1:assembleStageRelease - echo "Publishing WinBoll APK OK." + echo "Publishing WinBoLL APK OK." - # 添加 WinBoll 标签 - result=$(addWinBollTag $1) + # 添加 WinBoLL 标签 + result=$(addWinBoLLTag $1) echo $result if [[ $? -eq 0 ]]; then echo $result - # WinBoll 标签添加成功 + # WinBoLL 标签添加成功 else - echo -e "${0}: addWinBollTag $1\n${result}\nAdd WinBoll tag cancel." - exit 1 # addWinBollTag 异常 + echo -e "${0}: addWinBoLLTag $1\n${result}\nAdd WinBoLL tag cancel." + exit 1 # addWinBoLLTag 异常 fi # 添加 GitHub 工作流标签 diff --git a/.winboll/bashPublishDebugAPKAddTag.sh b/.winboll/bashPublishDebugAPKAddTag.sh index 4c66313..0deb526 100644 --- a/.winboll/bashPublishDebugAPKAddTag.sh +++ b/.winboll/bashPublishDebugAPKAddTag.sh @@ -38,24 +38,24 @@ function askAddWorkflowsTag { fi } -function addWinBollTag { +function addWinBoLLTag { # 就读取脚本 .winboll/winboll_app_build.gradle 生成的 publishVersion。 # 如果文件中有 publishVersion 这一项, # 使用grep找到包含"publishVersion="的那一行,然后用awk提取其后的值 PUBLISH_VERSION=$(grep -o "publishVersion=.*" $1/build.properties | awk -F '=' '{print $2}') echo "< $1/build.properties publishVersion : ${PUBLISH_VERSION} >" - ## 设新的 WinBoll 标签 + ## 设新的 WinBoLL 标签 # 脚本调试时使用 #tag="v7.6.4-test1" # 正式调试版设置标签时使用 tag=$1"-v"${PUBLISH_VERSION}"-debug" - echo "< WinBoll Tag To: $tag >"; - # 检查是否已经添加了 WinBoll Tag + echo "< WinBoLL Tag To: $tag >"; + # 检查是否已经添加了 WinBoLL Tag if [ "$(git tag -l ${tag})" == "${tag}" ]; then - echo -e "< WinBoll Tag ${tag} exist! >" - return 1 # WinBoll标签重复 + echo -e "< WinBoLL Tag ${tag} exist! >" + return 1 # WinBoLL标签重复 fi - # 添加WinBoll标签 + # 添加WinBoLL标签 git tag -a ${tag} -F $1/app_update_description.txt return 0 } @@ -119,22 +119,22 @@ if [[ $? -eq 0 ]]; then echo $result # 发布应用 - echo "Publishing WinBoll Debug APK ..." + echo "Publishing WinBoLL Debug APK ..." # 脚本调试时使用 #bash gradlew :$1:assembleBetaDebug # 正式发布调试版 bash gradlew :$1:assembleStageDebug - echo "Publishing WinBoll Debug APK OK." + echo "Publishing WinBoLL Debug APK OK." - # 添加 WinBoll 标签 - result=$(addWinBollTag $1) + # 添加 WinBoLL 标签 + result=$(addWinBoLLTag $1) echo $result if [[ $? -eq 0 ]]; then echo $result - # WinBoll 标签添加成功 + # WinBoLL 标签添加成功 else - echo -e "${0}: addWinBollTag $1\n${result}\nAdd WinBoll tag cancel." - exit 1 # addWinBollTag 异常 + echo -e "${0}: addWinBoLLTag $1\n${result}\nAdd WinBoLL tag cancel." + exit 1 # addWinBoLLTag 异常 fi # 添加 GitHub 工作流标签 diff --git a/.winboll/bashPublishLIBAddTag.sh b/.winboll/bashPublishLIBAddTag.sh index 035c2b4..7ab6a3a 100644 --- a/.winboll/bashPublishLIBAddTag.sh +++ b/.winboll/bashPublishLIBAddTag.sh @@ -8,7 +8,7 @@ if [ -z "$1" ]; then fi ## 正式发布使用 -git pull && bash gradlew :$1:publishReleasePublicationToWinBollReleaseRepository && bash .winboll/bashCommitLibReleaseBuildFlagInfo.sh $1 +git pull && bash gradlew :$1:publishReleasePublicationToWinBoLLReleaseRepository && bash .winboll/bashCommitLibReleaseBuildFlagInfo.sh $1 ## 调试使用 -#bash gradlew :$1:publishSnapshotWinBollPublicationToWinBollSnapshotRepository && bash .winboll/bashCommitLibReleaseBuildFlagInfo.sh $1 +#bash gradlew :$1:publishSnapshotWinBoLLPublicationToWinBoLLSnapshotRepository && bash .winboll/bashCommitLibReleaseBuildFlagInfo.sh $1 diff --git a/.winboll/winboll_app_build.gradle b/.winboll/winboll_app_build.gradle index eacc876..cea3c1f 100644 --- a/.winboll/winboll_app_build.gradle +++ b/.winboll/winboll_app_build.gradle @@ -1,4 +1,4 @@ -// WinBoll 应用签名配置 +// WinBoLL 应用签名配置 // android { @@ -31,18 +31,18 @@ android { } } - flavorDimensions "WinBollApp" + flavorDimensions "WinBoLLApp" productFlavors { beta { // 检查编译标志位配置 assert (winbollBuildProps['buildCount'] != null) - dimension "WinBollApp" + dimension "WinBoLLApp" applicationIdSuffix ".beta" LocalDateTime localDateTimeNow = LocalDateTime.now(ZoneId.of("Asia/Shanghai")); versionNameSuffix "-beta" + winbollBuildProps['buildCount'] + "_" + localDateTimeNow.format('mmss') } stage { - dimension "WinBollApp" + dimension "WinBoLLApp" } } @@ -61,7 +61,7 @@ android { } // - // WinBoll 应用包输出配置 + // WinBoLL 应用包输出配置 // 1. 配置 Stage Release 版应用包输出 // 2. 配置 Beta Debug 版应用包输出 // @@ -74,13 +74,13 @@ android { //def outputFileName="${rootProject.name}_${versionName}.apk" def outputFileName=project.rootDir.name + "_${versionName}.apk" - // 创建 WinBoll Studio 发布接口文件夹 - File fWinBollStudioDir = file("/sdcard/WinBollStudio/APKs"); - if(!fWinBollStudioDir.exists()) { - //fWinBollStudioDir.mkdirs(); + // 创建 WinBoLL Studio 发布接口文件夹 + File fWinBoLLStudioDir = file("/sdcard/WinBoLLStudio/APKs"); + if(!fWinBoLLStudioDir.exists()) { + //fWinBoLLStudioDir.mkdirs(); // 如果没有发布接口文件就不用进行APK发布和源码管理操作 - // 当前编译环境不是 WinBoll 主机, 以下将忽略APK发布和源码管理操作。 - println 'The current compilation environment is not in WinBoll host, and the following APK publishing and source management operations will be ignore.' + // 当前编译环境不是 WinBoLL 主机, 以下将忽略APK发布和源码管理操作。 + println 'The current compilation environment is not in WinBoLL host, and the following APK publishing and source management operations will be ignore.' } else { /// WINBOLL 主机的 APK 发布和源码管理操作 /// variant.getAssembleProvider().get().doFirst { @@ -91,15 +91,15 @@ android { // variant.getAssembleProvider().get().doLast { variant.outputs.forEach{ file-> - // 如果正在调试,就拷贝到 WinBoll 备份管理文件夹 + // 如果正在调试,就拷贝到 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}") + //File outBuildBckDir = new File(fWinBoLLStudioDir, "/${rootProject.name}/${variant.buildType.name}") + File outBuildBckDir = new File(fWinBoLLStudioDir, "/" + project.rootDir.name + "/${variant.buildType.name}") // 创建目标路径目录 if(!outBuildBckDir.exists()) { outBuildBckDir.mkdirs(); - println "Output Folder Created.(WinBollStudio) : " + outBuildBckDir.getAbsolutePath() + println "Output Folder Created.(WinBoLLStudio) : " + outBuildBckDir.getAbsolutePath() } if(outBuildBckDir.exists()) { copy{ @@ -108,7 +108,7 @@ android { rename { String fileName -> "${outputFileName}" } - println "Output APK (WinBollStudio): " + outBuildBckDir.getAbsolutePath() + "/${outputFileName}" + println "Output APK (WinBoLLStudio): " + outBuildBckDir.getAbsolutePath() + "/${outputFileName}" } // 检查编译标志位配置 assert (winbollBuildProps['buildCount'] != null) @@ -137,7 +137,7 @@ android { } } - // 如果正在发布,就拷贝到 WinBoll 标签管理文件夹 + // 如果正在发布,就拷贝到 WinBoLL 标签管理文件夹 // if((variant.flavorName == "stage"&&variant.buildType.name == "debug") || (variant.flavorName == "stage"&&variant.buildType.name == "release")){ @@ -151,8 +151,8 @@ android { 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/") + //File outTagDir = new File(fWinBoLLStudioDir, "/${rootProject.name}/tag/") + File outTagDir = new File(fWinBoLLStudioDir, "/" + project.rootDir.name + "/tag/") // 创建目标路径目录 if(!outTagDir.exists()) { outTagDir.mkdirs(); diff --git a/.winboll/winboll_lib_build.gradle b/.winboll/winboll_lib_build.gradle index 0f19949..f63aa52 100644 --- a/.winboll/winboll_lib_build.gradle +++ b/.winboll/winboll_lib_build.gradle @@ -1,4 +1,4 @@ -// 本机和 WinBoll Maven 仓库传输配置。 +// 本机和 WinBoLL Maven 仓库传输配置。 // def getDefaultVersion(){ @@ -9,12 +9,12 @@ def getDefaultVersion(){ } def siteUrl = 'https://winboll.cc/?page=studio/details.php&app=${rootProject.name}' // 项目主页 -def gitUrl = 'https://gitea.winboll.cc/WinBoll/${rootProject.name}' // 项目的git地址 +def gitUrl = 'https://gitea.winboll.cc/WinBoLL/${rootProject.name}' // 项目的git地址 def DefaultGroupId = 'cc.winboll.studio' // 类库所有者groupId def DefaultVersion = getDefaultVersion() // 版本号 def DeveloperId='zhangsken' // 开发者账号 def DeveloperName='ZhanGSKen' // 开发者名称 -def DeveloperEMail='ZhanGSKen@QQ.COM' // 开发者邮箱地址 +def DeveloperEMail='zhangsken@188.com' // 开发者邮箱地址 def LicenseName='The Apache Software License, Version 2.0' def LicenseUrl='http://www.apache.org/licenses/LICENSE-2.0.txt' @@ -27,10 +27,10 @@ afterEvaluate { properties.load(file("${RootProjectDir}/${winbollFilePath}").newDataInputStream()) def NexusUserName = properties.getProperty("Nexus.name") def NexusPassword = properties.getProperty("Nexus.password") - // WinBoll Release 仓库 + // WinBoLL Release 仓库 maven{ //仓库的名字和地址 - name = "WinBollRelease" + name = "WinBoLLRelease" url="https://nexus.winboll.cc/repository/maven-releases/" // 仓库用户名密码 credentials { @@ -38,10 +38,10 @@ afterEvaluate { password = NexusPassword } } - // WinBoll Snapshot 仓库 + // WinBoLL Snapshot 仓库 maven{ //仓库的名字和地址 - name = "WinBollSnapshot" + name = "WinBoLLSnapshot" url="https://nexus.winboll.cc/repository/maven-snapshots/" // 仓库用户名密码 credentials { @@ -101,9 +101,9 @@ afterEvaluate { } } - // WinBoll Maven Release 仓库传输任务 + // WinBoLL Maven Release 仓库传输任务 // - releaseWinBoll(MavenPublication) { + releaseWinBoLL(MavenPublication) { // 需要使用的变体,假设有free和pay两个变体,可以选择一个 //from components.free @@ -154,9 +154,9 @@ afterEvaluate { } // 创建名为 release 的任务结束 - // WinBoll Maven Snapshot 仓库传输任务 + // WinBoLL Maven Snapshot 仓库传输任务 // - snapshotWinBoll(MavenPublication) { + snapshotWinBoLL(MavenPublication) { // 需要使用的变体,假设有free和pay两个变体,可以选择一个 //from components.free diff --git a/README.md b/README.md index 207e65c..b3319e1 100644 --- a/README.md +++ b/README.md @@ -1,23 +1,23 @@ -# ☁ ☁ ☁ WinBoll APP ☁ ☁ ☁ ☁ ☁ ☁ ☁ ☁ ☁ ☁ ☁ ☁ ☁ ☁ ☁ ☁ ☁ ☁ ☁ -# ☁ ☁ 这是 WinBoll 系列 APP 汇总项目。☁ ☁ ☁ ☁ ☁ ☁ ☁ ☁ ☁ ☁ ☁ ☁ ☁ ☁ -# ☁ ☁ ☁ WinBoll 网站地址 https://www.winboll.cc/studio/app/ ☁ ☁ ☁ ☁ ☁ ☁ ☁ ☁ +# ☁ ☁ ☁ WinBoLL APP ☁ ☁ ☁ ☁ ☁ ☁ ☁ ☁ ☁ ☁ ☁ ☁ ☁ ☁ ☁ ☁ ☁ ☁ ☁ +# ☁ ☁ WinBoLL Studio Android 应用开源项目。☁ ☁ ☁ ☁ ☁ ☁ ☁ ☁ ☁ ☁ ☁ ☁ ☁ ☁ +# ☁ ☁ ☁ WinBoLL 网站地址 https://www.winboll.cc/ ☁ ☁ ☁ ☁ ☁ ☁ ☁ ☁ -## WinBoll 提问 +## WinBoLL 提问 同样是 /sdcard 目录,在开发 Android 应用时, 能否实现手机编译与电脑编译的源码同步。 -☁因而 WinBoll 项目组诞生了。 +☁因而 WinBoLL 项目组诞生了。 -## WinBoll 项目组研发计划 -致力于把 WinBoll-APP 应用在手机端 Android 项目开发。 -也在探索 https://gitea.winboll.cc//APP.git 应用于 WinBoll-APP APK 分发。 -更想进阶 https://github.com//APP.git 应用于 WinBoll-APP Beta APK 分发。 +## WinBoLL 项目组研发计划 +致力于把 WinBoLL-APP 应用在手机端 Android 项目开发。 +也在探索 https://gitea.winboll.cc//APP.git 应用于 WinBoLL-APP APK 分发。 +更想进阶 https://github.com//APP.git 应用于 WinBoLL-APP Beta APK 分发。 -## WinBoll-APP 汗下... +## WinBoLL-APP 汗下... #### ☁应用何置如此呢。且观用户云云。 #### ☁ 正当下 ☁ ### -#### ☁ 且容傻家叙说 ☁ WinBoll-APP 应用场景 -### ☁ WinBoll 设备资源概述 +#### ☁ 且容傻家叙说 ☁ WinBoLL-APP 应用场景 +### ☁ WinBoLL 设备资源概述 #### ☁ 1. Raid Disk. 概述:这是一个矩阵存储类设备。 优点:该设备具有数据容错存储功能, @@ -40,74 +40,79 @@ 设备位于操作系统内部文件系统。 数据持久性与操作系统挂钩。 -#### ☁ 4. WinBoll 用户资源概述。 -1> /home/<用户名> 位于 WinBoll 操作系统目录下。 +#### ☁ 4. WinBoLL 用户资源概述。 +1> /home/<用户名> 位于 WinBoLL 操作系统目录下。 2> /rdisk/<用户名> 挂载用户 Raid Disk. 3> /data/<用户名> 挂载用户 Data Disk. 4> /sdcard/<用户名> 挂载用户 SSD Disk. -#### ☁ 5. WinBoll-APP 用户资源概述。 +#### ☁ 5. WinBoLL-APP 用户资源概述。 1> /sdcard 挂载用户手机 SD 存储/storage/emulated/0 ### ☁ 稍稍歇 ☁ ### -### ☁ 急急停 ☁ WinBoll 应用前置条件 -☁ WinBoll 主机建立 1Panel MySQL 应用。 -☁ WinBoll 主机建立 1Panel Gitea 应用。 -☁ WinBoll 主机设置 WinBoll 应用为非登录状态。 -☁ WinBoll 主机建立 WinBoll 账户与 WinBoll 用户组。 -☁ WinBoll 账户 User ID 为: J。 -☁ WinBoll 用户组 Group ID 为: Studio。 -☁ WinBoll 主机 WinBoll 1Panel Gitea 建立 WinBoll 工作组。 -☁ WinBoll 主机 WinBoll 1Panel Gitea 用户项目 APK 编译输出目录为 /sdcard/WinBollStudio/<用户名>/APKs/ -☁ WinBoll 项目配置文件示例为 "/.winboll/winboll.properties-demo"(WinBoll 项目已设置) -☁ WinBoll 项目配置文件为 "/.winboll/winboll.properties" -☁ WinBoll 项目配置文件设定为源码提交时忽略。(WinBoll 项目已设置) -☁ Gradle 项目配置文件示例为 "/.winboll/local.properties-demo"(WinBoll 项目已设置) -☁ Gradle 项目配置文件为 "/local.properties"(WinBoll 项目已设置) -☁ Gradle 项目配置文件设定为源码提交时忽略。(WinBoll 项目已设置) +### ☁ 急急停 ☁ WinBoLL 应用前置条件 +☁ WinBoLL 主机建立 1Panel MySQL 应用。 +☁ WinBoLL 主机建立 1Panel Gitea 应用。 +☁ WinBoLL 主机设置 WinBoLL 应用为非登录状态。 +☁ WinBoLL 主机建立 WinBoLL 账户与 WinBoLL 用户组。 +☁ WinBoLL 账户 User ID 为: J。 +☁ WinBoLL 用户组 Group ID 为: Studio。 +☁ WinBoLL 主机 WinBoLL 1Panel Gitea 建立 WinBoLL 工作组。 +☁ WinBoLL 主机 WinBoLL 1Panel Gitea 用户项目 APK 编译输出目录为 /sdcard/WinBoLLStudio/<用户名>/APKs/ +☁ WinBoLL 项目配置文件示例为 "/.winboll/winboll.properties-demo"(WinBoLL 项目已设置) +☁ WinBoLL 项目配置文件为 "/.winboll/winboll.properties" +☁ WinBoLL 项目配置文件设定为源码提交时忽略。(WinBoLL 项目已设置) +☁ Gradle 项目配置文件示例为 "/.winboll/local.properties-demo"(WinBoLL 项目已设置) +☁ Gradle 项目配置文件为 "/local.properties"(WinBoLL 项目已设置) +☁ Gradle 项目配置文件设定为源码提交时忽略。(WinBoLL 项目已设置) -### ☁ 登高处 ☁ WinBoll 应用需求规划 -☁ WinBoll 主机建立 WinBoll 客户端用户数据库为 MySQL winbollclient 数据库。 -☁ WinBoll 主机设置 WinBoll 客户端用户信息存储在 winbollclient 数据库中。 +### ☁ 登高处 ☁ WinBoLL 应用需求规划 +☁ WinBoLL 主机建立 WinBoLL 客户端用户数据库为 MySQL winbollclient 数据库。 +☁ WinBoLL 主机设置 WinBoLL 客户端用户信息存储在 winbollclient 数据库中。 ☁ MySQL winbollclient 数据库中 - WinBoll 客户端用户信息设定为: + WinBoLL 客户端用户信息设定为: <用户名, 验证密码, 验证邮箱, 验证手机, 唯一存储令牌Token, 备用验证邮箱>。 -☁ WinBoll 项目源码仓库托管在 WinBoll 1Panel Gitea 目录 /opt/1panel/apps/gitea/gitea/data/git/repositories/studio/app.git中。 -☁ WinBoll 主机提供 WinBoll 1Panel Gitea 应用的 WinBoll 项目源码仓库存取功能。(Gitea 应用已提供) -☁ WinBoll 主机提供 WinBoll Gitea 项目仓库存档功能。(Gitea 应用已提供) -☁ 提供 WinBoll 客户端用户登录功能。(Gitea 应用已提供) +☁ WinBoLL 项目源码仓库托管在 WinBoLL 1Panel Gitea 目录 /opt/1panel/apps/gitea/gitea/data/git/repositories/studio/app.git中。 +☁ WinBoLL 主机提供 WinBoLL 1Panel Gitea 应用的 WinBoLL 项目源码仓库存取功能。(Gitea 应用已提供) +☁ WinBoLL 主机提供 WinBoLL Gitea 项目仓库存档功能。(Gitea 应用已提供) +☁ 提供 WinBoLL 客户端用户登录功能。(Gitea 应用已提供) ### ☁ 看远方 ☁ ### -### ☁ 心忧虑 ☁ WinBoll-APP 应用前置需求 -☁ WinBoll-APP WinBoll 项目根目录设定为手机的 /sdcard/WinBollStudio/Sources 目录。(需要用户手动建立文件夹) -☁ WinBoll-APP 具有手机 /sdcard/WinBoll 目录的存储权限。(需要手机操作系统授权) -☁ WinBoll-APP WinBoll 项目仓库源码存储路径为 /sdcard/WinBollStudio/Sources/APP.git(需要用户手动建立文件夹) -☁ WinBoll-APP 项目 APK 编译输出目录为 /sdcard/WinBollStudio/APKs/ -☁ WinBoll-APP 应用签名验证可定制化。(WinBoll 项目已提供) -☁ WinBoll-APP 与系列衍生 APP 应用共享 cc.winboll.studio 命名空间资源。(WinBoll 项目已提供) -☁ WinBoll-APP 用户客户端信息存储在命名空间为 WinBoll APP MySQLLite 应用的 winbollappclient 数据库中。 -☁ WinBoll-APP MySQLLite 应用的 winbollappclient 数据库中, - WinBoll 用户客户端信息设定为: +### ☁ 心忧虑 ☁ WinBoLL-APP 应用前置需求 +☁ WinBoLL-APP WinBoLL 项目根目录设定为手机的 /sdcard/WinBoLLStudio/Sources 目录。(需要用户手动建立文件夹) +☁ WinBoLL-APP 具有手机 /sdcard/WinBoLL 目录的存储权限。(需要手机操作系统授权) +☁ WinBoLL-APP WinBoLL 项目仓库源码存储路径为 /sdcard/WinBoLLStudio/Sources/APP.git(需要用户手动建立文件夹) +☁ WinBoLL-APP 项目 APK 编译输出目录为 /sdcard/WinBoLLStudio/APKs/ +☁ WinBoLL-APP 应用签名验证可定制化。(WinBoLL 项目已提供) +☁ WinBoLL-APP 与系列衍生 APP 应用共享 cc.winboll.studio 命名空间资源。(WinBoLL 项目已提供) +☁ WinBoLL-APP 用户客户端信息存储在命名空间为 WinBoLL APP MySQLLite 应用的 winbollappclient 数据库中。 +☁ WinBoLL-APP MySQLLite 应用的 winbollappclient 数据库中, + WinBoLL 用户客户端信息设定为: <用户名, 唯一存储令牌Token>。 ### ☁ 云游四方 ☁ ### -### ☁ 呔! ☁ WinBoll-APP 应用需求规划 -☁ WinBoll-APP 提供手机目录 /sdcard/WinBollStudio/Sources 的 WinBoll 项目源码管理功能。 +### ☁ 呔! ☁ WinBoLL-APP 应用需求规划 +☁ 如要使用 WinBoLL Android 项目的 Gradle 编译功能,则需要设置以下两个文件夹。 +☁ 1. 则需要建立数据存储目录 /sdcard/WinBoLLStudio/APKs。 + WinBoLL 项目源码编译出来的安装包会拷贝一份到 /sdcard/WinBoLLStudio/APKs 目录下。 +☁ 2. 则需要建立数据存储目录 /sdcard/AppProjects。 + WinBoLL 项目源码编译出来的安装包会拷贝一份并命名 "app.apk" 的安装文件为到 /sdcard/AppProjects 目录下。 -### ☁ 吁! ☁ WinBoll-APP 共享计划前景 -☁ WinBoll-APP 将会实现 https://winboll.cc/api 访问功能。 -☁ WinBoll-APP 将会实现手机端 Android 应用的开发与管理功能。 -## ☁ WinBoll ☁ WinBoll 主机忧虑 -☁ WinBoll 将会提供 gitea.winboll.cc 域名用户注册登录功能。 -☁ WinBoll 将会提供 WinBoll-APP 及其衍生应用的 Gitea 仓库管理服务。 -☁ WinBoll 将会提供 winboll.cc 域名 WinBoll 项目组注册登录功能。 +### ☁ 吁! ☁ WinBoLL-APP 共享计划前景 +☁ WinBoLL-APP 将会实现 https://winboll.cc/api 访问功能。 +☁ WinBoLL-APP 将会实现手机端 Android 应用的开发与管理功能。 + +## ☁ WinBoLL ☁ WinBoLL 主机忧虑 +☁ WinBoLL 将会提供 gitea.winboll.cc 域名用户注册登录功能。 +☁ WinBoLL 将会提供 WinBoLL-APP 及其衍生应用的 Gitea 仓库管理服务。 +☁ WinBoLL 将会提供 winboll.cc 域名 WinBoLL 项目组注册登录功能。 # 本项目要实际运用需要注意以下几个步骤: # 在项目根目录下: ## 1. 项目模块编译环境设置(必须),settings.gradle-demo 要复制为 settings.gradle,并取消相应项目模块的注释。 ## 2. 项目 Android SDK 编译环境设置(可选),local.properties-demo 要复制为 local.properties,并按需要设置 Android SDK 目录。 -## 3. 类库型模块编译环境设置(可选),winboll.properties-demo 要复制为 winboll.properties,并按需要设置 WinBoll Maven 库登录用户信息。 +## 3. 类库型模块编译环境设置(可选),winboll.properties-demo 要复制为 winboll.properties,并按需要设置 WinBoLL Maven 库登录用户信息。 # ☆类库型项目编译方法 @@ -116,12 +121,12 @@ 设置属性 libraryProject=<类库项目模块文件夹名称> ### 再编译测试项目 $ bash .winboll/bashPublishAPKAddTag.sh <应用项目模块文件夹名称> -#### 测试项目编译后,编译器会复制一份 APK 到以下路径:"/sdcard/WinBollStudio/APKs/<项目根目录名称>/tag/" 文件夹。 +#### 测试项目编译后,编译器会复制一份 APK 到以下路径:"/sdcard/WinBoLLStudio/APKs/<项目根目录名称>/tag/" 文件夹。 ### 最后编译类库项目 $ bash .winboll/bashPublishLIBAddTag.sh <类库项目模块文件夹名称> -#### 类库模块编译命令执行后,编译器会发布到 WinBoll Nexus Maven 库:Maven 库地址可以参阅根项目目录配置 build.gradle 文件。 +#### 类库模块编译命令执行后,编译器会发布到 WinBoLL Nexus Maven 库:Maven 库地址可以参阅根项目目录配置 build.gradle 文件。 # ☆应用型项目编译方法 ## 直接调用以下命令编译应用型项目 $ bash .winboll/bashPublishAPKAddTag.sh <应用项目模块文件夹名称> -#### 应用模块编译命令执行后,编译器会复制一份 APK 到以下路径:"/sdcard/WinBollStudio/APKs/<项目根目录名称>/tag/" 文件夹。 +#### 应用模块编译命令执行后,编译器会复制一份 APK 到以下路径:"/sdcard/WinBoLLStudio/APKs/<项目根目录名称>/tag/" 文件夹。 diff --git a/aes/build.gradle b/aes/build.gradle index 13025d9..40db0a1 100644 --- a/aes/build.gradle +++ b/aes/build.gradle @@ -24,12 +24,12 @@ android { defaultConfig { applicationId "cc.winboll.studio.aes" minSdkVersion 24 - targetSdkVersion 29 + targetSdkVersion 30 versionCode 1 // versionName 更新后需要手动设置 // 项目模块目录的 build.gradle 文件的 stageCount=0 // Gradle编译环境下合起来的 versionName 就是 "${versionName}.0" - versionName "15.2" + versionName "15.6" if(true) { versionName = genVersionName("${versionName}") } diff --git a/aes/build.properties b/aes/build.properties index 3498711..ec4f107 100644 --- a/aes/build.properties +++ b/aes/build.properties @@ -1,8 +1,8 @@ #Created by .winboll/winboll_app_build.gradle -#Wed Apr 02 20:09:04 HKT 2025 -stageCount=6 +#Tue Apr 29 15:14:41 HKT 2025 +stageCount=1 libraryProject=libaes -baseVersion=15.2 -publishVersion=15.2.5 +baseVersion=15.6 +publishVersion=15.6.0 buildCount=0 -baseBetaVersion=15.2.6 +baseBetaVersion=15.6.1 diff --git a/aes/src/main/java/cc/winboll/studio/aes/AboutActivity.java b/aes/src/main/java/cc/winboll/studio/aes/AboutActivity.java index 208327c..228cd19 100644 --- a/aes/src/main/java/cc/winboll/studio/aes/AboutActivity.java +++ b/aes/src/main/java/cc/winboll/studio/aes/AboutActivity.java @@ -10,14 +10,13 @@ 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.libaes.winboll.APPInfo; import cc.winboll.studio.libaes.winboll.AboutView; import cc.winboll.studio.libappbase.GlobalApplication; -import cc.winboll.studio.libappbase.winboll.IWinBollActivity; +import cc.winboll.studio.libappbase.winboll.IWinBoLLActivity; -public class AboutActivity extends WinBollActivity implements IWinBollActivity { +public class AboutActivity extends WinBoLLActivity implements IWinBoLLActivity { public static final String TAG = "AboutActivity"; @@ -64,13 +63,13 @@ public class AboutActivity extends WinBollActivity implements IWinBollActivity { ); layout.addView(aboutView, params); - GlobalApplication.getWinBollActivityManager().add(this); + GlobalApplication.getWinBoLLActivityManager().add(this); } @Override protected void onDestroy() { super.onDestroy(); - GlobalApplication.getWinBollActivityManager().registeRemove(this); + GlobalApplication.getWinBoLLActivityManager().registeRemove(this); } public AboutView CreateAboutView() { @@ -86,6 +85,8 @@ public class AboutActivity extends WinBollActivity implements IWinBollActivity { appInfo.setAppHomePage("https://www.winboll.cc/studio/details.php?app=AES"); appInfo.setAppAPKName("AES"); appInfo.setAppAPKFolderName("AES"); + //appInfo.setIsAddDebugTools(false); + appInfo.setIsAddDebugTools(BuildConfig.DEBUG); return new AboutView(mContext, appInfo); } } diff --git a/aes/src/main/java/cc/winboll/studio/aes/App.java b/aes/src/main/java/cc/winboll/studio/aes/App.java index 2dc8e06..b656adb 100644 --- a/aes/src/main/java/cc/winboll/studio/aes/App.java +++ b/aes/src/main/java/cc/winboll/studio/aes/App.java @@ -8,6 +8,7 @@ package cc.winboll.studio.aes; import android.view.Gravity; import cc.winboll.studio.libappbase.GlobalApplication; import com.hjq.toast.ToastUtils; +import com.hjq.toast.style.WhiteToastStyle; public class App extends GlobalApplication { @@ -21,8 +22,8 @@ public class App extends GlobalApplication { // 初始化 Toast 框架 ToastUtils.init(this); // 设置 Toast 布局样式 - ToastUtils.setView(R.layout.view_toast); - //ToastUtils.setStyle(new WhiteToastStyle()); + //ToastUtils.setView(R.layout.view_toast); + ToastUtils.setStyle(new WhiteToastStyle()); ToastUtils.setGravity(Gravity.BOTTOM, 0, 200); } diff --git a/aes/src/main/java/cc/winboll/studio/aes/MainActivity.java b/aes/src/main/java/cc/winboll/studio/aes/MainActivity.java index 7d76e83..73fcd02 100644 --- a/aes/src/main/java/cc/winboll/studio/aes/MainActivity.java +++ b/aes/src/main/java/cc/winboll/studio/aes/MainActivity.java @@ -25,12 +25,12 @@ import cc.winboll.studio.libaes.unittests.TestAToolbarActivity; import cc.winboll.studio.libaes.unittests.TestDrawerFragmentActivity; import cc.winboll.studio.libaes.unittests.TestViewPageFragment; import cc.winboll.studio.libappbase.LogUtils; -import cc.winboll.studio.libappbase.winboll.IWinBollActivity; +import cc.winboll.studio.libappbase.winboll.IWinBoLLActivity; import com.a4455jkjh.colorpicker.ColorPickerDialog; import com.hjq.toast.ToastUtils; import java.util.ArrayList; -public class MainActivity extends DrawerFragmentActivity implements IWinBollActivity { +public class MainActivity extends DrawerFragmentActivity implements IWinBoLLActivity { public static final String TAG = "MainActivity"; @@ -123,7 +123,7 @@ public class MainActivity extends DrawerFragmentActivity implements IWinBollActi public boolean onOptionsItemSelected(MenuItem item) { int nItemId = item.getItemId(); // if (item.getItemId() == R.id.item_log) { -// WinBollActivityManager.getInstance(this).startWinBollActivity(getApplicationContext(), LogActivity.class); +// WinBoLLActivityManager.getInstance(this).startWinBoLLActivity(getApplicationContext(), LogActivity.class); // } else if (nItemId == R.id.item_atoast) { Toast.makeText(getApplication(), "item_testatoast", Toast.LENGTH_SHORT).show(); diff --git a/app/src/main/java/cc/winboll/studio/app/WinBollActivity.java b/aes/src/main/java/cc/winboll/studio/aes/WinBoLLActivity.java similarity index 72% rename from app/src/main/java/cc/winboll/studio/app/WinBollActivity.java rename to aes/src/main/java/cc/winboll/studio/aes/WinBoLLActivity.java index 3fa6116..af25247 100644 --- a/app/src/main/java/cc/winboll/studio/app/WinBollActivity.java +++ b/aes/src/main/java/cc/winboll/studio/aes/WinBoLLActivity.java @@ -1,21 +1,21 @@ -package cc.winboll.studio.app; +package cc.winboll.studio.aes; -/** - * @Author ZhanGSKen@AliYun.Com - * @Date 2025/04/01 12:55:32 - * @Describe 应用窗口基类 - */ import android.app.Activity; import android.os.Bundle; -import android.view.MenuItem; import androidx.appcompat.app.AppCompatActivity; import cc.winboll.studio.libaes.beans.AESThemeBean; import cc.winboll.studio.libaes.utils.AESThemeUtil; -import cc.winboll.studio.libappbase.winboll.IWinBollActivity; +import cc.winboll.studio.libappbase.winboll.IWinBoLLActivity; +import android.view.MenuItem; -public class WinBollActivity extends AppCompatActivity implements IWinBollActivity { +/** + * @Author ZhanGSKen@AliYun.Com + * @Date 2025/03/30 00:34:02 + * @Describe WinBoLL 活动窗口通用基类 + */ +public class WinBoLLActivity extends AppCompatActivity implements IWinBoLLActivity { - public static final String TAG = "WinBollActivity"; + public static final String TAG = "WinBoLLActivity"; protected volatile AESThemeBean.ThemeType mThemeType; @@ -32,16 +32,10 @@ public class WinBollActivity extends AppCompatActivity implements IWinBollActivi @Override protected void onCreate(Bundle savedInstanceState) { mThemeType = getThemeType(); + setThemeStyle(); super.onCreate(savedInstanceState); - App.getWinBollActivityManager().add(this); } - @Override - protected void onDestroy() { - super.onDestroy(); - App.getWinBollActivityManager().registeRemove(this); - } - AESThemeBean.ThemeType getThemeType() { /*SharedPreferences sharedPreferences = getSharedPreferences( SHAREDPREFERENCES_NAME, MODE_PRIVATE); @@ -57,7 +51,7 @@ public class WinBollActivity extends AppCompatActivity implements IWinBollActivi @Override public boolean onOptionsItemSelected(MenuItem item) { - if (item.getItemId() == android.R.id.home) { + if(item.getItemId() == android.R.id.home) { finish(); return true; } diff --git a/androiddemo/build.gradle b/androiddemo/build.gradle index 0902de4..4468b09 100644 --- a/androiddemo/build.gradle +++ b/androiddemo/build.gradle @@ -67,6 +67,6 @@ dependencies { // https://mvnrepository.com/artifact/com.android.support/recyclerview-v7 api 'com.android.support:recyclerview-v7:28.0.0' - api 'cc.winboll.studio:libapputils:15.2.1' + api 'cc.winboll.studio:libapputils:15.2.2' api 'cc.winboll.studio:libappbase:15.2.2' } diff --git a/androiddemo/build.properties b/androiddemo/build.properties index 2fffde9..22730ee 100644 --- a/androiddemo/build.properties +++ b/androiddemo/build.properties @@ -1,8 +1,8 @@ #Created by .winboll/winboll_app_build.gradle -#Wed Apr 02 12:22:30 GMT 2025 +#Thu Apr 03 03:17:18 GMT 2025 stageCount=0 libraryProject= baseVersion=15.0 publishVersion=15.0.0 -buildCount=20 +buildCount=21 baseBetaVersion=15.0.1 diff --git a/androidxdemo/build.gradle b/androidxdemo/build.gradle index 004105d..ed769c1 100644 --- a/androidxdemo/build.gradle +++ b/androidxdemo/build.gradle @@ -67,7 +67,7 @@ dependencies { //api 'androidx.vectordrawable:vectordrawable-animated:1.1.0' //api 'androidx.fragment:fragment:1.1.0' - api 'cc.winboll.studio:libaes:15.2.5' - api 'cc.winboll.studio:libapputils:15.2.1' + api 'cc.winboll.studio:libaes:15.2.6' + api 'cc.winboll.studio:libapputils:15.2.2' api 'cc.winboll.studio:libappbase:15.2.2' } diff --git a/androidxdemo/build.properties b/androidxdemo/build.properties index ec3a471..cf539ae 100644 --- a/androidxdemo/build.properties +++ b/androidxdemo/build.properties @@ -1,8 +1,8 @@ #Created by .winboll/winboll_app_build.gradle -#Wed Apr 02 12:13:40 GMT 2025 +#Thu Apr 03 03:15:55 GMT 2025 stageCount=0 libraryProject= baseVersion=15.0 publishVersion=15.0.0 -buildCount=16 +buildCount=18 baseBetaVersion=15.0.1 diff --git a/app/build.properties b/app/build.properties deleted file mode 100644 index f17a6f0..0000000 --- a/app/build.properties +++ /dev/null @@ -1,8 +0,0 @@ -#Created by .winboll/winboll_app_build.gradle -#Tue Apr 01 13:50:28 HKT 2025 -stageCount=2 -libraryProject= -baseVersion=15.0 -publishVersion=15.0.1 -buildCount=0 -baseBetaVersion=15.0.2 diff --git a/app/src/beta/AndroidManifest.xml b/app/src/beta/AndroidManifest.xml deleted file mode 100644 index c598f4f..0000000 --- a/app/src/beta/AndroidManifest.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml deleted file mode 100644 index 8bc18a6..0000000 --- a/app/src/main/AndroidManifest.xml +++ /dev/null @@ -1,49 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/app/src/main/java/cc/winboll/studio/app/App.java b/app/src/main/java/cc/winboll/studio/app/App.java deleted file mode 100644 index a0cdd68..0000000 --- a/app/src/main/java/cc/winboll/studio/app/App.java +++ /dev/null @@ -1,30 +0,0 @@ -package cc.winboll.studio.app; - -/** - * @Author ZhanGSKen@QQ.COM - * @Date 2024/12/08 15:10:51 - * @Describe 全局应用类 - */ -import android.view.Gravity; -import cc.winboll.studio.libappbase.GlobalApplication; -import cc.winboll.studio.libappbase.winboll.WinBollActivityManager; -import com.hjq.toast.ToastUtils; - -public class App extends GlobalApplication { - - public static final String TAG = "App"; - - @Override - public void onCreate() { - super.onCreate(); - - // 初始化 Toast 框架 - ToastUtils.init(this); - // 设置 Toast 布局样式 - //ToastUtils.setView(R.layout.toast_custom_view); - //ToastUtils.setStyle(new WhiteToastStyle()); - ToastUtils.setGravity(Gravity.BOTTOM, 0, 200); - - getWinBollActivityManager().setWinBollUI_TYPE(WinBollActivityManager.WinBollUI_TYPE.Service); - } -} diff --git a/app/src/main/java/cc/winboll/studio/app/MainActivity.java b/app/src/main/java/cc/winboll/studio/app/MainActivity.java deleted file mode 100644 index 7fecc6d..0000000 --- a/app/src/main/java/cc/winboll/studio/app/MainActivity.java +++ /dev/null @@ -1,91 +0,0 @@ -package cc.winboll.studio.app; - -import android.app.Activity; -import android.content.Intent; -import android.os.Bundle; -import android.view.Menu; -import android.view.MenuItem; -import androidx.appcompat.widget.Toolbar; -import cc.winboll.studio.app.R; -import cc.winboll.studio.libappbase.LogUtils; -import cc.winboll.studio.libappbase.dialogs.YesNoAlertDialog; -import cc.winboll.studio.libappbase.winboll.IWinBollActivity; -import cc.winboll.studio.libappbase.winboll.WinBollActivityManager; - -final public class MainActivity extends WinBollActivity implements IWinBollActivity { - - public static final String TAG = "MainActivity"; - - Toolbar mToolbar; - - @Override - public Activity getActivity() { - return this; - } - - @Override - public String getTag() { - return TAG; - } - - @Override - protected void onCreate(Bundle savedInstanceState) { - LogUtils.d(TAG, "onCreate(Bundle savedInstanceState)"); - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_main); - - mToolbar = findViewById(R.id.toolbar); - setSupportActionBar(mToolbar); - mToolbar.setSubtitle(TAG); - } - - @Override - protected void onPostCreate(Bundle savedInstanceState) { - super.onPostCreate(savedInstanceState); - } - - @Override - protected void onDestroy() { - super.onDestroy(); - } - - @Override - public void onBackPressed() { - exit(); - } - - void exit() { - YesNoAlertDialog.OnDialogResultListener listener = new YesNoAlertDialog.OnDialogResultListener(){ - - @Override - public void onYes() { - App.getWinBollActivityManager().finishAll(); - } - - @Override - public void onNo() { - } - }; - YesNoAlertDialog.show(this, "[ " + getString(R.string.app_name) + " ]", "Exit(Yes/No).\nIs close all activity?", listener); - } - - @Override - public boolean onCreateOptionsMenu(Menu menu) { - getMenuInflater().inflate(R.menu.toolbar_main, menu); - return super.onCreateOptionsMenu(menu); - } - - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - if (item.getItemId() == R.id.item_log) { - App.getWinBollActivityManager().startLogActivity(this); - } else if (item.getItemId() == R.id.item_about) { - App.getWinBollActivityManager().startWinBollActivity(this, AboutActivity.class); - } else if (item.getItemId() == R.id.item_exit) { - exit(); - return true; - } - return super.onOptionsItemSelected(item); - } -} diff --git a/app/src/main/res/drawable/ic_launcher.xml b/app/src/main/res/drawable/ic_launcher.xml deleted file mode 100644 index d4d1eaf..0000000 --- a/app/src/main/res/drawable/ic_launcher.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - diff --git a/app/src/main/res/drawable/ic_launcher_background.xml b/app/src/main/res/drawable/ic_launcher_background.xml deleted file mode 100644 index 9486190..0000000 --- a/app/src/main/res/drawable/ic_launcher_background.xml +++ /dev/null @@ -1,170 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/app/src/main/res/drawable/ic_launcher_foreground.xml b/app/src/main/res/drawable/ic_launcher_foreground.xml deleted file mode 100644 index 872b04e..0000000 --- a/app/src/main/res/drawable/ic_launcher_foreground.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - diff --git a/app/src/main/res/drawable/shape_gradient.xml b/app/src/main/res/drawable/shape_gradient.xml deleted file mode 100644 index c164fe9..0000000 --- a/app/src/main/res/drawable/shape_gradient.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml deleted file mode 100644 index 2b9a1b2..0000000 --- a/app/src/main/res/layout/activity_main.xml +++ /dev/null @@ -1,29 +0,0 @@ - - - - - - - - - - - - - diff --git a/app/src/main/res/layout/view_toast.xml b/app/src/main/res/layout/view_toast.xml deleted file mode 100644 index d6a9915..0000000 --- a/app/src/main/res/layout/view_toast.xml +++ /dev/null @@ -1,32 +0,0 @@ - - - - - - - - - - - - diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml deleted file mode 100644 index 6be8764..0000000 --- a/app/src/main/res/values/colors.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - #FF196ABC - #FF002B57 - #FF80BFFF - #FFA9A9A9 - #FF000000 - #FFFFFFFF - diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml deleted file mode 100644 index 43bd91d..0000000 --- a/app/src/main/res/values/strings.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - APP - - diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml deleted file mode 100644 index 045e125..0000000 --- a/app/src/main/res/values/styles.xml +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/app/src/main/res/xml/studio_provider.xml b/app/src/main/res/xml/studio_provider.xml deleted file mode 100644 index f045677..0000000 --- a/app/src/main/res/xml/studio_provider.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - - - - - - - diff --git a/appbase/build.gradle b/appbase/build.gradle index 727c7be..66b0085 100644 --- a/appbase/build.gradle +++ b/appbase/build.gradle @@ -25,12 +25,12 @@ android { defaultConfig { applicationId "cc.winboll.studio.appbase" minSdkVersion 24 - targetSdkVersion 29 + targetSdkVersion 30 versionCode 1 // versionName 更新后需要手动设置 // .winboll/winbollBuildProps.properties 文件的 stageCount=0 // Gradle编译环境下合起来的 versionName 就是 "${versionName}.0" - versionName "15.2" + versionName "15.7" if(true) { versionName = genVersionName("${versionName}") } diff --git a/appbase/build.properties b/appbase/build.properties index 8a2d4b2..3cb59e8 100644 --- a/appbase/build.properties +++ b/appbase/build.properties @@ -1,8 +1,8 @@ #Created by .winboll/winboll_app_build.gradle -#Sat Mar 29 11:28:02 HKT 2025 -stageCount=3 +#Tue Apr 29 14:47:34 HKT 2025 +stageCount=7 libraryProject=libappbase -baseVersion=15.2 -publishVersion=15.2.2 +baseVersion=15.7 +publishVersion=15.7.6 buildCount=0 -baseBetaVersion=15.2.3 +baseBetaVersion=15.7.7 diff --git a/appbase/src/main/AndroidManifest.xml b/appbase/src/main/AndroidManifest.xml index 3ff9087..e8a8bdb 100644 --- a/appbase/src/main/AndroidManifest.xml +++ b/appbase/src/main/AndroidManifest.xml @@ -5,7 +5,7 @@ _WinBollNewsBeanList; + volatile static ArrayList _WinBoLLNewsBeanList; final static int _MAX_PAGES = 10; final static int _OnePageLinesCount = 5; volatile static int _CurrentPageIndex = 0; @Override public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) { - initWinBollNewsBeanList(context); + initWinBoLLNewsBeanList(context); for (int appWidgetId : appWidgetIds) { updateAppWidget(context, appWidgetManager, appWidgetId); } @@ -47,7 +47,7 @@ public class APPNewsWidget extends AppWidgetProvider { @Override public void onReceive(Context context, Intent intent) { super.onReceive(context, intent); - initWinBollNewsBeanList(context); + initWinBoLLNewsBeanList(context); if (intent.getAction().equals(ACTION_RELOAD_REPORT)) { LogUtils.d(TAG, "ACTION_RELOAD_REPORT"); AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context); @@ -57,7 +57,7 @@ public class APPNewsWidget extends AppWidgetProvider { } }else if (intent.getAction().equals(ACTION_WAKEUP_SERVICE)) { LogUtils.d(TAG, "ACTION_WAKEUP_SERVICE"); - String szAPPModel = intent.getStringExtra(WinBoll.EXTRA_APPMODEL); + String szAPPModel = intent.getStringExtra(WinBoLL.EXTRA_APPMODEL); LogUtils.d(TAG, String.format("szAPPModel %s", szAPPModel)); if (szAPPModel != null && !szAPPModel.equals("")) { try { @@ -71,7 +71,7 @@ public class APPNewsWidget extends AppWidgetProvider { String appName = AppUtils.getAppNameByPackageName(context, szAppPackageName); LogUtils.d(TAG, String.format("appName %s", appName)); - WinBollNewsBean winBollNewsBean = new WinBollNewsBean(appName); + WinBoLLNewsBean winBollNewsBean = new WinBoLLNewsBean(appName); SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss"); String currentTime = sdf.format(new Date()); StringBuilder sbLine = new StringBuilder(); @@ -81,7 +81,7 @@ public class APPNewsWidget extends AppWidgetProvider { sbLine.append(appName); winBollNewsBean.setMessage(sbLine.toString()); - addWinBollNewsBean(context, winBollNewsBean); + addWinBoLLNewsBean(context, winBollNewsBean); AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context); int[] appWidgetIds = appWidgetManager.getAppWidgetIds(new ComponentName(context, APPNewsWidget.class)); @@ -99,24 +99,24 @@ public class APPNewsWidget extends AppWidgetProvider { // // 加入新报告信息 // - public synchronized static void addWinBollNewsBean(Context context, WinBollNewsBean bean) { - initWinBollNewsBeanList(context); - _WinBollNewsBeanList.add(0, bean); + public synchronized static void addWinBoLLNewsBean(Context context, WinBoLLNewsBean bean) { + initWinBoLLNewsBeanList(context); + _WinBoLLNewsBeanList.add(0, bean); // 控制记录总数 - while (_WinBollNewsBeanList.size() > _MAX_PAGES * _OnePageLinesCount) { - _WinBollNewsBeanList.remove(_WinBollNewsBeanList.size() - 1); + while (_WinBoLLNewsBeanList.size() > _MAX_PAGES * _OnePageLinesCount) { + _WinBoLLNewsBeanList.remove(_WinBoLLNewsBeanList.size() - 1); } - WinBollNewsBean.saveBeanList(context, _WinBollNewsBeanList, WinBollNewsBean.class); + WinBoLLNewsBean.saveBeanList(context, _WinBoLLNewsBeanList, WinBoLLNewsBean.class); } - synchronized static void initWinBollNewsBeanList(Context context) { - if (_WinBollNewsBeanList == null) { - _WinBollNewsBeanList = new ArrayList(); - WinBollNewsBean.loadBeanList(context, _WinBollNewsBeanList, WinBollNewsBean.class); + synchronized static void initWinBoLLNewsBeanList(Context context) { + if (_WinBoLLNewsBeanList == null) { + _WinBoLLNewsBeanList = new ArrayList(); + WinBoLLNewsBean.loadBeanList(context, _WinBoLLNewsBeanList, WinBoLLNewsBean.class); } - if (_WinBollNewsBeanList == null) { - _WinBollNewsBeanList = new ArrayList(); - WinBollNewsBean.saveBeanList(context, _WinBollNewsBeanList, WinBollNewsBean.class); + if (_WinBoLLNewsBeanList == null) { + _WinBoLLNewsBeanList = new ArrayList(); + WinBoLLNewsBean.saveBeanList(context, _WinBoLLNewsBeanList, WinBoLLNewsBean.class); } } @@ -141,11 +141,11 @@ public class APPNewsWidget extends AppWidgetProvider { public static String getMessage() { ArrayList msgTemp = new ArrayList(); - if (_WinBollNewsBeanList != null) { + if (_WinBoLLNewsBeanList != null) { int start = _OnePageLinesCount * _CurrentPageIndex; - start = _WinBollNewsBeanList.size() > start ? start : _WinBollNewsBeanList.size() - 1; - for (int i = start, j = 0; i < _WinBollNewsBeanList.size() && j < _OnePageLinesCount && start > -1; i++, j++) { - msgTemp.add(_WinBollNewsBeanList.get(i).getMessage()); + start = _WinBoLLNewsBeanList.size() > start ? start : _WinBoLLNewsBeanList.size() - 1; + for (int i = start, j = 0; i < _WinBoLLNewsBeanList.size() && j < _OnePageLinesCount && start > -1; i++, j++) { + msgTemp.add(_WinBoLLNewsBeanList.get(i).getMessage()); } String message = String.join("\n", msgTemp); return message; @@ -154,7 +154,7 @@ public class APPNewsWidget extends AppWidgetProvider { } public static void prePage(Context context) { - if (_WinBollNewsBeanList != null) { + if (_WinBoLLNewsBeanList != null) { if (_CurrentPageIndex > 0) { _CurrentPageIndex = _CurrentPageIndex - 1; } @@ -165,8 +165,8 @@ public class APPNewsWidget extends AppWidgetProvider { } public static void nextPage(Context context) { - if (_WinBollNewsBeanList != null) { - if ((_CurrentPageIndex + 1) * _OnePageLinesCount < _WinBollNewsBeanList.size()) { + if (_WinBoLLNewsBeanList != null) { + if ((_CurrentPageIndex + 1) * _OnePageLinesCount < _WinBoLLNewsBeanList.size()) { _CurrentPageIndex = _CurrentPageIndex + 1; } Intent intentWidget = new Intent(context, APPNewsWidget.class); @@ -176,11 +176,11 @@ public class APPNewsWidget extends AppWidgetProvider { } String getPageInfo() { - if (_WinBollNewsBeanList == null) { + if (_WinBoLLNewsBeanList == null) { return "0/0"; } - int leftCount = _WinBollNewsBeanList.size() % _OnePageLinesCount; - int currentPageCount = _WinBollNewsBeanList.size() / _OnePageLinesCount + (leftCount == 0 ?0: 1); + int leftCount = _WinBoLLNewsBeanList.size() % _OnePageLinesCount; + int currentPageCount = _WinBoLLNewsBeanList.size() / _OnePageLinesCount + (leftCount == 0 ?0: 1); return String.format("%d/%d", _CurrentPageIndex + 1, currentPageCount); } } diff --git a/appbase/src/main/res/layout/activity_main.xml b/appbase/src/main/res/layout/activity_main.xml index 193d0cc..a0e011f 100644 --- a/appbase/src/main/res/layout/activity_main.xml +++ b/appbase/src/main/res/layout/activity_main.xml @@ -5,7 +5,7 @@ android:layout_width="match_parent" android:layout_height="match_parent"> - @@ -32,7 +32,7 @@ + android:text="Hello, WinBoLL!"/> - diff --git a/appbase/src/main/res/layout/activity_new2.xml b/appbase/src/main/res/layout/activity_new2.xml index 38b5906..697e613 100644 --- a/appbase/src/main/res/layout/activity_new2.xml +++ b/appbase/src/main/res/layout/activity_new2.xml @@ -6,7 +6,7 @@ android:layout_width="match_parent" android:layout_height="match_parent"> - diff --git a/appbase/src/main/res/layout/widget_news.xml b/appbase/src/main/res/layout/widget_news.xml index f5741ae..e85e3d2 100644 --- a/appbase/src/main/res/layout/widget_news.xml +++ b/appbase/src/main/res/layout/widget_news.xml @@ -18,7 +18,7 @@ android:layout_height="wrap_content" android:id="@+id/tv_title" android:layout_weight="1.0" - android:text="WinBollNews" + android:text="WinBoLLNews" android:textStyle="bold" android:textSize="16sp"/> diff --git a/appbase/src/main/res/values/strings.xml b/appbase/src/main/res/values/strings.xml index eb33ef7..fdd16ce 100644 --- a/appbase/src/main/res/values/strings.xml +++ b/appbase/src/main/res/values/strings.xml @@ -1,5 +1,5 @@ AppBase - WinBoll + WinBoLL diff --git a/apputils/build.gradle b/apputils/build.gradle index ce30b57..dfbe547 100644 --- a/apputils/build.gradle +++ b/apputils/build.gradle @@ -24,12 +24,12 @@ android { defaultConfig { applicationId "cc.winboll.studio.apputils" minSdkVersion 26 - targetSdkVersion 29 + targetSdkVersion 30 versionCode 1 // versionName 更新后需要手动设置 // 项目模块目录的 build.gradle 文件的 stageCount=0 // Gradle编译环境下合起来的 versionName 就是 "${versionName}.0" - versionName "15.2" + versionName "15.3" if(true) { versionName = genVersionName("${versionName}") } diff --git a/apputils/build.properties b/apputils/build.properties index 4fad09f..89f1c90 100644 --- a/apputils/build.properties +++ b/apputils/build.properties @@ -1,8 +1,8 @@ #Created by .winboll/winboll_app_build.gradle -#Sat Mar 29 12:14:55 HKT 2025 -stageCount=2 +#Tue Apr 29 15:04:17 HKT 2025 +stageCount=5 libraryProject=libapputils -baseVersion=15.2 -publishVersion=15.2.1 +baseVersion=15.3 +publishVersion=15.3.4 buildCount=0 -baseBetaVersion=15.2.2 +baseBetaVersion=15.3.5 diff --git a/apputils/src/main/java/cc/winboll/studio/apputils/AssetsHtmlActivity.java b/apputils/src/main/java/cc/winboll/studio/apputils/AssetsHtmlActivity.java index 432d5be..b504503 100644 --- a/apputils/src/main/java/cc/winboll/studio/apputils/AssetsHtmlActivity.java +++ b/apputils/src/main/java/cc/winboll/studio/apputils/AssetsHtmlActivity.java @@ -15,12 +15,12 @@ import android.view.MenuItem; import android.widget.Toolbar; import cc.winboll.studio.apputils.R; import cc.winboll.studio.libappbase.LogUtils; -import cc.winboll.studio.libappbase.winboll.IWinBollActivity; +import cc.winboll.studio.libappbase.winboll.IWinBoLLActivity; import cc.winboll.studio.libapputils.views.SimpleWebView; import java.io.IOException; import java.io.InputStream; -public class AssetsHtmlActivity extends WinBollActivityBase implements IWinBollActivity { +public class AssetsHtmlActivity extends WinBoLLActivityBase implements IWinBoLLActivity { @Override public Activity getActivity() { @@ -57,7 +57,7 @@ public class AssetsHtmlActivity extends WinBollActivityBase implements IWinBollA @Override public boolean onOptionsItemSelected(MenuItem item) { // if (item.getItemId() == android.R.id.home) { -// WinBollActivityManager.getInstance(this).finish(this); +// WinBoLLActivityManager.getInstance(this).finish(this); // } return super.onOptionsItemSelected(item); } diff --git a/apputils/src/main/java/cc/winboll/studio/apputils/MainActivity.java b/apputils/src/main/java/cc/winboll/studio/apputils/MainActivity.java index afe1708..34de3b5 100644 --- a/apputils/src/main/java/cc/winboll/studio/apputils/MainActivity.java +++ b/apputils/src/main/java/cc/winboll/studio/apputils/MainActivity.java @@ -60,9 +60,9 @@ final public class MainActivity extends Activity { //if (prosessIntents(getIntent())) return; // 以下正常创建主窗口 -// // 设置 WinBoll 应用 UI 类型 -// WinBollApplication.setWinBollUI_TYPE(WinBollApplication.WinBollUI_TYPE.Aplication); -// //ToastUtils.show("WinBollUI_TYPE " + WinBollApplication.getWinBollUI_TYPE()); +// // 设置 WinBoLL 应用 UI 类型 +// WinBoLLApplication.setWinBoLLUI_TYPE(WinBoLLApplication.WinBoLLUI_TYPE.Aplication); +// //ToastUtils.show("WinBoLLUI_TYPE " + WinBoLLApplication.getWinBoLLUI_TYPE()); // LogUtils.d(TAG, "BuildConfig.DEBUG : " + Boolean.toString(BuildConfig.DEBUG)); } @@ -77,7 +77,7 @@ final public class MainActivity extends Activity { if (intent.getAction() != null) { // if (intent.getAction().equals(cc.winboll.studio.libapputils.intent.action.DEBUGVIEW)) { // App.setIsDebug(true); -// //ToastUtils.show!("WinBollApplication.setIsDebug(true) by action : " + intent.getAction()); +// //ToastUtils.show!("WinBoLLApplication.setIsDebug(true) by action : " + intent.getAction()); // // } } @@ -130,12 +130,12 @@ final public class MainActivity extends Activity { protected void onPostCreate(Bundle savedInstanceState) { super.onPostCreate(savedInstanceState); // 缓存当前 activity - //WinBollActivityManager.getInstance(this).add(this); + //WinBoLLActivityManager.getInstance(this).add(this); } @Override public void onDestroy() { - //WinBollActivityManager.getInstance(this).registeRemove(this); + //WinBoLLActivityManager.getInstance(this).registeRemove(this); super.onDestroy(); } @@ -150,8 +150,8 @@ final public class MainActivity extends Activity { // intent.addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK); // startActivity(intent); - //WinBollActivityManager.getInstance().printAvtivityListInfo(); - //WinBollActivityManager.getInstance(this).startWinBollActivity(this, LogActivity.class); + //WinBoLLActivityManager.getInstance().printAvtivityListInfo(); + //WinBoLLActivityManager.getInstance(this).startWinBoLLActivity(this, LogActivity.class); } // @@ -165,7 +165,7 @@ final public class MainActivity extends Activity { // if (intent.getAction().equals(StringToQrCodeView.ACTION_UNITTEST_QRCODE)) { // try { -// WinBollActivity clazzActivity = UnitTestActivity.class.newInstance(); +// WinBoLLActivity clazzActivity = UnitTestActivity.class.newInstance(); // String tag = clazzActivity.getTag(); // LogUtils.d(TAG, "String tag = clazzActivity.getTag(); tag " + tag); // Intent subIntent = new Intent(this, UnitTestActivity.class); @@ -183,8 +183,8 @@ final public class MainActivity extends Activity { // } // // Files.copy(Paths.get(szSrcPath), Paths.get(file.getPath())); -// //startWinBollActivity(subIntent, tag); -// WinBollActivityManager.getInstance(this).startWinBollActivity(this, subIntent, UnitTestActivity.class); +// //startWinBoLLActivity(subIntent, tag); +// WinBoLLActivityManager.getInstance(this).startWinBoLLActivity(this, subIntent, UnitTestActivity.class); // } catch (IllegalAccessException | InstantiationException | IOException e) { // LogUtils.d(TAG, e, Thread.currentThread().getStackTrace()); // // 函数处理异常返回失败 @@ -201,8 +201,8 @@ final public class MainActivity extends Activity { public boolean onCreateOptionsMenu(Menu menu) { //ToastUtils.show("onCreateOptionsMenu"); getMenuInflater().inflate(R.menu.toolbar_main, menu); -// if (isAddWinBollToolBar()) { -// //ToastUtils.show("mIWinBoll.isAddWinBollToolBar()"); +// if (isAddWinBoLLToolBar()) { +// //ToastUtils.show("mIWinBoLL.isAddWinBoLLToolBar()"); // getMenuInflater().inflate(R.menu.toolbar_winboll_shared_main, menu); // } if (App.isDebuging()) { @@ -220,7 +220,7 @@ final public class MainActivity extends Activity { } else if (item.getItemId() == R.id.item_teststringtoqrcodeview) { Intent intent = new Intent(this, TestStringToQRCodeViewActivity.class); startActivityForResult(intent, REQUEST_QRCODEDECODE_ACTIVITY); - //WinBollActivityManager.getInstance(this).startWinBollActivity(this, TestStringToQrCodeViewActivity.class); + //WinBoLLActivityManager.getInstance(this).startWinBoLLActivity(this, TestStringToQrCodeViewActivity.class); } else if (item.getItemId() == R.id.item_testqrcodedecodeactivity) { Intent intent = new Intent(this, QRCodeDecodeActivity.class); startActivityForResult(intent, REQUEST_QRCODEDECODE_ACTIVITY); @@ -230,13 +230,13 @@ final public class MainActivity extends Activity { } return true; } else if (item.getItemId() == R.id.item_log) { - //WinBollActivityManager.getInstance(this).startWinBollActivity(this, LogActivity.class); + //WinBoLLActivityManager.getInstance(this).startWinBoLLActivity(this, LogActivity.class); return true; } else if (item.getItemId() == R.id.item_exitdebug) { //AboutView.setApp2NormalMode(this); return true; } else if (item.getItemId() == android.R.id.home) { - //WinBollActivityManager.getInstance(this).finish(this); + //WinBoLLActivityManager.getInstance(this).finish(this); return true; } return super.onOptionsItemSelected(item); @@ -247,7 +247,7 @@ final public class MainActivity extends Activity { // // @Override // public void onYes() { -// //WinBollActivityManager.getInstance(getApplicationContext()).finishAll(); +// //WinBoLLActivityManager.getInstance(getApplicationContext()).finishAll(); // } // // @Override @@ -260,10 +260,10 @@ final public class MainActivity extends Activity { @Override public void onBackPressed() { -// if (WinBollActivityManager.getInstance(getApplicationContext()).isFirstIWinBollActivity(this)) { +// if (WinBoLLActivityManager.getInstance(getApplicationContext()).isFirstIWinBoLLActivity(this)) { // exit(); // } else { -// WinBollActivityManager.getInstance(this).finish(this); +// WinBoLLActivityManager.getInstance(this).finish(this); // super.onBackPressed(); // } } @@ -275,7 +275,7 @@ final public class MainActivity extends Activity { intent.addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT); intent.addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK); startActivity(intent); - //WinBollActivityManager.getInstance(this).startWinBollActivity(this, intent, AssetsHtmlActivity.class); + //WinBoLLActivityManager.getInstance(this).startWinBoLLActivity(this, intent, AssetsHtmlActivity.class); } @Override diff --git a/apputils/src/main/java/cc/winboll/studio/apputils/WinBollActivityBase.java b/apputils/src/main/java/cc/winboll/studio/apputils/WinBoLLActivityBase.java similarity index 60% rename from apputils/src/main/java/cc/winboll/studio/apputils/WinBollActivityBase.java rename to apputils/src/main/java/cc/winboll/studio/apputils/WinBoLLActivityBase.java index 1e2e8cf..91c285e 100644 --- a/apputils/src/main/java/cc/winboll/studio/apputils/WinBollActivityBase.java +++ b/apputils/src/main/java/cc/winboll/studio/apputils/WinBoLLActivityBase.java @@ -8,14 +8,13 @@ package cc.winboll.studio.apputils; import android.app.Activity; import android.os.Bundle; import android.os.PersistableBundle; -import android.support.v7.app.AppCompatActivity; import cc.winboll.studio.libappbase.GlobalApplication; -import cc.winboll.studio.libappbase.winboll.IWinBollActivity; -import cc.winboll.studio.libappbase.winboll.WinBollActivityManager; +import cc.winboll.studio.libappbase.winboll.IWinBoLLActivity; +import cc.winboll.studio.libappbase.winboll.WinBoLLActivityManager; -public class WinBollActivityBase extends AppCompatActivity implements IWinBollActivity { +public class WinBoLLActivityBase extends Activity implements IWinBoLLActivity { - public static final String TAG = "WinBollActivityBase"; + public static final String TAG = "WinBoLLActivityBase"; @Override public Activity getActivity() { @@ -27,14 +26,14 @@ public class WinBollActivityBase extends AppCompatActivity implements IWinBollAc return TAG; } - WinBollActivityManager getWinBollActivityManager() { - return WinBollActivityManager.getInstance(GlobalApplication.getInstance()); + WinBoLLActivityManager getWinBoLLActivityManager() { + return WinBoLLActivityManager.getInstance(GlobalApplication.getInstance()); } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - getWinBollActivityManager().add(this); + getWinBoLLActivityManager().add(this); } @Override @@ -47,6 +46,6 @@ public class WinBollActivityBase extends AppCompatActivity implements IWinBollAc @Override protected void onDestroy() { super.onDestroy(); - getWinBollActivityManager().registeRemove(this); + getWinBoLLActivityManager().registeRemove(this); } } diff --git a/apputils/src/main/res/values/colors.xml b/apputils/src/main/res/values/colors.xml index bb20e29..60c561c 100644 --- a/apputils/src/main/res/values/colors.xml +++ b/apputils/src/main/res/values/colors.xml @@ -1,6 +1,6 @@ - + #FF196ABC #FF002B57 #FF80BFFF diff --git a/build.gradle b/build.gradle index 2af2782..ab63fe7 100644 --- a/build.gradle +++ b/build.gradle @@ -1,10 +1,15 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. buildscript { repositories { + // 本地 Maven 仓库(默认路径为 ~/.m2/repository) + mavenLocal() + // 或自定义本地仓库路径 + maven { url "file:///sdcard/.m2/repository" } + // Nexus Maven 库地址 - // "WinBoll Release" + // "WinBoLL Release" maven { url "https://nexus.winboll.cc/repository/maven-public/" } - // "WinBoll Snapshot" + // "WinBoLL Snapshot" maven { url "https://nexus.winboll.cc/repository/maven-snapshots/" } maven { url 'https://maven.aliyun.com/repository/public/' } @@ -15,7 +20,7 @@ buildscript { maven { url "https://jitpack.io" } mavenCentral() google() - mavenLocal() + //mavenLocal() } dependencies { classpath 'com.android.tools.build:gradle:7.2.1' // 对应 compileSdkVersion 32 @@ -27,9 +32,9 @@ buildscript { allprojects { repositories { // Nexus Maven 库地址 - // "WinBoll Release" + // "WinBoLL Release" maven { url "https://nexus.winboll.cc/repository/maven-public/" } - // "WinBoll Snapshot" + // "WinBoLL Snapshot" maven { url "https://nexus.winboll.cc/repository/maven-snapshots/" } maven { url 'https://maven.aliyun.com/repository/public/' } @@ -51,7 +56,7 @@ allprojects { bashCommitAppPublishBuildFlagInfoFilePath = ".winboll/bashCommitAppPublishBuildFlagInfo.sh" winbollFilePath = "winboll.properties" - keyPropsFilePath = "winboll-x/current.keystore" + keyPropsFilePath = "current.keystore" // 定义 lint 输出文件 lintXmlReportFilePath = "build/reports/lint-results.xml" lintHTMLReportFilePath = "build/reports/lint-results.html" diff --git a/contacts/build.gradle b/contacts/build.gradle index 552a42a..2fb0f2b 100644 --- a/contacts/build.gradle +++ b/contacts/build.gradle @@ -19,17 +19,17 @@ def genVersionName(def versionName){ android { compileSdkVersion 32 - buildToolsVersion "33.0.3" + buildToolsVersion "32.0.0" defaultConfig { applicationId "cc.winboll.studio.contacts" - minSdkVersion 21 - targetSdkVersion 30 + minSdkVersion 24 + targetSdkVersion 29 versionCode 1 // versionName 更新后需要手动设置 // 项目模块目录的 build.gradle 文件的 stageCount=0 // Gradle编译环境下合起来的 versionName 就是 "${versionName}.0" - versionName "1.0" + versionName "15.2" if(true) { versionName = genVersionName("${versionName}") } @@ -41,31 +41,48 @@ android { proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } - - compileOptions { - sourceCompatibility JavaVersion.VERSION_17 - targetCompatibility JavaVersion.VERSION_17 - } } dependencies { - // 二维码使用的类库 + api fileTree(dir: 'libs', include: ['*.jar']) + + // 权限请求框架:https://github.com/getActivity/XXPermissions + api 'com.github.getActivity:XXPermissions:18.63' + // 下拉控件 + api 'com.baoyz.pullrefreshlayout:library:1.2.0' + // 拼音搜索 + // https://mvnrepository.com/artifact/com.github.open-android/pinyin4j + api 'com.github.open-android:pinyin4j:2.5.0' + // SSH + api 'com.jcraft:jsch:0.1.55' + // Html 解析 + api 'org.jsoup:jsoup:1.13.1' + // 二维码类库 api 'com.google.zxing:core:3.4.1' api 'com.journeyapps:zxing-android-embedded:3.6.0' - + // 应用介绍页类库 api 'io.github.medyo:android-about-page:2.0.0' + // 吐司类库 api 'com.github.getActivity:ToastUtils:10.5' - api 'com.jcraft:jsch:0.1.55' - api 'org.jsoup:jsoup:1.13.1' + // 网络连接类库 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.viewpager:viewpager:1.0.0' - api 'androidx.fragment:fragment: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 'cc.winboll.studio:libapputils:9.3.2' - api 'cc.winboll.studio:libappbase:1.5.6' - - api fileTree(dir: 'libs', include: ['*.jar']) + api 'cc.winboll.studio:libaes:15.2.4' + api 'cc.winboll.studio:libapputils:15.2.1' + api 'cc.winboll.studio:libappbase:15.2.2' } diff --git a/contacts/build.properties b/contacts/build.properties index 86e2b57..764b4bf 100644 --- a/contacts/build.properties +++ b/contacts/build.properties @@ -1,8 +1,8 @@ #Created by .winboll/winboll_app_build.gradle -#Tue Feb 25 00:17:29 HKT 2025 -stageCount=2 +#Sun Apr 13 02:46:09 HKT 2025 +stageCount=8 libraryProject= -baseVersion=1.0 -publishVersion=1.0.1 +baseVersion=15.2 +publishVersion=15.2.7 buildCount=0 -baseBetaVersion=1.0.2 +baseBetaVersion=15.2.8 diff --git a/contacts/src/main/AndroidManifest.xml b/contacts/src/main/AndroidManifest.xml index 36ac1ee..1ff5d1b 100644 --- a/contacts/src/main/AndroidManifest.xml +++ b/contacts/src/main/AndroidManifest.xml @@ -29,9 +29,13 @@ - - - + + + + + + + @@ -182,6 +186,10 @@ + + + + - + \ No newline at end of file diff --git a/contacts/src/main/java/cc/winboll/studio/contacts/App.java b/contacts/src/main/java/cc/winboll/studio/contacts/App.java index 6f482d5..c5a8621 100644 --- a/contacts/src/main/java/cc/winboll/studio/contacts/App.java +++ b/contacts/src/main/java/cc/winboll/studio/contacts/App.java @@ -5,9 +5,10 @@ package cc.winboll.studio.contacts; * @Date 2024/12/08 15:10:51 * @Describe 全局应用类 */ +import android.view.Gravity; import cc.winboll.studio.libappbase.GlobalApplication; -import cc.winboll.studio.libappbase.LogUtils; -import cc.winboll.studio.libapputils.app.WinBollActivityManager; +import cc.winboll.studio.libappbase.winboll.WinBollActivityManager; +import com.hjq.toast.ToastUtils; public class App extends GlobalApplication { @@ -17,12 +18,20 @@ public class App extends GlobalApplication { public void onCreate() { // 必须在调用基类前设置应用调试标志, // 这样可以预先设置日志与数据的存储根目录。 - setIsDebuging(this, BuildConfig.DEBUG); + //setIsDebuging(BuildConfig.DEBUG); super.onCreate(); // 设置 WinBoll 应用 UI 类型 WinBollActivityManager.getInstance(this).setWinBollUI_TYPE(WinBollActivityManager.WinBollUI_TYPE.Aplication); - LogUtils.d(TAG, "onCreate"); + //LogUtils.d(TAG, "onCreate"); + + // 初始化 Toast 框架 + ToastUtils.init(this); + // 设置 Toast 布局样式 + //ToastUtils.setView(R.layout.toast_custom_view); + //ToastUtils.setStyle(new WhiteToastStyle()); + ToastUtils.setGravity(Gravity.BOTTOM, 0, 200); + } } diff --git a/contacts/src/main/java/cc/winboll/studio/contacts/MainActivity.java b/contacts/src/main/java/cc/winboll/studio/contacts/MainActivity.java index 24f9f4f..c2de167 100644 --- a/contacts/src/main/java/cc/winboll/studio/contacts/MainActivity.java +++ b/contacts/src/main/java/cc/winboll/studio/contacts/MainActivity.java @@ -1,52 +1,44 @@ package cc.winboll.studio.contacts; import android.Manifest; -import android.annotation.SuppressLint; import android.app.Activity; import android.app.ActivityManager; -import android.app.role.RoleManager; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; -import android.net.Uri; import android.os.Build; import android.os.Bundle; -import android.provider.Settings; import android.telecom.TelecomManager; -import android.view.LayoutInflater; +import android.telephony.PhoneStateListener; +import android.telephony.TelephonyManager; import android.view.Menu; import android.view.MenuItem; import android.view.View; -import android.view.WindowManager; import android.widget.CheckBox; -import android.widget.CompoundButton; import android.widget.ImageView; import android.widget.LinearLayout; -import android.widget.Switch; import android.widget.Toast; -import androidx.appcompat.app.AlertDialog; import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.widget.Toolbar; -import androidx.core.content.ContextCompat; +import androidx.core.app.ActivityCompat; +import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentManager; +import androidx.fragment.app.FragmentPagerAdapter; import androidx.viewpager.widget.ViewPager; import cc.winboll.studio.contacts.R; -import cc.winboll.studio.contacts.activities.CallActivity; -import cc.winboll.studio.contacts.adapters.MyPagerAdapter; +import cc.winboll.studio.contacts.activities.SettingsActivity; import cc.winboll.studio.contacts.beans.MainServiceBean; +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.services.MainService; +import cc.winboll.studio.libaes.winboll.APPInfo; import cc.winboll.studio.libappbase.LogUtils; import cc.winboll.studio.libappbase.LogView; -import cc.winboll.studio.libapputils.app.IWinBollActivity; -import cc.winboll.studio.libapputils.app.WinBollActivityManager; -import cc.winboll.studio.libapputils.bean.APPInfo; -import cc.winboll.studio.libapputils.view.YesNoAlertDialog; -import cc.winboll.studio.contacts.listenphonecall.CallListenerService; +import cc.winboll.studio.libappbase.winboll.IWinBollActivity; import com.google.android.material.tabs.TabLayout; -import java.lang.reflect.Field; import java.util.ArrayList; import java.util.List; -import android.content.DialogInterface; -import cc.winboll.studio.contacts.activities.SettingsActivity; final public class MainActivity extends AppCompatActivity implements IWinBollActivity, ViewPager.OnPageChangeListener, View.OnClickListener { @@ -57,11 +49,13 @@ final public class MainActivity extends AppCompatActivity implements IWinBollAct public static final String ACTION_SOS = "cc.winboll.studio.libappbase.WinBoll.ACTION_SOS"; + static MainActivity _MainActivity; LogView mLogView; Toolbar mToolbar; CheckBox cbMainService; MainServiceBean mMainServiceBean; - ViewPager viewPager; + private TabLayout tabLayout; + private ViewPager viewPager; private List views; //用来存放放进ViewPager里面的布局 //实例化存储imageView(导航原点)的集合 ImageView[] imageViews; @@ -70,15 +64,20 @@ final public class MainActivity extends AppCompatActivity implements IWinBollAct LinearLayout linearLayout;//下标所在在LinearLayout布局里 int currentPoint = 0;//当前被选中中页面的下标 + private TelephonyManager telephonyManager; + private MyPhoneStateListener phoneStateListener; + List fragmentList; + List tabTitleList; + private static final int DIALER_REQUEST_CODE = 1; @Override - public AppCompatActivity getActivity() { + public Activity getActivity() { return this; } - @Override - public APPInfo getAppInfo() { +// @Override +// public APPInfo getAppInfo() { // String szBranchName = "contacts"; // // APPInfo appInfo = AboutActivityFactory.buildDefaultAPPInfo(); @@ -93,8 +92,8 @@ final public class MainActivity extends AppCompatActivity implements IWinBollAct // appInfo.setAppAPKName("Contacts"); // appInfo.setAppAPKFolderName("Contacts"); // return appInfo; - return null; - } +// return null; +// } @Override protected void onCreate(Bundle savedInstanceState) { @@ -102,28 +101,51 @@ final public class MainActivity extends AppCompatActivity implements IWinBollAct //if (prosessIntents(getIntent())) return; // 以下正常创建主窗口 super.onCreate(savedInstanceState); + _MainActivity = this; setContentView(R.layout.activity_main); // 初始化工具栏 mToolbar = findViewById(R.id.activitymainToolbar1); setSupportActionBar(mToolbar); - if (isEnableDisplayHomeAsUp()) { - // 显示后退按钮 - getSupportActionBar().setDisplayHomeAsUpEnabled(true); - } +// if (isEnableDisplayHomeAsUp()) { +// // 显示后退按钮 +// getSupportActionBar().setDisplayHomeAsUpEnabled(true); +// } getSupportActionBar().setSubtitle(getTag()); - initData(); - initView(); - //initPoint();//调用初始化导航原点的方法 - viewPager.addOnPageChangeListener(this);//滑动事件 + tabLayout = findViewById(R.id.tabLayout); + viewPager = findViewById(R.id.viewPager); - ViewPager viewPager = findViewById(R.id.activitymainViewPager1); - MyPagerAdapter pagerAdapter = new MyPagerAdapter(getSupportFragmentManager()); - viewPager.setAdapter(pagerAdapter); - TabLayout tabLayout = findViewById(R.id.activitymainTabLayout1); + // 创建Fragment列表和标题列表 + fragmentList = new ArrayList<>(); + tabTitleList = new ArrayList<>(); + fragmentList.add(CallLogFragment.newInstance(0)); + fragmentList.add(ContactsFragment.newInstance(1)); + fragmentList.add(LogFragment.newInstance(2)); + tabTitleList.add("通话记录"); + tabTitleList.add("联系人"); + tabTitleList.add("应用日志"); + + // 设置ViewPager的适配器 + MyPagerAdapter adapter = new MyPagerAdapter(getSupportFragmentManager(), fragmentList, tabTitleList); + viewPager.setAdapter(adapter); + + // 关联TabLayout和ViewPager tabLayout.setupWithViewPager(viewPager); + + +// initData(); +// initView(); +// //initPoint();//调用初始化导航原点的方法 +// viewPager.addOnPageChangeListener(this);//滑动事件 + + //ViewPager viewPager = findViewById(R.id.activitymainViewPager1); + //MyPagerAdapter pagerAdapter = new MyPagerAdapter(getSupportFragmentManager()); + //viewPager.setAdapter(pagerAdapter); + //TabLayout tabLayout = findViewById(R.id.activitymainTabLayout1); + //tabLayout.setupWithViewPager(viewPager); + // mMainServiceBean = MainServiceBean.loadBean(this, MainServiceBean.class); // if (mMainServiceBean == null) { // mMainServiceBean = new MainServiceBean(); @@ -140,36 +162,86 @@ final public class MainActivity extends AppCompatActivity implements IWinBollAct // } // } // }); - MainService.startMainService(MainActivity.this); + + MainServiceBean mMainServiceBean = MainServiceBean.loadBean(this, MainServiceBean.class); + if (mMainServiceBean == null) { + mMainServiceBean = new MainServiceBean(); + MainServiceBean.saveBean(this, mMainServiceBean); + } + if (mMainServiceBean.isEnable()) { + MainService.startMainService(this); + } + + // 初始化TelephonyManager和PhoneStateListener + telephonyManager = (TelephonyManager) getSystemService(TELEPHONY_SERVICE); + phoneStateListener = new MyPhoneStateListener(); + telephonyManager.listen(phoneStateListener, PhoneStateListener.LISTEN_CALL_STATE); + } + + + // ViewPager的适配器 + private class MyPagerAdapter extends FragmentPagerAdapter { + + private List fragmentList; + private List tabTitleList; + + public MyPagerAdapter(FragmentManager fm, List fragmentList, List tabTitleList) { + super(fm); + this.fragmentList = fragmentList; + this.tabTitleList = tabTitleList; + } + + @Override + public Fragment getItem(int position) { + return fragmentList.get(position); + } + + @Override + public int getCount() { + return fragmentList.size(); + } + + @Override + public CharSequence getPageTitle(int position) { + return tabTitleList.get(position); + } + } + + public static void dialPhoneNumber(String phoneNumber) { + Intent intent = new Intent(Intent.ACTION_DIAL); + intent.setData(android.net.Uri.parse("tel:" + phoneNumber)); + if (ActivityCompat.checkSelfPermission(_MainActivity, Manifest.permission.CALL_PHONE) != PackageManager.PERMISSION_GRANTED) { + return; + } + _MainActivity.startActivity(intent); } //初始化view,即显示的图片 - void initView() { - viewPager = findViewById(R.id.activitymainViewPager1); - pagerAdapter = new MyPagerAdapter(getSupportFragmentManager()); - viewPager.setAdapter(pagerAdapter); - //adapter = new MyPagerAdapter(views); - //viewPager = findViewById(R.id.activitymainViewPager1); - //viewPager.setAdapter(adapter); - //linearLayout = findViewById(R.id.activitymainLinearLayout1); - //initPoint();//初始化页面下方的点 - viewPager.setOnPageChangeListener(this); - - } +// void initView() { +// viewPager = findViewById(R.id.activitymainViewPager1); +// pagerAdapter = new MyPagerAdapter(getSupportFragmentManager()); +// viewPager.setAdapter(pagerAdapter); +// //adapter = new MyPagerAdapter(views); +// //viewPager = findViewById(R.id.activitymainViewPager1); +// //viewPager.setAdapter(adapter); +// //linearLayout = findViewById(R.id.activitymainLinearLayout1); +// //initPoint();//初始化页面下方的点 +// viewPager.setOnPageChangeListener(this); +// +// } //初始化所要显示的布局 - void initData() { - ViewPager viewPager = findViewById(R.id.activitymainViewPager1); - LayoutInflater inflater = LayoutInflater.from(getActivity()); - View view1 = inflater.inflate(R.layout.fragment_call, viewPager, false); - View view2 = inflater.inflate(R.layout.fragment_contacts, viewPager, false); - View view3 = inflater.inflate(R.layout.fragment_log, viewPager, false); - - views = new ArrayList<>(); - views.add(view1); - views.add(view2); - views.add(view3); - } +// void initData() { +// LayoutInflater inflater = LayoutInflater.from(getActivity()); +// View view1 = inflater.inflate(R.layout.fragment_call_log, viewPager, false); +// View view2 = inflater.inflate(R.layout.fragment_contacts, viewPager, false); +// View view3 = inflater.inflate(R.layout.fragment_log, viewPager, false); +// +// views = new ArrayList<>(); +// views.add(view1); +// views.add(view2); +// views.add(view3); +// } // void initPoint() { // imageViews = new ImageView[5];//实例化5个图片 @@ -231,6 +303,23 @@ final public class MainActivity extends AppCompatActivity implements IWinBollAct //setSubTitle(""); } + private class MyPhoneStateListener extends PhoneStateListener { + @Override + public void onCallStateChanged(int state, String incomingNumber) { + switch (state) { + case TelephonyManager.CALL_STATE_IDLE: + LogUtils.d(TAG, "电话已挂断"); + break; + case TelephonyManager.CALL_STATE_OFFHOOK: + LogUtils.d(TAG, "正在通话中"); + break; + case TelephonyManager.CALL_STATE_RINGING: + LogUtils.d(TAG, "来电: " + incomingNumber); + break; + } + } + } + @Override protected void onDestroy() { super.onDestroy(); @@ -287,40 +376,25 @@ final public class MainActivity extends AppCompatActivity implements IWinBollAct return TAG; } - @Override - public Toolbar initToolBar() { - return findViewById(R.id.activitymainToolbar1); - } - - @Override - public boolean isAddWinBollToolBar() { - return true; - } - - @Override - public boolean isEnableDisplayHomeAsUp() { - return false; - } - - @Override - public void onBackPressed() { - exit(); - } - - void exit() { - YesNoAlertDialog.OnDialogResultListener listener = new YesNoAlertDialog.OnDialogResultListener(){ - - @Override - public void onYes() { - WinBollActivityManager.getInstance(getApplicationContext()).finishAll(); - } - - @Override - public void onNo() { - } - }; - YesNoAlertDialog.show(this, "[ " + getString(R.string.app_name) + " ]", "Exit(Yes/No).\nIs close all activity?", listener); - } +// @Override +// public void onBackPressed() { +// exit(); +// } +// +// void exit() { +// YesNoAlertDialog.OnDialogResultListener listener = new YesNoAlertDialog.OnDialogResultListener(){ +// +// @Override +// public void onYes() { +// WinBollActivityManager.getInstance(getApplicationContext()).finishAll(); +// } +// +// @Override +// public void onNo() { +// } +// }; +// YesNoAlertDialog.show(this, "[ " + getString(R.string.app_name) + " ]", "Exit(Yes/No).\nIs close all activity?", listener); +// } @Override public boolean onCreateOptionsMenu(Menu menu) { @@ -331,11 +405,7 @@ final public class MainActivity extends AppCompatActivity implements IWinBollAct @Override public boolean onOptionsItemSelected(MenuItem item) { - if (item.getItemId() == R.id.item_call) { - Intent intent = new Intent(this, CallActivity.class); - startActivity(intent); - //WinBollActivityManager.getInstance(this).startWinBollActivity(this, CallActivity.class); - } else if (item.getItemId() == R.id.item_settings) { + if (item.getItemId() == R.id.item_settings) { Intent intent = new Intent(this, SettingsActivity.class); startActivity(intent); //WinBollActivityManager.getInstance(this).startWinBollActivity(this, CallActivity.class); diff --git a/contacts/src/main/java/cc/winboll/studio/contacts/PhoneCallManager.java b/contacts/src/main/java/cc/winboll/studio/contacts/PhoneCallManager.java deleted file mode 100644 index 7f7292f..0000000 --- a/contacts/src/main/java/cc/winboll/studio/contacts/PhoneCallManager.java +++ /dev/null @@ -1,35 +0,0 @@ -package cc.winboll.studio.contacts; - -/** - * @Author ZhanGSKen@AliYun.Com - * @Date 2025/02/20 21:14:52 - * @Describe PhoneCallManager - */ - -import android.telecom.Call; -import android.telecom.VideoProfile; - -public class PhoneCallManager { - - public static final String TAG = "PhoneCallManager"; - - public static Call call; - - /** - * 接听电话 - */ - public void answer() { - if (call != null) { - call.answer(VideoProfile.STATE_AUDIO_ONLY); - } - } - - /** - * 断开电话,包括来电时的拒接以及接听后的挂断 - */ - public void disconnect() { - if (call != null) { - call.disconnect(); - } - } -} diff --git a/app/src/main/java/cc/winboll/studio/app/AboutActivity.java b/contacts/src/main/java/cc/winboll/studio/contacts/activities/AboutActivity.java similarity index 83% rename from app/src/main/java/cc/winboll/studio/app/AboutActivity.java rename to contacts/src/main/java/cc/winboll/studio/contacts/activities/AboutActivity.java index f723fc7..453062b 100644 --- a/app/src/main/java/cc/winboll/studio/app/AboutActivity.java +++ b/contacts/src/main/java/cc/winboll/studio/contacts/activities/AboutActivity.java @@ -1,8 +1,8 @@ -package cc.winboll.studio.app; +package cc.winboll.studio.contacts.activities; /** * @Author ZhanGSKen@AliYun.Com - * @Date 2025/03/24 23:52:29 + * @Date 2025/03/31 15:15:54 * @Describe 应用介绍窗口 */ import android.app.Activity; @@ -11,7 +11,7 @@ import android.os.Bundle; import android.view.ViewGroup; import android.widget.LinearLayout; import androidx.appcompat.widget.Toolbar; -import cc.winboll.studio.app.R; +import cc.winboll.studio.contacts.R; import cc.winboll.studio.libaes.winboll.APPInfo; import cc.winboll.studio.libaes.winboll.AboutView; import cc.winboll.studio.libappbase.GlobalApplication; @@ -63,26 +63,29 @@ public class AboutActivity extends WinBollActivity implements IWinBollActivity { ViewGroup.LayoutParams.MATCH_PARENT ); layout.addView(aboutView, params); + + GlobalApplication.getWinBollActivityManager().add(this); } @Override protected void onDestroy() { super.onDestroy(); + GlobalApplication.getWinBollActivityManager().registeRemove(this); } public AboutView CreateAboutView() { - String szBranchName = "app"; + String szBranchName = "contacts"; APPInfo appInfo = new APPInfo(); - appInfo.setAppName("APP"); + appInfo.setAppName("Contacts"); appInfo.setAppIcon(cc.winboll.studio.libaes.R.drawable.ic_winboll); - appInfo.setAppDescription("WinBoll APP"); + appInfo.setAppDescription("通讯录与拨号"); appInfo.setAppGitName("APP"); appInfo.setAppGitOwner("Studio"); appInfo.setAppGitAPPBranch(szBranchName); appInfo.setAppGitAPPSubProjectFolder(szBranchName); - appInfo.setAppHomePage("https://www.winboll.cc/studio/details.php?app=APP"); - appInfo.setAppAPKName("APP"); - appInfo.setAppAPKFolderName("APP"); + appInfo.setAppHomePage("https://www.winboll.cc/studio/details.php?app=Contacts"); + appInfo.setAppAPKName("Contacts"); + appInfo.setAppAPKFolderName("Contacts"); return new AboutView(mContext, appInfo); } } diff --git a/contacts/src/main/java/cc/winboll/studio/contacts/activities/CallActivity.java b/contacts/src/main/java/cc/winboll/studio/contacts/activities/CallActivity.java index e8f8e3e..ebff426 100644 --- a/contacts/src/main/java/cc/winboll/studio/contacts/activities/CallActivity.java +++ b/contacts/src/main/java/cc/winboll/studio/contacts/activities/CallActivity.java @@ -34,6 +34,7 @@ public class CallActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); //setContentView(R.layout.activity_main); setContentView(R.layout.activity_call); diff --git a/contacts/src/main/java/cc/winboll/studio/contacts/activities/SettingsActivity.java b/contacts/src/main/java/cc/winboll/studio/contacts/activities/SettingsActivity.java index bc54d95..09bd722 100644 --- a/contacts/src/main/java/cc/winboll/studio/contacts/activities/SettingsActivity.java +++ b/contacts/src/main/java/cc/winboll/studio/contacts/activities/SettingsActivity.java @@ -4,7 +4,6 @@ package cc.winboll.studio.contacts.activities; * @Author ZhanGSKen@AliYun.Com * @Date 2025/02/21 05:37:42 */ -import android.app.NotificationManager; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; @@ -15,17 +14,33 @@ import android.os.Bundle; import android.provider.Settings; import android.view.View; import android.view.WindowManager; +import android.widget.EditText; +import android.widget.SeekBar; import android.widget.Switch; +import android.widget.TextView; import android.widget.Toast; import androidx.appcompat.app.AlertDialog; import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.widget.Toolbar; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; import cc.winboll.studio.contacts.R; +import cc.winboll.studio.contacts.adapters.PhoneConnectRuleAdapter; +import cc.winboll.studio.contacts.beans.MainServiceBean; +import cc.winboll.studio.contacts.beans.PhoneConnectRuleModel; import cc.winboll.studio.contacts.beans.RingTongBean; -import cc.winboll.studio.libappbase.IWinBollActivity; -import cc.winboll.studio.libappbase.bean.APPInfo; +import cc.winboll.studio.contacts.beans.SettingsModel; +import cc.winboll.studio.contacts.bobulltoon.TomCat; +import cc.winboll.studio.contacts.dun.Rules; +import cc.winboll.studio.contacts.services.MainService; +import cc.winboll.studio.contacts.views.DuInfoTextView; +import cc.winboll.studio.libaes.winboll.APPInfo; +import cc.winboll.studio.libappbase.LogUtils; +import cc.winboll.studio.libappbase.winboll.IWinBollActivity; import com.hjq.toast.ToastUtils; import java.lang.reflect.Field; +import java.util.List; +import cc.winboll.studio.contacts.App; public class SettingsActivity extends AppCompatActivity implements IWinBollActivity { @@ -33,11 +48,25 @@ public class SettingsActivity extends AppCompatActivity implements IWinBollActiv Toolbar mToolbar; Switch swSilent; + SeekBar msbVolume; + TextView mtvVolume; + int mnStreamMaxVolume; + int mnStreamVolume; + Switch mswMainService; + static DuInfoTextView _DuInfoTextView; - @Override - public APPInfo getAppInfo() { - return null; - } + // 云盾防御层数量 + EditText etDunTotalCount; + // 防御层恢复时间间隔(秒钟) + EditText etDunResumeSecondCount; + // 每次恢复防御层数 + EditText etDunResumeCount; + // 是否启用云盾 + Switch swIsEnableDun; + + private RecyclerView recyclerView; + private PhoneConnectRuleAdapter adapter; + private List ruleList; @Override public AppCompatActivity getActivity() { @@ -49,21 +78,6 @@ public class SettingsActivity extends AppCompatActivity implements IWinBollActiv return TAG; } - @Override - public Toolbar initToolBar() { - return findViewById(R.id.activitymainToolbar1); - } - - @Override - public boolean isAddWinBollToolBar() { - return true; - } - - @Override - public boolean isEnableDisplayHomeAsUp() { - return false; - } - @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -72,11 +86,136 @@ public class SettingsActivity extends AppCompatActivity implements IWinBollActiv // 初始化工具栏 mToolbar = findViewById(R.id.activitymainToolbar1); setSupportActionBar(mToolbar); - if (isEnableDisplayHomeAsUp()) { - // 显示后退按钮 - getSupportActionBar().setDisplayHomeAsUpEnabled(true); - } + // 显示后退按钮 + getSupportActionBar().setDisplayHomeAsUpEnabled(true); getSupportActionBar().setSubtitle(getTag()); + + mswMainService = findViewById(R.id.sw_mainservice); + MainServiceBean mMainServiceBean = MainServiceBean.loadBean(this, MainServiceBean.class); + mswMainService.setChecked(mMainServiceBean.isEnable()); + mswMainService.setOnClickListener(new View.OnClickListener(){ + @Override + public void onClick(View arg0) { + LogUtils.d(TAG, "mswMainService onClick"); + // TODO: Implement this method + if (mswMainService.isChecked()) { + //ToastUtils.show("Is Checked"); + MainService.startMainServiceAndSaveStatus(SettingsActivity.this); + } else { + //ToastUtils.show("Not Checked"); + MainService.stopMainServiceAndSaveStatus(SettingsActivity.this); + } + } + }); + + msbVolume = findViewById(R.id.bellvolume); + mtvVolume = findViewById(R.id.tv_volume); + final AudioManager audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE); + + // 设置SeekBar的最大值为系统铃声音量的最大刻度 + mnStreamMaxVolume = audioManager.getStreamMaxVolume(AudioManager.STREAM_RING); + msbVolume.setMax(mnStreamMaxVolume); + // 获取当前铃声音量并设置为SeekBar的初始进度 + mnStreamVolume = audioManager.getStreamVolume(AudioManager.STREAM_RING); + msbVolume.setProgress(mnStreamVolume); + + updateStreamVolumeTextView(); + + msbVolume.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { + @Override + public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { + if (fromUser) { + // 设置铃声音量 + audioManager.setStreamVolume(AudioManager.STREAM_RING, progress, 0); + RingTongBean bean = RingTongBean.loadBean(SettingsActivity.this, RingTongBean.class); + if (bean == null) { + bean = new RingTongBean(); + } + bean.setStreamVolume(progress); + RingTongBean.saveBean(SettingsActivity.this, bean); + mnStreamVolume = progress; + updateStreamVolumeTextView(); + //Toast.makeText(SettingsActivity.this, "音量设置为: " + progress, Toast.LENGTH_SHORT).show(); + } + } + + @Override + public void onStartTrackingTouch(SeekBar seekBar) { + // 当开始拖动SeekBar时的操作 + } + + @Override + public void onStopTrackingTouch(SeekBar seekBar) { + // 当停止拖动SeekBar时的操作 + } + }); + + + recyclerView = findViewById(R.id.recycler_view); + recyclerView.setLayoutManager(new LinearLayoutManager(this)); + + ruleList = Rules.getInstance(this).getPhoneBlacRuleBeanList(); + + adapter = new PhoneConnectRuleAdapter(this, ruleList); + recyclerView.setAdapter(adapter); + + // 设置参数云盾 + _DuInfoTextView = findViewById(R.id.tv_DunInfo); + etDunTotalCount = findViewById(R.id.et_DunTotalCount); + etDunResumeSecondCount = findViewById(R.id.et_DunResumeSecondCount); + etDunResumeCount = findViewById(R.id.et_DunResumeCount); + swIsEnableDun = findViewById(R.id.sw_IsEnableDun); + SettingsModel settingsModel = Rules.getInstance(this).getSettingsModel(); + + etDunTotalCount.setText(Integer.toString(settingsModel.getDunTotalCount())); + etDunResumeSecondCount.setText(Integer.toString(settingsModel.getDunResumeSecondCount())); + etDunResumeCount.setText(Integer.toString(settingsModel.getDunResumeCount())); + swIsEnableDun.setChecked(settingsModel.isEnableDun()); + + boolean isEnableDun = settingsModel.isEnableDun(); + etDunTotalCount.setEnabled(!isEnableDun); + etDunResumeSecondCount.setEnabled(!isEnableDun); + etDunResumeCount.setEnabled(!isEnableDun); + + } + + public static void notifyDunInfoUpdate() { + if (_DuInfoTextView != null) { + _DuInfoTextView.notifyInfoUpdate(); + } + } + + public void onSW_IsEnableDun(View view) { + LogUtils.d(TAG, "onSW_IsEnableDun"); + boolean isEnableDun = swIsEnableDun.isChecked(); + etDunTotalCount.setEnabled(!isEnableDun); + etDunResumeSecondCount.setEnabled(!isEnableDun); + etDunResumeCount.setEnabled(!isEnableDun); + + SettingsModel settingsModel = Rules.getInstance(this).getSettingsModel(); + if (isEnableDun) { + settingsModel.setDunTotalCount(Integer.parseInt(etDunTotalCount.getText().toString())); + settingsModel.setDunResumeSecondCount(Integer.parseInt(etDunResumeSecondCount.getText().toString())); + settingsModel.setDunResumeCount(Integer.parseInt(etDunResumeCount.getText().toString())); + } + settingsModel.setIsEnableDun(isEnableDun); + Rules.getInstance(this).saveDun(); + Rules.getInstance(this).reload(); + } + + void updateStreamVolumeTextView() { + mtvVolume.setText(String.format("%d/%d", mnStreamVolume, mnStreamMaxVolume)); + } + + public void onUnitTest(View view) { + Intent intent = new Intent(this, UnitTestActivity.class); + startActivity(intent); + } + + public void onAddNewConnectionRule(View view) { + Rules.getInstance(this).getPhoneBlacRuleBeanList().add(new PhoneConnectRuleModel()); + Rules.getInstance(this).saveRules(); + adapter.notifyDataSetChanged(); } public void onDefaultPhone(View view) { @@ -94,6 +233,38 @@ public class SettingsActivity extends AppCompatActivity implements IWinBollActiv } } + public void onDownloadBoBullToon(View view) { + final TomCat tomCat = TomCat.getInstance(this); + new Thread(new Runnable() { + @Override + public void run() { + if (tomCat.downloadBoBullToon()) { + ToastUtils.show("BoBullToon downlaod OK!"); + MainService.restartMainService(SettingsActivity.this); + Rules.getInstance(SettingsActivity.this).reload(); + } + } + }).start(); + } + + + + public void onSearchBoBullToonPhone(View view) { + TomCat tomCat = TomCat.getInstance(this); + EditText etPhone = findViewById(R.id.activitysettingsEditText1); + String phone = etPhone.getText().toString().trim(); + if (tomCat.loadPhoneBoBullToon()) { + if (tomCat.isPhoneBoBullToon(phone)) { + ToastUtils.show("It is a BoBullToon Phone!"); + } else { + ToastUtils.show("Not in BoBullToon."); + } + } else { + ToastUtils.show("没有下载 BoBullToon。"); + } + + } + private void askForDrawOverlay() { AlertDialog alertDialog = new AlertDialog.Builder(this) .setTitle("允许显示悬浮框") @@ -140,4 +311,8 @@ public class SettingsActivity extends AppCompatActivity implements IWinBollActiv } } } + + public void onAbout(View view) { + App.getWinBollActivityManager().startWinBollActivity(this, AboutActivity.class); + } } diff --git a/contacts/src/main/java/cc/winboll/studio/contacts/activities/UnitTestActivity.java b/contacts/src/main/java/cc/winboll/studio/contacts/activities/UnitTestActivity.java new file mode 100644 index 0000000..6c42de2 --- /dev/null +++ b/contacts/src/main/java/cc/winboll/studio/contacts/activities/UnitTestActivity.java @@ -0,0 +1,94 @@ +package cc.winboll.studio.contacts.activities; + +import android.app.Activity; +import android.os.Bundle; +import android.view.View; +import android.widget.EditText; +import cc.winboll.studio.contacts.R; +import cc.winboll.studio.contacts.dun.Rules; +import cc.winboll.studio.contacts.utils.IntUtils; +import cc.winboll.studio.libappbase.LogUtils; +import cc.winboll.studio.libappbase.LogView; + +/** + * @Author ZhanGSKen@AliYun.Com + * @Date 2025/03/02 16:07:04 + */ +public class UnitTestActivity extends Activity { + + public static final String TAG = "UnitTestActivity"; + + LogView logView; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_unittest); + logView = findViewById(R.id.logview); + logView.start(); + } + + public void onTestPhone(View view) { + // 开始测试数据 + EditText etPhone = findViewById(R.id.phone_et); + Rules rules = Rules.getInstance(this); + String phone = etPhone.getText().toString().trim(); + LogUtils.d(TAG, String.format("Test phone : %s\n%s", phone, rules.isAllowed(phone))); + + } + + public void onTestMain(View view) { + LogUtils.d(TAG, "IntUtils.unittest_getIntInRange();"); + IntUtils.unittest_getIntInRange(); + + Rules rules = Rules.getInstance(this); + + // 如果没有规则就添加测试规则 + if (rules.getPhoneBlacRuleBeanList().size() == 0) { + // 手机号码允许 + // 中国手机号码正则表达式,以1开头,第二位可以是3、4、5、6、7、8、9,后面跟9位数字 + String regex = "^1[3-9]\\d{9}$"; + rules.add(regex, true, true); + + // 指定区号号码允许 + regex = "^0660\\d+$"; + rules.add(regex, true, true); + + // 指定区号号码允许 + regex = "^020\\d+$"; + rules.add(regex, true, true); + + // 添加默认拒接规则 + regex = ".*"; + rules.add(regex, false, true); + + // 保存规则到文件 + rules.saveRules(); + } + + // 开始测试数据 + String phone = "16769764848"; + LogUtils.d(TAG, String.format("Test phone : %s\n%s", phone, rules.isAllowed(phone))); + + phone = "16856582777"; + LogUtils.d(TAG, String.format("Test phone : %s\n%s", phone, rules.isAllowed(phone))); + + phone = "17519703124"; + LogUtils.d(TAG, String.format("Test phone : %s\n%s", phone, rules.isAllowed(phone))); + + phone = "0205658955"; + LogUtils.d(TAG, String.format("Test phone : %s\n%s", phone, rules.isAllowed(phone))); + + phone = "0108965253"; + LogUtils.d(TAG, String.format("Test phone : %s\n%s", phone, rules.isAllowed(phone))); + + phone = "+8616769764848"; + LogUtils.d(TAG, String.format("Test phone : %s\n%s", phone, rules.isAllowed(phone))); + + phone = "4005816769764848"; + LogUtils.d(TAG, String.format("Test phone : %s\n%s", phone, rules.isAllowed(phone))); + + phone = "95566"; + LogUtils.d(TAG, String.format("Test phone : %s\n%s", phone, rules.isAllowed(phone))); + } +} diff --git a/aes/src/main/java/cc/winboll/studio/aes/WinBollActivity.java b/contacts/src/main/java/cc/winboll/studio/contacts/activities/WinBollActivity.java similarity index 94% rename from aes/src/main/java/cc/winboll/studio/aes/WinBollActivity.java rename to contacts/src/main/java/cc/winboll/studio/contacts/activities/WinBollActivity.java index a15318d..c54650e 100644 --- a/aes/src/main/java/cc/winboll/studio/aes/WinBollActivity.java +++ b/contacts/src/main/java/cc/winboll/studio/contacts/activities/WinBollActivity.java @@ -1,18 +1,18 @@ -package cc.winboll.studio.aes; +package cc.winboll.studio.contacts.activities; +/** + * @Author ZhanGSKen@AliYun.Com + * @Date 2025/03/31 15:16:45 + * @Describe 应用窗口基类 + */ import android.app.Activity; import android.os.Bundle; +import android.view.MenuItem; import androidx.appcompat.app.AppCompatActivity; import cc.winboll.studio.libaes.beans.AESThemeBean; import cc.winboll.studio.libaes.utils.AESThemeUtil; import cc.winboll.studio.libappbase.winboll.IWinBollActivity; -import android.view.MenuItem; -/** - * @Author ZhanGSKen@AliYun.Com - * @Date 2025/03/30 00:34:02 - * @Describe WinBoll 活动窗口通用基类 - */ public class WinBollActivity extends AppCompatActivity implements IWinBollActivity { public static final String TAG = "WinBollActivity"; diff --git a/contacts/src/main/java/cc/winboll/studio/contacts/adapters/CallLogAdapter.java b/contacts/src/main/java/cc/winboll/studio/contacts/adapters/CallLogAdapter.java new file mode 100644 index 0000000..a986ad8 --- /dev/null +++ b/contacts/src/main/java/cc/winboll/studio/contacts/adapters/CallLogAdapter.java @@ -0,0 +1,93 @@ +package cc.winboll.studio.contacts.adapters; + +/** + * @Author ZhanGSKen@AliYun.Com + * @Date 2025/02/26 13:09:32 + * @Describe CallLogAdapter + */ +import android.content.Context; +import android.content.Intent; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Button; +import android.widget.TextView; +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.RecyclerView; +import cc.winboll.studio.contacts.R; +import cc.winboll.studio.contacts.beans.CallLogModel; +import cc.winboll.studio.contacts.utils.ContactUtils; +import cc.winboll.studio.libaes.views.AOHPCTCSeekBar; +import com.hjq.toast.ToastUtils; +import java.text.SimpleDateFormat; +import java.util.List; +import java.util.Locale; + +public class CallLogAdapter extends RecyclerView.Adapter { + public static final String TAG = "CallLogAdapter"; + + private List callLogList; + ContactUtils mContactUtils; + Context mContext; + + public CallLogAdapter(Context context, List callLogList) { + mContext = context; + this.mContactUtils = ContactUtils.getInstance(mContext); + this.callLogList = callLogList; + } + + @NonNull + @Override + public CallLogViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_call_log, parent, false); + return new CallLogViewHolder(view); + } + + @Override + public void onBindViewHolder(@NonNull CallLogViewHolder holder, int position) { + final CallLogModel callLog = callLogList.get(position); + holder.phoneNumber.setText(callLog.getPhoneNumber() + "☎" + mContactUtils.getContactsName(callLog.getPhoneNumber())); + holder.callStatus.setText(callLog.getCallStatus()); + SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault()); + holder.callDate.setText(dateFormat.format(callLog.getCallDate())); + + // 初始化拉动后拨号控件 + holder.dialAOHPCTCSeekBar.setThumb(holder.itemView.getContext().getDrawable(R.drawable.ic_call)); + holder.dialAOHPCTCSeekBar.setBlurRightDP(80); + holder.dialAOHPCTCSeekBar.setThumbOffset(0); + holder.dialAOHPCTCSeekBar.setOnOHPCListener( + new AOHPCTCSeekBar.OnOHPCListener(){ + @Override + public void onOHPCommit() { + String phoneNumber = callLog.getPhoneNumber().replaceAll("\\s", ""); + ToastUtils.show(phoneNumber); + Intent intent = new Intent(Intent.ACTION_CALL); + intent.setData(android.net.Uri.parse("tel:" + phoneNumber)); + // 添加 FLAG_ACTIVITY_NEW_TASK 标志 + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + holder.itemView.getContext().startActivity(intent); + } + }); + } + + @Override + public int getItemCount() { + return callLogList.size(); + } + + public class CallLogViewHolder extends RecyclerView.ViewHolder { + TextView phoneNumber, callStatus, callDate; + Button dialButton; + AOHPCTCSeekBar dialAOHPCTCSeekBar; + + public CallLogViewHolder(@NonNull View itemView) { + super(itemView); + phoneNumber = itemView.findViewById(R.id.phone_number); + callStatus = itemView.findViewById(R.id.call_status); + callDate = itemView.findViewById(R.id.call_date); + dialAOHPCTCSeekBar = itemView.findViewById(R.id.aohpctcseekbar_dial); + } + } + +} + diff --git a/contacts/src/main/java/cc/winboll/studio/contacts/adapters/ContactAdapter.java b/contacts/src/main/java/cc/winboll/studio/contacts/adapters/ContactAdapter.java new file mode 100644 index 0000000..ea5cc77 --- /dev/null +++ b/contacts/src/main/java/cc/winboll/studio/contacts/adapters/ContactAdapter.java @@ -0,0 +1,84 @@ +package cc.winboll.studio.contacts.adapters; + +/** + * @Author ZhanGSKen@AliYun.Com + * @Date 2025/02/26 13:35:44 + * @Describe ContactAdapter + */ +import android.content.Intent; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Button; +import android.widget.TextView; +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.RecyclerView; +import cc.winboll.studio.contacts.R; +import cc.winboll.studio.contacts.beans.ContactModel; +import com.hjq.toast.ToastUtils; +import java.util.List; +import cc.winboll.studio.libaes.views.AOHPCTCSeekBar; + +public class ContactAdapter extends RecyclerView.Adapter { + + public static final String TAG = "ContactAdapter"; + + private static final int REQUEST_CALL_PHONE = 1; + + private List contactList; + + public ContactAdapter(List contactList) { + this.contactList = contactList; + } + + @NonNull + @Override + public ContactViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_contact, parent, false); + return new ContactViewHolder(view); + } + + @Override + public void onBindViewHolder(@NonNull ContactViewHolder holder, int position) { + final ContactModel contact = contactList.get(position); + holder.contactName.setText(contact.getName()); + holder.contactNumber.setText(contact.getNumber()); + + // 初始化拉动后拨号控件 + holder.dialAOHPCTCSeekBar.setThumb(holder.itemView.getContext().getDrawable(R.drawable.ic_call)); + holder.dialAOHPCTCSeekBar.setBlurRightDP(80); + holder.dialAOHPCTCSeekBar.setThumbOffset(0); + holder.dialAOHPCTCSeekBar.setOnOHPCListener( + new AOHPCTCSeekBar.OnOHPCListener(){ + @Override + public void onOHPCommit() { + String phoneNumber = contact.getNumber().replaceAll("\\s", ""); + ToastUtils.show(phoneNumber); + Intent intent = new Intent(Intent.ACTION_CALL); + intent.setData(android.net.Uri.parse("tel:" + phoneNumber)); + // 添加 FLAG_ACTIVITY_NEW_TASK 标志 + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + holder.itemView.getContext().startActivity(intent); + } + }); + } + + @Override + public int getItemCount() { + return contactList.size(); + } + + public class ContactViewHolder extends RecyclerView.ViewHolder { + TextView contactName; + TextView contactNumber; + AOHPCTCSeekBar dialAOHPCTCSeekBar; + + public ContactViewHolder(@NonNull View itemView) { + super(itemView); + contactName = itemView.findViewById(R.id.contact_name); + contactNumber = itemView.findViewById(R.id.contact_number); + dialAOHPCTCSeekBar = itemView.findViewById(R.id.aohpctcseekbar_dial); + } + } +} + diff --git a/contacts/src/main/java/cc/winboll/studio/contacts/adapters/MyPagerAdapter.java b/contacts/src/main/java/cc/winboll/studio/contacts/adapters/MyPagerAdapter.java deleted file mode 100644 index 3212231..0000000 --- a/contacts/src/main/java/cc/winboll/studio/contacts/adapters/MyPagerAdapter.java +++ /dev/null @@ -1,42 +0,0 @@ -package cc.winboll.studio.contacts.adapters; - -/** - * @Author ZhanGSKen@AliYun.Com - * @Date 2025/02/20 13:33:04 - * @Describe MyPagerAdapter - */ -import androidx.annotation.NonNull; -import androidx.fragment.app.Fragment; -import androidx.fragment.app.FragmentManager; -import androidx.fragment.app.FragmentPagerAdapter; -import cc.winboll.studio.contacts.fragments.CallFragment; -import cc.winboll.studio.contacts.fragments.ContactsFragment; -import cc.winboll.studio.contacts.fragments.LogFragment; - -public class MyPagerAdapter extends FragmentPagerAdapter { - public static final String TAG = "MyPagerAdapter"; - - private static final int PAGE_COUNT = 3; - - public MyPagerAdapter(@NonNull FragmentManager fm) { - super(fm, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT); - } - - @NonNull - @Override - public Fragment getItem(int position) { - if(position == 1) { - return ContactsFragment.newInstance(position); - } else if(position == 2) { - return LogFragment.newInstance(position); - } else { - return CallFragment.newInstance(position); - } - } - - @Override - public int getCount() { - return PAGE_COUNT; - } -} - diff --git a/contacts/src/main/java/cc/winboll/studio/contacts/adapters/PhoneConnectRuleAdapter.java b/contacts/src/main/java/cc/winboll/studio/contacts/adapters/PhoneConnectRuleAdapter.java new file mode 100644 index 0000000..8483c55 --- /dev/null +++ b/contacts/src/main/java/cc/winboll/studio/contacts/adapters/PhoneConnectRuleAdapter.java @@ -0,0 +1,247 @@ +package cc.winboll.studio.contacts.adapters; + +/** + * @Author ZhanGSKen@AliYun.Com + * @Date 2025/03/02 17:27:41 + * @Describe PhoneConnectRuleAdapter + */ +import android.content.Context; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Button; +import android.widget.CheckBox; +import android.widget.EditText; +import android.widget.TextView; +import android.widget.Toast; +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.RecyclerView; +import cc.winboll.studio.contacts.R; +import cc.winboll.studio.contacts.beans.PhoneConnectRuleModel; +import cc.winboll.studio.contacts.dun.Rules; +import cc.winboll.studio.contacts.views.LeftScrollView; +import cc.winboll.studio.libappbase.dialogs.YesNoAlertDialog; +import com.hjq.toast.ToastUtils; +import java.util.ArrayList; +import java.util.List; + +public class PhoneConnectRuleAdapter extends RecyclerView.Adapter { + + public static final String TAG = "PhoneConnectRuleAdapter"; + + private static final int VIEW_TYPE_SIMPLE = 0; + private static final int VIEW_TYPE_EDIT = 1; + + private Context context; + private List ruleList; + + public PhoneConnectRuleAdapter(Context context, List ruleList) { + this.context = context; + this.ruleList = ruleList; + } + + @NonNull + @Override + public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + LayoutInflater inflater = LayoutInflater.from(context); + if (viewType == VIEW_TYPE_SIMPLE) { + View view = inflater.inflate(R.layout.view_phone_connect_rule_simple, parent, false); + return new SimpleViewHolder(parent, view); + } else { + View view = inflater.inflate(R.layout.view_phone_connect_rule, parent, false); + return new EditViewHolder(parent, view); + } + } + + @Override + public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, final int position) { + final PhoneConnectRuleModel model = ruleList.get(position); + if (holder instanceof SimpleViewHolder) { + final SimpleViewHolder simpleViewHolder = (SimpleViewHolder) holder; + String szView = model.getRuleText().trim().equals("") ?"[NULL]": model.getRuleText(); + simpleViewHolder.tvRuleText.setText(szView); + simpleViewHolder.scrollView.setOnActionListener(new LeftScrollView.OnActionListener(){ + + @Override + public void onUp() { + ArrayList list = Rules.getInstance(context).getPhoneBlacRuleBeanList(); + if (position > 0) { + ToastUtils.show("onUp"); + simpleViewHolder.scrollView.smoothScrollTo(0, 0); +// PhoneConnectRuleModel newBean = new PhoneConnectRuleModel(); +// newBean.setRuleText(list.get(position).getRuleText()); +// newBean.setIsAllowConnection(list.get(position).isAllowConnection()); +// newBean.setIsEnable(list.get(position).isEnable()); +// newBean.setIsSimpleView(list.get(position).isSimpleView()); + list.add(position - 1, list.get(position)); + list.remove(position + 1); + Rules.getInstance(context).saveRules(); + notifyDataSetChanged(); + } + } + + @Override + public void onDown() { + ArrayList list = Rules.getInstance(context).getPhoneBlacRuleBeanList(); + if (position < list.size() - 1) { + ToastUtils.show("onDown"); + simpleViewHolder.scrollView.smoothScrollTo(0, 0); +// PhoneConnectRuleModel newBean = new PhoneConnectRuleModel(); +// newBean.setRuleText(list.get(position).getRuleText()); +// newBean.setIsAllowConnection(list.get(position).isAllowConnection()); +// newBean.setIsEnable(list.get(position).isEnable()); +// newBean.setIsSimpleView(list.get(position).isSimpleView()); + list.add(position + 2, list.get(position)); + list.remove(position); + Rules.getInstance(context).saveRules(); + notifyDataSetChanged(); + } + } + + @Override + public void onEdit() { + simpleViewHolder.scrollView.smoothScrollTo(0, 0); + model.setIsSimpleView(false); + notifyDataSetChanged(); + //notifyItemChanged(position); + } + + @Override + public void onDelete() { + YesNoAlertDialog.show(simpleViewHolder.scrollView.getContext(), "删除确认", "是否删除该通话规则?", new YesNoAlertDialog.OnDialogResultListener(){ + + @Override + public void onYes() { + simpleViewHolder.scrollView.smoothScrollTo(0, 0); + model.setIsSimpleView(true); + ArrayList list = Rules.getInstance(context).getPhoneBlacRuleBeanList(); + list.remove(position); + Rules.getInstance(context).saveRules(); + notifyDataSetChanged(); + //notifyItemChanged(position); + } + + @Override + public void onNo() { + } + }); + + } + }); +// simpleViewHolder.editButton.setOnClickListener(new View.OnClickListener() { +// @Override +// public void onClick(View v) { +// model.setIsSimpleView(false); +// notifyItemChanged(position); +// } +// }); +// simpleViewHolder.deleteButton.setOnClickListener(new View.OnClickListener() { +// @Override +// public void onClick(View v) { +// model.setIsSimpleView(false); +// ArrayList list = Rules.getInstance(context).getPhoneBlacRuleBeanList(); +// list.remove(position); +// Rules.getInstance(context).saveRules(); +// notifyItemChanged(position); +// } +// }); +// // 触摸事件处理 +// simpleViewHolder.contentLayout.setOnTouchListener(new View.OnTouchListener() { +// @Override +// public boolean onTouch(View v, MotionEvent event) { +// switch (event.getAction()) { +// case MotionEvent.ACTION_DOWN: +// simpleViewHolder.startX = event.getX(); +// simpleViewHolder.isSwiping = true; +// break; +// case MotionEvent.ACTION_MOVE: +// if (simpleViewHolder.isSwiping) { +// float deltaX = simpleViewHolder.startX - event.getX(); +// if (deltaX > 0) { // 左滑 +// float translationX = Math.max(-simpleViewHolder.actionLayout.getWidth(), -deltaX); +// simpleViewHolder.contentLayout.setTranslationX(translationX); +// simpleViewHolder.actionLayout.setVisibility(View.VISIBLE); +// } +// } +// break; +// case MotionEvent.ACTION_UP: +// simpleViewHolder.isSwiping = false; +// if (simpleViewHolder.contentLayout.getTranslationX() < -simpleViewHolder.actionLayout.getWidth() / 2) { +// // 保持按钮显示 +// simpleViewHolder.contentLayout.setTranslationX(-actionLayout.getWidth()); +// } else { +// // 恢复原状 +// simpleViewHolder.contentLayout.animate().translationX(0).setDuration(200).start(); +// simpleViewHolder.actionLayout.setVisibility(View.INVISIBLE); +// } +// break; +// } +// return true; +// } +// }); + } else if (holder instanceof EditViewHolder) { + final EditViewHolder editViewHolder = (EditViewHolder) holder; + editViewHolder.editText.setText(model.getRuleText()); + editViewHolder.checkBoxAllow.setChecked(model.isAllowConnection()); + editViewHolder.checkBoxEnable.setChecked(model.isEnable()); + editViewHolder.buttonConfirm.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + model.setRuleText(editViewHolder.editText.getText().toString()); + model.setIsAllowConnection(editViewHolder.checkBoxAllow.isChecked()); + model.setIsEnable(editViewHolder.checkBoxEnable.isChecked()); + model.setIsSimpleView(true); + Rules.getInstance(context).saveRules(); + notifyItemChanged(position); + Toast.makeText(context, "保存成功", Toast.LENGTH_SHORT).show(); + } + }); + } + } + + @Override + public int getItemCount() { + return ruleList.size(); + } + + @Override + public int getItemViewType(int position) { + PhoneConnectRuleModel model = ruleList.get(position); + // 这里可以根据模型的状态来决定视图类型,简单起见,假设点击按钮后进入编辑视图 + return model.isSimpleView() ? VIEW_TYPE_SIMPLE : VIEW_TYPE_EDIT; + } + + static class SimpleViewHolder extends RecyclerView.ViewHolder { + + private final LeftScrollView scrollView; + private final TextView tvRuleText; + + + public SimpleViewHolder(@NonNull ViewGroup parent, @NonNull View itemView) { + super(itemView); + scrollView = itemView.findViewById(R.id.scrollView); + //tvRuleText = itemView.findViewById(R.id.ruletext_tv); + tvRuleText = new TextView(itemView.getContext()); + scrollView.setContentWidth(parent.getWidth()); + //scrollView.setContentWidth(600); + scrollView.addContentLayout(tvRuleText); + } + + } + + static class EditViewHolder extends RecyclerView.ViewHolder { + EditText editText; + CheckBox checkBoxAllow; + CheckBox checkBoxEnable; + Button buttonConfirm; + + public EditViewHolder(@NonNull ViewGroup parent, @NonNull View itemView) { + super(itemView); + editText = itemView.findViewById(R.id.edit_text); + checkBoxAllow = itemView.findViewById(R.id.checkbox_allow); + checkBoxEnable = itemView.findViewById(R.id.checkbox_enable); + buttonConfirm = itemView.findViewById(R.id.button_confirm); + } + } +} + diff --git a/contacts/src/main/java/cc/winboll/studio/contacts/beans/CallLogModel.java b/contacts/src/main/java/cc/winboll/studio/contacts/beans/CallLogModel.java new file mode 100644 index 0000000..32f7d58 --- /dev/null +++ b/contacts/src/main/java/cc/winboll/studio/contacts/beans/CallLogModel.java @@ -0,0 +1,36 @@ +package cc.winboll.studio.contacts.beans; + +/** + * @Author ZhanGSKen@AliYun.Com + * @Date 2025/02/26 13:10:57 + * @Describe CallLogModel + */ + +import java.util.Date; + +public class CallLogModel { + public static final String TAG = "CallLogModel"; + + private String phoneNumber; + private String callStatus; + private Date callDate; + + public CallLogModel(String phoneNumber, String callStatus, Date callDate) { + this.phoneNumber = phoneNumber.replaceAll("\\s", ""); + this.callStatus = callStatus; + this.callDate = callDate; + } + + public String getPhoneNumber() { + return phoneNumber; + } + + public String getCallStatus() { + return callStatus; + } + + public Date getCallDate() { + return callDate; + } +} + diff --git a/contacts/src/main/java/cc/winboll/studio/contacts/beans/ContactModel.java b/contacts/src/main/java/cc/winboll/studio/contacts/beans/ContactModel.java new file mode 100644 index 0000000..cf3c559 --- /dev/null +++ b/contacts/src/main/java/cc/winboll/studio/contacts/beans/ContactModel.java @@ -0,0 +1,64 @@ +package cc.winboll.studio.contacts.beans; + +/** + * @Author ZhanGSKen@AliYun.Com + * @Date 2025/02/26 13:37:00 + * @Describe ContactModel + */ +import net.sourceforge.pinyin4j.PinyinHelper; +import net.sourceforge.pinyin4j.format.HanyuPinyinCaseType; +import net.sourceforge.pinyin4j.format.HanyuPinyinOutputFormat; +import net.sourceforge.pinyin4j.format.HanyuPinyinToneType; +import net.sourceforge.pinyin4j.format.exception.BadHanyuPinyinOutputFormatCombination; + +public class ContactModel { + + public static final String TAG = "ContactModel"; + + private String name; + private String number; + private String pinyin; + + public ContactModel(String name, String number) { + this.name = name; + this.number = number.replaceAll("\\s", ""); + this.pinyin = convertToPinyin(name); + } + + private String convertToPinyin(String chinese) { + HanyuPinyinOutputFormat format = new HanyuPinyinOutputFormat(); + format.setCaseType(HanyuPinyinCaseType.LOWERCASE); + format.setToneType(HanyuPinyinToneType.WITHOUT_TONE); + + StringBuilder pinyin = new StringBuilder(); + for (int i = 0; i < chinese.length(); i++) { + char ch = chinese.charAt(i); + if (Character.toString(ch).matches("[\\u4e00-\\u9fa5]")) { + try { + String[] pinyinArray = PinyinHelper.toHanyuPinyinStringArray(ch, format); + if (pinyinArray != null) { + pinyin.append(pinyinArray[0]); + } + } catch (BadHanyuPinyinOutputFormatCombination e) { + e.printStackTrace(); + } + } else { + pinyin.append(ch); + } + } + return pinyin.toString(); + } + + public String getName() { + return name; + } + + public String getNumber() { + return number; + } + + public String getPinyin() { + return pinyin; + } +} + diff --git a/contacts/src/main/java/cc/winboll/studio/contacts/beans/PhoneBlackRuleBean.java b/contacts/src/main/java/cc/winboll/studio/contacts/beans/PhoneConnectRuleModel.java similarity index 64% rename from contacts/src/main/java/cc/winboll/studio/contacts/beans/PhoneBlackRuleBean.java rename to contacts/src/main/java/cc/winboll/studio/contacts/beans/PhoneConnectRuleModel.java index 69550cc..3975902 100644 --- a/contacts/src/main/java/cc/winboll/studio/contacts/beans/PhoneBlackRuleBean.java +++ b/contacts/src/main/java/cc/winboll/studio/contacts/beans/PhoneConnectRuleModel.java @@ -10,21 +10,35 @@ import android.util.JsonWriter; import cc.winboll.studio.libappbase.BaseBean; import java.io.IOException; -public class PhoneBlackRuleBean extends BaseBean { - - public static final String TAG = "PhoneBlackRuleBean"; - +public class PhoneConnectRuleModel extends BaseBean { + + public static final String TAG = "PhoneConnectRuleModel"; + String ruleText; + boolean isAllowConnection; boolean isEnable; - - public PhoneBlackRuleBean() { + boolean isSimpleView; + + public PhoneConnectRuleModel() { this.ruleText = ""; + this.isAllowConnection = false; this.isEnable = false; + this.isSimpleView = true; } - public PhoneBlackRuleBean(String ruleText, boolean isEnable) { + public PhoneConnectRuleModel(String ruleText, boolean isAllowConnection, boolean isEnable) { this.ruleText = ruleText; + this.isAllowConnection = isAllowConnection; this.isEnable = isEnable; + this.isSimpleView = true; + } + + public void setIsSimpleView(boolean isSimpleView) { + this.isSimpleView = isSimpleView; + } + + public boolean isSimpleView() { + return isSimpleView; } public void setRuleText(String ruleText) { @@ -35,6 +49,14 @@ public class PhoneBlackRuleBean extends BaseBean { return ruleText; } + public void setIsAllowConnection(boolean isAllowConnection) { + this.isAllowConnection = isAllowConnection; + } + + public boolean isAllowConnection() { + return isAllowConnection; + } + public void setIsEnable(boolean isEnable) { this.isEnable = isEnable; } @@ -43,17 +65,19 @@ public class PhoneBlackRuleBean extends BaseBean { return isEnable; } + + @Override public String getName() { - return PhoneBlackRuleBean.class.getName(); + return PhoneConnectRuleModel.class.getName(); } @Override public void writeThisToJsonWriter(JsonWriter jsonWriter) throws IOException { super.writeThisToJsonWriter(jsonWriter); jsonWriter.name("ruleText").value(getRuleText()); + jsonWriter.name("isAllowConnection").value(isAllowConnection()); jsonWriter.name("isEnable").value(isEnable()); - } @Override @@ -61,6 +85,8 @@ public class PhoneBlackRuleBean extends BaseBean { if (super.initObjectsFromJsonReader(jsonReader, name)) { return true; } else { if (name.equals("ruleText")) { setRuleText(jsonReader.nextString()); + } else if (name.equals("isAllowConnection")) { + setIsAllowConnection(jsonReader.nextBoolean()); } else if (name.equals("isEnable")) { setIsEnable(jsonReader.nextBoolean()); } else { @@ -83,6 +109,6 @@ public class PhoneBlackRuleBean extends BaseBean { jsonReader.endObject(); return this; } - - + + } diff --git a/contacts/src/main/java/cc/winboll/studio/contacts/beans/RingTongBean.java b/contacts/src/main/java/cc/winboll/studio/contacts/beans/RingTongBean.java index 792bce4..5db7262 100644 --- a/contacts/src/main/java/cc/winboll/studio/contacts/beans/RingTongBean.java +++ b/contacts/src/main/java/cc/winboll/studio/contacts/beans/RingTongBean.java @@ -12,26 +12,26 @@ import android.media.AudioManager; import android.util.JsonReader; public class RingTongBean extends BaseBean { - + public static final String TAG = "AudioRingTongBean"; - - // 模式 - int ringerMode; + + // 铃声音量 + int streamVolume; public RingTongBean() { - this.ringerMode = AudioManager.RINGER_MODE_NORMAL; + this.streamVolume = 100; } - public RingTongBean(int ringerMode) { - this.ringerMode = ringerMode; + public RingTongBean(int streamVolume) { + this.streamVolume = streamVolume; } - public void setRingerMode(int ringerMode) { - this.ringerMode = ringerMode; + public void setStreamVolume(int streamVolume) { + this.streamVolume = streamVolume; } - public int getRingerMode() { - return ringerMode; + public int getStreamVolume() { + return streamVolume; } @Override @@ -42,15 +42,15 @@ public class RingTongBean extends BaseBean { @Override public void writeThisToJsonWriter(JsonWriter jsonWriter) throws IOException { super.writeThisToJsonWriter(jsonWriter); - jsonWriter.name("ringerMode").value(getRingerMode()); + jsonWriter.name("streamVolume").value(getStreamVolume()); } @Override public boolean initObjectsFromJsonReader(JsonReader jsonReader, String name) throws IOException { if (super.initObjectsFromJsonReader(jsonReader, name)) { return true; } else { - if (name.equals("ringerMode")) { - setRingerMode(jsonReader.nextInt()); + if (name.equals("streamVolume")) { + setStreamVolume(jsonReader.nextInt()); } else { return false; } diff --git a/contacts/src/main/java/cc/winboll/studio/contacts/beans/SettingsModel.java b/contacts/src/main/java/cc/winboll/studio/contacts/beans/SettingsModel.java new file mode 100644 index 0000000..cacfc4f --- /dev/null +++ b/contacts/src/main/java/cc/winboll/studio/contacts/beans/SettingsModel.java @@ -0,0 +1,141 @@ +package cc.winboll.studio.contacts.beans; + +/** + * @Author ZhanGSKen@AliYun.Com + * @Date 2025/03/02 19:51:40 + * @Describe SettingsModel + */ +import android.util.JsonReader; +import android.util.JsonWriter; +import cc.winboll.studio.libappbase.BaseBean; +import java.io.IOException; +import cc.winboll.studio.contacts.utils.IntUtils; + +public class SettingsModel extends BaseBean { + + public static final String TAG = "SettingsModel"; + public static final int MAX_INTRANGE = 666666; + public static final int MIN_INTRANGE = 1; + + // 云盾防御层数量 + int dunTotalCount; + // 当前云盾防御层 + int dunCurrentCount; + // 防御层恢复时间间隔(秒钟) + int dunResumeSecondCount; + // 每次恢复防御层数 + int dunResumeCount; + // 是否启用云盾 + boolean isEnableDun; + + public SettingsModel() { + this.dunTotalCount = 6; + this.dunCurrentCount = 6; + this.dunResumeSecondCount = 60; + this.dunResumeCount = 1; + this.isEnableDun = false; + } + + public SettingsModel(int dunTotalCount, int dunCurrentCount, int dunResumeSecondCount, int dunResumeCount, boolean isEnableDun) { + this.dunTotalCount = getSettingsModelRangeInt(dunTotalCount); + this.dunCurrentCount = getSettingsModelRangeInt(dunCurrentCount); + this.dunResumeSecondCount = getSettingsModelRangeInt(dunResumeSecondCount); + this.dunResumeCount = getSettingsModelRangeInt(dunResumeCount); + this.isEnableDun = isEnableDun; + } + + public void setDunTotalCount(int dunTotalCount) { + this.dunTotalCount = getSettingsModelRangeInt(dunTotalCount); + } + + public int getDunTotalCount() { + return dunTotalCount; + } + + public void setDunCurrentCount(int dunCurrentCount) { + this.dunCurrentCount = getSettingsModelRangeInt(dunCurrentCount); + } + + public int getDunCurrentCount() { + return dunCurrentCount; + } + + public void setDunResumeSecondCount(int dunResumeSecondCount) { + this.dunResumeSecondCount = getSettingsModelRangeInt(dunResumeSecondCount); + } + + public int getDunResumeSecondCount() { + return dunResumeSecondCount; + } + + public void setDunResumeCount(int dunResumeCount) { + this.dunResumeCount = getSettingsModelRangeInt(dunResumeCount); + } + + public int getDunResumeCount() { + return dunResumeCount; + } + + public void setIsEnableDun(boolean isEnableDun) { + this.isEnableDun = isEnableDun; + } + + public boolean isEnableDun() { + return isEnableDun; + } + + int getSettingsModelRangeInt(int origin) { + return IntUtils.getIntInRange(origin, MIN_INTRANGE, MAX_INTRANGE); + } + + + @Override + public String getName() { + return SettingsModel.class.getName(); + } + + @Override + public void writeThisToJsonWriter(JsonWriter jsonWriter) throws IOException { + super.writeThisToJsonWriter(jsonWriter); + jsonWriter.name("dunTotalCount").value(getDunTotalCount()); + jsonWriter.name("dunCurrentCount").value(getDunCurrentCount()); + jsonWriter.name("dunResumeSecondCount").value(getDunResumeSecondCount()); + jsonWriter.name("dunResumeCount").value(getDunResumeCount()); + jsonWriter.name("isEnableDun").value(isEnableDun()); + + } + + @Override + public boolean initObjectsFromJsonReader(JsonReader jsonReader, String name) throws IOException { + if (super.initObjectsFromJsonReader(jsonReader, name)) { return true; } else { + if (name.equals("dunTotalCount")) { + setDunTotalCount(getSettingsModelRangeInt(jsonReader.nextInt())); + } else if (name.equals("dunCurrentCount")) { + setDunCurrentCount(getSettingsModelRangeInt(jsonReader.nextInt())); + } else if (name.equals("dunResumeSecondCount")) { + setDunResumeSecondCount(getSettingsModelRangeInt(jsonReader.nextInt())); + } else if (name.equals("dunResumeCount")) { + setDunResumeCount(getSettingsModelRangeInt(jsonReader.nextInt())); + } else if (name.equals("isEnableDun")) { + setIsEnableDun(jsonReader.nextBoolean()); + } else { + return false; + } + } + return true; + } + + @Override + public BaseBean readBeanFromJsonReader(JsonReader jsonReader) throws IOException { + jsonReader.beginObject(); + while (jsonReader.hasNext()) { + String name = jsonReader.nextName(); + if (!initObjectsFromJsonReader(jsonReader, name)) { + jsonReader.skipValue(); + } + } + // 结束 JSON 对象 + jsonReader.endObject(); + return this; + } +} diff --git a/contacts/src/main/java/cc/winboll/studio/contacts/bobulltoon/TomCat.java b/contacts/src/main/java/cc/winboll/studio/contacts/bobulltoon/TomCat.java new file mode 100644 index 0000000..ffd20eb --- /dev/null +++ b/contacts/src/main/java/cc/winboll/studio/contacts/bobulltoon/TomCat.java @@ -0,0 +1,178 @@ +package cc.winboll.studio.contacts.bobulltoon; + +/** + * @Author ZhanGSKen@AliYun.Com + * @Date 2025/03/02 13:47:48 + * @Describe 汤姆猫管家 :使用 BoBullToon 项目,对通讯地址进行筛选判断的好朋友。 + */ +import android.content.Context; +import cc.winboll.studio.libappbase.LogUtils; +import com.hjq.toast.ToastUtils; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.List; +import java.util.zip.ZipEntry; +import java.util.zip.ZipInputStream; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.Response; + +public class TomCat { + + public static final String TAG = "TomCat"; + + List listPhoneBoBullToon = new ArrayList(); + + static volatile TomCat _TomCat; + Context mContext; + TomCat(Context context) { + mContext = context; + } + + public static synchronized TomCat getInstance(Context context) { + if (_TomCat == null) { + _TomCat = new TomCat(context); + } + return _TomCat; + } + + void downloadAndExtractZip(String zipUrl, String destinationFolder) throws IOException { + OkHttpClient client = new OkHttpClient(); + Request request = new Request.Builder() + .url(zipUrl) + .build(); + + try { + Response response = client.newCall(request).execute(); + if (!response.isSuccessful()) { + throw new IOException("Unexpected code " + response); + } + + // 下载 ZIP 文件到临时位置 + File tempZipFile = File.createTempFile("temp", ".zip"); + try { + InputStream inputStream = response.body().byteStream(); + FileOutputStream outputStream = new FileOutputStream(tempZipFile); + byte[] buffer = new byte[1024]; + int length; + while ((length = inputStream.read(buffer)) > 0) { + outputStream.write(buffer, 0, length); + } + } catch (Exception e) { + LogUtils.d(TAG, e, Thread.currentThread().getStackTrace()); + } + + // 解压 ZIP 文件到指定文件夹 + try { + ZipInputStream zipInputStream = new ZipInputStream(Files.newInputStream(tempZipFile.toPath())); + ZipEntry zipEntry; + while ((zipEntry = zipInputStream.getNextEntry()) != null) { + Path targetFilePath = Paths.get(destinationFolder, zipEntry.getName()); + if (zipEntry.isDirectory()) { + Files.createDirectories(targetFilePath); + } else { + Files.createDirectories(targetFilePath.getParent()); + try (FileOutputStream fos = new FileOutputStream(targetFilePath.toFile())) { + byte[] buffer = new byte[1024]; + int len; + while ((len = zipInputStream.read(buffer)) > 0) { + fos.write(buffer, 0, len); + } + } + } + zipInputStream.closeEntry(); + } + } catch (Exception e) { + LogUtils.d(TAG, e, Thread.currentThread().getStackTrace()); + } + + // 删除临时 ZIP 文件 + tempZipFile.delete(); + LogUtils.d(TAG, "已更新 BoBullToon 数据"); + } catch (Exception e) { + LogUtils.d(TAG, e, Thread.currentThread().getStackTrace()); + } + } + + public boolean downloadBoBullToon() { + String zipUrl = "http://10.8.0.12:3000/Studio/BoBullToon/archive/main.zip"; // 替换为实际的 ZIP 文件 URL + String destinationFolder = getWorkingFolder().getPath(); // 替换为实际的目标文件夹路径 + try { + // 删除旧文件 + File fOldFolder = new File(destinationFolder); + if (fOldFolder.exists()) { + deleteFolderRecursive(fOldFolder); + fOldFolder.mkdirs(); + LogUtils.d(TAG, "已清空 BoBullToon 数据"); + } + + // 更新新文件 + downloadAndExtractZip(zipUrl, destinationFolder); + LogUtils.d(TAG, "ZIP 文件下载并解压成功。"); + return true; + } catch (IOException e) { + LogUtils.d(TAG, e, Thread.currentThread().getStackTrace()); + } + return false; + } + + // 递归删除文件夹及其内容的方法 + public static void deleteFolderRecursive(File file) { + // 判断是否为文件夹 + if (file.isDirectory()) { + // 列出文件夹中的所有文件和子文件夹 + File[] files = file.listFiles(); + if (files != null) { + // 遍历并递归删除每个文件和子文件夹 + for (File f : files) { + deleteFolderRecursive(f); + } + } + } + // 删除文件或空文件夹 + file.delete(); + } + + File getWorkingFolder() { + return mContext.getExternalFilesDir(TAG); + } + + public boolean loadPhoneBoBullToon() { + listPhoneBoBullToon.clear(); + File fBoBullToon = new File(getWorkingFolder(), "bobulltoon"); + if (fBoBullToon.exists()) { + LogUtils.d(TAG, String.format("getWorkingFolder() %s", getWorkingFolder())); + for (File userFolder : fBoBullToon.listFiles()) { + if (userFolder.isDirectory()) { + for (File recordFile : userFolder.listFiles()) { + listPhoneBoBullToon.add(recordFile.getName()); + } + } + } + + for (int i = 0; i < listPhoneBoBullToon.size(); i++) { + LogUtils.d(TAG, String.format("listPhoneBoBullToon add : %s", listPhoneBoBullToon.get(i))); + } + return true; + } else { + LogUtils.d(TAG, "fBoBullToon not exists。"); + } + return false; + } + + public boolean isPhoneBoBullToon(String phone) { + for (int i = 0; i < listPhoneBoBullToon.size(); i++) { + LogUtils.d(TAG, String.format("isPhoneBoBullToon(...) get(i) phone : %s", listPhoneBoBullToon.get(i))); + if (listPhoneBoBullToon.get(i).equals(phone)) { + return true; + } + } + return false; + } +} diff --git a/contacts/src/main/java/cc/winboll/studio/contacts/dun/Rules.java b/contacts/src/main/java/cc/winboll/studio/contacts/dun/Rules.java index a4f9a1e..8137f38 100644 --- a/contacts/src/main/java/cc/winboll/studio/contacts/dun/Rules.java +++ b/contacts/src/main/java/cc/winboll/studio/contacts/dun/Rules.java @@ -5,25 +5,36 @@ package cc.winboll.studio.contacts.dun; * @Date 2025/02/21 06:15:10 * @Describe 云盾防御规则 */ -import cc.winboll.studio.contacts.beans.PhoneBlackRuleBean; -import java.util.ArrayList; -import java.util.regex.Pattern; import android.content.Context; +import cc.winboll.studio.contacts.activities.SettingsActivity; +import cc.winboll.studio.contacts.beans.PhoneConnectRuleModel; +import cc.winboll.studio.contacts.beans.SettingsModel; +import cc.winboll.studio.contacts.services.MainService; +import cc.winboll.studio.contacts.utils.ContactUtils; +import cc.winboll.studio.contacts.utils.IntUtils; +import cc.winboll.studio.contacts.utils.RegexPPiUtils; +import cc.winboll.studio.libappbase.LogUtils; +import java.util.ArrayList; +import java.util.Timer; +import java.util.TimerTask; +import java.util.regex.Pattern; public class Rules { public static final String TAG = "Rules"; - ArrayList _PhoneBlacRuleBeanList; + ArrayList _PhoneConnectRuleModelList; static volatile Rules _Rules; Context mContext; + SettingsModel mSettingsModel; + Timer mDunResumeTimer; Rules(Context context) { mContext = context; - _PhoneBlacRuleBeanList = new ArrayList(); - PhoneBlackRuleBean.loadBeanList(mContext, _PhoneBlacRuleBeanList, PhoneBlackRuleBean.class); - + _PhoneConnectRuleModelList = new ArrayList(); + reload(); } + public static synchronized Rules getInstance(Context context) { if (_Rules == null) { _Rules = new Rules(context); @@ -31,46 +42,168 @@ public class Rules { return _Rules; } + public void reload() { + LogUtils.d(TAG, "reload()"); + loadRules(); + loadDun(); + setDunResumTimer(); + } + + public void setDunResumTimer() { + if (mDunResumeTimer != null) { + mDunResumeTimer.cancel(); + } + + // 盾牌恢复定时器 + mDunResumeTimer = new Timer(); + int ss = IntUtils.getIntInRange(mSettingsModel.getDunResumeSecondCount() * 1000, SettingsModel.MIN_INTRANGE, SettingsModel.MAX_INTRANGE); + mDunResumeTimer.schedule(new TimerTask() { + @Override + public void run() { + if (mSettingsModel.getDunCurrentCount() != mSettingsModel.getDunTotalCount()) { + LogUtils.d(TAG, String.format("当前防御值为%d,最大防御值为%d", mSettingsModel.getDunCurrentCount(), mSettingsModel.getDunTotalCount())); + int newDunCount = mSettingsModel.getDunCurrentCount() + mSettingsModel.getDunResumeCount(); + // 设置盾值在[0,DunTotalCount]之内其他值一律重置为 DunTotalCount。 + newDunCount = (newDunCount > mSettingsModel.getDunTotalCount()) ?mSettingsModel.getDunTotalCount(): newDunCount; + mSettingsModel.setDunCurrentCount(newDunCount); + LogUtils.d(TAG, String.format("设置防御值为%d", newDunCount)); + saveDun(); + SettingsActivity.notifyDunInfoUpdate(); + } + } + }, 1000, ss); + } + + public void loadRules() { + _PhoneConnectRuleModelList.clear(); + PhoneConnectRuleModel.loadBeanList(mContext, _PhoneConnectRuleModelList, PhoneConnectRuleModel.class); + } + + public void saveRules() { + LogUtils.d(TAG, String.format("saveRules()")); + PhoneConnectRuleModel.saveBeanList(mContext, _PhoneConnectRuleModelList, PhoneConnectRuleModel.class); + } + + public void loadDun() { + mSettingsModel = SettingsModel.loadBean(mContext, SettingsModel.class); + if (mSettingsModel == null) { + mSettingsModel = new SettingsModel(); + SettingsModel.saveBean(mContext, mSettingsModel); + } + } + + public void saveDun() { + LogUtils.d(TAG, String.format("saveDun()")); + SettingsModel.saveBean(mContext, mSettingsModel); + } + public boolean isAllowed(String phoneNumber) { - // 黑名单拒接 - for (int i = 0; i < _PhoneBlacRuleBeanList.size(); i++) { - if (_PhoneBlacRuleBeanList.get(i).isEnable()) { - String regex = _PhoneBlacRuleBeanList.get(i).getRuleText(); - if (Pattern.matches(regex, phoneNumber)) { - return false; + // 没有启用云盾,默认允许接通任何电话 + if (!mSettingsModel.isEnableDun()) { + LogUtils.d(TAG, String.format("没有启用云盾,默认允许接通任何电话。isAllowed(...) return true")); + return true; + } + + // + // 以下是云盾防御体系 + boolean isDefend = false; // 盾牌是否生效 + boolean isConnect = true; // 防御结果是否连接 + + // 如果盾值小于1,则解除防御 + if (!isDefend && mSettingsModel.getDunCurrentCount() < 1) { + // 盾层为1以下,防御解除 + LogUtils.d(TAG, "盾层为1以下,防御解除"); + isDefend = true; + isConnect = true; + LogUtils.d(TAG, String.format("isDefend == %s\nisConnect == %s", isDefend, isConnect)); + } + + // 正则运算预防针 + if (!isDefend && !RegexPPiUtils.isPPiOK(phoneNumber)) { + LogUtils.d(TAG, "正则运算预防针生效。"); + isDefend = true; + isConnect = false; + LogUtils.d(TAG, String.format("isDefend == %s\nisConnect == %s", isDefend, isConnect)); + } + + // 查询通讯录是否有该联系人 + boolean isPhoneInContacts = ContactUtils.getInstance(mContext).isPhoneInContacts(mContext, phoneNumber); + if (!isDefend) { + if (isPhoneInContacts) { + LogUtils.d(TAG, String.format("Phone %s is in contacts.", phoneNumber)); + isDefend = true; + isConnect = true; + LogUtils.d(TAG, String.format("isDefend == %s\nisConnect == %s", isDefend, isConnect)); + } else { + LogUtils.d(TAG, String.format("Phone %s is not in contacts.", phoneNumber)); + } + } + + // 检验拨不通号码群 + if (!isDefend && MainService.isPhoneInBoBullToon(phoneNumber)) { + LogUtils.d(TAG, String.format("PhoneNumber %s\n Is In BoBullToon", phoneNumber)); + isDefend = true; + isConnect = false; + LogUtils.d(TAG, String.format("isDefend == %s\nisConnect == %s", isDefend, isConnect)); + } + + // 正则匹配规则名单校验 + if (!isDefend) { + for (int i = 0; i < _PhoneConnectRuleModelList.size(); i++) { + if (_PhoneConnectRuleModelList.get(i).isEnable()) { + String regex = _PhoneConnectRuleModelList.get(i).getRuleText(); + if (Pattern.matches(regex, phoneNumber)) { + LogUtils.d(TAG, String.format("Phone Number [%s] is matched by rule : %s", phoneNumber, _PhoneConnectRuleModelList.get(i))); + isDefend = true; + isConnect = _PhoneConnectRuleModelList.get(i).isAllowConnection(); + LogUtils.d(TAG, String.format("isDefend == %s\nisConnect == %s", isDefend, isConnect)); + break; + } } } } - // 手机号码允许 - // 中国手机号码正则表达式,以1开头,第二位可以是3、4、5、6、7、8、9,后面跟9位数字 - String regex = "^1[3-9]\\d{9}$"; - if (Pattern.matches(regex, phoneNumber)) { - return true; - } - - // 指定区号号码允许 - regex = "^0660\\d+$"; - if (Pattern.matches(regex, phoneNumber)) { - return true; - } - - // 指定区号号码允许 - regex = "^020\\d+$"; - if (Pattern.matches(regex, phoneNumber)) { - return true; + if (isConnect) { + // 如果防御结果为连接,则恢复防御盾牌最大值层数 + mSettingsModel.setDunCurrentCount(mSettingsModel.getDunTotalCount()); + LogUtils.d(TAG, String.format("防御结果为连接,恢复防御盾牌最大值层数 %d", mSettingsModel.getDunTotalCount())); + saveDun(); + SettingsActivity.notifyDunInfoUpdate(); + } else if (isDefend) { + // 如果触发了以上某个防御模块, + // 就减少防御盾牌层数。 + // 每校验一次规则,云盾防御层数减1 + // 当云盾防御层数为0时,再次进行以下程序段则恢复满值防御。 + int newDunCount = mSettingsModel.getDunCurrentCount() - 1; + LogUtils.d(TAG, String.format("新的防御层数预计为 %d", newDunCount)); + + // 保证盾值在[0,DunTotalCount]之内其他值一律重置为 DunTotalCount。 + if (newDunCount < 0 || newDunCount > mSettingsModel.getDunTotalCount()) { + mSettingsModel.setDunCurrentCount(mSettingsModel.getDunTotalCount()); + LogUtils.d(TAG, String.format("盾值不在[0,%d]区间,恢复防御最大值%d", mSettingsModel.getDunTotalCount(), mSettingsModel.getDunTotalCount())); + } else { + mSettingsModel.setDunCurrentCount(newDunCount); + LogUtils.d(TAG, String.format("设置防御层数为 %d", newDunCount)); + } + + saveDun(); + SettingsActivity.notifyDunInfoUpdate(); } - // 其他拒接 - return false; + // 返回校验结果 + LogUtils.d(TAG, String.format("返回校验结果 isConnect == %s", isConnect)); + return isConnect; } - public void add(String phoneRuleBlack, boolean isEnable) { - _PhoneBlacRuleBeanList.add(new PhoneBlackRuleBean(phoneRuleBlack, isEnable)); - PhoneBlackRuleBean.saveBeanList(mContext, _PhoneBlacRuleBeanList, PhoneBlackRuleBean.class); + public void add(String szPhoneConnectRule, boolean isAllowConnection, boolean isEnable) { + _PhoneConnectRuleModelList.add(new PhoneConnectRuleModel(szPhoneConnectRule, isAllowConnection, isEnable)); } - public ArrayList getPhoneBlacRuleBeanList() { - return _PhoneBlacRuleBeanList; + public ArrayList getPhoneBlacRuleBeanList() { + return _PhoneConnectRuleModelList; + } + + public SettingsModel getSettingsModel() { + return mSettingsModel; } } diff --git a/contacts/src/main/java/cc/winboll/studio/contacts/fragments/CallFragment.java b/contacts/src/main/java/cc/winboll/studio/contacts/fragments/CallFragment.java deleted file mode 100644 index 6acb172..0000000 --- a/contacts/src/main/java/cc/winboll/studio/contacts/fragments/CallFragment.java +++ /dev/null @@ -1,51 +0,0 @@ -package cc.winboll.studio.contacts.fragments; - -/** - * @Author ZhanGSKen@AliYun.Com - * @Date 2025/02/20 12:57:00 - * @Describe 拨号 - */ -import android.os.Bundle; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import androidx.fragment.app.Fragment; -import cc.winboll.studio.contacts.R; -import cc.winboll.studio.libappbase.LogView; -import androidx.annotation.Nullable; -import androidx.annotation.NonNull; -import android.widget.TextView; - -public class CallFragment extends Fragment { - - public static final String TAG = "CallFragment"; - - private static final String ARG_PAGE = "ARG_PAGE"; - private int mPage; - - public static CallFragment newInstance(int page) { - Bundle args = new Bundle(); - args.putInt(ARG_PAGE, page); - CallFragment fragment = new CallFragment(); - fragment.setArguments(args); - return fragment; - } - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - if (getArguments()!= null) { - mPage = getArguments().getInt(ARG_PAGE); - } - } - - @Nullable - @Override - public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, - @Nullable Bundle savedInstanceState) { - View view = inflater.inflate(R.layout.fragment_call, container, false); - TextView textView = view.findViewById(R.id.page_text); - textView.setText("这是第 " + mPage + " 页"); - return view; - } -} diff --git a/contacts/src/main/java/cc/winboll/studio/contacts/fragments/CallLogFragment.java b/contacts/src/main/java/cc/winboll/studio/contacts/fragments/CallLogFragment.java new file mode 100644 index 0000000..d39033e --- /dev/null +++ b/contacts/src/main/java/cc/winboll/studio/contacts/fragments/CallLogFragment.java @@ -0,0 +1,164 @@ +package cc.winboll.studio.contacts.fragments; + +/** + * @Author ZhanGSKen@AliYun.Com + * @Date 2025/02/20 12:57:00 + * @Describe 拨号 + */ +import android.Manifest; +import android.content.pm.PackageManager; +import android.database.Cursor; +import android.os.Bundle; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import android.provider.CallLog; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.core.app.ActivityCompat; +import androidx.fragment.app.Fragment; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; +import cc.winboll.studio.contacts.R; +import cc.winboll.studio.contacts.adapters.CallLogAdapter; +import cc.winboll.studio.contacts.beans.CallLogModel; +import com.hjq.toast.ToastUtils; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +public class CallLogFragment extends Fragment { + + public static final String TAG = "CallFragment"; + + static volatile CallLogFragment _CallLogFragment; + + public static final int MSG_UPDATE = 1; // 添加消息常量 + + private static final String ARG_PAGE = "ARG_PAGE"; + private int mPage; + + private static final int REQUEST_READ_CALL_LOG = 1; + private RecyclerView recyclerView; + private CallLogAdapter callLogAdapter; + private List callLogList = new ArrayList<>(); + + // 添加Handler + private final Handler mHandler = new Handler(Looper.getMainLooper()) { + @Override + public void handleMessage(@NonNull Message msg) { + if (msg.what == MSG_UPDATE) { + readCallLog(); // 接收到消息时更新通话记录 + } + } + }; + + CallLogFragment() { + super(); + } + + public static CallLogFragment newInstance(int page) { + Bundle args = new Bundle(); + args.putInt(ARG_PAGE, page); + CallLogFragment fragment = new CallLogFragment(); + fragment.setArguments(args); + _CallLogFragment = fragment; + return fragment; + } + + @Nullable + @Override + public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + return inflater.inflate(R.layout.fragment_call_log, container, false); + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + if (getArguments() != null) { + mPage = getArguments().getInt(ARG_PAGE); + } + } + + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + recyclerView = view.findViewById(R.id.recyclerView); + recyclerView.setLayoutManager(new LinearLayoutManager(getContext())); + callLogAdapter = new CallLogAdapter(getContext(), callLogList); + recyclerView.setAdapter(callLogAdapter); + + if (ActivityCompat.checkSelfPermission(requireContext(), Manifest.permission.READ_CALL_LOG) != PackageManager.PERMISSION_GRANTED) { + ActivityCompat.requestPermissions(requireActivity(), new String[]{Manifest.permission.READ_CALL_LOG}, REQUEST_READ_CALL_LOG); + } else { + mHandler.sendEmptyMessage(MSG_UPDATE); // 通过Handler触发更新 + } + } + + @Override + public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults); + if (requestCode == REQUEST_READ_CALL_LOG) { + if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { + mHandler.sendEmptyMessage(MSG_UPDATE); // 通过Handler触发更新 + } + } + } + + private void readCallLog() { + callLogList.clear(); // 清空原有数据 + Cursor cursor = requireContext().getContentResolver().query( + CallLog.Calls.CONTENT_URI, + null, + null, + null, + CallLog.Calls.DATE + " DESC"); + + if (cursor != null) { + while (cursor.moveToNext()) { + String phoneNumber = cursor.getString(cursor.getColumnIndex(CallLog.Calls.NUMBER)); + int callType = cursor.getInt(cursor.getColumnIndex(CallLog.Calls.TYPE)); + long callDateLong = cursor.getLong(cursor.getColumnIndex(CallLog.Calls.DATE)); + Date callDate = new Date(callDateLong); + + String callStatus = getCallStatus(callType); + + callLogList.add(new CallLogModel(phoneNumber, callStatus, callDate)); + } + cursor.close(); + callLogAdapter.notifyDataSetChanged(); + } + } + + private String getCallStatus(int callType) { + switch (callType) { + case CallLog.Calls.OUTGOING_TYPE: + return "Outgoing"; + case CallLog.Calls.INCOMING_TYPE: + return "Incoming"; + case CallLog.Calls.MISSED_TYPE: + return "Missed"; + default: + return "Unknown"; + } + } + + @Override + public void onDestroy() { + super.onDestroy(); + mHandler.removeCallbacksAndMessages(null); // 清理Handler防止内存泄漏 + } + + public void triggerUpdate() { + mHandler.sendEmptyMessage(MSG_UPDATE); + } + + public static void updateCallLogFragment() { + if (_CallLogFragment != null) { + _CallLogFragment.triggerUpdate(); + } + } +} diff --git a/contacts/src/main/java/cc/winboll/studio/contacts/fragments/ContactsFragment.java b/contacts/src/main/java/cc/winboll/studio/contacts/fragments/ContactsFragment.java index 2f7dad6..356201e 100644 --- a/contacts/src/main/java/cc/winboll/studio/contacts/fragments/ContactsFragment.java +++ b/contacts/src/main/java/cc/winboll/studio/contacts/fragments/ContactsFragment.java @@ -5,23 +5,47 @@ package cc.winboll.studio.contacts.fragments; * @Date 2025/02/20 12:57:50 * @Describe 联系人 */ +import android.Manifest; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.database.Cursor; import android.os.Bundle; +import android.provider.ContactsContract; +import android.text.Editable; +import android.text.TextWatcher; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; -import android.widget.TextView; +import android.widget.Button; +import android.widget.EditText; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.core.app.ActivityCompat; import androidx.fragment.app.Fragment; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; import cc.winboll.studio.contacts.R; +import cc.winboll.studio.contacts.adapters.ContactAdapter; +import cc.winboll.studio.contacts.beans.ContactModel; +import com.hjq.toast.ToastUtils; +import java.util.ArrayList; +import java.util.List; + public class ContactsFragment extends Fragment { - + public static final String TAG = "ContactsFragment"; - + private static final String ARG_PAGE = "ARG_PAGE"; private int mPage; + private static final int REQUEST_READ_CONTACTS = 1; + private RecyclerView recyclerView; + private ContactAdapter contactAdapter; + private List contactList = new ArrayList<>(); + private List originalContactList = new ArrayList<>(); + private EditText searchEditText; + public static ContactsFragment newInstance(int page) { Bundle args = new Bundle(); args.putInt(ARG_PAGE, page); @@ -33,18 +57,111 @@ public class ContactsFragment extends Fragment { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - if (getArguments()!= null) { + if (getArguments() != null) { mPage = getArguments().getInt(ARG_PAGE); } } @Nullable @Override - public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, - @Nullable Bundle savedInstanceState) { - View view = inflater.inflate(R.layout.fragment_contacts, container, false); - TextView textView = view.findViewById(R.id.page_text); - textView.setText("这是第 " + mPage + " 页"); - return view; + public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + return inflater.inflate(R.layout.fragment_contacts, container, false); + } + + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + recyclerView = view.findViewById(R.id.contacts_recycler_view); + recyclerView.setLayoutManager(new LinearLayoutManager(getContext())); + contactAdapter = new ContactAdapter(contactList); + recyclerView.setAdapter(contactAdapter); + + searchEditText = view.findViewById(R.id.search_edit_text); + searchEditText.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) { + filterContacts(s.toString()); + } + + @Override + public void afterTextChanged(Editable s) { + } + }); + + if (ActivityCompat.checkSelfPermission(requireContext(), Manifest.permission.READ_CONTACTS) != PackageManager.PERMISSION_GRANTED) { + ActivityCompat.requestPermissions(requireActivity(), new String[]{Manifest.permission.READ_CONTACTS}, REQUEST_READ_CONTACTS); + } else { + readContacts(); + } + + Button btnDial = view.findViewById(R.id.btn_dial); + btnDial.setOnClickListener(new View.OnClickListener(){ + @Override + public void onClick(View p1) { + + String phoneNumber = searchEditText.getText().toString().replaceAll("\\s", ""); + //phoneNumber = "+8616769764848"; + ToastUtils.show(phoneNumber); + Intent intent = new Intent(Intent.ACTION_CALL); + intent.setData(android.net.Uri.parse("tel:" + phoneNumber)); + // 添加 FLAG_ACTIVITY_NEW_TASK 标志 + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + startActivity(intent); + } + }); + } + + @Override + public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults); + if (requestCode == REQUEST_READ_CONTACTS) { + if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { + readContacts(); + } + } + } + + private void readContacts() { + contactList.clear(); + originalContactList.clear(); + Cursor cursor = requireContext().getContentResolver().query( + ContactsContract.CommonDataKinds.Phone.CONTENT_URI, + null, + null, + null, + ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME + " ASC"); + + if (cursor != null) { + while (cursor.moveToNext()) { + String name = cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME)); + String number = cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER)); + ContactModel contact = new ContactModel(name, number); + contactList.add(contact); + originalContactList.add(contact); + } + cursor.close(); + contactAdapter.notifyDataSetChanged(); + } + } + + private void filterContacts(String query) { + contactList.clear(); + if (query.isEmpty()) { + contactList.addAll(originalContactList); + } else { + for (ContactModel contact : originalContactList) { + if (contact.getName().toLowerCase().contains(query.toLowerCase()) || + contact.getPinyin().toLowerCase().contains(query.toLowerCase()) || + contact.getNumber().toLowerCase().contains(query.toLowerCase())) { + contactList.add(contact); + } + } + } + contactAdapter.notifyDataSetChanged(); } } + diff --git a/contacts/src/main/java/cc/winboll/studio/contacts/fragments/LogFragment.java b/contacts/src/main/java/cc/winboll/studio/contacts/fragments/LogFragment.java index e2df22a..656b7d1 100644 --- a/contacts/src/main/java/cc/winboll/studio/contacts/fragments/LogFragment.java +++ b/contacts/src/main/java/cc/winboll/studio/contacts/fragments/LogFragment.java @@ -14,6 +14,7 @@ import androidx.annotation.Nullable; import androidx.fragment.app.Fragment; import cc.winboll.studio.contacts.R; import cc.winboll.studio.libappbase.LogView; +import com.hjq.toast.ToastUtils; public class LogFragment extends Fragment { @@ -21,6 +22,8 @@ public class LogFragment extends Fragment { private static final String ARG_PAGE = "ARG_PAGE"; private int mPage; + + LogView mLogView; public static LogFragment newInstance(int page) { Bundle args = new Bundle(); @@ -43,8 +46,17 @@ public class LogFragment extends Fragment { public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { View view = inflater.inflate(R.layout.fragment_log, container, false); - LogView logView = view.findViewById(R.id.logview); - logView.start(); + mLogView = view.findViewById(R.id.logview); + mLogView.start(); return view; } + + @Override + public void onResume() { + super.onResume(); + //ToastUtils.show("onResume"); + mLogView.start(); + } + + } diff --git a/contacts/src/main/java/cc/winboll/studio/contacts/listenphonecall/CallListenerService.java b/contacts/src/main/java/cc/winboll/studio/contacts/listenphonecall/CallListenerService.java index f555114..42e98aa 100644 --- a/contacts/src/main/java/cc/winboll/studio/contacts/listenphonecall/CallListenerService.java +++ b/contacts/src/main/java/cc/winboll/studio/contacts/listenphonecall/CallListenerService.java @@ -21,6 +21,8 @@ import android.widget.TextView; import androidx.annotation.Nullable; import cc.winboll.studio.contacts.MainActivity; import cc.winboll.studio.contacts.R; +import cc.winboll.studio.contacts.phonecallui.PhoneCallActivity; +import cc.winboll.studio.contacts.phonecallui.PhoneCallService; public class CallListenerService extends Service { @@ -152,9 +154,12 @@ public class CallListenerService extends Service { @Override public void onClick(View view) { - Intent intent = new Intent(getApplicationContext(), MainActivity.class); - intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - CallListenerService.this.startActivity(intent); +// Intent intent = new Intent(getApplicationContext(), MainActivity.class); +// intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); +// CallListenerService.this.startActivity(intent); + + PhoneCallService.CallType callType = isCallingIn ? PhoneCallService.CallType.CALL_IN: PhoneCallService.CallType.CALL_OUT; + PhoneCallActivity.actionStart(CallListenerService.this, callNumber, callType); } }); } diff --git a/contacts/src/main/java/cc/winboll/studio/contacts/phonecallui/PhoneCallActivity.java b/contacts/src/main/java/cc/winboll/studio/contacts/phonecallui/PhoneCallActivity.java index eec1ab7..db50fbe 100644 --- a/contacts/src/main/java/cc/winboll/studio/contacts/phonecallui/PhoneCallActivity.java +++ b/contacts/src/main/java/cc/winboll/studio/contacts/phonecallui/PhoneCallActivity.java @@ -1,7 +1,5 @@ package cc.winboll.studio.contacts.phonecallui; -import static cc.winboll.studio.contacts.listenphonecall.CallListenerService.formatPhoneNumber; - import android.annotation.SuppressLint; import android.content.Context; import android.content.Intent; @@ -10,16 +8,16 @@ import android.os.Bundle; import android.view.View; import android.view.WindowManager; import android.widget.TextView; - import androidx.annotation.RequiresApi; import androidx.appcompat.app.AppCompatActivity; - import cc.winboll.studio.contacts.ActivityStack; +import cc.winboll.studio.contacts.MainActivity; import cc.winboll.studio.contacts.R; - import java.util.Timer; import java.util.TimerTask; +import static cc.winboll.studio.contacts.listenphonecall.CallListenerService.formatPhoneNumber; + /** * 提供接打电话的界面,仅支持 Android M (6.0, API 23) 及以上的系统 @@ -57,10 +55,9 @@ public class PhoneCallActivity extends AppCompatActivity implements View.OnClick setContentView(R.layout.activity_phone_call); ActivityStack.getInstance().addActivity(this); - initData(); - initView(); + } private void initData() { @@ -74,9 +71,9 @@ public class PhoneCallActivity extends AppCompatActivity implements View.OnClick private void initView() { int uiOptions = View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION - | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION //hide navigationBar - | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY - | View.SYSTEM_UI_FLAG_LAYOUT_STABLE; + | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION //hide navigationBar + | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY + | View.SYSTEM_UI_FLAG_LAYOUT_STABLE; getWindow().getDecorView().setSystemUiVisibility(uiOptions); getWindow().addFlags(WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON); @@ -94,9 +91,7 @@ public class PhoneCallActivity extends AppCompatActivity implements View.OnClick if (callType == PhoneCallService.CallType.CALL_IN) { tvCallNumberLabel.setText("来电号码"); tvPickUp.setVisibility(View.VISIBLE); - } - // 打出的电话 - else if (callType == PhoneCallService.CallType.CALL_OUT) { + } else if (callType == PhoneCallService.CallType.CALL_OUT) { tvCallNumberLabel.setText("呼叫号码"); tvPickUp.setVisibility(View.GONE); phoneCallManager.openSpeaker(); @@ -107,13 +102,13 @@ public class PhoneCallActivity extends AppCompatActivity implements View.OnClick public void showOnLockScreen() { this.getWindow().setFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON | - WindowManager.LayoutParams.FLAG_FULLSCREEN | - WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED | - WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON, - WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON | - WindowManager.LayoutParams.FLAG_FULLSCREEN | - WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED | - WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON); + WindowManager.LayoutParams.FLAG_FULLSCREEN | + WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED | + WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON, + WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON | + WindowManager.LayoutParams.FLAG_FULLSCREEN | + WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED | + WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON); } @Override @@ -123,18 +118,18 @@ public class PhoneCallActivity extends AppCompatActivity implements View.OnClick tvPickUp.setVisibility(View.GONE); tvCallingTime.setVisibility(View.VISIBLE); onGoingCallTimer.schedule(new TimerTask() { - @Override - public void run() { - runOnUiThread(new Runnable() { - @SuppressLint("SetTextI18n") - @Override - public void run() { - callingTime++; - tvCallingTime.setText("通话中:" + getCallingTime()); - } - }); - } - }, 0, 1000); + @Override + public void run() { + runOnUiThread(new Runnable() { + @SuppressLint("SetTextI18n") + @Override + public void run() { + callingTime++; + tvCallingTime.setText("通话中:" + getCallingTime()); + } + }); + } + }, 0, 1000); } else if (v.getId() == R.id.tv_phone_hang_up) { phoneCallManager.disconnect(); stopTimer(); @@ -145,8 +140,8 @@ public class PhoneCallActivity extends AppCompatActivity implements View.OnClick int minute = callingTime / 60; int second = callingTime % 60; return (minute < 10 ? "0" + minute : minute) + - ":" + - (second < 10 ? "0" + second : second); + ":" + + (second < 10 ? "0" + second : second); } private void stopTimer() { @@ -160,7 +155,7 @@ public class PhoneCallActivity extends AppCompatActivity implements View.OnClick @Override protected void onDestroy() { super.onDestroy(); - + //MainActivity.updateCallLogFragment(); phoneCallManager.destroy(); } } diff --git a/contacts/src/main/java/cc/winboll/studio/contacts/phonecallui/PhoneCallManager.java b/contacts/src/main/java/cc/winboll/studio/contacts/phonecallui/PhoneCallManager.java index 6fd2f52..70d7484 100644 --- a/contacts/src/main/java/cc/winboll/studio/contacts/phonecallui/PhoneCallManager.java +++ b/contacts/src/main/java/cc/winboll/studio/contacts/phonecallui/PhoneCallManager.java @@ -6,7 +6,7 @@ import android.os.Build; import android.telecom.Call; import android.telecom.VideoProfile; import androidx.annotation.RequiresApi; -import cc.winboll.studio.contacts.dun.Rules; +import cc.winboll.studio.contacts.MainActivity; @RequiresApi(api = Build.VERSION_CODES.M) diff --git a/contacts/src/main/java/cc/winboll/studio/contacts/phonecallui/PhoneCallService.java b/contacts/src/main/java/cc/winboll/studio/contacts/phonecallui/PhoneCallService.java index 88eb69e..ee7a249 100644 --- a/contacts/src/main/java/cc/winboll/studio/contacts/phonecallui/PhoneCallService.java +++ b/contacts/src/main/java/cc/winboll/studio/contacts/phonecallui/PhoneCallService.java @@ -7,28 +7,53 @@ package cc.winboll.studio.contacts.phonecallui; * @see PhoneCallActivity * @see android.telecom.InCallService */ +import android.content.ContentResolver; +import android.database.Cursor; import android.media.AudioManager; +import android.media.MediaRecorder; +import android.net.Uri; import android.os.Build; +import android.provider.CallLog; import android.telecom.Call; import android.telecom.InCallService; +import android.telephony.TelephonyManager; import androidx.annotation.RequiresApi; import cc.winboll.studio.contacts.ActivityStack; import cc.winboll.studio.contacts.beans.RingTongBean; import cc.winboll.studio.contacts.dun.Rules; +import cc.winboll.studio.contacts.fragments.CallLogFragment; import cc.winboll.studio.libappbase.LogUtils; +import java.io.File; +import java.io.IOException; @RequiresApi(api = Build.VERSION_CODES.M) public class PhoneCallService extends InCallService { public static final String TAG = "PhoneCallService"; - - private volatile int originalRingVolume; + + MediaRecorder mediaRecorder; private final Call.Callback callback = new Call.Callback() { @Override public void onStateChanged(Call call, int state) { super.onStateChanged(call, state); switch (state) { + case TelephonyManager.CALL_STATE_OFFHOOK: + { + long callId = getCurrentCallId(); + if (callId != -1) { + // 在这里可以对获取到的通话记录ID进行处理 + //System.out.println("当前通话记录ID: " + callId); + + // 电话接通,开始录音 + startRecording(callId); + } + break; + } + case TelephonyManager.CALL_STATE_IDLE: + // 电话挂断,停止录音 + stopRecording(); + break; case Call.STATE_ACTIVE: { break; } @@ -61,25 +86,58 @@ public class PhoneCallService extends InCallService { String phoneNumber = details.getHandle().getSchemeSpecificPart(); // 记录原始铃声音量 + // AudioManager audioManager = (AudioManager) getSystemService(AUDIO_SERVICE); - originalRingVolume = audioManager.getStreamVolume(AudioManager.STREAM_RING); + int ringerVolume = audioManager.getStreamVolume(AudioManager.STREAM_RING); + // 恢复铃声音量,预防其他意外条件导致的音量变化问题 + // + + // 读取应用配置,未配置就初始化配置文件 + RingTongBean bean = RingTongBean.loadBean(this, RingTongBean.class); + if (bean == null) { + // 初始化配置 + bean = new RingTongBean(); + RingTongBean.saveBean(this, bean); + } + // 如果当前音量和应用保存的不一致就恢复为应用设定值 + // 恢复铃声音量 + try { + if (ringerVolume != bean.getStreamVolume()) { + audioManager.setStreamVolume(AudioManager.STREAM_RING, bean.getStreamVolume(), 0); + //audioManager.setMode(AudioManager.RINGER_MODE_NORMAL); + } + } catch (java.lang.SecurityException e) { + LogUtils.d(TAG, e, Thread.currentThread().getStackTrace()); + } + // 检查电话接收规则 if (!Rules.getInstance(this).isAllowed(phoneNumber)) { - // 预先静音 - audioManager.setStreamVolume(AudioManager.STREAM_RING, 0, 0); + // 调低音量 + try { + audioManager.setStreamVolume(AudioManager.STREAM_RING, 0, 0); + //audioManager.setMode(AudioManager.RINGER_MODE_SILENT); + } catch (java.lang.SecurityException e) { + LogUtils.d(TAG, e, Thread.currentThread().getStackTrace()); + } // 断开电话 call.disconnect(); // 停顿1秒,预防第一声铃声响动 try { - Thread.sleep(1000); + Thread.sleep(500); } catch (InterruptedException e) { LogUtils.d(TAG, ""); } // 恢复铃声音量 - audioManager.setStreamVolume(AudioManager.STREAM_RING, originalRingVolume, 0); + try { + audioManager.setStreamVolume(AudioManager.STREAM_RING, bean.getStreamVolume(), 0); + //audioManager.setMode(AudioManager.RINGER_MODE_NORMAL); + } catch (java.lang.SecurityException e) { + LogUtils.d(TAG, e, Thread.currentThread().getStackTrace()); + } // 屏蔽电话结束 return; } + // 正常接听电话 PhoneCallActivity.actionStart(this, phoneNumber, callType); } @@ -88,16 +146,70 @@ public class PhoneCallService extends InCallService { @Override public void onCallRemoved(Call call) { super.onCallRemoved(call); - call.unregisterCallback(callback); PhoneCallManager.call = null; - AudioManager audioManager = (AudioManager) getSystemService(AUDIO_SERVICE); - // 恢复铃声音量 - audioManager.setStreamVolume(AudioManager.STREAM_RING, originalRingVolume, 0); + } + + @Override + public void onDestroy() { + super.onDestroy(); + CallLogFragment.updateCallLogFragment(); } public enum CallType { CALL_IN, CALL_OUT, } + + + private void startRecording(long callId) { + LogUtils.d(TAG, "startRecording(...)"); + mediaRecorder = new MediaRecorder(); + mediaRecorder.setAudioSource(MediaRecorder.AudioSource.VOICE_CALL); + mediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4); + mediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC); + mediaRecorder.setOutputFile(getOutputFilePath(callId)); + try { + mediaRecorder.prepare(); + mediaRecorder.start(); + } catch (IOException e) { + LogUtils.d(TAG, e, Thread.currentThread().getStackTrace()); + } + } + + private String getOutputFilePath(long callId) { + LogUtils.d(TAG, "getOutputFilePath(...)"); + // 设置录音文件的保存路径 + File file = new File(getExternalFilesDir(TAG), String.format("call_%d.mp4", callId)); + return file.getAbsolutePath(); + } + + private void stopRecording() { + LogUtils.d(TAG, "stopRecording()"); + if (mediaRecorder != null) { + mediaRecorder.stop(); + mediaRecorder.release(); + mediaRecorder = null; + } + } + + private long getCurrentCallId() { + LogUtils.d(TAG, "getCurrentCallId()"); + ContentResolver contentResolver = getApplicationContext().getContentResolver(); + Uri callLogUri = Uri.parse("content://call_log/calls"); + String[] projection = {"_id", "number", "call_type", "date"}; + String selection = "call_type = " + CallLog.Calls.OUTGOING_TYPE + " OR call_type = " + CallLog.Calls.INCOMING_TYPE; + String sortOrder = "date DESC"; + + try { + Cursor cursor = contentResolver.query(callLogUri, projection, selection, null, sortOrder); + if (cursor != null && cursor.moveToFirst()) { + return cursor.getLong(cursor.getColumnIndex("_id")); + } + } catch (Exception e) { + LogUtils.d(TAG, e, Thread.currentThread().getStackTrace()); + } + + return -1; + } } diff --git a/contacts/src/main/java/cc/winboll/studio/contacts/receivers/MainReceiver.java b/contacts/src/main/java/cc/winboll/studio/contacts/receivers/MainReceiver.java index d1dfb52..924ddab 100644 --- a/contacts/src/main/java/cc/winboll/studio/contacts/receivers/MainReceiver.java +++ b/contacts/src/main/java/cc/winboll/studio/contacts/receivers/MainReceiver.java @@ -9,13 +9,9 @@ import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; -import android.media.RingtoneManager; -import android.net.Uri; -import android.util.Log; import cc.winboll.studio.contacts.services.MainService; import com.hjq.toast.ToastUtils; import java.lang.ref.WeakReference; -import cc.winboll.studio.libappbase.LogUtils; public class MainReceiver extends BroadcastReceiver { @@ -43,7 +39,7 @@ public class MainReceiver extends BroadcastReceiver { public void registerAction(Context context) { IntentFilter filter=new IntentFilter(); filter.addAction(ACTION_BOOT_COMPLETED); - //filter.addAction(Intent.ACTION_BATTERY_CHANGED); + //filter.addAction(AudioManager.RINGER_MODE_CHANGED_ACTION); context.registerReceiver(this, filter); } } diff --git a/contacts/src/main/java/cc/winboll/studio/contacts/services/AssistantService.java b/contacts/src/main/java/cc/winboll/studio/contacts/services/AssistantService.java index a031f4b..6fd8673 100644 --- a/contacts/src/main/java/cc/winboll/studio/contacts/services/AssistantService.java +++ b/contacts/src/main/java/cc/winboll/studio/contacts/services/AssistantService.java @@ -15,8 +15,6 @@ import android.os.IBinder; import cc.winboll.studio.contacts.beans.MainServiceBean; import cc.winboll.studio.contacts.services.MainService; import cc.winboll.studio.libappbase.LogUtils; -import cc.winboll.studio.libappbase.SOS; -import cc.winboll.studio.libappbase.bean.APPSOSBean; public class AssistantService extends Service { diff --git a/contacts/src/main/java/cc/winboll/studio/contacts/services/MainService.java b/contacts/src/main/java/cc/winboll/studio/contacts/services/MainService.java index 3eef1a4..f61cde5 100644 --- a/contacts/src/main/java/cc/winboll/studio/contacts/services/MainService.java +++ b/contacts/src/main/java/cc/winboll/studio/contacts/services/MainService.java @@ -11,25 +11,29 @@ package cc.winboll.studio.contacts.services; * https://blog.csdn.net/cyp331203/article/details/38920491 */ import android.app.Service; -import cc.winboll.studio.contacts.listenphonecall.CallListenerService; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.ServiceConnection; +import android.media.AudioManager; import android.os.Binder; import android.os.IBinder; import cc.winboll.studio.contacts.beans.MainServiceBean; +import cc.winboll.studio.contacts.beans.RingTongBean; +import cc.winboll.studio.contacts.bobulltoon.TomCat; +import cc.winboll.studio.contacts.dun.Rules; import cc.winboll.studio.contacts.handlers.MainServiceHandler; +import cc.winboll.studio.contacts.listenphonecall.CallListenerService; import cc.winboll.studio.contacts.receivers.MainReceiver; import cc.winboll.studio.contacts.services.MainService; import cc.winboll.studio.contacts.threads.MainServiceThread; -import cc.winboll.studio.contacts.widgets.APPStatusWidget; import cc.winboll.studio.libappbase.LogUtils; -import cc.winboll.studio.libappbase.SOS; -import cc.winboll.studio.libappbase.bean.APPSOSBean; -import cc.winboll.studio.contacts.dun.Rules; -import android.media.AudioManager; -import com.hjq.toast.ToastUtils; +import cc.winboll.studio.libappbase.sos.SOS; +import java.util.Timer; +import java.util.TimerTask; +import cc.winboll.studio.libappbase.sos.WinBoll; +import cc.winboll.studio.contacts.App; +import cc.winboll.studio.libappbase.sos.APPModel; public class MainService extends Service { @@ -48,8 +52,9 @@ public class MainService extends Service { AssistantService mAssistantService; boolean isBound = false; MainReceiver mMainReceiver; - - + Timer mStreamVolumeCheckTimer; + static volatile TomCat _TomCat; + @Override public IBinder onBind(Intent intent) { return new MyBinder(); @@ -71,9 +76,37 @@ public class MainService extends Service { mMyServiceConnection = new MyServiceConnection(); } mMainServiceHandler = new MainServiceHandler(this); - - - + + // 铃声检查定时器 + mStreamVolumeCheckTimer = new Timer(); + mStreamVolumeCheckTimer.schedule(new TimerTask() { + @Override + public void run() { + AudioManager audioManager = (AudioManager) getSystemService(AUDIO_SERVICE); + int ringerVolume = audioManager.getStreamVolume(AudioManager.STREAM_RING); + // 恢复铃声音量,预防其他意外条件导致的音量变化问题 + // + + // 读取应用配置,未配置就初始化配置文件 + RingTongBean bean = RingTongBean.loadBean(MainService.this, RingTongBean.class); + if (bean == null) { + // 初始化配置 + bean = new RingTongBean(); + RingTongBean.saveBean(MainService.this, bean); + } + // 如果当前音量和应用保存的不一致就恢复为应用设定值 + // 恢复铃声音量 + try { + if (ringerVolume != bean.getStreamVolume()) { + audioManager.setStreamVolume(AudioManager.STREAM_RING, bean.getStreamVolume(), 0); + //audioManager.setMode(AudioManager.RINGER_MODE_NORMAL); + } + } catch (java.lang.SecurityException e) { + LogUtils.d(TAG, e, Thread.currentThread().getStackTrace()); + } + } + }, 1000, 60000); + // 运行服务内容 mainService(); } @@ -97,18 +130,26 @@ public class MainService extends Service { // 唤醒守护进程 wakeupAndBindAssistant(); // 召唤 WinBoll APP 绑定本服务 - SOS.bindToAPPService(this, new APPSOSBean(getPackageName(), MainService.class.getName())); + if (App.isDebuging()) { + WinBoll.bindToAPPBaseBeta(this, MainService.class.getName()); + } else { + WinBoll.bindToAPPBase(this, MainService.class.getName()); + } + + // 初始化服务运行参数 + _TomCat = TomCat.getInstance(this); + if (!_TomCat.loadPhoneBoBullToon()) { + LogUtils.d(TAG, "没有下载 BoBullToon 数据。BoBullToon 参数无法加载。"); + } if (mMainReceiver == null) { // 注册广播接收器 mMainReceiver = new MainReceiver(this); mMainReceiver.registerAction(this); } - - Rules.getInstance(this); - //Rules.getInstance(this).add("18888888888", true); - //Rules.getInstance(this).add("16769764848", true); - + + Rules.getInstance(this).loadRules(); + startPhoneCallListener(); MainServiceThread.getInstance(this, mMainServiceHandler).start(); @@ -117,6 +158,14 @@ public class MainService extends Service { } } + public static boolean isPhoneInBoBullToon(String phone) { + if (_TomCat != null) { + return _TomCat.isPhoneBoBullToon(phone); + } + return false; + } + + // 唤醒和绑定守护进程 // void wakeupAndBindAssistant() { @@ -137,7 +186,7 @@ public class MainService extends Service { // LogUtils.d(TAG, "startService(intent)"); // bindService(new Intent(this, AssistantService.class), mMyServiceConnection, Context.BIND_IMPORTANT); } - + void startPhoneCallListener() { Intent callListener = new Intent(this, CallListenerService.class); startService(callListener); @@ -168,7 +217,7 @@ public class MainService extends Service { // 停止主要进程 MainServiceThread.getInstance(this, mMainServiceHandler).setIsExit(true); - + } super.onDestroy(); @@ -191,7 +240,11 @@ public class MainService extends Service { if (mMainServiceBean.isEnable()) { // 唤醒守护进程 wakeupAndBindAssistant(); - SOS.sosWinBollService(getApplicationContext(), new APPSOSBean(getPackageName(), MainService.class.getName())); + if (App.isDebuging()) { + SOS.sosToAppBase(getApplicationContext(), MainService.class.getName()); + } else { + SOS.sosToAppBaseBeta(getApplicationContext(), MainService.class.getName()); + } } isBound = false; mAssistantService = null; @@ -230,14 +283,40 @@ public class MainService extends Service { public static void stopMainService(Context context) { LogUtils.d(TAG, "stopMainService"); + context.stopService(new Intent(context, MainService.class)); + } + + public static void startMainService(Context context) { + LogUtils.d(TAG, "startMainService"); + context.startService(new Intent(context, MainService.class)); + } + + public static void restartMainService(Context context) { + LogUtils.d(TAG, "restartMainService"); + + MainServiceBean bean = MainServiceBean.loadBean(context, MainServiceBean.class); + if (bean != null && bean.isEnable()) { + context.stopService(new Intent(context, MainService.class)); +// try { +// Thread.sleep(1000); +// } catch (InterruptedException e) { +// LogUtils.d(TAG, e, Thread.currentThread().getStackTrace()); +// } + context.startService(new Intent(context, MainService.class)); + LogUtils.d(TAG, "已重启 MainService"); + } + } + + public static void stopMainServiceAndSaveStatus(Context context) { + LogUtils.d(TAG, "stopMainServiceAndSaveStatus"); MainServiceBean bean = new MainServiceBean(); bean.setIsEnable(false); MainServiceBean.saveBean(context, bean); context.stopService(new Intent(context, MainService.class)); } - public static void startMainService(Context context) { - LogUtils.d(TAG, "startMainService"); + public static void startMainServiceAndSaveStatus(Context context) { + LogUtils.d(TAG, "startMainServiceAndSaveStatus"); MainServiceBean bean = new MainServiceBean(); bean.setIsEnable(true); MainServiceBean.saveBean(context, bean); diff --git a/contacts/src/main/java/cc/winboll/studio/contacts/threads/MainServiceThread.java b/contacts/src/main/java/cc/winboll/studio/contacts/threads/MainServiceThread.java index 0dfd918..c703d83 100644 --- a/contacts/src/main/java/cc/winboll/studio/contacts/threads/MainServiceThread.java +++ b/contacts/src/main/java/cc/winboll/studio/contacts/threads/MainServiceThread.java @@ -6,11 +6,7 @@ package cc.winboll.studio.contacts.threads; */ import android.content.Context; import cc.winboll.studio.contacts.handlers.MainServiceHandler; -import cc.winboll.studio.contacts.services.MainService; import cc.winboll.studio.libappbase.LogUtils; -import cc.winboll.studio.libappbase.SOS; -import cc.winboll.studio.libappbase.bean.APPSOSBean; -import com.hjq.toast.ToastUtils; import java.lang.ref.WeakReference; public class MainServiceThread extends Thread { diff --git a/contacts/src/main/java/cc/winboll/studio/contacts/utils/ContactUtils.java b/contacts/src/main/java/cc/winboll/studio/contacts/utils/ContactUtils.java new file mode 100644 index 0000000..cc91eec --- /dev/null +++ b/contacts/src/main/java/cc/winboll/studio/contacts/utils/ContactUtils.java @@ -0,0 +1,123 @@ +package cc.winboll.studio.contacts.utils; + +/** + * @Author ZhanGSKen@AliYun.Com + * @Date 2025/03/06 21:08:16 + * @Describe ContactUtils + */ +import android.content.ContentResolver; +import android.content.Context; +import android.database.Cursor; +import android.provider.ContactsContract; +import cc.winboll.studio.libappbase.LogUtils; +import java.util.HashMap; +import java.util.Map; + +public class ContactUtils { + + public static final String TAG = "ContactUtils"; + + Map contactMap = new HashMap<>(); + + static volatile ContactUtils _ContactUtils; + Context mContext; + ContactUtils(Context context) { + mContext = context; + relaodContacts(); + } + public synchronized static ContactUtils getInstance(Context context) { + if (_ContactUtils == null) { + _ContactUtils = new ContactUtils(context); + } + return _ContactUtils; + } + + public void relaodContacts() { + readContacts(); + } + + private void readContacts() { + contactMap.clear(); + ContentResolver contentResolver = mContext.getContentResolver(); + Cursor cursor = contentResolver.query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI, + null, null, null, null); + if (cursor != null) { + while (cursor.moveToNext()) { + String displayName = cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME)); + String phoneNumber = cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER)); + //Map contactMap = new HashMap<>(); + contactMap.put(formatToSimplePhoneNumber(phoneNumber), displayName); + } + cursor.close(); + } + // 此时 contactList 就是存储联系人信息的 Map 列表 + } + + public String getContactsName(String phone) { + String result = contactMap.get(formatToSimplePhoneNumber(phone)); + return result == null ? "[NotInContacts]" : result; + } + +// static String getSimplePhone(String phone) { +// return phone.replaceAll("[+\\s]", ""); +// } + + public static String formatToSimplePhoneNumber(String number) { + // 去除所有空格和非数字字符 + return number.replaceAll("[^0-9]", ""); + } + + public static String getDisplayNameByPhone(Context context, String phoneNumber) { + String displayName = null; + ContentResolver resolver = context.getContentResolver(); + String[] projection = {ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME}; + Cursor cursor = resolver.query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI, projection, ContactsContract.CommonDataKinds.Phone.NUMBER + "=?", new String[]{phoneNumber}, null); + if (cursor != null && cursor.moveToFirst()) { + displayName = cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME)); + cursor.close(); + } + return displayName; + } + + public static String getDisplayNameByPhoneSimple(Context context, String phoneNumber) { + String displayName = null; + ContentResolver resolver = context.getContentResolver(); + String[] projection = {ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME}; + Cursor cursor = resolver.query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI, projection, ContactsContract.CommonDataKinds.Phone.NUMBER + "=?", new String[]{formatToSimplePhoneNumber(phoneNumber)}, null); + if (cursor != null && cursor.moveToFirst()) { + displayName = cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME)); + cursor.close(); + } + return displayName; + } + + public static boolean isPhoneInContacts(Context context, String phoneNumber) { + String szPhoneNumber = formatToSimplePhoneNumber(phoneNumber); + String szDisplayName = getDisplayNameByPhone(context, szPhoneNumber); + if (szDisplayName == null) { + LogUtils.d(TAG, String.format("Phone %s is not in contacts.", szPhoneNumber)); + szPhoneNumber = formatToSpacePhoneNumber(szPhoneNumber); + szDisplayName = getDisplayNameByPhone(context, szPhoneNumber); + if (szDisplayName == null) { + LogUtils.d(TAG, String.format("Phone %s is not in contacts.", szPhoneNumber)); + return false; + } + } + LogUtils.d(TAG, String.format("Phone %s is found in contacts %s.", szPhoneNumber, szDisplayName)); + return true; + } + + public static String formatToSpacePhoneNumber(String simpleNumber) { + // 去除所有空格和非数字字符 + StringBuilder sbSpaceNumber = new StringBuilder(); + String regex = "^1[0-9]{10}$"; + if (simpleNumber.matches(regex)) { + sbSpaceNumber.append(simpleNumber.substring(0, 3)); + sbSpaceNumber.append(" "); + sbSpaceNumber.append(simpleNumber.substring(3, 7)); + sbSpaceNumber.append(" "); + sbSpaceNumber.append(simpleNumber.substring(7, 11)); + } + return sbSpaceNumber.toString(); + } +} diff --git a/contacts/src/main/java/cc/winboll/studio/contacts/utils/EditTextIntUtils.java b/contacts/src/main/java/cc/winboll/studio/contacts/utils/EditTextIntUtils.java new file mode 100644 index 0000000..5b05034 --- /dev/null +++ b/contacts/src/main/java/cc/winboll/studio/contacts/utils/EditTextIntUtils.java @@ -0,0 +1,24 @@ +package cc.winboll.studio.contacts.utils; +import android.widget.EditText; +import cc.winboll.studio.libappbase.LogUtils; + +/** + * @Author ZhanGSKen@AliYun.Com + * @Date 2025/04/13 00:59:13 + * @Describe Int类型数字输入框工具集 + */ +public class EditTextIntUtils { + + public static final String TAG = "EditTextIntUtils"; + + public static int getIntFromEditText(EditText editText) { + try { + String sz = editText.getText().toString().trim(); + return Integer.parseInt(sz); + } catch (NumberFormatException e) { + LogUtils.d(TAG, e, Thread.currentThread().getStackTrace()); + return 0; + } + } + +} diff --git a/contacts/src/main/java/cc/winboll/studio/contacts/utils/IntUtils.java b/contacts/src/main/java/cc/winboll/studio/contacts/utils/IntUtils.java new file mode 100644 index 0000000..60c9639 --- /dev/null +++ b/contacts/src/main/java/cc/winboll/studio/contacts/utils/IntUtils.java @@ -0,0 +1,37 @@ +package cc.winboll.studio.contacts.utils; + +/** + * @Author ZhanGSKen@AliYun.Com + * @Date 2025/04/13 01:16:28 + * @Describe Int数字操作工具集 + */ +import cc.winboll.studio.libappbase.LogUtils; + +public class IntUtils { + + public static final String TAG = "IntUtils"; + + public static int getIntInRange(int origin, int range_a, int range_b) { + int min = Math.min(range_a, range_b); + int max = Math.max(range_a, range_b); + int res = Math.min(origin, max); + res = Math.max(res, min); + return res; + } + + public static void unittest_getIntInRange() { + LogUtils.d(TAG, String.format("getIntInRange(-100, 5, 10); %d", getIntInRange(-100, 5, 10))); + LogUtils.d(TAG, String.format("getIntInRange(8, 5, 10); %d", getIntInRange(8, 5, 10))); + LogUtils.d(TAG, String.format("getIntInRange(200, 5, 10); %d", getIntInRange(200, 5, 10))); + LogUtils.d(TAG, String.format("getIntInRange(-100, -5, 10); %d", getIntInRange(-100, -5, 10))); + LogUtils.d(TAG, String.format("getIntInRange(9, -5, 10); %d", getIntInRange(9, -5, 10))); + LogUtils.d(TAG, String.format("getIntInRange(100, -5, 10); %d", getIntInRange(100, -5, 10))); + + LogUtils.d(TAG, String.format("getIntInRange(500, 5, -10); %d", getIntInRange(500, 5, -10))); + LogUtils.d(TAG, String.format("getIntInRange(4, 5, -10); %d", getIntInRange(4, 5, -10))); + LogUtils.d(TAG, String.format("getIntInRange(-20, 5, -10); %d", getIntInRange(-20, 5, -10))); + LogUtils.d(TAG, String.format("getIntInRange(500, 50, 10); %d", getIntInRange(500, 50, 10))); + LogUtils.d(TAG, String.format("getIntInRange(30, 50, 10); %d", getIntInRange(30, 50, 10))); + LogUtils.d(TAG, String.format("getIntInRange(6, 50, 10); %d", getIntInRange(6, 50, 10))); + } +} diff --git a/contacts/src/main/java/cc/winboll/studio/contacts/utils/PhoneUtils.java b/contacts/src/main/java/cc/winboll/studio/contacts/utils/PhoneUtils.java new file mode 100644 index 0000000..9fa3e17 --- /dev/null +++ b/contacts/src/main/java/cc/winboll/studio/contacts/utils/PhoneUtils.java @@ -0,0 +1,27 @@ +package cc.winboll.studio.contacts.utils; + +/** + * @Author ZhanGSKen@AliYun.Com + * @Date 2025/02/26 15:21:48 + * @Describe PhoneUtils + */ +import android.Manifest; +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; +import androidx.core.app.ActivityCompat; + +public class PhoneUtils { + + public static final String TAG = "PhoneUtils"; + + public static void call(Context context, String phoneNumber) { + Intent intent = new Intent(Intent.ACTION_CALL); + intent.setData(android.net.Uri.parse("tel:" + phoneNumber)); + if (ActivityCompat.checkSelfPermission(context, Manifest.permission.CALL_PHONE) != PackageManager.PERMISSION_GRANTED) { + return; + } + context.startActivity(intent); + } + +} diff --git a/contacts/src/main/java/cc/winboll/studio/contacts/utils/RegexPPiUtils.java b/contacts/src/main/java/cc/winboll/studio/contacts/utils/RegexPPiUtils.java new file mode 100644 index 0000000..c00a9f2 --- /dev/null +++ b/contacts/src/main/java/cc/winboll/studio/contacts/utils/RegexPPiUtils.java @@ -0,0 +1,32 @@ +package cc.winboll.studio.contacts.utils; + +/** + * @Author ZhanGSKen@QQ.COM + * @Date 2024/12/09 19:00:21 + * @Describe .* 前置预防针 + regex pointer preventive injection + 简称 RegexPPi + */ +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class RegexPPiUtils { + + public static final String TAG = "RegexPPiUtils"; + + // + // 检验文本是否满足适合正则表达式模式计算 + // + public static boolean isPPiOK(String text) { + //String text = "这里是一些任意的文本内容"; + String regex = ".*"; + Pattern pattern = Pattern.compile(regex); + Matcher matcher = pattern.matcher(text); + /*if (matcher.matches()) { + System.out.println("文本满足该正则表达式模式"); + } else { + System.out.println("文本不满足该正则表达式模式"); + }*/ + return matcher.matches(); + } +} diff --git a/contacts/src/main/java/cc/winboll/studio/contacts/views/DuInfoTextView.java b/contacts/src/main/java/cc/winboll/studio/contacts/views/DuInfoTextView.java new file mode 100644 index 0000000..62d968c --- /dev/null +++ b/contacts/src/main/java/cc/winboll/studio/contacts/views/DuInfoTextView.java @@ -0,0 +1,68 @@ +package cc.winboll.studio.contacts.views; + +/** + * @Author ZhanGSKen@AliYun.Com + * @Date 2025/03/02 21:11:03 + * @Describe 云盾防御信息 + */ +import android.content.Context; +import android.os.Handler; +import android.os.Message; +import android.widget.TextView; +import cc.winboll.studio.contacts.beans.SettingsModel; +import cc.winboll.studio.contacts.dun.Rules; +import cc.winboll.studio.libappbase.LogUtils; + +public class DuInfoTextView extends TextView { + + public static final String TAG = "DuInfoTextView"; + + public static final int MSG_NOTIFY_INFO_UPDATE = 0; + + Context mContext; + + public DuInfoTextView(android.content.Context context) { + super(context); + } + + public DuInfoTextView(android.content.Context context, android.util.AttributeSet attrs) { + super(context, attrs); + initView(context); + } + + public DuInfoTextView(android.content.Context context, android.util.AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + } + + public DuInfoTextView(android.content.Context context, android.util.AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + } + + void initView(android.content.Context context) { + mContext = context; + updateInfo(); + } + + void updateInfo() { + LogUtils.d(TAG, "updateInfo()"); + SettingsModel settingsModel = Rules.getInstance(mContext).getSettingsModel(); + String info = String.format("(云盾防御值【%d/%d】)", settingsModel.getDunCurrentCount(), settingsModel.getDunTotalCount()); + setText(info); + } + + Handler mHandler = new Handler(){ + @Override + public void handleMessage(Message msg) { + super.handleMessage(msg); + if(msg.what == MSG_NOTIFY_INFO_UPDATE) { + updateInfo(); + } + } + + }; + + public void notifyInfoUpdate() { + LogUtils.d(TAG, "notifyInfoUpdate()"); + mHandler.sendMessage(mHandler.obtainMessage(MSG_NOTIFY_INFO_UPDATE)); + } +} diff --git a/contacts/src/main/java/cc/winboll/studio/contacts/views/LeftScrollView.java b/contacts/src/main/java/cc/winboll/studio/contacts/views/LeftScrollView.java new file mode 100644 index 0000000..2bebd41 --- /dev/null +++ b/contacts/src/main/java/cc/winboll/studio/contacts/views/LeftScrollView.java @@ -0,0 +1,220 @@ +package cc.winboll.studio.contacts.views; + +/** + * @Author ZhanGSKen@AliYun.Com + * @Date 2025/03/04 10:51:50 + * @Describe CustomHorizontalScrollView + */ +import android.content.Context; +import android.util.AttributeSet; +import android.view.MotionEvent; +import android.view.View; +import android.widget.Button; +import android.widget.HorizontalScrollView; +import android.widget.LinearLayout; +import android.widget.TextView; +import cc.winboll.studio.contacts.R; +import cc.winboll.studio.libappbase.LogUtils; + +public class LeftScrollView extends HorizontalScrollView { + + public static final String TAG = "LeftScrollView"; + + private LinearLayout contentLayout; + private LinearLayout toolLayout; + private TextView textView; + private Button editButton; + private Button deleteButton; + private Button upButton; + private Button downButton; + private float mStartX; + private float mEndX; + private boolean isScrolling = false; + private int nScrollAcceptSize; + + public LeftScrollView(Context context) { + super(context); + init(); + } + + public LeftScrollView(Context context, AttributeSet attrs) { + super(context, attrs); + init(); + } + + public LeftScrollView(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + init(); + } + + public void addContentLayout(TextView textView) { + contentLayout.addView(textView, LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.MATCH_PARENT); + } + + public void setContentWidth(int contentWidth) { + LinearLayout.LayoutParams layoutParams = (LinearLayout.LayoutParams) contentLayout.getLayoutParams(); + layoutParams.width = contentWidth; + contentLayout.setLayoutParams(layoutParams); + + } + + private void init() { + View viewMain = inflate(getContext(), R.layout.view_left_scroll, null); + + // 创建内容布局 + contentLayout = viewMain.findViewById(R.id.content_layout); + toolLayout = viewMain.findViewById(R.id.action_layout); + + //LogUtils.d(TAG, String.format("getWidth() %d", getWidth())); + + addView(viewMain); + + // 创建编辑按钮 + editButton = viewMain.findViewById(R.id.edit_btn); + // 创建删除按钮 + deleteButton = viewMain.findViewById(R.id.delete_btn); + // 向上按钮 + upButton = viewMain.findViewById(R.id.up_btn); + // 向下按钮 + downButton = viewMain.findViewById(R.id.down_btn); + + // 编辑按钮点击事件 + editButton.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + if (onActionListener != null) { + onActionListener.onEdit(); + } + } + }); + + // 删除按钮点击事件 + deleteButton.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + if (onActionListener != null) { + onActionListener.onDelete(); + } + } + }); + // 编辑按钮点击事件 + upButton.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + if (onActionListener != null) { + onActionListener.onUp(); + } + } + }); + + // 删除按钮点击事件 + downButton.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + if (onActionListener != null) { + onActionListener.onDown(); + } + } + }); + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + switch (event.getAction()) { + case MotionEvent.ACTION_DOWN: + LogUtils.d(TAG, "ACTION_DOWN"); + mStartX = event.getX(); +// isScrolling = false; + break; + case MotionEvent.ACTION_MOVE: + //LogUtils.d(TAG, "ACTION_MOVE"); +// float currentX = event.getX(); +// float deltaX = mStartX - currentX; +// //mLastX = currentX; +// if (Math.abs(deltaX) > 0) { +// isScrolling = true; +// } + break; + case MotionEvent.ACTION_UP: + case MotionEvent.ACTION_CANCEL: + if (getScrollX() > 0) { + LogUtils.d(TAG, "ACTION_UP"); + mEndX = event.getX(); + LogUtils.d(TAG, String.format("mStartX %f, mEndX %f", mStartX, mEndX)); + if (mEndX < mStartX) { + LogUtils.d(TAG, String.format("mEndX >= mStartX \ngetScrollX() %d", getScrollX())); + //if (getScrollX() > editButton.getWidth()) { + if (Math.abs(mStartX - mEndX) > editButton.getWidth()) { + smoothScrollToRight(); + } else { + smoothScrollToLeft(); + } + } else { + LogUtils.d(TAG, String.format("mEndX >= mStartX \ngetScrollX() %d", getScrollX())); + //if (getScrollX() > deleteButton.getWidth()) { + if (Math.abs(mEndX - mStartX) > deleteButton.getWidth()) { + smoothScrollToLeft(); + } else { + smoothScrollToRight(); + } + } + } + break; + } + return super.onTouchEvent(event); + } + + void smoothScrollToRight() { + mEndX = 0; + mStartX = 0; + View childView = getChildAt(0); + if (childView != null) { + // 计算需要滑动到最右边的距离 + int scrollToX = childView.getWidth() - getWidth(); + // 确保滑动距离不小于0 + final int scrollToX2 = Math.max(0, scrollToX); + // 平滑滑动到最右边 + post(new Runnable() { + @Override + public void run() { + smoothScrollTo(scrollToX2, 0); + LogUtils.d(TAG, "smoothScrollTo(0, 0);"); + } + }); + LogUtils.d(TAG, "smoothScrollTo(scrollToX, 0);"); + } + } + + void smoothScrollToLeft() { + mEndX = 0; + mStartX = 0; + // 在手指抬起时,使用 post 方法调用 smoothScrollTo(0, 0) + post(new Runnable() { + @Override + public void run() { + smoothScrollTo(0, 0); + LogUtils.d(TAG, "smoothScrollTo(0, 0);"); + } + }); + } + + // 设置文本内容 + public void setText(CharSequence text) { + textView.setText(text); + } + + // 定义回调接口 + public interface OnActionListener { + void onEdit(); + void onDelete(); + void onUp(); + void onDown(); + } + + private OnActionListener onActionListener; + + public void setOnActionListener(OnActionListener listener) { + this.onActionListener = listener; + } +} + diff --git a/contacts/src/main/java/cc/winboll/studio/contacts/views/ScrollDoView.java b/contacts/src/main/java/cc/winboll/studio/contacts/views/ScrollDoView.java new file mode 100644 index 0000000..1a915d4 --- /dev/null +++ b/contacts/src/main/java/cc/winboll/studio/contacts/views/ScrollDoView.java @@ -0,0 +1,14 @@ +package cc.winboll.studio.contacts.views; + +/** + * @Author ZhanGSKen@AliYun.Com + * @Date 2025/03/19 14:04:20 + * @Describe 云盾滑视度热备控件 + */ +public class ScrollDoView { + + public static final String TAG = "ScrollDoView"; + + + +} diff --git a/contacts/src/main/res/drawable/ic_call.xml b/contacts/src/main/res/drawable/ic_call.xml new file mode 100644 index 0000000..c5802bb --- /dev/null +++ b/contacts/src/main/res/drawable/ic_call.xml @@ -0,0 +1,11 @@ + + + + + \ No newline at end of file diff --git a/contacts/src/main/res/drawable/recycler_view_border.xml b/contacts/src/main/res/drawable/recycler_view_border.xml new file mode 100644 index 0000000..d538cab --- /dev/null +++ b/contacts/src/main/res/drawable/recycler_view_border.xml @@ -0,0 +1,9 @@ + + + + + + diff --git a/app/src/main/res/layout/activity_about.xml b/contacts/src/main/res/layout/activity_about.xml similarity index 87% rename from app/src/main/res/layout/activity_about.xml rename to contacts/src/main/res/layout/activity_about.xml index 787ec5b..425769a 100644 --- a/app/src/main/res/layout/activity_about.xml +++ b/contacts/src/main/res/layout/activity_about.xml @@ -6,10 +6,10 @@ android:layout_width="match_parent" android:layout_height="match_parent"> - + android:id="@+id/toolbar"/> - + diff --git a/contacts/src/main/res/layout/activity_main.xml b/contacts/src/main/res/layout/activity_main.xml index 5376c86..88441cd 100644 --- a/contacts/src/main/res/layout/activity_main.xml +++ b/contacts/src/main/res/layout/activity_main.xml @@ -12,16 +12,26 @@ android:layout_height="wrap_content" android:id="@+id/activitymainToolbar1"/> - + android:padding="10dp" + android:layout_weight="1.0"> + + + + + + - diff --git a/contacts/src/main/res/layout/activity_phone_call.xml b/contacts/src/main/res/layout/activity_phone_call.xml index 9768e9f..327130c 100644 --- a/contacts/src/main/res/layout/activity_phone_call.xml +++ b/contacts/src/main/res/layout/activity_phone_call.xml @@ -1,95 +1,98 @@ + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical" + tools:context=".phonecallui.PhoneCallActivity"> - + - + + - + - + - + - + - + - + - - + + + + + - - \ No newline at end of file diff --git a/contacts/src/main/res/layout/activity_settings.xml b/contacts/src/main/res/layout/activity_settings.xml index 2122230..d1c90ce 100644 --- a/contacts/src/main/res/layout/activity_settings.xml +++ b/contacts/src/main/res/layout/activity_settings.xml @@ -1,51 +1,309 @@ - - - + android:layout_height="wrap_content"> - - - + android:id="@+id/activitymainToolbar1"/> -