diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 00000000..b84ee7d2 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,3 @@ +# These are supported funding model platforms + +custom: https://gitee.com/pp/SmsForwarder/wikis/pages?sort_id=4912193&doc_id=1821427 diff --git a/.github/workflows/issue_check.yml b/.github/workflows/issue_check.yml new file mode 100644 index 00000000..e36da2f4 --- /dev/null +++ b/.github/workflows/issue_check.yml @@ -0,0 +1,18 @@ +name: No Free usage issue checker # Action名字。可以自定义 + +on: + issues: + types: [ opened, reopened ] # 在issue打开和重新打开时调用 + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + - name: Check issue actor # 步骤名字。可以自定义。 + uses: fluttercandies/no-free-usage-action@v1.0.1 + with: + token: ${{ secrets.GITHUB_TOKEN }} # 由GitHub提供的临时Token,必须在此处进行传递,且必须为这个值。 + forked: '--no-forked' + words: To support our project, please file the issue after you starred the repo. Thanks! 🙂 diff --git a/.gitignore b/.gitignore index 0c6511e8..bccd1136 100644 --- a/.gitignore +++ b/.gitignore @@ -1,10 +1,19 @@ -.idea/ -.gradle -.git -build -local.properties -gradle.properties *.iml +.gradle +/LocalRepository +/keystores +/local.properties +/.idea/caches +/.idea/codeStyles +/.idea/inspectionProfiles +/.idea/libraries +/.idea/dictionaries +/.idea/markdown-navigator +/.idea/*.xml +.DS_Store +/build +/captures +.externalNativeBuild *.project */*.project *.classpath @@ -21,3 +30,7 @@ gradle.properties *.bak /pic/working_principle.drawio /app/debug +/.idea +/app/mapping.txt +/app/seeds.txt +/app/unused.txt diff --git a/.gitmodules b/.gitmodules deleted file mode 100644 index f074c9fa..00000000 --- a/.gitmodules +++ /dev/null @@ -1,3 +0,0 @@ -[submodule "keystore"] - path = keystore - url = https://github.com/pppscn/keystore.git diff --git a/PRIVACY b/PRIVACY new file mode 100644 index 00000000..e1a86b9f --- /dev/null +++ b/PRIVACY @@ -0,0 +1,25 @@ +BSD 2-Clause License + +Copyright (c) 2021, pppscn +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/README.md b/README.md index b1da7e58..f4db97ae 100644 --- a/README.md +++ b/README.md @@ -6,23 +6,19 @@ [![GitHub release](https://img.shields.io/github/release/pppscn/SmsForwarder.svg)](https://github.com/pppscn/SmsForwarder/releases) [![GitHub stars](https://img.shields.io/github/stars/pppscn/SmsForwarder)](https://github.com/pppscn/SmsForwarder/stargazers) [![GitHub forks](https://img.shields.io/github/forks/pppscn/SmsForwarder)](https://github.com/pppscn/SmsForwarder/network/members) [![GitHub issues](https://img.shields.io/github/issues/pppscn/SmsForwarder)](https://github.com/pppscn/SmsForwarder/issues) [![GitHub license](https://img.shields.io/github/license/pppscn/SmsForwarder)](https://github.com/pppscn/SmsForwarder/blob/main/LICENSE) -短信转发器——监控Android手机短信、来电、APP通知,并根据指定规则转发到其他手机:钉钉机器人、企业微信群机器人、飞书机器人、企业微信应用消息、邮箱、bark、webhook、Telegram机器人、Server酱、PushPlus、手机短信等。 - -### 下载地址 - -> ⚠ 首发地址:https://github.com/pppscn/SmsForwarder/releases +-------- -> ⚠ 国内镜像:https://gitee.com/pp/SmsForwarder/releases +短信转发器——不仅只转发短信,备用机必备神器! -> ⚠ 网盘下载:https://wws.lanzoui.com/b025yl86h 访问密码:`pppscn` +监控Android手机短信、来电、APP通知,并根据指定规则转发到其他手机:钉钉机器人、企业微信群机器人、飞书机器人、企业微信应用消息、邮箱、bark、webhook、Telegram机器人、Server酱、PushPlus、手机短信等。 -> ⚠ 酷安应用市场:https://www.coolapk.com/apk/com.idormy.sms.forwarder +包括主动控制服务端与客户端,让你轻松远程发短信、查短信、查通话、查话簿、查电量等。(V3.0 新增) -### 使用文档 +> 注意:从`2022-06-06`开始,原`Java版`的代码归档到`v2.x`分支,不再更新! -> ⚠ 首发地址:https://github.com/pppscn/SmsForwarder/wiki +> 1、从`v2.x`到`v3.x`不是简单的功能迭代,采用`kotlin`全新重构了(不是单纯的迁移代码,起初我也是这么认为的),由于我是第一次使用`kotlin`开发(Java版也是第一次),到处踩坑,每一行代码都是度娘手把手教会我的,所以`v3.x`版本可能一开始并不稳定。另外,眼睛葡萄膜炎还没好,晚上不敢肝,中间停摆了个把月,进度缓慢,历时2个月终于让`V3.x`顺产了! -> ⚠ 国内镜像:https://gitee.com/pp/SmsForwarder/wikis/pages +> 2、如果目前`v2.x`用的好好的没必要升级(之前也是这么建议大家的,没必要每版必跟,除非你急需新功能) -------- @@ -38,75 +34,39 @@ -------- -## 特点和准则: - -**简单** 只做两件事:监听手机短信/来电/APP通知 --> 根据指定规则转发 - -由此带来的好处: - -* 简洁:(当时用Pad的时候,看手机验证码各种不方便,网上搜了好久也没有理想的解决方案) - > + AirDroid:手机管理工具功能太多,看着都耗电,权限太多,数据经过三方,账号分级 - > + IFTTT:功能太多,看着耗电,权限太多,数据经过三方,收费 - > + 还有一些其他的APP(例如:Tasker)也是这些毛病 -* 省电:运行时只监听广播,有短信才执行转发,并记录最近n条的转发内容和转发状态 -* 健壮:越简单越不会出错(UNIX设计哲学),就越少崩溃,运行越稳定持久 - -### 工作流程: - -![工作流程](pic/working_principle.png "工作流程") - -### 功能列表: - -- [x] 监听短信,按规则转发(规则:什么短信内容/来源转发到哪里) -- [x] 转发到钉钉机器人(支持:单个钉钉群,@某人) -- [x] 转发到邮箱(支持:SMTP) -- [x] 转发到Bark(支持:验证码/动态密码自动复制) -- [x] 转发到webhook(支持:单个web页面([向设置的url发送POST/GET请求](doc/POST_WEB.md))) -- [x] 转发到企业微信群机器人 -- [x] 转发到企业微信应用消息 -- [x] 转发到ServerChan(Server酱·Turbo版) -- [x] 转发到Telegram机器人(支持设置Socks5/Http代理、POST/GET、[CloudFlare反向代理](doc/TGBOT_cfwork_reverse_proxy.md)) -- [x] 转发到其他手机短信【注意:非免费的,转发短信运营商有收费的,建议没有网络时启用,并设置好内容过滤规则】 -- [x] 在线检测新版本、升级 -- [x] 清理缓存 -- [x] 兼容 Android 5.xx、6.xx、7.xx、8.xx、9.xx、10.xx、11.xx、12.xx -- [x] 支持双卡手机,增加卡槽标识/运营商/手机号(如果能获取的话) -- [x] 支持多重匹配规则 -- [x] 支持标注卡槽号码(优先使用)、设备信息;自定义转发信息模版 -- [x] 支持正则匹配规则 -- [x] 支持卡槽匹配规则 -- [x] 转发未接来电提醒(固定sim1卡发出提醒) -- [x] 接口请求失败后延时重试5次(可配置间隔时间,成功一次则终止重试) -- [x] 转发到飞书机器人 -- [x] 自定义 Scheme(forwarder://main)用于唤起App -- [x] 电池电量、状态变化预警 -- [x] 多语言支持(目前:中文、英文) -- [x] 增加配置导出导入功能(一键克隆) -- [x] 监听其他APP通知信息并转发(可自动消除) -- [x] 转发到PushPlus -- [x] 转发规则上允许自定义模板(留空则取全局设置) -- [x] 转发规则上支持配置正则替换内容 -- [x] 转发到 Gotify发送通道(自主推送通知服务) -- [x] 被动接收本地 HttpServer -- [x] 主动轮询远程 SmsHub Api(v2.5.0+已删除) -- [x] 适配暗夜模式 +## 工作流程: + +![工作流程](https://images.gitee.com/uploads/images/2022/0126/133916_ca965452_16273.png "working_principle.png") -------- -### 应用截图: +## 界面预览: -| 前台服务常驻状态栏 | 应用主界面 | 发送通道 | 转发规则 | -| :--: | :--: | :--: | :--: | -| ![前台服务常驻状态栏](pic/taskbar.jpg "前台服务常驻状态栏") | ![应用主界面](pic/main.jpg "应用主界面") | ![发送通道](pic/sender.png "发送通道") | ![转发规则](pic/rule.jpg "转发规则") | -| 转发规则--短信转发 | 转发规则--通话记录 | 转发规则--APP通知 | 转发日志详情 | -| ![短信转发](pic/rule_sms.jpg "短信转发") | ![通话转发](pic/rule_call.jpg "通话转发") | ![通知转发](pic/rule_app.jpg "通知转发") | ![转发日志详情](pic/maindetail.jpg "转发日志详情") | -| 设置界面--总开关 | 设置界面--电量监控&保活措施 | 设置界面--个性设置 | 一键克隆(配置导出导入) | -| ![设置界面--总开关](pic/setting_1.jpg "设置界面--总开关") | ![设置界面--电量监控&保活措施](pic/setting_2.jpg "设置界面--电量监控&保活措施") | ![设置界面--个性设置](pic/setting_3.jpg "设置界面--个性设置") | ![配置导出导入功能(一键克隆)](pic/clone.jpg "配置导出导入功能(一键克隆)") | +![界面预览](https://images.gitee.com/uploads/images/2022/0606/133422_808b4589_16273.png "界面预览.png") 更多截图参见 https://github.com/pppscn/SmsForwarder/wiki -------- +## 下载地址 + +> ⚠ 首发地址:https://github.com/pppscn/SmsForwarder/releases + +> ⚠ 国内镜像:https://gitee.com/pp/SmsForwarder/releases + +> ⚠ 网盘下载:https://wws.lanzoui.com/b025yl86h 访问密码:`pppscn` + +> ⚠ 酷安应用市场:https://www.coolapk.com/apk/com.idormy.sms.forwarder + +-------- + +## 使用文档【新用户必看!】 + +> ⚠ GitHub Wiki:https://github.com/pppscn/SmsForwarder/wiki + +> ⚠ Gitee Wiki:https://gitee.com/pp/SmsForwarder/wikis/pages +-------- + ## 反馈与建议: + 提交issues 或 pr @@ -116,18 +76,16 @@ | ---- | ---- | ---- | ---- | | ![钉钉客户群](pic/dingtalk.png "钉钉客户群") | ![QQ交流群:562854376](pic/qqgroup_1.jpg "QQ交流群:562854376") | ![QQ交流群:31330492](pic/qqgroup_2.jpg "QQ交流群:31330492") | ![企业微信群](pic/qywechat.png "企业微信群") | +PS.如果QQ群已满员,请看群简介加入其他群 + ## 感谢 > 本项目得到以下项目的支持与帮助,在此表示衷心的感谢! -+ https://github.com/xiaoyuanhost/TranspondSms (基于此项目优化改造) -+ https://github.com/square/okhttp (网络请求) ++ https://github.com/xiaoyuanhost/TranspondSms (项目原型) ++ https://github.com/xuexiangjys/XUI (UI框架) + https://github.com/xuexiangjys/XUpdateAPI (在线升级) -+ https://github.com/mailhu/emailkit (邮件发送) -+ https://github.com/alibaba/fastjson (Json解析) + https://github.com/getActivity/XXPermissions (权限请求框架) -+ https://github.com/Xcreen/RestSMS (被动接收本地API方案) -+ ~~https://github.com/juancrescente/SMSHub (主动轮询远程API方案,v2.5.0+删除)~~ + https://github.com/mainfunx/frpc_android (内网穿透) + [GitHub license](https://jb.gg/OpenSourceSupport) (License Certificate for JetBrains All Products Pack) diff --git a/README_en.md b/README_en.md index 91474ed4..372bd839 100644 --- a/README_en.md +++ b/README_en.md @@ -6,23 +6,13 @@ [![GitHub release](https://img.shields.io/github/release/pppscn/SmsForwarder.svg)](https://github.com/pppscn/SmsForwarder/releases) [![GitHub stars](https://img.shields.io/github/stars/pppscn/SmsForwarder)](https://github.com/pppscn/SmsForwarder/stargazers) [![GitHub forks](https://img.shields.io/github/forks/pppscn/SmsForwarder)](https://github.com/pppscn/SmsForwarder/network/members) [![GitHub issues](https://img.shields.io/github/issues/pppscn/SmsForwarder)](https://github.com/pppscn/SmsForwarder/issues) [![GitHub license](https://img.shields.io/github/license/pppscn/SmsForwarder)](https://github.com/pppscn/SmsForwarder/blob/main/LICENSE) -SmsForwarder - listens to SMS, incoming calls, and App notifications on Android mobile devices, and forward according to user defined rules to another App/device, including DingTalk, WeCom and WeCom Group Bot, Feishi Bot, E-mail, Bark, Webhook, Telegram Bot, ServerChan, PushPlus, SMS, etc. - -### Download - -> ⚠ Repo address: https://github.com/pppscn/SmsForwarder/releases - -> ⚠ Repo mirror in China: https://gitee.com/pp/SmsForwarder/releases - -> ⚠ Internet storage: https://wws.lanzoui.com/b025yl86h, access password: `pppscn` - -> ⚠ CoolAPK.com: https://www.coolapk.com/apk/com.idormy.sms.forwarder +-------- -### Manual +SmsForwarder - Not only forwarding text messages, but also a must-have for backup devices! -> ⚠ GitHub: https://github.com/pppscn/SmsForwarder/wiki +listens to SMS, incoming calls, and App notifications on Android mobile devices, and forward according to user defined rules to another App/device, including DingTalk, WeCom and WeCom Group Bot, Feishi Bot, E-mail, Bark, Webhook, Telegram Bot, ServerChan, PushPlus, SMS, etc. -> ⚠ Gitee: https://gitee.com/pp/SmsForwarder/wikis/pages +Including active control of the server and client, allowing you to easily and remotely send text messages, check text messages, check calls, check the phone book, check the battery, etc. -------- @@ -38,74 +28,35 @@ SmsForwarder - listens to SMS, incoming calls, and App notifications on Android -------- -## Features and standards +## Workflow: + +![Workflow](pic/working_principle_en.png "Workflow") -**Simplicity** - `SmsForwarder` does two things only: Listen to "SMS service/Incoming calls/App notifications", and forward according to rules specified by user. +-------- -Benefit by simplicity: +## Screenshots : -* **E**fficient: (It's inconvenient to read the security codes such as OTP on a mobile phone, when you are using another device; and no solution satisfices our needs) +![界面预览](https://images.gitee.com/uploads/images/2022/0606/133422_808b4589_16273.png "界面预览.png") - > + AirDroid: Too many functionalities, power consuming, requiring to many permissions, data relayed by a 3rd party, paid premium service... - > + IFTTT: Too many functionalities, power consuming, requiring to many permissions, data relayed by a 3rd party, paid premium service... - > + And other Apps (e.g. Tasker) with similar features. +See more screenshots:https://github.com/pppscn/SmsForwarder/wiki -* **E**nergy friendly: listens to broadcast only when running, and forwards message only when texts are received and logs recent forwarding contents and status. -* **E**ndurance: "Simplicity is the Ultimate Sophistication." The simpler the code is, the less it errs or crashes; that is what make the app runs longer. +-------- -### Workflow: +## Download -![Workflow](pic/working_principle_en.png "Workflow") +> ⚠ Repo address: https://github.com/pppscn/SmsForwarder/releases -### Features: - -- [x] Listen to SMS service, and forward according to user-defined rules (SMS contents to destination); -- [x] Forward to DingTalk Bot (to a group chat and @SOMBODY); -- [x] Forward to E-mail (SMTP with SSL encryption); -- [x] Forward to Bark; -- [x] Forward to webhook (a single web page [sending POST/GET requests to a designated URL](doc/POST_WEB.md)); -- [x] Forward to WeCom Bots; -- [x] Forward to WeCom enterprise channels; -- [x] Forward to ServerChan·Turbo; -- [x] Forward to Telegram Bots (Proxy support ready); -- [x] Forward to another mobile phone via SMS [Note: Paid service, carriers may charge for SMS forwarding. SMS forwarding should apply with filtered rules when device has no Internet access.] -- [x] Check for new version and upgrade; -- [x] Cache purge; -- [x] Compatible with Android 5.xx, 6.xx, 7.xx, 8.xx, 9.xx, and 10.xx; -- [x] Support for dual SIM slots smartphones and label different slots/carrier/phone number (if available); -- [x] Support for multi-level rules; -- [x] Support for customized labeling of SIM slots and device, and customized forwarding templates; -- [x] Support for rules with regular expression -- [x] Support for rules for different SIM slots; -- [x] Forward missed call information (forwarded by SIM1 slot by default); -- [x] Retry 5 times after a failed request (customized interval time, stop retrying once successfully request); -- [x] Forward to FeiShu Bot; -- [x] Customized scheme (forwarder://main) wake up other Apps; -- [x] Monitor of battery status changes; -- [x] I18n support (Chinese and English currently); -- [x] Support for setting import and export functions (One-key cloning); -- [x] Listen to notifications of other Apps and forward; -- [x] Forward to PushPlus; -- [x] Support for customized template of forwarding rules (default template overrides if left blank); -- [x] Support for variables in regular expression of forwarding rules; -- [x] 转发到 Gotify发送通道(自主推送通知服务) -- [x] 被动接收本地 HttpServer -- [x] 主动轮询远程 SmsHub Api(v2.5.0+已删除) -- [x] 适配暗夜模式 +> ⚠ Repo mirror in China: https://gitee.com/pp/SmsForwarder/releases --------- +> ⚠ Internet storage: https://wws.lanzoui.com/b025yl86h, access password: `pppscn` + +> ⚠ CoolAPK.com: https://www.coolapk.com/apk/com.idormy.sms.forwarder -### Screenshots : +## Manual -| 前台服务常驻状态栏 | 应用主界面 | 发送通道 | 转发规则 | -| :--: | :--: | :--: | :--: | -| ![前台服务常驻状态栏](pic/taskbar.jpg "前台服务常驻状态栏") | ![应用主界面](pic/main.jpg "应用主界面") | ![发送通道](pic/sender.png "发送通道") | ![转发规则](pic/rule.jpg "转发规则") | -| 转发规则--短信转发 | 转发规则--通话记录 | 转发规则--APP通知 | 转发日志详情 | -| ![短信转发](pic/rule_sms.jpg "短信转发") | ![通话转发](pic/rule_call.jpg "通话转发") | ![通知转发](pic/rule_app.jpg "通知转发") | ![转发日志详情](pic/maindetail.jpg "转发日志详情") | -| 设置界面--总开关 | 设置界面--电量监控&保活措施 | 设置界面--个性设置 | 一键克隆(配置导出导入) | -| ![设置界面--总开关](pic/setting_1.jpg "设置界面--总开关") | ![设置界面--电量监控&保活措施](pic/setting_2.jpg "设置界面--电量监控&保活措施") | ![设置界面--个性设置](pic/setting_3.jpg "设置界面--个性设置") | ![配置导出导入功能(一键克隆)](pic/clone.jpg "配置导出导入功能(一键克隆)") | +> ⚠ GitHub: https://github.com/pppscn/SmsForwarder/wiki -更多截图参见 https://github.com/pppscn/SmsForwarder/wiki +> ⚠ Gitee: https://gitee.com/pp/SmsForwarder/wikis/pages -------- @@ -118,18 +69,16 @@ Benefit by simplicity: | ---- | ---- | ---- | ---- | | ![钉钉客户群](pic/dingtalk.png "钉钉客户群") | ![QQ交流群:562854376](pic/qqgroup_1.jpg "QQ交流群:562854376") | ![QQ交流群:31330492](pic/qqgroup_2.jpg "QQ交流群:31330492") | ![企业微信群](pic/qywechat.png "企业微信群") | +PS.If the QQ group is full, please see the group introduction to join other groups + ## Acknowledgements > Thanks to the projects below, `SmsForwarder` won't exists without them! + https://github.com/xiaoyuanhost/TranspondSms (Foundation of `SmsForwarder`) -+ https://github.com/square/okhttp (http communications) ++ https://github.com/xuexiangjys/XUI (UI Framework) + https://github.com/xuexiangjys/XUpdateAPI (online update) -+ https://github.com/mailhu/emailkit (email sending) -+ https://github.com/alibaba/fastjson (json parsing) + https://github.com/getActivity/XXPermissions (permission requiring) -+ https://github.com/Xcreen/RestSMS(被动接收本地API方案) -+ ~~https://github.com/juancrescente/SMSHub(主动轮询远程API方案,v2.5.0+删除)~~ + https://github.com/mainfunx/frpc_android (内网穿透) + [GitHub license](https://jb.gg/OpenSourceSupport) (License Certificate for JetBrains All Products Pack) diff --git a/_config.yml b/_config.yml deleted file mode 100644 index c4192631..00000000 --- a/_config.yml +++ /dev/null @@ -1 +0,0 @@ -theme: jekyll-theme-cayman \ No newline at end of file diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 00000000..796b96d1 --- /dev/null +++ b/app/.gitignore @@ -0,0 +1 @@ +/build diff --git a/app/build.gradle b/app/build.gradle index f2b6276b..78e5b5fb 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,4 +1,11 @@ -apply plugin: 'com.android.application' +plugins { + id 'com.android.application' + id 'kotlin-android' + id 'kotlin-kapt' + id 'kotlin-parcelize' + id 'img-optimizer' + id 'com.yanzhenjie.andserver' +} def keyProps = new Properties() def keyPropsFile = rootProject.file('keystore/keystore.properties') @@ -6,32 +13,47 @@ if (keyPropsFile.exists()) { keyProps.load(new FileInputStream(keyPropsFile)) } -// 读取version.properties -def versionProps = new Properties() -def versionPropsFile = rootProject.file('version.properties') -if (versionPropsFile.exists()) { - versionProps.load(new FileInputStream(versionPropsFile)) +//打包时,记得设置true启用 +if (isNeedPackage.toBoolean() && isUseBooster.toBoolean()) { + apply plugin: 'com.didiglobal.booster' } android { //noinspection GradleDependency - buildToolsVersion '32.0.0' - compileSdkVersion 32 + buildToolsVersion build_versions.build_tools + compileSdkVersion build_versions.target_sdk + compileOptions { - sourceCompatibility 11 - targetCompatibility 11 + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + buildFeatures { + viewBinding true } + defaultConfig { applicationId "com.idormy.sms.forwarder" - minSdkVersion 21 - targetSdkVersion 32 - versionCode versionProps['versionCode'].toInteger() - versionName versionProps['versionName'] - testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner' + minSdkVersion build_versions.min_sdk + targetSdkVersion build_versions.target_sdk + versionCode build_versions.version_code + versionName build_versions.version_name + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + + multiDexEnabled true + vectorDrawables.useSupportLibrary = true + + javaCompileOptions { + annotationProcessorOptions { + arguments = [moduleName: project.getName()] + } + } + ndk { - abiFilters 'armeabi-v7a', 'arm64-v8a', 'x86'//, 'x86_64' + abiFilters 'armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64' } } + signingConfigs { release { keyAlias keyProps['keyAlias'] @@ -46,31 +68,81 @@ android { storePassword keyProps['storePassword'] } } + buildTypes { release { - minifyEnabled false - //shrinkResources true + minifyEnabled true + shrinkResources true proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' - signingConfig signingConfigs.release + if (isNeedPackage.toBoolean()) { + signingConfig signingConfigs.release + if (file('local.properties').exists()) { + Properties properties = new Properties() + properties.load(project.rootProject.file('local.properties').newDataInputStream()) + def appID = properties.getProperty("APP_ID_UMENG") + if (appID != null) { + buildConfigField "String", "APP_ID_UMENG", appID + } else { + buildConfigField "String", "APP_ID_UMENG", '"60254fc7425ec25f10f4293e"' + } + } else { + buildConfigField "String", "APP_ID_UMENG", '"60254fc7425ec25f10f4293e"' + } + } else { + signingConfig signingConfigs.debug + buildConfigField "String", "APP_ID_UMENG", '"60254fc7425ec25f10f4293e"' + } } debug { - minifyEnabled false - //shrinkResources true + minifyEnabled true + shrinkResources true proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' - signingConfig signingConfigs.debug + if (isNeedPackage.toBoolean()) { + signingConfig signingConfigs.release + if (file('local.properties').exists()) { + Properties properties = new Properties() + properties.load(project.rootProject.file('local.properties').newDataInputStream()) + def appID = properties.getProperty("APP_ID_UMENG") + if (appID != null) { + buildConfigField "String", "APP_ID_UMENG", appID + } else { + buildConfigField "String", "APP_ID_UMENG", '"60254fc7425ec25f10f4293e"' + } + } else { + buildConfigField "String", "APP_ID_UMENG", '"60254fc7425ec25f10f4293e"' + } + } else { + signingConfig signingConfigs.debug + buildConfigField "String", "APP_ID_UMENG", '"60254fc7425ec25f10f4293e"' + } } + /*debug { + debuggable true + minifyEnabled false + + signingConfig signingConfigs.debug + buildConfigField "String", "APP_ID_UMENG", '"60254fc7425ec25f10f4293e"' + }*/ } + //ABI配置——按CPU架构分别打包 splits { abi { enable true reset() - include 'armeabi-v7a', 'arm64-v8a', 'x86'//, 'x86_64' + include 'armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64' universalApk true } } def abiCodes = ['universal': 1, 'armeabi-v7a': 2, 'arm64-v8a': 3, 'x86': 4, 'x86_64': 5] packagingOptions { + //去除FrpcLib的so,用时下载并动态加载 + if (isNeedPackage.toBoolean()) { + exclude 'lib/armeabi-v7a/libgojni.so' + exclude 'lib/arm64-v8a/libgojni.so' + exclude 'lib/x86/libgojni.so' + exclude 'lib/x86_64/libgojni.so' + } resources { pickFirst 'META-INF/LICENSE.md' pickFirst 'META-INF/NOTICE.md' @@ -82,6 +154,7 @@ android { variant.outputs.each { output -> def date = new Date().format("yyyyMMdd", TimeZone.getTimeZone("GMT+08")) + //noinspection GrDeprecatedAPIUsage def abiName = output.getFilter(com.android.build.OutputFile.ABI) if (abiName == null) abiName = "universal" output.versionCodeOverride = abiCodes.get(abiName, 0) * 100000 + variant.versionCode @@ -89,8 +162,8 @@ android { } } - lint { - checkReleaseBuilds false + lintOptions { + abortOnError false } sourceSets { @@ -101,110 +174,97 @@ android { } +dependencies { + implementation fileTree(dir: 'libs', include: ['*.jar']) + //frpc + implementation files('libs/frpclib.aar') -task upgradeVersion { - group 'help' - description '构建新版本' - doLast { - println("---自动升级版本号---\n") - String oldVersionCode = versionProps['versionCode'] - String oldVersionName = versionProps['versionName'] - if (oldVersionCode == null || oldVersionName == null || - oldVersionCode.isEmpty() || oldVersionName.isEmpty()) { - println("error:版本号不能为空") - return - } - versionProps['versionCode'] = String.valueOf(versionProps['versionCode'].toInteger() + 1) - String str = versionProps['versionName'].toString() - versionProps['versionName'] = str.substring(0, str.lastIndexOf('.') + 1) + - (str.substring(str.lastIndexOf('.') + 1).toInteger() + 1) - String tip = - "版本号从$oldVersionName($oldVersionCode)升级到${versionProps['versionName']}(${versionProps['versionCode']})" - println(tip) - - def writer = new FileWriter(versionPropsFile) - versionProps.store(writer, null) - writer.flush() - writer.close() - def tag = "v${versionProps['versionName']}" - cmdExecute("git pull") - cmdExecute("git add version.properties") - cmdExecute("git commit -m \"版本号升级为:$tag\"") - cmdExecute("git push origin") - cmdExecute("git tag $tag") - cmdExecute("git push origin $tag") - } -} + testImplementation deps.junit + androidTestImplementation 'androidx.test.ext:junit:1.1.3' + androidTestImplementation deps.espresso.core -void cmdExecute(String cmd) { - println "\n执行$cmd" - println cmd.execute().text -} + implementation 'androidx.core:core-ktx:1.8.0' + implementation "androidx.activity:activity-ktx:1.4.0" + implementation "androidx.fragment:fragment-ktx:1.4.1" + implementation "androidx.cardview:cardview:1.0.0" + implementation 'androidx.appcompat:appcompat:1.4.2' + implementation 'androidx.preference:preference-ktx:1.2.0' -dependencies { - implementation fileTree(include: ['*.jar'], dir: 'libs') - //noinspection GradleDependency - implementation 'androidx.appcompat:appcompat:1.4.1' - implementation 'androidx.constraintlayout:constraintlayout:2.1.3' - implementation 'com.google.android.material:material:1.5.0' - testImplementation 'junit:junit:4.13.2' - androidTestImplementation 'androidx.test.ext:junit:1.1.3' - androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' + //分包 + implementation deps.androidx.multidex - //okhttp - //noinspection GradleDependency - implementation 'com.squareup.okhttp3:okhttp:4.9.3' - implementation 'com.squareup.okio:okio:3.0.0' + implementation 'com.alibaba.android:vlayout:1.3.0' + //下拉刷新 + implementation 'com.github.xuexiangjys.SmartRefreshLayout:refresh-header:1.1.5' + implementation 'com.github.xuexiangjys.SmartRefreshLayout:refresh-layout:1.1.5' + //WebView + implementation 'com.github.xuexiangjys.AgentWeb:agentweb-core:1.0.0' + implementation 'com.github.xuexiangjys.AgentWeb:agentweb-download:1.0.0'//选填 + //腾讯的键值对存储mmkv + implementation 'com.tencent:mmkv:1.2.13' + //屏幕适配AutoSize + implementation 'me.jessyan:autosize:1.2.1' + //umeng统计 + implementation 'com.umeng.umsdk:common:9.5.0' + implementation 'com.umeng.umsdk:asms:1.6.3' + + //预加载占位控件 + implementation 'me.samlss:broccoli:1.0.0' + + implementation 'com.zzhoujay.richtext:richtext:3.0.8' + implementation 'com.squareup.retrofit2:retrofit:2.9.0' - //fastjson - implementation "com.alibaba:fastjson:1.2.80" + //ANR异常捕获 + implementation 'com.github.anrwatchdog:anrwatchdog:1.4.0' - //XUpdate - implementation 'com.github.xuexiangjys:XUpdate:2.1.1' - implementation 'com.github.xuexiangjys.XUpdateAPI:xupdate-easy:1.0.1' - implementation 'com.github.xuexiangjys.XUpdateAPI:xupdate-downloader-aria:1.0.1' + //美团多渠道打包 + implementation 'com.meituan.android.walle:library:1.1.6' - //EmailKit - implementation 'com.github.mailhu:emailkit:4.2.2' - implementation 'com.sun.mail:android-mail:1.6.7' - implementation 'com.sun.mail:android-activation:1.6.7' + api("androidx.work:work-multiprocess:2.7.1") + api("androidx.work:work-runtime-ktx:2.7.1") - //Lombok - //noinspection AnnotationProcessorOnCompilePath - compileOnly 'org.projectlombok:lombok:1.18.22' - annotationProcessor 'org.projectlombok:lombok:1.18.22' + //Android Room + def room_version = '2.4.2' + implementation "androidx.room:room-ktx:$room_version" + implementation "androidx.room:room-runtime:$room_version" + implementation "androidx.room:room-paging:$room_version" + implementation "androidx.room:room-rxjava2:$room_version" + kapt "androidx.room:room-compiler:$room_version" - //RxJava - //implementation 'io.reactivex.rxjava3:rxjava:3.1.3' + implementation 'com.github.AmrDeveloper:CodeView:1.3.4' + implementation 'io.github.jeremyliao:live-event-bus-x:1.8.0' - //AndroidAsync - implementation 'com.koushikdutta.async:androidasync:3.1.0' + implementation 'com.github.tiagohm.MarkdownView:library:0.19.0' + implementation 'com.github.tiagohm.MarkdownView:emoji:0.19.0' + + def retrofit2_version = '2.9.0' + implementation "com.squareup.retrofit2:retrofit:$retrofit2_version" + implementation "com.squareup.retrofit2:converter-gson:$retrofit2_version" + implementation "com.squareup.retrofit2:adapter-rxjava2:$retrofit2_version" + + def paging_version = "3.1.1" + implementation "androidx.paging:paging-runtime-ktx:$paging_version" + // alternatively - without Android dependencies for tests + testImplementation "androidx.paging:paging-common-ktx:$paging_version" - //吐司框架:https://github.com/getActivity/ToastUtils - implementation 'com.github.getActivity:ToastUtils:10.3' //权限请求框架:https://github.com/getActivity/XXPermissions - implementation 'com.github.getActivity:XXPermissions:13.2' + implementation 'com.github.getActivity:XXPermissions:13.6' - //jetty - def jetty_version = '9.2.30.v20200428' - //noinspection GradleDependency - implementation "org.eclipse.jetty:jetty-server:$jetty_version" - //noinspection GradleDependency - implementation "org.eclipse.jetty:jetty-servlet:$jetty_version" + def mail_version = '1.6.7' + implementation "com.sun.mail:android-mail:$mail_version" + implementation "com.sun.mail:android-activation:$mail_version" - //友盟统计SDK - implementation 'com.umeng.umsdk:common:9.4.7'// (必选) - implementation 'com.umeng.umsdk:asms:1.6.0'// 必选 + //Android Keep Alive(安卓保活),Cactus 集成双进程前台服务,JobScheduler,onePix(一像素),WorkManager,无声音乐 + //https://github.com/gyf-dev/Cactus + implementation 'com.gyf.cactus:cactus:1.1.3-beta13' + + //HTTP服务器:https://github.com/yanzhenjie/AndServer + implementation 'com.yanzhenjie.andserver:api:2.1.10' + kapt 'com.yanzhenjie.andserver:processor:2.1.10' +} +//自动添加X-Library依赖 +apply from: 'x-library.gradle' +//walle多渠道打包 +apply from: 'multiple-channel.gradle' - //frpc - //implementation(name: 'frpclib', ext: 'aar') - implementation files('libs/frpclib.aar') - implementation 'io.github.jeremyliao:live-event-bus-x:1.8.0' - implementation 'io.reactivex.rxjava2:rxandroid:2.1.1' - implementation 'io.reactivex.rxjava2:rxjava:2.2.21' - def room_version = "2.4.2" - implementation "androidx.room:room-runtime:$room_version" - annotationProcessor "androidx.room:room-compiler:$room_version" - implementation "androidx.room:room-rxjava2:$room_version" -} \ No newline at end of file diff --git a/app/channel b/app/channel new file mode 100644 index 00000000..10c8afd1 --- /dev/null +++ b/app/channel @@ -0,0 +1,25 @@ +# 美团 +meituan +# 三星 +samsungapps +# 小米 +xiaomi +# 91助手 +91com +# 魅族 +meizu +# 豌豆荚 +wandou +# Google Play +googleplay +# 百度 +baidu +# 360 +360cn +# 应用宝 +myapp +# 华为 +huawei +# 蒲公英 +pgyer +github \ No newline at end of file diff --git a/app/libs/frpclib-sources.jar b/app/libs/frpclib-sources.jar index 4d6800cc..68d672b9 100644 Binary files a/app/libs/frpclib-sources.jar and b/app/libs/frpclib-sources.jar differ diff --git a/app/libs/frpclib.aar b/app/libs/frpclib.aar index fc6dd286..a3eaa0e8 100644 Binary files a/app/libs/frpclib.aar and b/app/libs/frpclib.aar differ diff --git a/app/lint.xml b/app/lint.xml new file mode 100644 index 00000000..04f82282 --- /dev/null +++ b/app/lint.xml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/multiple-channel.gradle b/app/multiple-channel.gradle new file mode 100644 index 00000000..5dc6dbf1 --- /dev/null +++ b/app/multiple-channel.gradle @@ -0,0 +1,10 @@ +apply plugin: 'walle' + +walle { + // 指定渠道包的输出路径 + apkOutputFolder = new File("${project.buildDir}/outputs/channels") + // 定制渠道包的APK的文件名称 + apkFileNameFormat = '${appName}-${packageName}-${channel}-${buildType}-v${versionName}-${versionCode}-${buildTime}.apk' + // 渠道配置文件 + channelFile = new File("${project.getProjectDir()}/channel") +} \ No newline at end of file diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index 110d973b..d2a696ec 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -1,28 +1,283 @@ -# Add project specific ProGuard rules here. -# You can control the set of applied configuration files using the -# proguardFiles setting in build.gradle. -# -# For more details, see -# http://developer.android.com/guide/developing/tools/proguard.html - -# If your project uses WebView with JS, uncomment the following -# and specify the fully qualified class name to the JavaScript interface -# class: -#-keepclassmembers class fqcn.of.javascript.interface.for.webview { -# public *; +#=========================================基础不变的混淆配置=========================================## +#指定代码的压缩级别 +-optimizationpasses 5 +#包名不混合大小写 +-dontusemixedcaseclassnames +#不去忽略非公共的库类 +-dontskipnonpubliclibraryclasses +# 指定不去忽略非公共的库的类的成员 +-dontskipnonpubliclibraryclassmembers +#优化 不优化输入的类文件 +-dontoptimize +#预校验 +-dontpreverify +#混淆时是否记录日志 +-verbose +# 混淆时所采用的算法 +-optimizations !code/simplification/arithmetic,!field/*,!class/merging/* +#保护注解 +-keepattributes *Annotation* +#忽略警告 +-ignorewarnings + +##记录生成的日志数据,gradle build时在本项目根目录输出## +#apk 包内所有 class 的内部结构 +-dump class_files.txt +#未混淆的类和成员 +-printseeds seeds.txt +#列出从 apk 中删除的代码 +-printusage unused.txt +#混淆前后的映射 +-printmapping mapping.txt +# 并保留源文件名为"Proguard"字符串,而非原始的类名 并保留行号 +-keepattributes SourceFile,LineNumberTable +########记录生成的日志数据,gradle build时 在本项目根目录输出-end##### + +#需要保留的东西 +# 保持哪些类不被混淆 +-keep public class * extends android.app.Fragment +-keep public class * extends android.app.Activity +-keep public class * extends android.app.Application +-keep public class * extends android.app.Service +-keep public class * extends android.content.BroadcastReceiver +-keep public class * extends android.content.ContentProvider +-keep public class * extends android.app.backup.BackupAgentHelper +-keep public class * extends android.preference.Preference +-keep public class * extends android.support.v4.** +#-keep public class com.android.vending.licensing.ILicensingService + +#如果有引用v4包可以添加下面这行 +#-keep public class * extends android.support.v4.app.Fragment + +##########JS接口类不混淆,否则执行不了 +-dontwarn com.android.JsInterface.** +-keep class com.android.JsInterface.** {*; } + +#极光推送和百度lbs android sdk一起使用proguard 混淆的问题#http的类被混淆后,导致apk定位失败,保持apache 的http类不被混淆就好了 +-dontwarn org.apache.** +-keep class org.apache.**{ *; } + +-keep public class * extends android.view.View { + public (android.content.Context); + public (android.content.Context, android.util.AttributeSet); + public (android.content.Context, android.util.AttributeSet, int); + public void set*(...); + } + +#保持 native 方法不被混淆 +-keepclasseswithmembernames class * { + native ; +} + +#保持自定义控件类不被混淆 +-keepclasseswithmembers class * { + public (android.content.Context, android.util.AttributeSet); +} + +#保持自定义控件类不被混淆 +-keepclassmembers class * extends android.app.Activity { + public void *(android.view.View); +} + +#保持 Parcelable 不被混淆 +-keep class * implements android.os.Parcelable { + public static final android.os.Parcelable$Creator *; +} + +#保持 Serializable 不被混淆 +-keepnames class * implements java.io.Serializable + +#保持 Serializable 不被混淆并且enum 类也不被混淆 +-keepclassmembers class * implements java.io.Serializable { + static final long serialVersionUID; + private static final java.io.ObjectStreamField[] serialPersistentFields; + !static !transient ; + !private ; + !private ; + private void writeObject(java.io.ObjectOutputStream); + private void readObject(java.io.ObjectInputStream); + java.lang.Object writeReplace(); + java.lang.Object readResolve(); +} + +#保持枚举 enum 类不被混淆 如果混淆报错,建议直接使用上面的 -keepclassmembers class * implements java.io.Serializable即可 +-keepclassmembers enum * { + public static **[] values(); + public static ** valueOf(java.lang.String); +} + +-keepclassmembers class * { + public void *ButtonClicked(android.view.View); +} + +#不混淆资源类 +-keep class **.R$* {*;} + +#===================================混淆保护自己项目的部分代码以及引用的第三方jar包library=============================####### +#如果引用了v4或者v7包 +-dontwarn android.support.** + + +# AndroidX 防止混淆 +-dontwarn com.google.android.material.** +-dontnote com.google.android.material.** +-dontwarn androidx.** +-keep class com.google.android.material.** {*;} +-keep class androidx.** {*;} +-keep public class * extends androidx.** +-keep interface androidx.** {*;} +-keepclassmembers class * { + @androidx.annotation.Keep *; +} + +# zxing +-dontwarn com.google.zxing.** +-keep class com.google.zxing.**{*;} + +#SignalR推送 +-keep class microsoft.aspnet.signalr.** { *; } + +# 极光推送混淆 +-dontoptimize +-dontpreverify +-dontwarn cn.jpush.** +-keep class cn.jpush.** { *; } +-dontwarn cn.jiguang.** +-keep class cn.jiguang.** { *; } + +# 数据库框架OrmLite +-keepattributes *DatabaseField* +-keepattributes *DatabaseTable* +-keepattributes *SerializedName* +-keep class com.j256.** +-keepclassmembers class com.j256.** { *; } +-keep enum com.j256.** +-keepclassmembers enum com.j256.** { *; } +-keep interface com.j256.** +-keepclassmembers interface com.j256.** { *; } + +#XHttp2 +-keep class com.xuexiang.xhttp2.model.** { *; } +-keep class com.xuexiang.xhttp2.cache.model.** { *; } +-keep class com.xuexiang.xhttp2.cache.stategy.**{*;} +-keep class com.xuexiang.xhttp2.annotation.** { *; } + +#okhttp +-dontwarn com.squareup.okhttp3.** +-keep class com.squareup.okhttp3.** { *;} +-dontwarn okio.** +-dontwarn javax.annotation.Nullable +-dontwarn javax.annotation.ParametersAreNonnullByDefault +-dontwarn javax.annotation.** + +#如果用到Gson解析包的,直接添加下面这几行就能成功混淆,不然会报错 +-keepattributes Signature +-keep class com.google.gson.stream.** { *; } +-keepattributes EnclosingMethod +-keep class org.xz_sale.entity.**{*;} +-keep class com.google.gson.** {*;} +-keep class com.google.**{*;} +#-keep class sun.misc.Unsafe { *; } +-keep class com.google.gson.stream.** { *; } +-keep class com.google.gson.examples.android.model.** { *; } + +# Glide +-keep public class * implements com.bumptech.glide.module.GlideModule +-keep public class * extends com.bumptech.glide.module.AppGlideModule +-keep public enum com.bumptech.glide.load.ImageHeaderParser$** { + **[] $VALUES; + public *; +} + +# Retrofit +-dontwarn retrofit2.** +-keep class retrofit2.** { *; } +-keepattributes Exceptions + +# RxJava RxAndroid +-dontwarn sun.misc.** +-keepclassmembers class rx.internal.util.unsafe.*ArrayQueue*Field* { + long producerIndex; + long consumerIndex; +} +#-keepclassmembers class rx.internal.util.unsafe.BaseLinkedQueueProducerNodeRef { +# rx.internal.util.atomic.LinkedQueueNode producerNode; +#} +#-keepclassmembers class rx.internal.util.unsafe.BaseLinkedQueueConsumerNodeRef { +# rx.internal.util.atomic.LinkedQueueNode consumerNode; #} -# Uncomment this to preserve the line number information for -# debugging stack traces. -#-keepattributes SourceFile,LineNumberTable +-dontwarn okio.** +-dontwarn javax.annotation.Nullable +-dontwarn javax.annotation.ParametersAreNonnullByDefault +-dontwarn javax.annotation.** + +# fastjson +-dontwarn com.alibaba.fastjson.** +-keep class com.alibaba.fastjson.** { *; } +-keepattributes Signature + +# xpage +-keep class com.xuexiang.xpage.annotation.** { *; } +-keep class com.xuexiang.xpage.config.** { *; } + +# xaop +-keep @com.xuexiang.xaop.annotation.* class * {*;} +-keep @org.aspectj.lang.annotation.* class * {*;} +-keep class * { + @com.xuexiang.xaop.annotation.* ; + @org.aspectj.lang.annotation.* ; +} +-keepclassmembers class * { + @com.xuexiang.xaop.annotation.* ; + @org.aspectj.lang.annotation.* ; +} + +# xrouter +-keep public class com.xuexiang.xrouter.routes.**{*;} +-keep class * implements com.xuexiang.xrouter.facade.template.ISyringe{*;} +# 如果使用了 byType 的方式获取 Service,需添加下面规则,保护接口 +-keep interface * implements com.xuexiang.xrouter.facade.template.IProvider +# 如果使用了 单类注入,即不定义接口实现 IProvider,需添加下面规则,保护实现 +-keep class * implements com.xuexiang.xrouter.facade.template.IProvider + +# xupdate +-keep class com.xuexiang.xupdate.entity.** { *; } + +# xvideo +-keep class com.xuexiang.xvideo.jniinterface.** { *; } + +# xipc +-keep @com.xuexiang.xipc.annotation.* class * {*;} +-keep class * { + @com.xuexiang.xipc.annotation.* ; +} +-keepclassmembers class * { + @com.xuexiang.xipc.annotation.* ; +} + +# umeng统计 +-keep class com.umeng.** {*;} +-keepclassmembers class * { + public (org.json.JSONObject); +} +-keepclassmembers enum * { + public static **[] values(); + public static ** valueOf(java.lang.String); +} + +-keep class com.xuexiang.xui.widget.edittext.materialedittext.** { *; } -# If you keep the line number information, uncomment this to -# hide the original source file name. -#-renamesourcefileattribute SourceFile +# Android Keep Alive(安卓保活),Cactus 集成双进程前台服务,JobScheduler,onePix(一像素),WorkManager,无声音乐 +-keep class com.gyf.cactus.entity.* {*;} --keep class com.idormy.**{*;} +# 排除实体类 +-keep class com.idormy.sms.forwarder.core.http.entity.** {*;} +-keep class com.idormy.sms.forwarder.database.entity.** {*;} +-keep class com.idormy.sms.forwarder.entity.** {*;} +-keep class com.idormy.sms.forwarder.server.model.** {*;} -#emailkit +# javax.mail -dontwarn com.sun.** -dontwarn javax.mail.** -dontwarn javax.activation.** @@ -30,48 +285,4 @@ -keep class javax.mail.** { *;} -keep class javax.activation.** { *;} -keep class com.smailnet.emailkit.** { *;} - -#xupdate --keep class com.xuexiang.xupdate.entity.** { *; } --dontwarn com.arialyy.aria.** --keep class com.arialyy.aria.**{*;} --keep class **$$DownloadListenerProxy{ *; } --keep class **$$UploadListenerProxy{ *; } --keep class **$$DownloadGroupListenerProxy{ *; } --keep class **$$DGSubListenerProxy{ *; } --keepclasseswithmembernames class * { - @Download.* ; - @Upload.* ; - @DownloadGroup.* ; -} - -#友盟统计SDK --dontwarn com.umeng.** --dontwarn com.taobao.** --dontwarn anet.channel.** --dontwarn anetwork.channel.** --dontwarn org.android.** --dontwarn org.apache.thrift.** --dontwarn com.xiaomi.** --dontwarn com.huawei.** --dontwarn com.meizu.** - --keepattributes *Annotation* - --keep class com.taobao.** {*;} --keep class org.android.** {*;} --keep class anet.channel.** {*;} --keep class com.umeng.** {*;} --keep class com.xiaomi.** {*;} --keep class com.huawei.** {*;} --keep class com.meizu.** {*;} --keep class org.apache.thrift.** {*;} - --keep class com.alibaba.sdk.android.** {*;} --keep class com.ut.** {*;} --keep class com.uc.** {*;} --keep class com.ta.** {*;} - --keep public class **.R$* { - public static final int *; -} \ No newline at end of file +-keep class com.idormy.sms.forwarder.utils.mail.** {*;} \ No newline at end of file diff --git a/app/src/androidTest/java/com/idormy/sms/forwarder/ExampleInstrumentedTest.kt b/app/src/androidTest/java/com/idormy/sms/forwarder/ExampleInstrumentedTest.kt index f6b7d657..02b5e3a6 100644 --- a/app/src/androidTest/java/com/idormy/sms/forwarder/ExampleInstrumentedTest.kt +++ b/app/src/androidTest/java/com/idormy/sms/forwarder/ExampleInstrumentedTest.kt @@ -1,15 +1,32 @@ +/* + * Copyright (C) 2022 xuexiangjys(xuexiangjys@163.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + package com.idormy.sms.forwarder import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.platform.app.InstrumentationRegistry -import org.junit.Assert.assertEquals +import org.junit.Assert import org.junit.Test import org.junit.runner.RunWith /** * Instrumented test, which will execute on an Android device. * - * See [testing documentation](http://d.android.com/tools/testing). + * @see [Testing documentation](http://d.android.com/tools/testing) */ @RunWith(AndroidJUnit4::class) class ExampleInstrumentedTest { @@ -17,6 +34,6 @@ class ExampleInstrumentedTest { fun useAppContext() { // Context of the app under test. val appContext = InstrumentationRegistry.getInstrumentation().targetContext - assertEquals("com.idormy.sms.forwarder", appContext.packageName) + Assert.assertEquals("com.idormy.sms.forwarder", appContext.packageName) } -} +} \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 3a307982..b86a8d5d 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -7,6 +7,9 @@ + + + @@ -16,9 +19,8 @@ tools:ignore="ProtectedPermissions" /> - + + @@ -28,6 +30,8 @@ + + @@ -44,10 +48,16 @@ + + + + android:windowSoftInputMode="adjustPan|stateHidden" + tools:ignore="DataExtractionRules,LockedOrientationActivity,UnusedAttribute" + tools:replace="android:allowBackup"> + android:screenOrientation="portrait" + android:taskAffinity=":splash" + android:theme="@style/AppTheme.Launch.App" + android:windowSoftInputMode="adjustPan|stateHidden" + tools:ignore="TranslucentOrientation"> + + + + + + + + - + + + + + + + + - - - - - - - + + + + + + + + + + + + + + + + + + + + + + android:screenOrientation="portrait" + android:windowSoftInputMode="adjustPan|stateHidden" /> + + android:screenOrientation="portrait" + android:theme="@style/DialogTheme" /> + + android:screenOrientation="portrait" + android:theme="@style/DialogTheme" /> + - + + + - + - + + + + + + android:label="@string/app_name" + android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE"> + + + + + @@ -163,30 +247,6 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/assets/protocol/account_protocol.txt b/app/src/main/assets/protocol/account_protocol.txt new file mode 100644 index 00000000..376a6323 --- /dev/null +++ b/app/src/main/assets/protocol/account_protocol.txt @@ -0,0 +1,107 @@ +软件许可及服务协议 +【重要须知】 +  +【福州多米信息科技有限公司】(如下简称“多米科技”)在此特别提醒用户认真阅读、充分理解本《软件许可及服务协议》(下称“本协议”)。用户应认真阅读、充分理解本协议中各条款,特别涉及免除或者限制多米科技责任、争议解决和法律适用的条款。免除或者限制责任的条款将以粗体标识,您需要重点阅读。请您审慎阅读并选择接受或不接受本协议(未成年人应在法定监护人陪同下阅读)。您的下载、安装、使用本软件以及账号获取和登录等行为将视为对本协议的接受,并同意接受本协议各项条款的约束。 +  +多米科技有权修订本协议,更新后的协议条款将公布于官网或软件,自公布之日起生效。用户可重新下载安装本软件或网站查阅最新版协议条款。在多米科技修改本协议条款后,如果用户不接受修改后的条款,请立即停止使用多米科技提供的“多米科技”软件和服务,用户继续使用多米科技提供的“多米科技”软件和服务将被视为已接受了修改后的协议。 +  +一、总则 +  +1.1. 本协议是您(如下也称“用户”)与多米科技及其运营合作单位(如下简称“合作单位”)之间关于用户下载、安装、使用多米科技“多米科技”软件(下称“本软件”)以及使用多米科技相关服务所订立的协议。 +  +1.2. 本软件及服务是多米科技提供的安装在包括但不限于移动智能终端设备上的软件和服务,为使用该智能终端的用户提供绑定、操作智能产品等服务等。 +  +1.3. 本软件及服务的所有权和运营权均归多米科技所有。 +  +二、软件授权范围 +  +2.1. 多米科技就本软件给予用户一项个人的、不可转让、不可转授权以及非独占性的许可。 +  +2.2. 用户可以为非商业目的在单一台移动终端设备上安装、使用、显示、运行本软件。但用户不得为商业运营目的安装、使用、运行本软件,不可以对本软件或者本软件运行过程中释放到任何终端设备内存中的数据及本软件运行过程中客户端与服务器端的交互数据进行复制、更改、修改、挂接运行或创作任何衍生作品,形式包括但不限于使用插件、外挂或非经授权的第三方工具/服务接入本软件和相关系统。如果需要进行商业性的销售、复制和散发,例如软件预装和捆绑,必须获得多米科技的书面授权和许可。 +  +2.3. 用户不得未经多米科技许可,将本软件安装在未经多米科技明示许可的其他终端设备上,包括但不限于机顶盒、游戏机、电视机、DVD机等。 +  +2.4. 用户可以为使用本软件及服务的目的复制本软件的一个副本,仅用作备份。备份副本必须包含原软件中含有的所有著作权信息。 +  +2.5. 除本《协议》明示授权外,多米科技未授权给用户其他权利,若用户使用其他权利时须另外取得多米科技的书面同意。 +  +三、软件的获取、安装、升级 +  +3.1. 用户应当按照多米科技的指定网站或指定方式下载安装本软件产品。谨防在非指定网站下载本软件,以免移动终端设备感染能破坏用户数据和获取用户隐私信息的恶意程序。如果用户从未经多米科技授权的第三方获取本软件或与本软件名称相同的安装程序,多米科技无法保证该软件能够正常使用,并对因此给您造成的损失不予负责。 +  +3.2. 用户必须选择与所安装终端设备相匹配的本软件版本,否则,由于软件与设备型号不相匹配所导致的任何软件问题、设备问题或损害,均由用户自行承担。 +  +3.3. 为了改善用户体验、完善服务内容,多米科技有权不时地为您提供本软件替换、修改、升级版本,也有权为替换、修改或升级收取费用,但将收费提前征得您的同意。本软件为用户默认开通“升级提示”功能,视用户使用的软件版本差异,多米科技提供给用户自行选择是否需要开通此功能。软件新版本发布后,多米科技不保证旧版本软件的继续可用。 +  +四、使用规范 +  +4.1. 用户在遵守法律及本《协议》的前提下可依本《协议》使用本软件及服务,用户不得实施如下行为: +  +4.1.1. 删除本软件及其他副本上一切关于版权的信息,以及修改、删除或避开本软件为保护知识产权而设置的技术措施; +4.1.2. 对本软件进行反向工程,如反汇编、反编译或者其他试图获得本软件的源代码; +4.1.3. 通过修改或伪造软件运行中的指令、数据,增加、删减、变动软件的功能或运行效果,或者将用于上述用途的软件、方法进行运营或向公众传播,无论这些行为是否为商业目的; +4.1.4. 使用本软件进行任何危害网络安全的行为,包括但不限于:使用未经许可的数据或进入未经许可的服务器/账户;未经允许进入公众网络或者他人操作系统并删除、修改、增加存储信息;未经许可企图探查、扫描、测试本软件的系统或网络的弱点或其它实施破坏网络安全的行为; 企图干涉、破坏本软件系统或网站的正常运行,故意传播恶意程序或病毒以及其他破坏干扰正常网络信息服务的行为;伪造TCP/IP数据包名称或部分名称; +4.1.5. 用户通过非多米科技公司开发、授权或认可的第三方兼容软件、系统登录或使用本软件及服务,或制作、发布、传播上述工具; +4.1.6. 未经多米科技书面同意,用户对软件及其中的信息擅自实施包括但不限于下列行为:使用、出租、出借、复制、修改、链接、转载、汇编、发表、出版,建立镜像站点、擅自借助本软件发展与之有关的衍生产品、作品、服务、插件、外挂、兼容、互联等; +4.1.7. 利用本软件发表、传送、传播、储存违反当地法律法规的内容; +4.1.8. 利用本软件发表、传送、传播、储存侵害他人知识产权、商业秘密等合法权利的内容; +4.1.9. 利用本软件批量发表、传送、传播广告信息及垃圾信息; +4.1.10. 其他以任何不合法的方式、为任何不合法的目的、或以任何与本协议许可使用不一致的方式使用本软件和多米科技提供的其他服务; +4.2. 信息发布规范 +  +4.2.1.您可使用本软件发表属于您原创或您有权发表的观点看法、数据、文字、信息、用户名、图片、照片、个人信息、音频、视频文件、链接等信息内容。您必须保证,您拥有您所上传信息内容的知识产权或已获得合法授权,您使用本软件及服务的任何行为未侵犯任何第三方之合法权益。 +4.2.2.您在使用本软件时需遵守当地法律法规要求。 +4.2.3.您在使用本软件时不得利用本软件从事以下行为,包括但不限于: +  +4.2.3.1.制作、复制、发布、传播、储存违反当地法律法规的内容; +  +4.2.3.2.发布、传送、传播、储存侵害他人名誉权、肖像权、知识产权、商业秘密等合法权利的内容; +  +4.2.3.3.虚构事实、隐瞒真相以误导、欺骗他人; +  +4.2.3.4.发表、传送、传播广告信息及垃圾信息; +  +4.2.3.5.从事其他违反当地法律法规的行为。 +  +4.2.4. 未经多米科技许可,您不得在本软件中进行任何诸如发布广告、销售商品的商业行为。 +  +4.3.您理解并同意: +  +4.3.1. 多米科技会对用户是否涉嫌违反上述使用规范做出认定,并根据认定结果中止、终止对您的使用许可或采取其他依本约定可采取的限制措施; +4.3.2. 对于用户使用许可软件时发布的涉嫌违法或涉嫌侵犯他人合法权利或违反本协议的信息,多米科技会直接删除; +4.3.3. 对于用户违反上述使用规范的行为对第三方造成损害的,您需要以自己的名义独立承担法律责任,并应确保多米科技免于因此产生损失或增加费用; +4.3.4.若用户违反有关法律规定或协议约定,使多米科技遭受损失,或受到第三方的索赔,或受到行政管理机关的处罚,用户应当赔偿多米科技因此造成的损失和(或)发生的费用,包括合理的律师费、调查取证费用。 +五、服务风险及免责声明 +  +5.1. 用户必须自行配备移动终端设备上网和使用电信增值业务所需的设备,自行负担个人移动终端设备上网或第三方(包括但不限于电信或移动通信提供商)收取的通讯费、信息费等有关费用。如涉及电信增值服务的,我们建议您与您的电信增值服务提供商确认相关的费用问题。 +  +5.2. 用户因第三方如通讯线路故障、技术问题、网络、移动终端设备故障、系统不稳定性及其他各种不可抗力原因而遭受的一切损失,多米科技及合作单位不承担责任。 +  +5.3. 本软件同大多数互联网软件一样,受包括但不限于用户原因、网络服务质量、社会环境等因素的差异影响,可能受到各种安全问题的侵扰,如他人利用用户的资料,造成现实生活中的骚扰;用户下载安装的其它软件或访问的其他网站中含有“特洛伊木马”等病毒,威胁到用户的终端设备信息和数据的安全,继而影响本软件的正常使用等等。用户应加强信息安全及使用者资料的保护意识,要注意加强密码保护,以免遭致损失和骚扰。 +  +5.4. 因用户使用本软件或要求多米科技提供特定服务时,本软件可能会调用第三方系统或第三方软件支持用户的使用或访问,使用或访问的结果由该第三方提供,多米科技不保证通过第三方系统或第三方软件支持实现的结果的安全性、准确性、有效性及其他不确定的风险,由此若引发的任何争议及损害,多米科技不承担任何责任。 +  +5.5. 多米科技特别提请用户注意,多米科技为了保障公司业务发展和调整的自主权,多米科技公司拥有随时修改或中断服务而不需通知用户的权利,多米科技行使修改或中断服务的权利不需对用户或任何第三方负责。 +  +5.6. 除法律法规有明确规定外,我们将尽最大努力确保软件及其所涉及的技术及信息安全、有效、准确、可靠,但受限于现有技术,用户理解多米科技不能对此进行担保。 +  +5.7. 由于用户因下述任一情况所引起或与此有关的人身伤害或附带的、间接的经济损害赔偿,包括但不限于利润损失、资料损失、业务中断的损害赔偿或其他商业损害赔偿或损失,需由用户自行承担: +  +5.7.1.使用或未能使用许可软件; +5.7.2.第三方未经许可的使用软件或更改用户的数据; +5.7.3.用户使用软件进行的行为产生的费用及损失; +5.7.4.用户对软件的误解; +5.7.5.非因多米科技的原因引起的与软件有关的其他损失。 +5.8. 用户与其他使用软件的用户之间通过软件进行的行为,因您受误导或欺骗而导致或可能导致的任何人身或经济上的伤害或损失,均由过错方依法承担所有责任。 +  +六、知识产权声明 +  +6.1. 多米科技是本软件的知识产权权利人。本软件的一切著作权、商标权、专利权、商业秘密等知识产权,以及与本软件相关的所有信息内容(包括但不限于文字、图片、音 频、视频、图表、界面设计、版面框架、有关数据或电子文档等)均受您所在当地法律法规和相应的国际条约保护,多米科技享有上述知识产权。 +  +6.2 未经多米科技书面同意,用户不得为任何商业或非商业目的自行或许可任何第三方实施、利用、转让上述知识产权,多米科技保留追究上述行为法律责任的权利。 +  +七、协议变更 +  +7.1. 多米科技有权在必要时修改本协议条款,协议条款一旦发生变动,将会在相关页面上公布修改后的协议条款。如果不同意所改动的内容,用户应主动取消此项服务。如果用户继续使用服务,则视为接受协议条款的变动。 +  +7.2. 多米科技和合作公司有权按需要修改或变更所提供的收费服务、收费标准、收费方式、服务费及服务条款。多米科技在提供服务时,可能现在或日后对部分服务的用户开始收取一定的费用如用户拒绝支付该等费用,则不能在收费开始后继续使用相关的服务。多米科技和合作公司将尽最大努力通过电邮或其他方式通知用户有关的修改或变更。 diff --git a/app/src/main/assets/protocol/privacy_protocol.txt b/app/src/main/assets/protocol/privacy_protocol.txt new file mode 100644 index 00000000..66850eac --- /dev/null +++ b/app/src/main/assets/protocol/privacy_protocol.txt @@ -0,0 +1,70 @@ +SmsForwarder尊重并保护所有使用服务用户的个人隐私权。为了给您提供更准确、更有个性化的 服务,SmsForwarder会按照本隐私权政策的规定使用和披露您的个人信息。但SmsForwarder将以高 度的勤勉、审慎义务对待这些信息。除本隐私权政策另有规定外,在未征得您事先许可的情况下 ,SmsForwarder不会将这些信息对外披露或向第三方提供。SmsForwarder会不时更新本隐私权政策 。 您在同意SmsForwarder服务使用协议之时,即视为您已经同意本隐私权政策全部内容。本隐私 权政策属于SmsForwarder服务使用协议不可分割的一部分。 + + +1. 适用范围 + +a) 在您注册SmsForwarder帐号时,您根据SmsForwarder要求提供的个人注册信息; + +b) 在您使用SmsForwarder网络服务,或访问SmsForwarder平台网页时,SmsForwarder自动接收并记 录的您的浏览器和计算机上的信息,包括但不限于您的IP地址、浏览器的类型、使用的语言、访 问日期和时间、软硬件特征信息及您需求的网页记录等数据; + +c) SmsForwarder通过合法途径从商业伙伴处取得的用户个人数据。 + +您了解并同意,以下信息不适用本隐私权政策: + +a) 您在使用SmsForwarder平台提供的搜索服务时输入的关键字信息; + +b) SmsForwarder收集到的您在SmsForwarder发布的有关信息数据,包括但不限于参与活动、成交 信息及评价详情; + +c) 违反法律规定或违反SmsForwarder规则行为及SmsForwarder已对您采取的措施。 + +2. 信息使用 + +a) SmsForwarder不会向任何无关第三方提供、出售、出租、分享或交易您的个人信息,除非事先 得到您的许可,或该第三方和SmsForwarder(含SmsForwarder关联公司)单独或共同为您提供服务 ,且在该服务结束后,其将被禁止访问包括其以前能够访问的所有这些资料。 + +b) SmsForwarder亦不允许任何第三方以任何手段收集、编辑、出售或者无偿传播您的个人信息。 任何SmsForwarder平台用户如从事上述活动,一经发现,SmsForwarder有权立即终止与该用户的服 务协议。 + +c) 为服务用户的目的,SmsForwarder可能通过使用您的个人信息,向您提供您感兴趣的信息,包 括但不限于向您发出产品和服务信息,或者与SmsForwarder合作伙伴共享信息以便他们向您发送 有关其产品和服务的信息(后者需要您的事先同意)。 + +3. 信息披露 在如下情况下,SmsForwarder将依据您的个人意愿或法律的规定全部或部分的披露您的个人信息 : + +a) 经您事先同意,向第三方披露; + +b) 为提供您所要求的产品和服务,而必须和第三方分享您的个人信息; + +c) 根据法律的有关规定,或者行政或司法机构的要求,向第三方或者行政、司法机构披露; + +d) 如您出现违反中国有关法律、法规或者SmsForwarder服务协议或相关规则的情况,需要向第三 方披露; + +e) 如您是适格的知识产权投诉人并已提起投诉,应被投诉人要求,向被投诉人披露,以便双方 处理可能的权利纠纷; + +f) 在SmsForwarder平台上创建的某一交易中,如交易任何一方履行或部分履行了交易义务并提出 信息披露请求的,SmsForwarder有权决定向该用户提供其交易对方的联络方式等必要信息,以促 成交易的完成或纠纷的解决。 + +g) 其它SmsForwarder根据法律、法规或者网站政策认为合适的披露。 + +4. 信息存储和交换 SmsForwarder收集的有关您的信息和资料将保存在SmsForwarder及(或)其关联公司的服务器上, 这些信息和资料可能传送至您所在国家、地区或SmsForwarder收集信息和资料所在地的境外并在 境外被访问、存储和展示。 + +5. Cookie的使用 + +a) 在您未拒绝接受cookies的情况下,SmsForwarder会在您的计算机上设定或取用cookies ,以便您能登录或使用依赖于cookies的SmsForwarder平台服务或功能。SmsForwarder使用cookies 可为您提供更加周到的个性化服务,包括推广服务。 + +b) 您有权选择接受或拒绝接受cookies。 您可以通过修改浏览器设置的方式拒绝接受cookies。但如果您选择拒绝接受cookies,则您可能 无法登录或使用依赖于cookies的SmsForwarder网络服务或功能。 + +c) 通过SmsForwarder所设cookies所取得的有关信息,将适用本政策。 + +6. 信息安全 + +a) SmsForwarder帐号均有安全保护功能,请妥善保管您的用户名及密码信息。SmsForwarder将通 过对用户密码进行加密等安全措施确保您的信息不丢失,不被滥用和变造。尽管有前述安全措施 ,但同时也请您注意在信息网络上不存在“完善的安全措施”。 + +b) 在使用SmsForwarder网络服务进行网上交易时,您不可避免的要向交易对方或潜在的交易对方 披露自己的个人信息,如联络方式或者邮政地址。请您妥善保护自己的个人信息,仅在必要的情 形下向他人提供。如您发现自己的个人信息泄密,尤其是SmsForwarder用户名及密码发生泄露, 请您立即联络SmsForwarder客服,以便SmsForwarder采取相应措施。 + +7. 接入的第三方SDK说明 + +a) 友盟统计SDK(com.umeng) + 使用目的: 统计应用运营数据 + 使用范围: 应用运营数据统计 + +8. 敏感信息收集说明 + 我们的产品集成友盟+SDK,友盟+SDK需要收集您的设备Mac地址、唯一设备识别码(IMEI/android ID/IDFA/OPENUDID/GUID、SIM 卡 IMSI 信息)以提供统计分析服务,并通过地理位置校准报表数据准确性,提供基础反作弊能力。 + + + diff --git a/app/src/main/assets/tips.json b/app/src/main/assets/tips.json new file mode 100644 index 00000000..5a77023d --- /dev/null +++ b/app/src/main/assets/tips.json @@ -0,0 +1,17 @@ +{ + "Code": 0, + "Data": [ + { + "title": "新用户必读", + "content": "开始设置之前,请您认真地看一遍 Wiki
\n遇到问题,请按照 常见问题 章节进行排查!
\n没找到答案的,再加入QQ互助交流群里提问,请清楚地描述问题,并给出对应的配置截图与相关日志,方便大家直观的判断问题! " + }, + { + "title": "QQ互助交流群", + "content": "QQ互助交流①群
QQ互助交流②群
QQ互助交流③群
QQ互助交流④群
QQ互助交流⑤群" + }, + { + "title": "打赏名单", + "content": "感谢热心网友们对开源项目的喜爱和支持!查看赞助名单!" + } + ] +} diff --git a/app/src/main/assets/web/index.html b/app/src/main/assets/web/index.html new file mode 100644 index 00000000..7a35b591 --- /dev/null +++ b/app/src/main/assets/web/index.html @@ -0,0 +1,3 @@ + +Hello SmsForwarder!!! + \ No newline at end of file diff --git a/app/src/main/assets/web/main.html b/app/src/main/assets/web/main.html new file mode 100644 index 00000000..f4b8500c --- /dev/null +++ b/app/src/main/assets/web/main.html @@ -0,0 +1,3 @@ + +Welcome to Main Page!!! + \ No newline at end of file diff --git a/app/src/main/ic_launcher-playstore.png b/app/src/main/ic_launcher-playstore.png deleted file mode 100644 index 62cd18bb..00000000 Binary files a/app/src/main/ic_launcher-playstore.png and /dev/null differ diff --git a/app/src/main/java/com/idormy/sms/forwarder/AboutActivity.java b/app/src/main/java/com/idormy/sms/forwarder/AboutActivity.java deleted file mode 100644 index f1af88f8..00000000 --- a/app/src/main/java/com/idormy/sms/forwarder/AboutActivity.java +++ /dev/null @@ -1,146 +0,0 @@ -package com.idormy.sms.forwarder; - -import android.content.Intent; -import android.net.Uri; -import android.os.Bundle; -import android.util.Log; -import android.widget.Button; -import android.widget.TextView; - -import com.hjq.permissions.OnPermissionCallback; -import com.hjq.permissions.Permission; -import com.hjq.permissions.XXPermissions; -import com.hjq.toast.ToastUtils; -import com.idormy.sms.forwarder.receiver.RebootBroadcastReceiver; -import com.idormy.sms.forwarder.utils.CacheUtils; -import com.idormy.sms.forwarder.utils.CommonUtils; -import com.idormy.sms.forwarder.utils.SettingUtils; -import com.xuexiang.xupdate.easy.EasyUpdate; -import com.xuexiang.xupdate.proxy.impl.DefaultUpdateChecker; - -import java.util.List; - -public class AboutActivity extends BaseActivity { - - private final String TAG = "AboutActivity"; - - @Override - public void onCreate(Bundle savedInstanceState) { - Log.d(TAG, "onCreate"); - super.onCreate(savedInstanceState); - - setContentView(R.layout.activity_about); - Log.d(TAG, "onCreate: " + RebootBroadcastReceiver.class.getName()); - - XXPermissions.with(this) - // 申请安装包权限 - .permission(Permission.REQUEST_INSTALL_PACKAGES) - // 申请通知栏权限 - .permission(Permission.NOTIFICATION_SERVICE) - .request(new OnPermissionCallback() { - - @Override - public void onGranted(List permissions, boolean all) { - if (all) { - ToastUtils.show(R.string.toast_granted_all); - } else { - ToastUtils.show(R.string.toast_granted_part); - } - SettingUtils.switchEnableSms(true); - } - - @Override - public void onDenied(List permissions, boolean never) { - if (never) { - ToastUtils.show(R.string.toast_denied_never); - // 如果是被永久拒绝就跳转到应用权限系统设置页面 - XXPermissions.startPermissionActivity(AboutActivity.this, permissions); - } else { - ToastUtils.show(R.string.toast_denied); - } - SettingUtils.switchEnableSms(false); - } - }); - - final TextView version_now = findViewById(R.id.version_now); - Button check_version_now = findViewById(R.id.check_version_now); - try { - version_now.setText(CommonUtils.getVersionName(AboutActivity.this)); - } catch (Exception e) { - e.printStackTrace(); - } - - check_version_now.setOnClickListener(v -> { - try { - String updateUrl = "https://xupdate.bms.ink/update/checkVersion?appKey=com.idormy.sms.forwarder&versionCode="; - updateUrl += CommonUtils.getVersionCode(AboutActivity.this); - Log.d(TAG, updateUrl); - - EasyUpdate.create(AboutActivity.this, updateUrl) - .updateChecker(new DefaultUpdateChecker() { - @Override - public void onBeforeCheck() { - super.onBeforeCheck(); - ToastUtils.delayedShow(R.string.checking, 3000); - } - - @Override - public void noNewVersion(Throwable throwable) { - super.noNewVersion(throwable); - // 没有最新版本的处理 - ToastUtils.delayedShow(R.string.up_to_date, 3000); - } - }) - .update(); - } catch (Exception e) { - e.printStackTrace(); - } - - }); - - final TextView cache_size = findViewById(R.id.cache_size); - try { - cache_size.setText(CacheUtils.getTotalCacheSize(AboutActivity.this)); - } catch (Exception e) { - e.printStackTrace(); - } - Button clear_all_cache = findViewById(R.id.clear_all_cache); - clear_all_cache.setOnClickListener(v -> { - CacheUtils.clearAllCache(AboutActivity.this); - try { - cache_size.setText(CacheUtils.getTotalCacheSize(AboutActivity.this)); - } catch (Exception e) { - e.printStackTrace(); - } - ToastUtils.delayedShow(R.string.cache_purged, 3000); - }); - - Button join_qq_group1 = findViewById(R.id.join_qq_group1); - join_qq_group1.setOnClickListener(v -> { - String key = "Mj5m39bqy6eodOImrFLI19Tdeqvv-9zf"; - joinQQGroup(key); - }); - - Button join_qq_group2 = findViewById(R.id.join_qq_group2); - join_qq_group2.setOnClickListener(v -> { - String key = "jPXy4YaUzA7Uo0yPPbZXdkb66NS1smU_"; - joinQQGroup(key); - }); - - } - - //发起添加群流程 - public void joinQQGroup(String key) { - Intent intent = new Intent(); - intent.setData(Uri.parse("mqqopensdkapi://bizAgent/qm/qr?url=http%3A%2F%2Fqm.qq.com%2Fcgi-bin%2Fqm%2Fqr%3Ffrom%3Dapp%26p%3Dandroid%26jump_from%3Dwebapi%26k%3D" + key)); - // 此Flag可根据具体产品需要自定义,如设置,则在加群界面按返回,返回手Q主界面,不设置,按返回会返回到呼起产品界面 - //intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) - try { - startActivity(intent); - } catch (Exception e) { - // 未安装手Q或安装的版本不支持 - ToastUtils.delayedShow(R.string.unknown_qq_version, 3000); - } - } - -} diff --git a/app/src/main/java/com/idormy/sms/forwarder/App.kt b/app/src/main/java/com/idormy/sms/forwarder/App.kt new file mode 100644 index 00000000..56ad62ae --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/App.kt @@ -0,0 +1,250 @@ +package com.idormy.sms.forwarder + +import android.annotation.SuppressLint +import android.app.Application +import android.app.PendingIntent +import android.content.Context +import android.content.Intent +import android.content.IntentFilter +import android.os.Build +import android.util.Log +import androidx.lifecycle.MutableLiveData +import androidx.multidex.MultiDex +import androidx.work.Configuration +import com.gyf.cactus.Cactus +import com.gyf.cactus.callback.CactusCallback +import com.gyf.cactus.ext.cactus +import com.idormy.sms.forwarder.activity.MainActivity +import com.idormy.sms.forwarder.core.Core +import com.idormy.sms.forwarder.database.AppDatabase +import com.idormy.sms.forwarder.database.repository.FrpcRepository +import com.idormy.sms.forwarder.database.repository.LogsRepository +import com.idormy.sms.forwarder.database.repository.RuleRepository +import com.idormy.sms.forwarder.database.repository.SenderRepository +import com.idormy.sms.forwarder.entity.SimInfo +import com.idormy.sms.forwarder.receiver.CactusReceiver +import com.idormy.sms.forwarder.service.BatteryService +import com.idormy.sms.forwarder.service.ForegroundService +import com.idormy.sms.forwarder.service.HttpService +import com.idormy.sms.forwarder.utils.* +import com.idormy.sms.forwarder.utils.sdkinit.ANRWatchDogInit +import com.idormy.sms.forwarder.utils.sdkinit.UMengInit +import com.idormy.sms.forwarder.utils.sdkinit.XBasicLibInit +import com.idormy.sms.forwarder.utils.sdkinit.XUpdateInit +import com.idormy.sms.forwarder.utils.tinker.TinkerLoadLibrary +import com.xuexiang.xutil.app.AppUtils +import io.reactivex.Observable +import io.reactivex.android.schedulers.AndroidSchedulers +import io.reactivex.disposables.Disposable +import io.reactivex.schedulers.Schedulers +import kotlinx.coroutines.* +import java.io.File +import java.text.SimpleDateFormat +import java.util.* +import java.util.concurrent.TimeUnit + +@Suppress("PrivatePropertyName") +class App : Application(), CactusCallback, Configuration.Provider by Core { + + val applicationScope = CoroutineScope(SupervisorJob()) + val database by lazy { AppDatabase.getInstance(this) } + val frpcRepository by lazy { FrpcRepository(database.frpcDao()) } + val logsRepository by lazy { LogsRepository(database.logsDao()) } + val ruleRepository by lazy { RuleRepository(database.ruleDao()) } + val senderRepository by lazy { SenderRepository(database.senderDao()) } + + companion object { + const val TAG: String = "SmsForwarder" + + @SuppressLint("StaticFieldLeak") + lateinit var context: Context + + //已插入SIM卡信息 + var SimInfoList: MutableMap = mutableMapOf() + + //已安装App信息 + var AppInfoList: List = arrayListOf() + + /** + * @return 当前app是否是调试开发模式 + */ + val isDebug: Boolean + get() = BuildConfig.DEBUG + + //Cactus结束时间 + val mEndDate = MutableLiveData() + + //Cactus上次存活时间 + val mLastTimer = MutableLiveData() + + //Cactus存活时间 + val mTimer = MutableLiveData() + + //Cactus运行状态 + val mStatus = MutableLiveData().apply { value = true } + } + + override fun attachBaseContext(base: Context) { + super.attachBaseContext(base) + //解决4.x运行崩溃的问题 + MultiDex.install(this) + } + + override fun onCreate() { + super.onCreate() + try { + //动态加载FrpcLib + val libPath = filesDir.absolutePath + "/libs" + val soFile = File(libPath) + try { + TinkerLoadLibrary.installNativeLibraryPath(classLoader, soFile) + } catch (throwable: Throwable) { + Log.e("APP", throwable.message!!) + } + + context = applicationContext + initLibs() + + //启动前台服务 + val intent = Intent(this, ForegroundService::class.java) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + startForegroundService(intent) + } else { + startService(intent) + } + + //电池状态监听 + val batteryServiceIntent = Intent(this, BatteryService::class.java) + startService(batteryServiceIntent) + + //异步获取所有已安装 App 信息 + val get = GlobalScope.async(Dispatchers.IO) { + AppInfoList = AppUtils.getAppsInfo() + } + GlobalScope.launch(Dispatchers.Main) { + runCatching { + get.await() + Log.d("GlobalScope", "AppUtils.getAppsInfo() Done") + }.onFailure { + Log.e("GlobalScope", it.message.toString()) + } + } + + //启动HttpServer + if (HttpServerUtils.enableServerAutorun) { + startService(Intent(this, HttpService::class.java)) + } + + //Cactus 集成双进程前台服务,JobScheduler,onePix(一像素),WorkManager,无声音乐 + if (!isDebug) { + //注册广播监听器 + registerReceiver(CactusReceiver(), IntentFilter().apply { + addAction(Cactus.CACTUS_WORK) + addAction(Cactus.CACTUS_STOP) + addAction(Cactus.CACTUS_BACKGROUND) + addAction(Cactus.CACTUS_FOREGROUND) + }) + //设置通知栏点击事件 + val activityIntent = Intent(this, MainActivity::class.java) + val flags = if (Build.VERSION.SDK_INT >= 30) PendingIntent.FLAG_IMMUTABLE else PendingIntent.FLAG_UPDATE_CURRENT + val pendingIntent = PendingIntent.getActivity(this, 0, activityIntent, flags) + cactus { + setServiceId(FRONT_NOTIFY_ID) //服务Id + setChannelId(FRONT_CHANNEL_ID) //渠道Id + setChannelName(FRONT_CHANNEL_NAME) //渠道名 + setTitle(getString(R.string.app_name)) + setContent(SettingUtils.notifyContent.toString()) + setSmallIcon(R.drawable.ic_forwarder) + setLargeIcon(R.mipmap.ic_launcher) + setPendingIntent(pendingIntent) + //无声音乐 + if (SettingUtils.enablePlaySilenceMusic) { + setMusicEnabled(true) + setBackgroundMusicEnabled(true) + setMusicId(R.raw.silence) + //设置音乐间隔时间,时间间隔越长,越省电 + setMusicInterval(10) + isDebug(true) + } + //是否可以使用一像素,默认可以使用,只有在android p以下可以使用 + if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.P && SettingUtils.enableOnePixelActivity) { + setOnePixEnabled(true) + } + //奔溃是否可以重启用户界面 + setCrashRestartUIEnabled(true) + addCallback({ + Log.d(TAG, "Cactus保活:onStop回调") + }) { + Log.d(TAG, "Cactus保活:doWork回调") + } + //切后台切换回调 + addBackgroundCallback { + Log.d(TAG, if (it) "SmsForwarder 切换到后台运行" else "SmsForwarder 切换到前台运行") + } + } + } + + } catch (e: Exception) { + e.printStackTrace() + } + } + + /** + * 初始化基础库 + */ + private fun initLibs() { + Core.init(this) + // 转发历史工具类初始化 + HistoryUtils.init(this) + // X系列基础库初始化 + XBasicLibInit.init(this) + // 版本更新初始化 + XUpdateInit.init(this) + // 运营统计数据 + UMengInit.init(this) + // ANR监控 + ANRWatchDogInit.init() + } + + private var mDisposable: Disposable? = null + + @SuppressLint("CheckResult") + override fun doWork(times: Int) { + Log.d(TAG, "doWork:$times") + mStatus.postValue(true) + val dateFormat = SimpleDateFormat("HH:mm:ss", Locale.getDefault()) + dateFormat.timeZone = TimeZone.getTimeZone("GMT+00:00") + var oldTimer = CactusSave.timer + if (times == 1) { + CactusSave.lastTimer = oldTimer + CactusSave.endDate = CactusSave.date + oldTimer = 0L + } + mLastTimer.postValue(dateFormat.format(Date(CactusSave.lastTimer * 1000))) + mEndDate.postValue(CactusSave.endDate) + mDisposable = Observable.interval(1, TimeUnit.SECONDS) + .map { + oldTimer + it + } + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe { aLong -> + CactusSave.timer = aLong + CactusSave.date = SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault()).run { + format(Date()) + } + mTimer.value = dateFormat.format(Date(aLong * 1000)) + } + } + + override fun onStop() { + Log.d(TAG, "onStop") + mStatus.postValue(false) + mDisposable?.apply { + if (!isDisposed) { + dispose() + } + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/AppListActivity.java b/app/src/main/java/com/idormy/sms/forwarder/AppListActivity.java deleted file mode 100644 index 894c13b3..00000000 --- a/app/src/main/java/com/idormy/sms/forwarder/AppListActivity.java +++ /dev/null @@ -1,151 +0,0 @@ -package com.idormy.sms.forwarder; - -import static com.idormy.sms.forwarder.SenderActivity.NOTIFY; - -import android.annotation.SuppressLint; -import android.content.ClipData; -import android.content.ClipboardManager; -import android.content.Context; -import android.content.Intent; -import android.content.pm.ApplicationInfo; -import android.content.pm.PackageInfo; -import android.content.pm.PackageManager; -import android.graphics.drawable.Drawable; -import android.os.Bundle; -import android.os.Handler; -import android.os.Message; -import android.util.Log; -import android.view.View; -import android.widget.ListView; -import android.widget.RadioButton; -import android.widget.RadioGroup; -import android.widget.TextView; - -import com.hjq.toast.ToastUtils; -import com.idormy.sms.forwarder.adapter.AppAdapter; -import com.idormy.sms.forwarder.model.AppInfo; - -import java.util.ArrayList; -import java.util.List; - -@SuppressWarnings("deprecation") -public class AppListActivity extends BaseActivity { - - public static final int APP_LIST = 0x9731991; - private final String TAG = "AppListActivity"; - private List appInfoList = new ArrayList<>(); - private ListView listView; - private String currentType = "user"; - - //消息处理者,创建一个Handler的子类对象,目的是重写Handler的处理消息的方法(handleMessage()) - @SuppressLint("HandlerLeak") - private final Handler handler = new Handler() { - @Override - public void handleMessage(Message msg) { - if (msg.what == NOTIFY) { - ToastUtils.delayedShow(msg.getData().getString("DATA"), 3000); - } else if (msg.what == APP_LIST) { - AppAdapter adapter = new AppAdapter(AppListActivity.this, R.layout.item_app, appInfoList); - listView.setAdapter(adapter); - } - } - }; - - @Override - protected void onCreate(Bundle savedInstanceState) { - Log.d(TAG, "onCreate"); - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_applist); - } - - @Override - protected void onStart() { - super.onStart(); - Log.d(TAG, "onStart"); - - //是否关闭页面提示 - TextView help_tip = findViewById(R.id.help_tip); - help_tip.setVisibility(MyApplication.showHelpTip ? View.VISIBLE : View.GONE); - - //获取应用列表 - getAppList(); - - //切换日志类别 - int typeCheckId = "user".equals(currentType) ? R.id.btnTypeUser : R.id.btnTypeSys; - final RadioGroup radioGroupTypeCheck = findViewById(R.id.radioGroupTypeCheck); - radioGroupTypeCheck.check(typeCheckId); - radioGroupTypeCheck.setOnCheckedChangeListener((group, checkedId) -> { - RadioButton rb = findViewById(checkedId); - currentType = (String) rb.getTag(); - getAppList(); - }); - - listView = findViewById(R.id.list_view_app); - listView.setOnItemClickListener((parent, view, position, id) -> { - AppInfo appInfo = appInfoList.get(position); - Log.d(TAG, "onItemClick: " + appInfo.toString()); - //复制到剪贴板 - ClipboardManager cm = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE); - ClipData mClipData = ClipData.newPlainText("pkgName", appInfo.getPkgName()); - cm.setPrimaryClip(mClipData); - - ToastUtils.delayedShow(getString(R.string.package_name_copied) + appInfo.getPkgName(), 3000); - }); - listView.setOnItemLongClickListener((parent, view, position, id) -> { - AppInfo appInfo = appInfoList.get(position); - Log.d(TAG, "onItemClick: " + appInfo.toString()); - //启动应用 - Intent intent; - intent = getPackageManager().getLaunchIntentForPackage(appInfo.getPkgName()); - startActivity(intent); - - return true; - }); - } - - //获取应用列表 - private void getAppList() { - new Thread(() -> { - Message msg = new Message(); - msg.what = NOTIFY; - Bundle bundle = new Bundle(); - bundle.putString("DATA", "user".equals(currentType) ? getString(R.string.loading_user_app) : getString(R.string.loading_system_app)); - msg.setData(bundle); - handler.sendMessage(msg); - - appInfoList = new ArrayList<>(); - PackageManager pm = getApplication().getPackageManager(); - try { - List packages = pm.getInstalledPackages(PackageManager.GET_ACTIVITIES | PackageManager.GET_SERVICES); - for (PackageInfo packageInfo : packages) { - //只取用户应用 - if ("user".equals(currentType) && isSystemApp(packageInfo)) continue; - //只取系统应用 - if ("sys".equals(currentType) && !isSystemApp(packageInfo)) continue; - - String appName = packageInfo.applicationInfo.loadLabel(pm).toString(); - String packageName = packageInfo.packageName; - Drawable drawable = packageInfo.applicationInfo.loadIcon(pm); - String verName = packageInfo.versionName; - int verCode = packageInfo.versionCode; - AppInfo appInfo = new AppInfo(appName, packageName, drawable, verName, verCode); - appInfoList.add(appInfo); - Log.d(TAG, appInfo.toString()); - } - } catch (Throwable t) { - t.printStackTrace(); - } - - Message message = new Message(); - message.what = APP_LIST; - message.obj = appInfoList; - handler.sendMessage(message); - }).start(); - } - - // 通过packName得到PackageInfo,作为参数传入即可 - private boolean isSystemApp(PackageInfo pi) { - return (pi.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) == 1; - } - -} diff --git a/app/src/main/java/com/idormy/sms/forwarder/BaseActivity.java b/app/src/main/java/com/idormy/sms/forwarder/BaseActivity.java deleted file mode 100644 index dc168838..00000000 --- a/app/src/main/java/com/idormy/sms/forwarder/BaseActivity.java +++ /dev/null @@ -1,68 +0,0 @@ -package com.idormy.sms.forwarder; - -import android.annotation.SuppressLint; -import android.content.Intent; -import android.util.Log; -import android.view.Menu; -import android.view.MenuItem; - -import androidx.appcompat.app.AppCompatActivity; - -import java.lang.reflect.Method; - -public class BaseActivity extends AppCompatActivity { - - //启用menu - @Override - public boolean onCreateOptionsMenu(Menu menu) { - getMenuInflater().inflate(R.menu.menu_main, menu); - return super.onCreateOptionsMenu(menu); - } - - //menu点击事件 - @SuppressLint("NonConstantResourceId") - @Override - public boolean onOptionsItemSelected(MenuItem item) { - Intent intent; - switch (item.getItemId()) { - case R.id.to_app_list: - intent = new Intent(this, AppListActivity.class); - break; - case R.id.to_clone: - intent = new Intent(this, CloneActivity.class); - break; - case R.id.to_about: - intent = new Intent(this, AboutActivity.class); - break; - case R.id.to_help: - intent = new Intent(this, HelpActivity.class); - break; - default: - return super.onOptionsItemSelected(item); - } - - startActivity(intent); - return true; - } - - //设置menu图标显示 - @Override - public boolean onMenuOpened(int featureId, Menu menu) { - String TAG = "BaseActivity"; - Log.d(TAG, "onMenuOpened, featureId=" + featureId); - if (menu != null) { - if (menu.getClass().getSimpleName().equals("MenuBuilder")) { - try { - Method m = menu.getClass().getDeclaredMethod("setOptionalIconsVisible", Boolean.TYPE); - m.setAccessible(true); - m.invoke(menu, true); - } catch (NoSuchMethodException e) { - Log.e(TAG, "onMenuOpened", e); - } catch (Exception e) { - throw new RuntimeException(e); - } - } - } - return super.onMenuOpened(featureId, menu); - } -} diff --git a/app/src/main/java/com/idormy/sms/forwarder/CloneActivity.java b/app/src/main/java/com/idormy/sms/forwarder/CloneActivity.java deleted file mode 100644 index 8f150d1f..00000000 --- a/app/src/main/java/com/idormy/sms/forwarder/CloneActivity.java +++ /dev/null @@ -1,300 +0,0 @@ -package com.idormy.sms.forwarder; - -import android.annotation.SuppressLint; -import android.content.Context; -import android.os.Bundle; -import android.os.Environment; -import android.text.TextUtils; -import android.util.Log; -import android.view.View; -import android.widget.Button; -import android.widget.LinearLayout; -import android.widget.RadioGroup; -import android.widget.TextView; - -import androidx.annotation.NonNull; - -import com.alibaba.fastjson.JSON; -import com.hjq.permissions.OnPermissionCallback; -import com.hjq.permissions.Permission; -import com.hjq.permissions.XXPermissions; -import com.hjq.toast.ToastUtils; -import com.idormy.sms.forwarder.model.vo.CloneInfoVo; -import com.idormy.sms.forwarder.receiver.BaseServlet; -import com.idormy.sms.forwarder.receiver.RebootBroadcastReceiver; -import com.idormy.sms.forwarder.sender.HttpServer; -import com.idormy.sms.forwarder.utils.CloneUtils; -import com.idormy.sms.forwarder.utils.Define; -import com.idormy.sms.forwarder.utils.FileUtils; -import com.idormy.sms.forwarder.utils.HttpUtils; -import com.idormy.sms.forwarder.utils.NetUtils; -import com.idormy.sms.forwarder.utils.SettingUtils; -import com.idormy.sms.forwarder.view.IPEditText; - -import java.io.File; -import java.io.IOException; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.concurrent.TimeUnit; - -import okhttp3.Call; -import okhttp3.Callback; -import okhttp3.MediaType; -import okhttp3.OkHttpClient; -import okhttp3.Request; -import okhttp3.RequestBody; -import okhttp3.Response; - -public class CloneActivity extends BaseActivity { - private final String TAG = "CloneActivity"; - private Context context; - private String serverIp; - private String backupPath; - private final String backupFile = "SmsForwarder.json"; - private IPEditText textServerIp; - private TextView sendTxt; - private TextView receiveTxt; - private TextView backupPathTxt; - private Button sendBtn; - - @Override - public void onCreate(Bundle savedInstanceState) { - Log.d(TAG, "onCreate"); - super.onCreate(savedInstanceState); - - setContentView(R.layout.activity_clone); - Log.d(TAG, "onCreate: " + RebootBroadcastReceiver.class.getName()); - - HttpUtils.init(this); - HttpServer.init(this); - } - - @SuppressWarnings({"rawtypes", "unchecked", "deprecation"}) - @SuppressLint("SetTextI18n") - @Override - protected void onStart() { - super.onStart(); - Log.d(TAG, "onStart"); - - backupPathTxt = findViewById(R.id.backupPathTxt); - // 申请储存权限 - XXPermissions.with(this).permission(Permission.Group.STORAGE).request(new OnPermissionCallback() { - @Override - public void onGranted(List permissions, boolean all) { - backupPath = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).getPath(); - backupPathTxt.setText(backupPath + File.separator + backupFile); - } - - @Override - public void onDenied(List permissions, boolean never) { - if (never) { - ToastUtils.show(R.string.toast_denied_never); - // 如果是被永久拒绝就跳转到应用权限系统设置页面 - XXPermissions.startPermissionActivity(CloneActivity.this, permissions); - } else { - ToastUtils.show(R.string.toast_denied); - } - backupPathTxt.setText("未授权储存权限,该功能无法使用!"); - } - }); - - LinearLayout layoutNetwork = findViewById(R.id.layoutNetwork); - LinearLayout layoutOffline = findViewById(R.id.layoutOffline); - final RadioGroup radioGroupTypeCheck = findViewById(R.id.radioGroupTypeCheck); - radioGroupTypeCheck.setOnCheckedChangeListener((group, checkedId) -> { - if (checkedId == R.id.btnTypeOffline) { - layoutNetwork.setVisibility(View.GONE); - layoutOffline.setVisibility(View.VISIBLE); - } else { - layoutNetwork.setVisibility(View.VISIBLE); - layoutOffline.setVisibility(View.GONE); - } - }); - - sendBtn = findViewById(R.id.sendBtn); - sendTxt = findViewById(R.id.sendTxt); - TextView ipText = findViewById(R.id.ipText); - textServerIp = findViewById(R.id.textServerIp); - receiveTxt = findViewById(R.id.receiveTxt); - Button receiveBtn = findViewById(R.id.receiveBtn); - - serverIp = NetUtils.getLocalIp(CloneActivity.this); - ipText.setText(serverIp); - - if (HttpServer.asRunning()) { - sendBtn.setText(R.string.stop); - sendTxt.setText(R.string.server_has_started); - textServerIp.setIP(serverIp); - } else { - sendBtn.setText(R.string.send); - sendTxt.setText(R.string.server_has_stopped); - } - - //发送 - sendBtn.setOnClickListener(v -> { - if (!HttpServer.asRunning() && NetUtils.NETWORK_WIFI != NetUtils.getNetWorkStatus()) { - ToastUtils.show(getString(R.string.no_wifi_network)); - return; - } - - SettingUtils.switchEnableHttpServer(!SettingUtils.getSwitchEnableHttpServer()); - if (!HttpServer.update()) { - SettingUtils.switchEnableHttpServer(!SettingUtils.getSwitchEnableHttpServer()); - return; - } - if (!HttpServer.asRunning()) { - sendTxt.setText(R.string.server_has_stopped); - textServerIp.setIP(""); - sendBtn.setText(R.string.send); - } else { - sendTxt.setText(R.string.server_has_started); - textServerIp.setIP(serverIp); - sendBtn.setText(R.string.stop); - } - }); - - //接收 - receiveBtn.setOnClickListener(v -> { - if (HttpServer.asRunning()) { - receiveTxt.setText(R.string.sender_cannot_receive); - ToastUtils.show(getString(R.string.sender_cannot_receive)); - return; - } - - if (NetUtils.NETWORK_WIFI != NetUtils.getNetWorkStatus()) { - receiveTxt.setText(R.string.no_wifi_network); - ToastUtils.show(getString(R.string.no_wifi_network)); - return; - } - - serverIp = textServerIp.getIP(); - if (serverIp == null || serverIp.isEmpty()) { - receiveTxt.setText(R.string.invalid_server_ip); - ToastUtils.show(getString(R.string.invalid_server_ip)); - return; - } - - OkHttpClient.Builder builder = new OkHttpClient.Builder(); - //设置读取超时时间 - OkHttpClient client = builder - .readTimeout(Define.REQUEST_TIMEOUT_SECONDS, TimeUnit.SECONDS) - .writeTimeout(Define.REQUEST_TIMEOUT_SECONDS, TimeUnit.SECONDS) - .connectTimeout(Define.REQUEST_TIMEOUT_SECONDS, TimeUnit.SECONDS) - .build(); - - Map msgMap = new HashMap(); - msgMap.put("versionCode", SettingUtils.getVersionCode()); - msgMap.put("versionName", SettingUtils.getVersionName()); - - String requestMsg = JSON.toJSONString(msgMap); - Log.i(TAG, "requestMsg:" + requestMsg); - RequestBody requestBody = RequestBody.create(MediaType.parse("application/json;charset=utf-8"), requestMsg); - - //请求链接:post 获取版本信息,get 下载备份文件 - final String requestUrl = "http://" + serverIp + ":" + Define.HTTP_SERVER_PORT + BaseServlet.CLONE_PATH + "?" + System.currentTimeMillis(); - Log.i(TAG, "requestUrl:" + requestUrl); - - //获取版本信息 - final Request request = new Request.Builder() - .url(requestUrl) - .addHeader("Content-Type", "application/json; charset=utf-8") - .post(requestBody) - .build(); - client.newCall(request).enqueue(new Callback() { - @Override - public void onFailure(@NonNull Call call, @NonNull final IOException e) { - ToastUtils.show(getString(R.string.tips_get_info_failed)); - } - - @Override - public void onResponse(@NonNull Call call, @NonNull Response response) throws IOException { - final String responseStr = Objects.requireNonNull(response.body()).string(); - Log.d(TAG, "Response:" + response.code() + "," + responseStr); - - if (TextUtils.isEmpty(responseStr)) { - ToastUtils.show(getString(R.string.tips_get_info_failed)); - return; - } - - try { - CloneInfoVo cloneInfoVo = JSON.parseObject(responseStr, CloneInfoVo.class); - Log.d(TAG, cloneInfoVo.toString()); - - if (!SettingUtils.getVersionName().equals(cloneInfoVo.getVersionName())) { - ToastUtils.show(getString(R.string.tips_versions_inconsistent)); - return; - } - - if (CloneUtils.restoreSettings(cloneInfoVo)) { - ToastUtils.show(getString(R.string.tips_clone_done)); - } else { - ToastUtils.show(getString(R.string.tips_clone_failed)); - } - - } catch (Exception e) { - ToastUtils.show(getString(R.string.tips_clone_failed) + e.getMessage()); - } - } - }); - - }); - - Button exportBtn = findViewById(R.id.exportBtn); - TextView exportTxt = findViewById(R.id.exportTxt); - Button importBtn = findViewById(R.id.importBtn); - TextView importTxt = findViewById(R.id.importTxt); - - //导出 - exportBtn.setOnClickListener(v -> { - if (FileUtils.writeFileR(CloneUtils.exportSettings(), backupPath, backupFile, true)) { - ToastUtils.show("导出配置成功!"); - } else { - exportTxt.setText("导出失败,请检查写入权限!"); - ToastUtils.show("导出失败,请检查写入权限!"); - } - }); - - //导入 - importBtn.setOnClickListener(v -> { - try { - String responseStr = FileUtils.readFileI(backupPath, backupFile); - if (TextUtils.isEmpty(responseStr)) { - ToastUtils.show(getString(R.string.tips_get_info_failed)); - return; - } - - CloneInfoVo cloneInfoVo = JSON.parseObject(responseStr, CloneInfoVo.class); - Log.d(TAG, Objects.requireNonNull(cloneInfoVo).toString()); - - if (!SettingUtils.getVersionName().equals(cloneInfoVo.getVersionName())) { - ToastUtils.show(getString(R.string.tips_versions_inconsistent)); - return; - } - - if (CloneUtils.restoreSettings(cloneInfoVo)) { - ToastUtils.show(getString(R.string.tips_clone_done)); - } else { - ToastUtils.show(getString(R.string.tips_clone_failed)); - } - - } catch (Exception e) { - e.printStackTrace(); - importTxt.setText("还原失败:" + e.getMessage()); - } - }); - - } - - @SuppressLint("SetTextI18n") - @Override - protected void onResume() { - super.onResume(); - - serverIp = NetUtils.getLocalIp(CloneActivity.this); - TextView ipText = findViewById(R.id.ipText); - ipText.setText(getString(R.string.local_ip) + serverIp); - } - -} diff --git a/app/src/main/java/com/idormy/sms/forwarder/HelpActivity.java b/app/src/main/java/com/idormy/sms/forwarder/HelpActivity.java deleted file mode 100644 index 213492db..00000000 --- a/app/src/main/java/com/idormy/sms/forwarder/HelpActivity.java +++ /dev/null @@ -1,58 +0,0 @@ -package com.idormy.sms.forwarder; - -import android.annotation.SuppressLint; -import android.os.Build; -import android.os.Bundle; -import android.webkit.WebSettings; -import android.webkit.WebView; -import android.webkit.WebViewClient; - -@SuppressWarnings("deprecation") -public class HelpActivity extends BaseActivity { - - @SuppressLint("SetJavaScriptEnabled") - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_help); - //获得控件 - WebView webView = findViewById(R.id.wv_webview); - - //设置 - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - webView.getSettings().setSafeBrowsingEnabled(false); - } - WebSettings webSetting = webView.getSettings(); - webSetting.setJavaScriptEnabled(true); - - webSetting.setBuiltInZoomControls(true); - webSetting.setDisplayZoomControls(false); - webSetting.setUseWideViewPort(true); - - webSetting.setBlockNetworkImage(false); - //缓存模式 - webSetting.setCacheMode(WebSettings.LOAD_NO_CACHE); - webSetting.setDatabaseEnabled(true); - webSetting.setDomStorageEnabled(true); - webSetting.setAppCacheMaxSize(1024 * 1024 * 8); - webSetting.setAppCachePath(getFilesDir().getAbsolutePath()); - webSetting.setDatabasePath(getFilesDir().getAbsolutePath()); - webSetting.setAllowFileAccess(true); - webSetting.setAppCacheEnabled(true); - //webSetting.setTextZoom(100); - webSetting.setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW); - - //访问网页 - webView.loadUrl("https://gitee.com/pp/SmsForwarder/wikis/pages"); - //系统默认会通过手机浏览器打开网页,为了能够直接通过WebView显示网页,则必须设置 - webView.setWebViewClient(new WebViewClient() { - @Override - public boolean shouldOverrideUrlLoading(WebView view, String url) { - //使用WebView加载显示url - view.loadUrl(url); - //返回true - return true; - } - }); - } -} \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/MainActivity.java b/app/src/main/java/com/idormy/sms/forwarder/MainActivity.java deleted file mode 100644 index 6e11289d..00000000 --- a/app/src/main/java/com/idormy/sms/forwarder/MainActivity.java +++ /dev/null @@ -1,539 +0,0 @@ -package com.idormy.sms.forwarder; - -import android.annotation.SuppressLint; -import android.app.Dialog; -import android.content.Context; -import android.content.Intent; -import android.content.pm.PackageManager; -import android.os.Build; -import android.os.Bundle; -import android.os.Handler; -import android.os.Looper; -import android.util.Log; -import android.view.Gravity; -import android.view.LayoutInflater; -import android.view.View; -import android.view.Window; -import android.view.WindowManager; -import android.widget.RadioButton; -import android.widget.RadioGroup; -import android.widget.TextView; - -import androidx.annotation.NonNull; -import androidx.appcompat.app.AlertDialog; - -import com.google.android.material.floatingactionbutton.FloatingActionButton; -import com.hjq.permissions.OnPermissionCallback; -import com.hjq.permissions.Permission; -import com.hjq.permissions.XXPermissions; -import com.hjq.toast.ToastUtils; -import com.idormy.sms.forwarder.adapter.LogAdapter; -import com.idormy.sms.forwarder.model.vo.LogVo; -import com.idormy.sms.forwarder.sender.BatteryReportCronTask; -import com.idormy.sms.forwarder.sender.HttpServer; -import com.idormy.sms.forwarder.sender.SendUtil; -import com.idormy.sms.forwarder.sender.SenderUtil; -import com.idormy.sms.forwarder.service.BatteryService; -import com.idormy.sms.forwarder.service.FrontService; -import com.idormy.sms.forwarder.service.MusicService; -import com.idormy.sms.forwarder.utils.CommonUtils; -import com.idormy.sms.forwarder.utils.HttpUtils; -import com.idormy.sms.forwarder.utils.KeepAliveUtils; -import com.idormy.sms.forwarder.utils.LogUtils; -import com.idormy.sms.forwarder.utils.NetUtils; -import com.idormy.sms.forwarder.utils.OnePixelManager; -import com.idormy.sms.forwarder.utils.PhoneUtils; -import com.idormy.sms.forwarder.utils.RuleUtils; -import com.idormy.sms.forwarder.utils.SettingUtils; -import com.idormy.sms.forwarder.utils.SharedPreferencesHelper; -import com.idormy.sms.forwarder.utils.SmsUtils; -import com.idormy.sms.forwarder.utils.TimeUtils; -import com.idormy.sms.forwarder.utils.UmInitConfig; -import com.idormy.sms.forwarder.view.RefreshListView; -import com.idormy.sms.forwarder.view.StepBar; -import com.umeng.commonsdk.UMConfigure; - -import java.util.ArrayList; -import java.util.List; - -public class MainActivity extends BaseActivity implements RefreshListView.IRefreshListener { - - private final String TAG = "MainActivity"; - // logVoList用于存储数据 - private List logVos = new ArrayList<>(); - private LogAdapter adapter; - private RefreshListView listView; - private Intent serviceIntent; - private String currentType = "sms"; - OnePixelManager onePixelManager; - - @Override - protected void onCreate(Bundle savedInstanceState) { - Log.d(TAG, "onCreate"); - - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_main); - - //是否同意隐私协议 - if (!MyApplication.allowPrivacyPolicy) return; - - //短信&网络组件初始化 - SmsUtils.init(this); - NetUtils.init(this); - - LogUtils.init(this); - RuleUtils.init(this); - SenderUtil.init(this); - - //前台服务 - try { - serviceIntent = new Intent(MainActivity.this, FrontService.class); - serviceIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - startService(serviceIntent); - } catch (Exception e) { - Log.e(TAG, "FrontService:", e); - } - - //监听电池状态 - try { - Intent batteryServiceIntent = new Intent(this, BatteryService.class); - batteryServiceIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - startService(batteryServiceIntent); - } catch (Exception e) { - Log.e(TAG, "BatteryService:", e); - } - - //后台播放无声音乐 - if (SettingUtils.getPlaySilenceMusic()) { - try { - Intent musicServiceIntent = new Intent(this, MusicService.class); - musicServiceIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - startService(musicServiceIntent); - } catch (Exception e) { - Log.e(TAG, "MusicService:", e); - } - } - - //1像素透明Activity保活 or 仅锁屏状态转发APP通知 - if (SettingUtils.getOnePixelActivity() || SettingUtils.getSwitchNotUserPresent()) { - try { - onePixelManager = new OnePixelManager(); - onePixelManager.registerOnePixelReceiver(this);//注册广播接收者 - } catch (Exception e) { - Log.e(TAG, "OnePixelManager:", e); - } - } - - HttpUtils.init(this); - //启用HttpServer - if (SettingUtils.getSwitchEnableHttpServer()) { - HttpServer.init(this); - try { - HttpServer.update(); - } catch (Exception e) { - Log.e(TAG, "Start HttpServer:", e); - } - } - - //电池状态定时推送 - if (SettingUtils.getSwitchEnableBatteryCron()) { - try { - BatteryReportCronTask.getSingleton().updateTimer(); - } catch (Exception e) { - Log.e(TAG, "BatteryReportCronTask:", e); - } - } - - } - - @Override - protected void onStart() { - super.onStart(); - Log.d(TAG, "onStart"); - - //是否同意隐私协议 - if (!MyApplication.allowPrivacyPolicy) { - dialog(this); - return; - } - - //检查权限是否获取 - PackageManager pm = getPackageManager(); - CommonUtils.CheckPermission(pm, this); - XXPermissions.with(this) - // 接收短信 - .permission(Permission.RECEIVE_SMS) - // 发送短信 - //.permission(Permission.SEND_SMS) - // 读取短信 - .permission(Permission.READ_SMS) - // 读取电话状态 - .permission(Permission.READ_PHONE_STATE) - // 读取手机号码 - .permission(Permission.READ_PHONE_NUMBERS) - // 读取通话记录 - .permission(Permission.READ_CALL_LOG) - // 读取联系人 - .permission(Permission.READ_CONTACTS) - // 储存权限 - .permission(Permission.Group.STORAGE) - // 申请安装包权限 - //.permission(Permission.REQUEST_INSTALL_PACKAGES) - // 申请通知栏权限 - .permission(Permission.NOTIFICATION_SERVICE) - // 申请系统设置权限 - //.permission(Permission.WRITE_SETTINGS) - .request(new OnPermissionCallback() { - - @Override - public void onGranted(List permissions, boolean all) { - if (MyApplication.showHelpTip) { - if (all) { - ToastUtils.show(R.string.toast_granted_all); - } else { - ToastUtils.show(R.string.toast_granted_part); - } - } - SettingUtils.switchEnableSms(true); - - //首次使用重要提醒 - final SharedPreferencesHelper sharedPreferencesHelper = new SharedPreferencesHelper(MainActivity.this, "umeng"); - boolean firstTime = sharedPreferencesHelper.getSharedPreference("firstTime", "true").equals("true"); - if (firstTime && LogUtils.countLog("2", null, null) == 0) { - AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this) - .setIcon(R.mipmap.ic_launcher) - .setTitle("首次使用重要提醒") - .setMessage(R.string.tips_first_time) - .setCancelable(false)//点击对话框以外的区域是否让对话框消失 - .setPositiveButton("前往系统设置", (dialogInterface, i) -> { - sharedPreferencesHelper.put("firstTime", "false"); - dialogInterface.dismiss(); - XXPermissions.startPermissionActivity(MainActivity.this); - }).setNegativeButton("稍后自行处理", (dialogInterface, i) -> { - sharedPreferencesHelper.put("firstTime", "false"); - dialogInterface.dismiss(); - }); - builder.create().show(); - } - } - - @Override - public void onDenied(List permissions, boolean never) { - if (MyApplication.showHelpTip) { - if (never) { - ToastUtils.show(R.string.toast_denied_never); - // 如果是被永久拒绝就跳转到应用权限系统设置页面 - XXPermissions.startPermissionActivity(MainActivity.this, permissions); - } else { - ToastUtils.show(R.string.toast_denied); - } - } - SettingUtils.switchEnableSms(false); - } - }); - - //计算浮动按钮位置 - FloatingActionButton btnFloat = findViewById(R.id.btnCleanLog); - RefreshListView viewList = findViewById(R.id.list_view_log); - CommonUtils.calcMarginBottom(this, btnFloat, viewList, null); - - //清空日志 - btnFloat.setOnClickListener(v -> { - AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this); - builder.setTitle(R.string.clear_logs_tips) - .setPositiveButton(R.string.confirm, (dialog, which) -> { - // TODO Auto-generated method stub - LogUtils.delLog(null, null); - initTLogs(); - adapter.add(logVos); - }); - builder.show(); - }); - - // 先拿到数据并放在适配器上 - initTLogs(); //初始化数据 - showList(logVos); - - //切换日志类别 - int typeCheckId = getTypeCheckId(currentType); - final RadioGroup radioGroupTypeCheck = findViewById(R.id.radioGroupTypeCheck); - radioGroupTypeCheck.check(typeCheckId); - radioGroupTypeCheck.setOnCheckedChangeListener((group, checkedId) -> { - RadioButton rb = findViewById(checkedId); - currentType = (String) rb.getTag(); - initTLogs(); - showList(logVos); - }); - - // 为ListView注册一个监听器,当用户点击了ListView中的任何一个子项时,就会回调onItemClick()方法 - // 在这个方法中可以通过position参数判断出用户点击的是那一个子项 - listView.setOnItemClickListener((parent, view, position, id) -> { - if (position <= 0) return; - - LogVo logVo = logVos.get(position - 1); - logDetail(logVo); - }); - - listView.setOnItemLongClickListener((parent, view, position, id) -> { - if (position <= 0) return false; - - //定义AlertDialog.Builder对象,当长按列表项的时候弹出确认删除对话框 - AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this); - builder.setTitle(R.string.delete_log_title); - builder.setMessage(R.string.delete_log_tips); - - //添加AlertDialog.Builder对象的setPositiveButton()方法 - builder.setPositiveButton(R.string.confirm, (dialog, which) -> { - Long id1 = logVos.get(position - 1).getId(); - Log.d(TAG, "id = " + id1); - LogUtils.delLog(id1, null); - initTLogs(); //初始化数据 - showList(logVos); - ToastUtils.show(R.string.delete_log_toast); - }); - - //添加AlertDialog.Builder对象的setNegativeButton()方法 - builder.setNegativeButton(R.string.cancel, (dialog, which) -> { - }); - - builder.create().show(); - return true; - }); - - //步骤完成状态校验 - StepBar stepBar = findViewById(R.id.stepBar); - stepBar.setHighlight(); - } - - private int getTypeCheckId(String currentType) { - switch (currentType) { - case "call": - return R.id.btnTypeCall; - case "app": - return R.id.btnTypeApp; - default: - return R.id.btnTypeSms; - } - } - - @SuppressLint("ObsoleteSdkInt") - @Override - protected void onResume() { - super.onResume(); - - try { - //是否同意隐私协议 - if (!MyApplication.allowPrivacyPolicy) return; - - //第一次打开,未授权无法获取SIM信息,尝试在此重新获取 - if (MyApplication.SimInfoList.isEmpty()) { - MyApplication.SimInfoList = PhoneUtils.getSimMultiInfo(); - } - Log.d(TAG, "SimInfoList = " + MyApplication.SimInfoList.size()); - - //省电优化设置为无限制 - if (MyApplication.showHelpTip && Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) { - if (!KeepAliveUtils.isIgnoreBatteryOptimization(this)) { - ToastUtils.delayedShow(R.string.tips_battery_optimization, 3000); - } - } - - //开启读取通知栏权限 - if (SettingUtils.getSwitchEnableAppNotify() && !CommonUtils.isNotificationListenerServiceEnabled(this)) { - CommonUtils.toggleNotificationListenerService(this); - SettingUtils.switchEnableAppNotify(false); - ToastUtils.delayedShow(R.string.tips_notification_listener, 3000); - return; - } - - if (serviceIntent != null) startService(serviceIntent); - } catch (Exception e) { - Log.e(TAG, "onResume:", e); - } - } - - @Override - protected void onDestroy() { - super.onDestroy(); - - //是否同意隐私协议 - if (!MyApplication.allowPrivacyPolicy) return; - - try { - if (serviceIntent != null) startService(serviceIntent); - } catch (Exception e) { - Log.e(TAG, "onDestroy:", e); - } - - if (onePixelManager != null) onePixelManager.unregisterOnePixelReceiver(this); - } - - @Override - protected void onPause() { - overridePendingTransition(0, 0); - super.onPause(); - - //是否同意隐私协议 - if (!MyApplication.allowPrivacyPolicy) return; - - try { - if (serviceIntent != null) startService(serviceIntent); - } catch (Exception e) { - Log.e(TAG, "onPause:", e); - } - } - - @Override - protected void onActivityResult(int requestCode, int resultCode, Intent data) { - super.onActivityResult(requestCode, resultCode, data); - - //是否同意隐私协议 - if (!MyApplication.allowPrivacyPolicy) return; - - if (requestCode == CommonUtils.NOTIFICATION_REQUEST_CODE) { - if (CommonUtils.isNotificationListenerServiceEnabled(this)) { - ToastUtils.show(R.string.notification_listener_service_enabled); - CommonUtils.toggleNotificationListenerService(this); - } else { - ToastUtils.show(R.string.notification_listener_service_disabled); - } - } - } - - // 权限判断相关 - @Override - public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { - super.onRequestPermissionsResult(requestCode, permissions, grantResults); - } - - // 初始化数据 - private void initTLogs() { - logVos = LogUtils.getLog(null, null, currentType); - } - - private void showList(List logVosN) { - //Log.d(TAG, "showList: " + logVosN); - if (adapter == null) { - // 将适配器上的数据传递给listView - listView = findViewById(R.id.list_view_log); - listView.setInterface(this); - adapter = new LogAdapter(MainActivity.this, R.layout.item_log, logVosN); - listView.setAdapter(adapter); - } else { - adapter.onDateChange(logVosN); - } - } - - @Override - public void onRefresh() { - Handler handler = new Handler(); - handler.postDelayed(() -> { - // TODO Auto-generated method stub - //获取最新数据 - initTLogs(); - //通知界面显示 - showList(logVos); - //通知listview 刷新数据完毕; - listView.refreshComplete(); - }, 2000); - } - - public void logDetail(LogVo logVo) { - AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this); - builder.setTitle(R.string.details); - String simInfo = logVo.getSimInfo(); - if (simInfo != null) { - builder.setMessage(getString(R.string.from) + logVo.getFrom() + "\n\n" + getString(R.string.msg) + logVo.getContent() + "\n\n" + getString(R.string.slot) + logVo.getSimInfo() + "\n\n" + getString(R.string.rule) + logVo.getRule() + "\n\n" + getString(R.string.time) + TimeUtils.utc2Local(logVo.getTime()) + getString(R.string.result) + logVo.getForwardResponse()); - } else { - builder.setMessage(getString(R.string.from) + logVo.getFrom() + "\n\n" + getString(R.string.msg) + logVo.getContent() + "\n\n" + getString(R.string.rule) + logVo.getRule() + "\n\n" + getString(R.string.time) + TimeUtils.utc2Local(logVo.getTime()) + getString(R.string.result) + logVo.getForwardResponse()); - } - //删除 - builder.setNegativeButton(R.string.del, (dialog, which) -> { - Long id = logVo.getId(); - Log.d(TAG, "id = " + id); - LogUtils.delLog(id, null); - initTLogs(); //初始化数据 - showList(logVos); - ToastUtils.show(R.string.delete_log_toast); - dialog.dismiss(); - }); - //取消 - builder.setPositiveButton(R.string.cancel, (dialog, which) -> dialog.dismiss()); - - //重发消息回调,重发失败也会触发 - Handler handler = new Handler(Looper.myLooper(), msg -> { - initTLogs(); - showList(logVos); - return true; - }); - //对于发送失败的消息添加重发按钮 - if (logVo.getForwardStatus() != 2) { - builder.setNeutralButton(R.string.resend, (dialog, which) -> { - ToastUtils.show(R.string.resend_toast); - SendUtil.resendMsgByLog(MainActivity.this, handler, logVo); - dialog.dismiss(); - }); - } - builder.show(); - } - - //按返回键不退出回到桌面 - @Override - public void onBackPressed() { - Intent intent = new Intent(Intent.ACTION_MAIN); - intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - intent.addCategory(Intent.CATEGORY_HOME); - startActivity(intent); - } - - /*** 隐私协议授权弹窗*/ - public void dialog(Context context) { - Dialog dialog = new Dialog(context, R.style.dialog); - @SuppressLint("InflateParams") View inflate = LayoutInflater.from(context).inflate(R.layout.diaolog_privacy_policy, null); - TextView succsebtn = inflate.findViewById(R.id.succsebtn); - TextView canclebtn = inflate.findViewById(R.id.caclebtn); - - succsebtn.setOnClickListener(v -> { - /* uminit为1时代表已经同意隐私协议,sp记录当前状态*/ - SharedPreferencesHelper sharedPreferencesHelper = new SharedPreferencesHelper(MainActivity.this, "umeng"); - sharedPreferencesHelper.put("uminit", "1"); - UMConfigure.submitPolicyGrantResult(getApplicationContext(), true); - /* 友盟sdk正式初始化*/ - UmInitConfig umInitConfig = new UmInitConfig(); - umInitConfig.UMinit(getApplicationContext()); - //关闭弹窗 - dialog.dismiss(); - - //跳转到HomeActivity - final Intent intent = context.getPackageManager().getLaunchIntentForPackage(getPackageName()); - intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); - startActivity(intent); - - //杀掉以前进程 - android.os.Process.killProcess(android.os.Process.myPid()); - finish(); - }); - - canclebtn.setOnClickListener(v -> { - dialog.dismiss(); - - UMConfigure.submitPolicyGrantResult(getApplicationContext(), false); - //不同意隐私协议,退出app - android.os.Process.killProcess(android.os.Process.myPid()); - finish(); - }); - - dialog.setContentView(inflate); - Window dialogWindow = dialog.getWindow(); - dialogWindow.setGravity(Gravity.CENTER); - - //自适应大小 - WindowManager.LayoutParams dialogParams = dialogWindow.getAttributes(); - dialogParams.width = (int) (context.getResources().getDisplayMetrics().widthPixels * 0.85); - //dialogParams.height = (int) (context.getResources().getDisplayMetrics().heightPixels * 0.7); - dialogWindow.setAttributes(dialogParams); - - dialog.setCancelable(false); - dialog.show(); - } -} diff --git a/app/src/main/java/com/idormy/sms/forwarder/MyApplication.java b/app/src/main/java/com/idormy/sms/forwarder/MyApplication.java deleted file mode 100644 index 96411a22..00000000 --- a/app/src/main/java/com/idormy/sms/forwarder/MyApplication.java +++ /dev/null @@ -1,137 +0,0 @@ -package com.idormy.sms.forwarder; - -import android.annotation.SuppressLint; -import android.app.ActivityManager; -import android.app.Application; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.content.SharedPreferences; -import android.os.Build; -import android.util.Log; - -import com.hjq.permissions.XXPermissions; -import com.hjq.toast.ToastUtils; -import com.hjq.toast.style.WhiteToastStyle; -import com.idormy.sms.forwarder.receiver.SimStateReceiver; -import com.idormy.sms.forwarder.sender.SendHistory; -import com.idormy.sms.forwarder.service.BatteryService; -import com.idormy.sms.forwarder.service.FrontService; -import com.idormy.sms.forwarder.service.MusicService; -import com.idormy.sms.forwarder.utils.CrashHandler; -import com.idormy.sms.forwarder.utils.Define; -import com.idormy.sms.forwarder.utils.PermissionInterceptor; -import com.idormy.sms.forwarder.utils.PhoneUtils; -import com.idormy.sms.forwarder.utils.SettingUtils; -import com.idormy.sms.forwarder.utils.SharedPreferencesHelper; -import com.idormy.sms.forwarder.utils.UmInitConfig; -import com.smailnet.emailkit.EmailKit; -import com.umeng.commonsdk.UMConfigure; - -import java.util.ArrayList; -import java.util.List; - -public class MyApplication extends Application { - private static final String TAG = "MyApplication"; - //SIM卡信息 - public static List SimInfoList = new ArrayList<>(); - //是否关闭页面提示 - public static boolean showHelpTip = true; - SharedPreferencesHelper sharedPreferencesHelper; - //是否同意隐私协议 - public static boolean allowPrivacyPolicy = false; - @SuppressLint("StaticFieldLeak") - private static Context context; - //是否已解锁 - public static boolean isUserPresent = true; - - @Override - protected void attachBaseContext(Context base) { - super.attachBaseContext(base); - } - - @Override - public void onCreate() { - Log.d(TAG, "onCreate"); - super.onCreate(); - context = getApplicationContext(); - - try { - //异常捕获类 - CrashHandler crashHandler = CrashHandler.getInstance(); - crashHandler.init(getApplicationContext()); - - // 初始化吐司工具类 - ToastUtils.init(this, new WhiteToastStyle()); - // 设置权限申请拦截器(全局设置) - XXPermissions.setInterceptor(new PermissionInterceptor()); - - //友盟统计 - sharedPreferencesHelper = new SharedPreferencesHelper(this, "umeng"); - //设置LOG开关,默认为false - //UMConfigure.setLogEnabled(true); - //友盟预初始化 - UMConfigure.preInit(getApplicationContext(), "60254fc7425ec25f10f4293e", "Umeng"); - - //判断是否同意隐私协议,uminit为1时为已经同意,直接初始化umsdk - if (sharedPreferencesHelper.getSharedPreference("uminit", "").equals("1")) { - allowPrivacyPolicy = true; - //友盟正式初始化 - UmInitConfig umInitConfig = new UmInitConfig(); - umInitConfig.UMinit(getApplicationContext()); - } - - //是否同意隐私协议 - if (!MyApplication.allowPrivacyPolicy) return; - - //前台服务 - Intent intent = new Intent(this, FrontService.class); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - startForegroundService(intent); - } else { - startService(intent); - } - - SendHistory.init(this); - SettingUtils.init(this); - EmailKit.initialize(this); - - SharedPreferences sp = MyApplication.this.getSharedPreferences(Define.SP_CONFIG, Context.MODE_PRIVATE); - showHelpTip = sp.getBoolean(Define.SP_CONFIG_SWITCH_HELP_TIP, true); - - if (SettingUtils.getExcludeFromRecents()) { - ActivityManager am = (ActivityManager) this.getSystemService(Context.ACTIVITY_SERVICE); - if (am != null) { - List appTasks = am.getAppTasks(); - if (appTasks != null && !appTasks.isEmpty()) { - appTasks.get(0).setExcludeFromRecents(true); - } - } - } - - //电池状态监听 - Intent batteryServiceIntent = new Intent(this, BatteryService.class); - startService(batteryServiceIntent); - - //后台播放无声音乐 - if (SettingUtils.getPlaySilenceMusic()) { - startService(new Intent(context, MusicService.class)); - } - - //SIM卡插拔状态广播监听 - PhoneUtils.init(this); - IntentFilter simStateFilter = new IntentFilter(SimStateReceiver.ACTION_SIM_STATE_CHANGED); - registerReceiver(new SimStateReceiver(), simStateFilter); - - } catch (Exception e) { - Log.e(TAG, "onCreate:", e); - } - } - - /** - * 获取全局上下文 - */ - public static Context getContext() { - return context; - } -} diff --git a/app/src/main/java/com/idormy/sms/forwarder/OnePixelActivity.java b/app/src/main/java/com/idormy/sms/forwarder/OnePixelActivity.java deleted file mode 100644 index 63e10ed9..00000000 --- a/app/src/main/java/com/idormy/sms/forwarder/OnePixelActivity.java +++ /dev/null @@ -1,65 +0,0 @@ -package com.idormy.sms.forwarder; - -import android.app.Activity; -import android.os.Bundle; -import android.util.Log; -import android.view.Gravity; -import android.view.Window; -import android.view.WindowManager; - -import androidx.annotation.Nullable; - -import com.idormy.sms.forwarder.utils.OnePixelManager; - -public class OnePixelActivity extends Activity { - private static final String TAG = "OnePixelActivity"; - - @Override - protected void onCreate(@Nullable Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - Window window = getWindow(); - window.setGravity(Gravity.START | Gravity.TOP); - WindowManager.LayoutParams params = window.getAttributes(); - params.x = 0; - params.y = 0; - params.height = 1; - params.width = 1; - window.setAttributes(params); - OnePixelManager manager = new OnePixelManager(); - manager.setKeepAliveReference(this);//将引用传给OnePixelManager - - Log.e(TAG, "onCreate"); - } - - @Override - protected void onDestroy() { - super.onDestroy(); - Log.e(TAG, "onDestroy"); - } - - @Override - protected void onStop() { - super.onStop(); - Log.e(TAG, "onStop"); - } - - @Override - protected void onPause() { - super.onPause(); - Log.e(TAG, "onPause"); - } - - @Override - protected void onStart() { - super.onStart(); - Log.e(TAG, "onStart"); - } - - @Override - protected void onResume() { - super.onResume(); - Log.e(TAG, "onResume"); - } -} - - diff --git a/app/src/main/java/com/idormy/sms/forwarder/RuleActivity.java b/app/src/main/java/com/idormy/sms/forwarder/RuleActivity.java deleted file mode 100644 index 5e4a3d5e..00000000 --- a/app/src/main/java/com/idormy/sms/forwarder/RuleActivity.java +++ /dev/null @@ -1,618 +0,0 @@ -package com.idormy.sms.forwarder; - -import static com.idormy.sms.forwarder.SenderActivity.NOTIFY; -import static com.idormy.sms.forwarder.model.RuleModel.STATUS_OFF; -import static com.idormy.sms.forwarder.model.RuleModel.STATUS_ON; - -import android.annotation.SuppressLint; -import android.os.Bundle; -import android.os.Handler; -import android.os.Message; -import android.util.Log; -import android.view.View; -import android.widget.Button; -import android.widget.EditText; -import android.widget.LinearLayout; -import android.widget.ListView; -import android.widget.RadioButton; -import android.widget.RadioGroup; -import android.widget.Switch; -import android.widget.TextView; - -import androidx.appcompat.app.AlertDialog; - -import com.google.android.material.floatingactionbutton.FloatingActionButton; -import com.hjq.toast.ToastUtils; -import com.idormy.sms.forwarder.adapter.RuleAdapter; -import com.idormy.sms.forwarder.model.RuleModel; -import com.idormy.sms.forwarder.model.SenderModel; -import com.idormy.sms.forwarder.model.vo.SmsVo; -import com.idormy.sms.forwarder.sender.SendUtil; -import com.idormy.sms.forwarder.sender.SenderUtil; -import com.idormy.sms.forwarder.utils.CommonUtils; -import com.idormy.sms.forwarder.utils.LogUtils; -import com.idormy.sms.forwarder.utils.RuleUtils; -import com.idormy.sms.forwarder.utils.SettingUtils; -import com.idormy.sms.forwarder.view.StepBar; - -import java.util.ArrayList; -import java.util.Date; -import java.util.List; - -@SuppressWarnings("deprecation") -public class RuleActivity extends BaseActivity { - - private final String TAG = "RuleActivity"; - // 用于存储数据 - private List ruleModels = new ArrayList<>(); - private RuleAdapter adapter; - private String currentType = "sms"; - private ListView listView; - - //消息处理者,创建一个Handler的子类对象,目的是重写Handler的处理消息的方法(handleMessage()) - @SuppressLint("HandlerLeak") - private final Handler handler = new Handler() { - @Override - public void handleMessage(Message msg) { - if (msg.what == NOTIFY) { - ToastUtils.delayedShow(msg.getData().getString("DATA"), 3000); - } - } - }; - - @Override - protected void onCreate(Bundle savedInstanceState) { - Log.d(TAG, "onCreate"); - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_rule); - - LogUtils.init(this); - RuleUtils.init(this); - SenderUtil.init(this); - } - - @Override - protected void onStart() { - super.onStart(); - Log.d(TAG, "onStart"); - - // 先拿到数据并放在适配器上 - initRules(); //初始化数据 - adapter = new RuleAdapter(RuleActivity.this, R.layout.item_rule, ruleModels); - - // 将适配器上的数据传递给listView - listView = findViewById(R.id.list_view_rule); - listView.setAdapter(adapter); - - // 为ListView注册一个监听器,当用户点击了ListView中的任何一个子项时,就会回调onItemClick()方法 - // 在这个方法中可以通过position参数判断出用户点击的是那一个子项 - listView.setOnItemClickListener((parent, view, position, id) -> { - RuleModel ruleModel = ruleModels.get(position); - Log.d(TAG, "onItemClick: " + ruleModel); - setRule(ruleModel, false); - }); - - listView.setOnItemLongClickListener((parent, view, position, id) -> { - //定义AlertDialog.Builder对象,当长按列表项的时候弹出确认删除对话框 - AlertDialog.Builder builder = new AlertDialog.Builder(RuleActivity.this); - builder.setTitle(R.string.delete_rule_title); - builder.setMessage(R.string.delete_rule_tips); - - builder.setPositiveButton(R.string.confirm, (dialog, which) -> { - RuleUtils.delRule(ruleModels.get(position).getId()); - initRules(); - adapter.del(ruleModels); - ToastUtils.show(R.string.delete_rule_toast); - }); - - builder.setNeutralButton(R.string.clone, (dialog, which) -> { - RuleModel ruleModel = ruleModels.get(position); - setRule(ruleModel, true); - }); - - builder.setNegativeButton(R.string.cancel, (dialog, which) -> { - - }); - - builder.create().show(); - return true; - }); - - //切换日志类别 - int typeCheckId = getTypeCheckId(currentType); - final RadioGroup radioGroupTypeCheck = findViewById(R.id.radioGroupTypeCheck); - radioGroupTypeCheck.check(typeCheckId); - radioGroupTypeCheck.setOnCheckedChangeListener((group, checkedId) -> { - RadioButton rb = findViewById(checkedId); - currentType = (String) rb.getTag(); - initRules(); //初始化数据 - adapter = new RuleAdapter(RuleActivity.this, R.layout.item_rule, ruleModels); - listView.setAdapter(adapter); - }); - - - //计算浮动按钮位置 - FloatingActionButton btnAddRule = findViewById(R.id.btnAddRule); - CommonUtils.calcMarginBottom(this, btnAddRule, listView, null); - - //添加规则 - btnAddRule.setOnClickListener(v -> setRule(null, false)); - - //步骤完成状态校验 - StepBar stepBar = findViewById(R.id.stepBar); - stepBar.setHighlight(); - } - - private int getTypeCheckId(String curType) { - switch (curType) { - case "call": - return R.id.btnTypeCall; - case "app": - return R.id.btnTypeApp; - default: - return R.id.btnTypeSms; - } - } - - private int getDialogView(String curType) { - switch (curType) { - case "call": - return R.layout.alert_dialog_setview_rule_call; - case "app": - return R.layout.alert_dialog_setview_rule_app; - default: - return R.layout.alert_dialog_setview_rule; - } - } - - private int getDialogTitle(String curType) { - switch (curType) { - case "call": - return R.string.setrule_call; - case "app": - return R.string.setrule_app; - default: - return R.string.setrule; - } - } - - // 初始化数据 - private void initRules() { - ruleModels = RuleUtils.getRule(null, null, currentType); - } - - private void setRule(final RuleModel ruleModel, final boolean isClone) { - final AlertDialog.Builder alertDialog71 = new AlertDialog.Builder(RuleActivity.this); - final View view1 = View.inflate(RuleActivity.this, getDialogView(currentType), null); - - final RadioGroup radioGroupRuleFiled = view1.findViewById(R.id.radioGroupRuleFiled); - if (ruleModel != null) radioGroupRuleFiled.check(ruleModel.getRuleFiledCheckId()); - - final RadioGroup radioGroupRuleCheck = view1.findViewById(R.id.radioGroupRuleCheck); - final RadioGroup radioGroupRuleCheck2 = view1.findViewById(R.id.radioGroupRuleCheck2); - if (ruleModel != null) { - int ruleCheckCheckId = ruleModel.getRuleCheckCheckId(); - if (ruleCheckCheckId == R.id.btnIs || ruleCheckCheckId == R.id.btnNotContain || ruleCheckCheckId == R.id.btnContain) { - radioGroupRuleCheck.check(ruleCheckCheckId); - } else { - radioGroupRuleCheck2.check(ruleCheckCheckId); - } - } else { - radioGroupRuleCheck.check(R.id.btnIs); - } - - final RadioGroup radioGroupSimSlot = view1.findViewById(R.id.radioGroupSimSlot); - if (ruleModel != null) radioGroupSimSlot.check(ruleModel.getRuleSimSlotCheckId()); - - final TextView tv_mu_rule_tips = view1.findViewById(R.id.tv_mu_rule_tips); - final TextView ruleSenderTv = view1.findViewById(R.id.ruleSenderTv); - if (ruleModel != null && ruleModel.getSenderId() != null) { - List getSenders = SenderUtil.getSender(ruleModel.getSenderId(), null); - if (!getSenders.isEmpty()) { - ruleSenderTv.setText(getSenders.get(0).getName()); - ruleSenderTv.setTag(getSenders.get(0).getId()); - } - } - final Button btSetRuleSender = view1.findViewById(R.id.btSetRuleSender); - btSetRuleSender.setOnClickListener(view -> { - //ToastUtils.show("selectSender", 3000); - selectSender(ruleSenderTv); - }); - - final EditText editTextRuleValue = view1.findViewById(R.id.editTextRuleValue); - if (ruleModel != null) - editTextRuleValue.setText(ruleModel.getValue()); - - //当更新选择的字段的时候,更新之下各个选项的状态 - final LinearLayout matchTypeLayout = view1.findViewById(R.id.matchTypeLayout); - final LinearLayout matchValueLayout = view1.findViewById(R.id.matchValueLayout); - refreshSelectRadioGroupRuleFiled(radioGroupRuleFiled, radioGroupRuleCheck, radioGroupRuleCheck2, editTextRuleValue, tv_mu_rule_tips, matchTypeLayout, matchValueLayout); - - //是否启用该规则 - @SuppressLint("UseSwitchCompatOrMaterialCode") Switch switchRuleStatus = view1.findViewById(R.id.switch_rule_status); - if (ruleModel != null) { - switchRuleStatus.setChecked(ruleModel.getStatusChecked()); - } - //自定义模板 - @SuppressLint("UseSwitchCompatOrMaterialCode") Switch switchSmsTemplate = view1.findViewById(R.id.switch_sms_template); - EditText textSmsTemplate = view1.findViewById(R.id.text_sms_template); - if (ruleModel != null) { - switchSmsTemplate.setChecked(ruleModel.getSwitchSmsTemplate()); - textSmsTemplate.setText(ruleModel.getSmsTemplate()); - } - - //正则替换 - @SuppressLint("UseSwitchCompatOrMaterialCode") Switch switchRegexReplace = view1.findViewById(R.id.switch_regex_replace); - EditText textRegexReplace = view1.findViewById(R.id.text_regex_replace); - if (ruleModel != null) { - switchRegexReplace.setChecked(ruleModel.getSwitchRegexReplace()); - textRegexReplace.setText(ruleModel.getRegexReplace()); - } - - Button buttonRuleOk = view1.findViewById(R.id.buttonRuleOk); - Button buttonRuleDel = view1.findViewById(R.id.buttonRuleDel); - buttonRuleDel.setText(ruleModel != null ? R.string.del : R.string.cancel); - Button buttonRuleTest = view1.findViewById(R.id.buttonRuleTest); - alertDialog71 - .setTitle(getDialogTitle(currentType)) - .setView(view1) - .create(); - final AlertDialog show = alertDialog71.show(); - buttonRuleOk.setOnClickListener(view -> { - Object senderId = ruleSenderTv.getTag(); - if (senderId == null) { - ToastUtils.delayedShow(R.string.new_sender_first, 3000); - return; - } - - //检查正则替换填写是否正确 - String regexReplace = textRegexReplace.getText().toString().trim(); - int lineNum = checkRegexReplace(regexReplace); - if (lineNum > 0) { - ToastUtils.show("lineNum=" + lineNum); - return; - } - - int radioGroupRuleCheckId = Math.max(radioGroupRuleCheck.getCheckedRadioButtonId(), radioGroupRuleCheck2.getCheckedRadioButtonId()); - Log.d(TAG, radioGroupRuleCheck.getCheckedRadioButtonId() + " " + radioGroupRuleCheck2.getCheckedRadioButtonId() + " " + radioGroupRuleCheckId); - if (isClone || ruleModel == null) { - RuleModel newRuleModel = new RuleModel(); - newRuleModel.setType(currentType); - newRuleModel.setFiled(RuleModel.getRuleFiledFromCheckId(radioGroupRuleFiled.getCheckedRadioButtonId())); - newRuleModel.setCheck(RuleModel.getRuleCheckFromCheckId(radioGroupRuleCheckId)); - newRuleModel.setSimSlot(RuleModel.getRuleSimSlotFromCheckId(radioGroupSimSlot.getCheckedRadioButtonId())); - newRuleModel.setValue(editTextRuleValue.getText().toString().trim()); - newRuleModel.setSwitchSmsTemplate(switchSmsTemplate.isChecked()); - newRuleModel.setSmsTemplate(textSmsTemplate.getText().toString().trim()); - newRuleModel.setSwitchRegexReplace(switchRegexReplace.isChecked()); - newRuleModel.setRegexReplace(regexReplace); - newRuleModel.setSenderId(Long.valueOf(senderId.toString())); - newRuleModel.setStatus(switchRuleStatus.isChecked() ? STATUS_ON : STATUS_OFF); - RuleUtils.addRule(newRuleModel); - initRules(); - adapter.add(ruleModels); - } else { - ruleModel.setFiled(RuleModel.getRuleFiledFromCheckId(radioGroupRuleFiled.getCheckedRadioButtonId())); - ruleModel.setCheck(RuleModel.getRuleCheckFromCheckId(radioGroupRuleCheckId)); - ruleModel.setSimSlot(RuleModel.getRuleSimSlotFromCheckId(radioGroupSimSlot.getCheckedRadioButtonId())); - ruleModel.setValue(editTextRuleValue.getText().toString().trim()); - ruleModel.setSwitchSmsTemplate(switchSmsTemplate.isChecked()); - ruleModel.setSmsTemplate(textSmsTemplate.getText().toString().trim()); - ruleModel.setSwitchRegexReplace(switchRegexReplace.isChecked()); - ruleModel.setRegexReplace(regexReplace); - ruleModel.setSenderId(Long.valueOf(senderId.toString())); - ruleModel.setStatus(switchRuleStatus.isChecked() ? STATUS_ON : STATUS_OFF); - RuleUtils.updateRule(ruleModel); - initRules(); - adapter.update(ruleModels); - } - show.dismiss(); - }); - - buttonRuleDel.setOnClickListener(view -> { - if (ruleModel != null) { - RuleUtils.delRule(ruleModel.getId()); - initRules(); - adapter.del(ruleModels); - } - show.dismiss(); - }); - - buttonRuleTest.setOnClickListener(view -> { - Object senderId = ruleSenderTv.getTag(); - if (senderId == null) { - ToastUtils.delayedShow(R.string.new_sender_first, 3000); - return; - } - - //检查正则替换填写是否正确 - String regexReplace = textRegexReplace.getText().toString().trim(); - int lineNum = checkRegexReplace(regexReplace); - if (lineNum > 0) { - ToastUtils.show("lineNum=" + lineNum); - return; - } - - int radioGroupRuleCheckId = Math.max(radioGroupRuleCheck.getCheckedRadioButtonId(), radioGroupRuleCheck2.getCheckedRadioButtonId()); - if (ruleModel == null) { - RuleModel newRuleModel = new RuleModel(); - newRuleModel.setFiled(RuleModel.getRuleFiledFromCheckId(radioGroupRuleFiled.getCheckedRadioButtonId())); - newRuleModel.setCheck(RuleModel.getRuleCheckFromCheckId(radioGroupRuleCheckId)); - newRuleModel.setSimSlot(RuleModel.getRuleSimSlotFromCheckId(radioGroupSimSlot.getCheckedRadioButtonId())); - newRuleModel.setValue(editTextRuleValue.getText().toString().trim()); - newRuleModel.setSenderId(Long.valueOf(senderId.toString())); - newRuleModel.setSwitchSmsTemplate(switchSmsTemplate.isChecked()); - newRuleModel.setSmsTemplate(textSmsTemplate.getText().toString().trim()); - newRuleModel.setSwitchRegexReplace(switchRegexReplace.isChecked()); - newRuleModel.setRegexReplace(regexReplace); - newRuleModel.setStatus(switchRuleStatus.isChecked() ? STATUS_ON : STATUS_OFF); - - testRule(newRuleModel, Long.valueOf(senderId.toString())); - } else { - ruleModel.setFiled(RuleModel.getRuleFiledFromCheckId(radioGroupRuleFiled.getCheckedRadioButtonId())); - ruleModel.setCheck(RuleModel.getRuleCheckFromCheckId(radioGroupRuleCheckId)); - ruleModel.setSimSlot(RuleModel.getRuleSimSlotFromCheckId(radioGroupSimSlot.getCheckedRadioButtonId())); - ruleModel.setValue(editTextRuleValue.getText().toString().trim()); - ruleModel.setSenderId(Long.valueOf(senderId.toString())); - ruleModel.setSwitchSmsTemplate(switchSmsTemplate.isChecked()); - ruleModel.setSmsTemplate(textSmsTemplate.getText().toString().trim()); - ruleModel.setSwitchRegexReplace(switchRegexReplace.isChecked()); - ruleModel.setRegexReplace(regexReplace); - ruleModel.setStatus(switchRuleStatus.isChecked() ? STATUS_ON : STATUS_OFF); - - testRule(ruleModel, Long.valueOf(senderId.toString())); - } - }); - - //自定义模板 - final LinearLayout layout_sms_template = view1.findViewById(R.id.layout_sms_template); - if (ruleModel != null) { - layout_sms_template.setVisibility(ruleModel.getSwitchSmsTemplate() ? View.VISIBLE : View.GONE); - } - switchSmsTemplate.setOnCheckedChangeListener((buttonView, isChecked) -> { - layout_sms_template.setVisibility(isChecked ? View.VISIBLE : View.GONE); - if (!isChecked) { - textSmsTemplate.setText(""); - } - }); - - Button buttonInsertSender = view1.findViewById(R.id.bt_insert_sender); - buttonInsertSender.setOnClickListener(view -> { - textSmsTemplate.setFocusable(true); - textSmsTemplate.requestFocus(); - CommonUtils.insertOrReplaceText2Cursor(textSmsTemplate, getString(R.string.tag_from)); - }); - - Button buttonInsertContent = view1.findViewById(R.id.bt_insert_content); - buttonInsertContent.setOnClickListener(view -> { - textSmsTemplate.setFocusable(true); - textSmsTemplate.requestFocus(); - CommonUtils.insertOrReplaceText2Cursor(textSmsTemplate, getString(R.string.tag_sms)); - }); - - Button buttonInsertSenderApp = view1.findViewById(R.id.bt_insert_sender_app); - buttonInsertSenderApp.setOnClickListener(view -> { - textSmsTemplate.setFocusable(true); - textSmsTemplate.requestFocus(); - CommonUtils.insertOrReplaceText2Cursor(textSmsTemplate, getString(R.string.tag_package_name)); - }); - - Button buttonInsertContentApp = view1.findViewById(R.id.bt_insert_content_app); - buttonInsertContentApp.setOnClickListener(view -> { - textSmsTemplate.setFocusable(true); - textSmsTemplate.requestFocus(); - CommonUtils.insertOrReplaceText2Cursor(textSmsTemplate, getString(R.string.tag_msg)); - }); - - Button buttonInsertExtra = view1.findViewById(R.id.bt_insert_extra); - buttonInsertExtra.setOnClickListener(view -> { - textSmsTemplate.setFocusable(true); - textSmsTemplate.requestFocus(); - CommonUtils.insertOrReplaceText2Cursor(textSmsTemplate, getString(R.string.tag_card_slot)); - }); - - Button buttonInsertTime = view1.findViewById(R.id.bt_insert_time); - buttonInsertTime.setOnClickListener(view -> { - textSmsTemplate.setFocusable(true); - textSmsTemplate.requestFocus(); - CommonUtils.insertOrReplaceText2Cursor(textSmsTemplate, getString(R.string.tag_receive_time)); - }); - - Button buttonInsertDeviceName = view1.findViewById(R.id.bt_insert_device_name); - buttonInsertDeviceName.setOnClickListener(view -> { - textSmsTemplate.setFocusable(true); - textSmsTemplate.requestFocus(); - CommonUtils.insertOrReplaceText2Cursor(textSmsTemplate, getString(R.string.tag_device_name)); - }); - - //正则替换 - final LinearLayout layout_regex_replace = view1.findViewById(R.id.layout_regex_replace); - if (ruleModel != null) { - layout_regex_replace.setVisibility(ruleModel.getSwitchRegexReplace() ? View.VISIBLE : View.GONE); - } - switchRegexReplace.setOnCheckedChangeListener((buttonView, isChecked) -> { - layout_regex_replace.setVisibility(isChecked ? View.VISIBLE : View.GONE); - if (!isChecked) { - textRegexReplace.setText(""); - } - }); - - } - - //当更新选择的字段的时候,更新之下各个选项的状态 - // 如果设置了转发全部,禁用选择模式和匹配值输入 - // 如果设置了多重规则,选择模式置为是 - private void refreshSelectRadioGroupRuleFiled(RadioGroup radioGroupRuleFiled, final RadioGroup radioGroupRuleCheck, final RadioGroup radioGroupRuleCheck2, final EditText editTextRuleValue, final TextView tv_mu_rule_tips, final LinearLayout matchTypeLayout, final LinearLayout matchValueLayout) { - refreshSelectRadioGroupRuleFiledAction(radioGroupRuleFiled.getCheckedRadioButtonId(), radioGroupRuleCheck, radioGroupRuleCheck2, editTextRuleValue, tv_mu_rule_tips, matchTypeLayout, matchValueLayout); - - radioGroupRuleCheck.setOnCheckedChangeListener((group, checkedId) -> { - Log.d(TAG, String.valueOf(group)); - Log.d(TAG, String.valueOf(checkedId)); - if (group != null && checkedId > 0) { - if (group == radioGroupRuleCheck) { - radioGroupRuleCheck2.clearCheck(); - } else if (group == radioGroupRuleCheck2) { - radioGroupRuleCheck.clearCheck(); - } - group.check(checkedId); - } - }); - radioGroupRuleCheck2.setOnCheckedChangeListener((group, checkedId) -> { - Log.d(TAG, String.valueOf(group)); - Log.d(TAG, String.valueOf(checkedId)); - if (group != null && checkedId > 0) { - if (group == radioGroupRuleCheck) { - radioGroupRuleCheck2.clearCheck(); - } else if (group == radioGroupRuleCheck2) { - radioGroupRuleCheck.clearCheck(); - } - group.check(checkedId); - } - }); - radioGroupRuleFiled.setOnCheckedChangeListener((group, checkedId) -> { - Log.d(TAG, String.valueOf(group)); - Log.d(TAG, String.valueOf(checkedId)); - if (group == radioGroupRuleCheck) { - radioGroupRuleCheck2.clearCheck(); - } else if (group == radioGroupRuleCheck2) { - radioGroupRuleCheck.clearCheck(); - } - refreshSelectRadioGroupRuleFiledAction(checkedId, radioGroupRuleCheck, radioGroupRuleCheck2, editTextRuleValue, tv_mu_rule_tips, matchTypeLayout, matchValueLayout); - }); - } - - @SuppressLint("NonConstantResourceId") - private void refreshSelectRadioGroupRuleFiledAction(int checkedRuleFiledId, final RadioGroup radioGroupRuleCheck, final RadioGroup radioGroupRuleCheck2, final EditText editTextRuleValue, final TextView tv_mu_rule_tips, final LinearLayout matchTypeLayout, final LinearLayout matchValueLayout) { - tv_mu_rule_tips.setVisibility(View.GONE); - matchTypeLayout.setVisibility(View.VISIBLE); - matchValueLayout.setVisibility(View.VISIBLE); - - switch (checkedRuleFiledId) { - case R.id.btnTranspondAll: - for (int i = 0; i < radioGroupRuleCheck.getChildCount(); i++) { - radioGroupRuleCheck.getChildAt(i).setEnabled(false); - } - for (int i = 0; i < radioGroupRuleCheck2.getChildCount(); i++) { - radioGroupRuleCheck2.getChildAt(i).setEnabled(false); - } - editTextRuleValue.setEnabled(false); - matchTypeLayout.setVisibility(View.GONE); - matchValueLayout.setVisibility(View.GONE); - break; - case R.id.btnMultiMatch: - for (int i = 0; i < radioGroupRuleCheck.getChildCount(); i++) { - radioGroupRuleCheck.getChildAt(i).setEnabled(false); - } - for (int i = 0; i < radioGroupRuleCheck2.getChildCount(); i++) { - radioGroupRuleCheck2.getChildAt(i).setEnabled(false); - } - editTextRuleValue.setEnabled(true); - matchTypeLayout.setVisibility(View.GONE); - tv_mu_rule_tips.setVisibility(MyApplication.showHelpTip ? View.VISIBLE : View.GONE); - break; - default: - for (int i = 0; i < radioGroupRuleCheck.getChildCount(); i++) { - radioGroupRuleCheck.getChildAt(i).setEnabled(true); - } - for (int i = 0; i < radioGroupRuleCheck2.getChildCount(); i++) { - radioGroupRuleCheck2.getChildAt(i).setEnabled(true); - } - editTextRuleValue.setEnabled(true); - break; - } - } - - public void selectSender(final TextView showTv) { - final List senderModels = SenderUtil.getSender(null, null); - if (senderModels.isEmpty()) { - ToastUtils.show(R.string.add_sender_first); - return; - } - final CharSequence[] senderNames = new CharSequence[senderModels.size()]; - for (int i = 0; i < senderModels.size(); i++) { - senderNames[i] = senderModels.get(i).getName(); - } - AlertDialog.Builder builder = new AlertDialog.Builder(RuleActivity.this); - builder.setTitle(R.string.select_sender); - //添加列表 - builder.setItems(senderNames, (dialogInterface, which) -> { - //ToastUtils.delayedShow(senderNames[which], 3000); - showTv.setText(senderNames[which]); - showTv.setTag(senderModels.get(which).getId()); - }); - builder.show(); - } - - public void testRule(final RuleModel ruleModel, final Long senderId) { - final View view = View.inflate(RuleActivity.this, R.layout.alert_dialog_setview_rule_test, null); - final TextView textTestSimSlot = view.findViewById(R.id.textTestSimSlot); - final TextView textTestPhone = view.findViewById(R.id.textTestPhone); - final TextView textTestContent = view.findViewById(R.id.textTestContent); - final RadioGroup radioGroupTestSimSlot = view.findViewById(R.id.radioGroupTestSimSlot); - final EditText editTextTestPhone = view.findViewById(R.id.editTextTestPhone); - final EditText editTextTestMsgContent = view.findViewById(R.id.editTextTestMsgContent); - - if ("app".equals(currentType)) { - textTestSimSlot.setVisibility(View.GONE); - radioGroupTestSimSlot.setVisibility(View.GONE); - textTestPhone.setText(R.string.test_package_name); - textTestContent.setText(R.string.test_inform_content); - } else if ("call".equals(currentType)) { - textTestContent.setVisibility(View.GONE); - editTextTestMsgContent.setVisibility(View.GONE); - } - - Button buttonRuleTest = view.findViewById(R.id.buttonRuleTest); - AlertDialog.Builder ad1 = new AlertDialog.Builder(RuleActivity.this); - ad1.setTitle(R.string.rule_tester); - ad1.setIcon(android.R.drawable.ic_dialog_email); - ad1.setView(view); - buttonRuleTest.setOnClickListener(v -> { - - Log.i("editTextTestPhone", editTextTestPhone.getText().toString().trim()); - Log.i("editTextTestMsgContent", editTextTestMsgContent.getText().toString().trim()); - - try { - String simSlot = RuleModel.getRuleSimSlotFromCheckId(radioGroupTestSimSlot.getCheckedRadioButtonId()); - String simInfo; - if (simSlot.equals("SIM2")) { - simInfo = simSlot + "_" + SettingUtils.getAddExtraSim2(); - } else { - simInfo = simSlot + "_" + SettingUtils.getAddExtraSim1(); - } - SmsVo testSmsVo = new SmsVo(editTextTestPhone.getText().toString().trim(), editTextTestMsgContent.getText().toString().trim(), new Date(), simInfo); - SendUtil.sendMsgByRuleModelSenderId(handler, ruleModel, testSmsVo, senderId); - } catch (Exception e) { - ToastUtils.delayedShow(e.getMessage(), 3000); - } - }); - ad1.show();// 显示对话框 - } - - @Override - protected void onDestroy() { - Log.d(TAG, "onDestroy"); - super.onDestroy(); - } - - @Override - protected void onPause() { - overridePendingTransition(0, 0); - super.onPause(); - } - - private int checkRegexReplace(String regexReplace) { - if (regexReplace == null || regexReplace.isEmpty()) return 0; - - int lineNum = 1; - String[] lineArray = regexReplace.split("\\n"); - for (String line : lineArray) { - int position = line.indexOf("==="); - if (position < 1) return lineNum; - lineNum++; - } - - return 0; - } - -} diff --git a/app/src/main/java/com/idormy/sms/forwarder/SenderActivity.java b/app/src/main/java/com/idormy/sms/forwarder/SenderActivity.java deleted file mode 100644 index 967a28b1..00000000 --- a/app/src/main/java/com/idormy/sms/forwarder/SenderActivity.java +++ /dev/null @@ -1,2212 +0,0 @@ -package com.idormy.sms.forwarder; - -import static com.idormy.sms.forwarder.model.SenderModel.STATUS_OFF; -import static com.idormy.sms.forwarder.model.SenderModel.STATUS_ON; -import static com.idormy.sms.forwarder.model.SenderModel.TYPE_BARK; -import static com.idormy.sms.forwarder.model.SenderModel.TYPE_DINGDING; -import static com.idormy.sms.forwarder.model.SenderModel.TYPE_EMAIL; -import static com.idormy.sms.forwarder.model.SenderModel.TYPE_FEISHU; -import static com.idormy.sms.forwarder.model.SenderModel.TYPE_GOTIFY; -import static com.idormy.sms.forwarder.model.SenderModel.TYPE_PUSHPLUS; -import static com.idormy.sms.forwarder.model.SenderModel.TYPE_QYWX_APP; -import static com.idormy.sms.forwarder.model.SenderModel.TYPE_QYWX_GROUP_ROBOT; -import static com.idormy.sms.forwarder.model.SenderModel.TYPE_SERVER_CHAN; -import static com.idormy.sms.forwarder.model.SenderModel.TYPE_SMS; -import static com.idormy.sms.forwarder.model.SenderModel.TYPE_TELEGRAM; -import static com.idormy.sms.forwarder.model.SenderModel.TYPE_WEB_NOTIFY; - -import android.annotation.SuppressLint; -import android.app.Dialog; -import android.os.Bundle; -import android.os.Handler; -import android.os.Message; -import android.text.SpannableString; -import android.text.Spanned; -import android.text.SpannedString; -import android.text.TextUtils; -import android.text.style.AbsoluteSizeSpan; -import android.util.Log; -import android.view.LayoutInflater; -import android.view.View; -import android.widget.AdapterView; -import android.widget.Button; -import android.widget.EditText; -import android.widget.GridView; -import android.widget.ImageView; -import android.widget.LinearLayout; -import android.widget.ListView; -import android.widget.RadioGroup; -import android.widget.SimpleAdapter; -import android.widget.Spinner; -import android.widget.Switch; - -import androidx.appcompat.app.AlertDialog; - -import com.alibaba.fastjson.JSON; -import com.google.android.material.floatingactionbutton.FloatingActionButton; -import com.hjq.permissions.OnPermissionCallback; -import com.hjq.permissions.Permission; -import com.hjq.permissions.XXPermissions; -import com.hjq.toast.ToastUtils; -import com.idormy.sms.forwarder.adapter.SenderAdapter; -import com.idormy.sms.forwarder.model.SenderModel; -import com.idormy.sms.forwarder.model.vo.BarkSettingVo; -import com.idormy.sms.forwarder.model.vo.DingDingSettingVo; -import com.idormy.sms.forwarder.model.vo.EmailSettingVo; -import com.idormy.sms.forwarder.model.vo.FeiShuSettingVo; -import com.idormy.sms.forwarder.model.vo.GotifySettingVo; -import com.idormy.sms.forwarder.model.vo.PushPlusSettingVo; -import com.idormy.sms.forwarder.model.vo.QYWXAppSettingVo; -import com.idormy.sms.forwarder.model.vo.QYWXGroupRobotSettingVo; -import com.idormy.sms.forwarder.model.vo.ServerChanSettingVo; -import com.idormy.sms.forwarder.model.vo.SmsSettingVo; -import com.idormy.sms.forwarder.model.vo.SmsVo; -import com.idormy.sms.forwarder.model.vo.TelegramSettingVo; -import com.idormy.sms.forwarder.model.vo.WebNotifySettingVo; -import com.idormy.sms.forwarder.sender.SenderBarkMsg; -import com.idormy.sms.forwarder.sender.SenderDingdingMsg; -import com.idormy.sms.forwarder.sender.SenderFeishuMsg; -import com.idormy.sms.forwarder.sender.SenderGotifyMsg; -import com.idormy.sms.forwarder.sender.SenderMailMsg; -import com.idormy.sms.forwarder.sender.SenderPushPlusMsg; -import com.idormy.sms.forwarder.sender.SenderQyWxAppMsg; -import com.idormy.sms.forwarder.sender.SenderQyWxGroupRobotMsg; -import com.idormy.sms.forwarder.sender.SenderServerChanMsg; -import com.idormy.sms.forwarder.sender.SenderSmsMsg; -import com.idormy.sms.forwarder.sender.SenderTelegramMsg; -import com.idormy.sms.forwarder.sender.SenderUtil; -import com.idormy.sms.forwarder.sender.SenderWebNotifyMsg; -import com.idormy.sms.forwarder.utils.CommonUtils; -import com.idormy.sms.forwarder.utils.LogUtils; -import com.idormy.sms.forwarder.utils.RuleUtils; -import com.idormy.sms.forwarder.view.ClearEditText; -import com.idormy.sms.forwarder.view.StepBar; - -import java.net.Proxy; -import java.util.ArrayList; -import java.util.Date; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -@SuppressWarnings("deprecation") -public class SenderActivity extends BaseActivity { - - public static final int NOTIFY = 0x9731993; - private final String TAG = "SenderActivity"; - //消息处理者,创建一个Handler的子类对象,目的是重写Handler的处理消息的方法(handleMessage()) - @SuppressLint("HandlerLeak") - private final Handler handler = new Handler() { - @Override - public void handleMessage(Message msg) { - if (msg.what == NOTIFY) { - ToastUtils.delayedShow(msg.getData().getString("DATA"), 3000); - } - } - }; - // 用于存储数据 - private List senderModels = new ArrayList<>(); - private SenderAdapter adapter; - - @Override - protected void onCreate(Bundle savedInstanceState) { - Log.d(TAG, "onCreate"); - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_sender); - - LogUtils.init(this); - RuleUtils.init(this); - SenderUtil.init(this); - } - - @Override - protected void onStart() { - super.onStart(); - Log.d(TAG, "onStart"); - - // 先拿到数据并放在适配器上 - initSenders(); //初始化数据 - adapter = new SenderAdapter(SenderActivity.this, R.layout.item_sender, senderModels); - - // 将适配器上的数据传递给listView - ListView listView = findViewById(R.id.list_view_sender); - listView.setAdapter(adapter); - - // 为ListView注册一个监听器,当用户点击了ListView中的任何一个子项时,就会回调onItemClick()方法 - // 在这个方法中可以通过position参数判断出用户点击的是那一个子项 - listView.setOnItemClickListener((parent, view, position, id) -> { - SenderModel senderModel = senderModels.get(position); - Log.d(TAG, "onItemClick: " + senderModel); - - switch (senderModel.getType()) { - case TYPE_DINGDING: - setDingDing(senderModel, false); - break; - case TYPE_EMAIL: - setEmail(senderModel, false); - break; - case TYPE_BARK: - setBark(senderModel, false); - break; - case TYPE_WEB_NOTIFY: - setWebNotify(senderModel, false); - break; - case TYPE_QYWX_GROUP_ROBOT: - setQYWXGroupRobot(senderModel, false); - break; - case TYPE_QYWX_APP: - setQYWXApp(senderModel, false); - break; - case TYPE_SERVER_CHAN: - setServerChan(senderModel, false); - break; - case TYPE_TELEGRAM: - setTelegram(senderModel, false); - break; - case TYPE_SMS: - setSms(senderModel, false); - break; - case TYPE_FEISHU: - setFeiShu(senderModel, false); - break; - case TYPE_PUSHPLUS: - setPushPlus(senderModel, false); - break; - case TYPE_GOTIFY: - setGotify(senderModel, false); - break; - default: - ToastUtils.delayedShow(R.string.invalid_sender, 3000); - SenderUtil.delSender(senderModel.getId()); - initSenders(); - adapter.del(senderModels); - break; - } - - }); - - listView.setOnItemLongClickListener((parent, view, position, id) -> { - //定义AlertDialog.Builder对象,当长按列表项的时候弹出确认删除对话框 - AlertDialog.Builder builder = new AlertDialog.Builder(SenderActivity.this); - builder.setTitle(R.string.delete_sender_title); - builder.setMessage(R.string.delete_sender_tips); - - //添加AlertDialog.Builder对象的setPositiveButton()方法 - builder.setPositiveButton(R.string.confirm, (dialog, which) -> { - SenderUtil.delSender(senderModels.get(position).getId()); - initSenders(); - adapter.del(senderModels); - ToastUtils.show(R.string.delete_sender_toast); - }); - - builder.setNeutralButton(R.string.clone, (dialog, which) -> { - SenderModel senderModel = senderModels.get(position); - switch (senderModel.getType()) { - case TYPE_DINGDING: - setDingDing(senderModel, true); - break; - case TYPE_EMAIL: - setEmail(senderModel, true); - break; - case TYPE_BARK: - setBark(senderModel, true); - break; - case TYPE_WEB_NOTIFY: - setWebNotify(senderModel, true); - break; - case TYPE_QYWX_GROUP_ROBOT: - setQYWXGroupRobot(senderModel, true); - break; - case TYPE_QYWX_APP: - setQYWXApp(senderModel, true); - break; - case TYPE_SERVER_CHAN: - setServerChan(senderModel, true); - break; - case TYPE_TELEGRAM: - setTelegram(senderModel, true); - break; - case TYPE_SMS: - setSms(senderModel, true); - break; - case TYPE_FEISHU: - setFeiShu(senderModel, true); - break; - case TYPE_PUSHPLUS: - setPushPlus(senderModel, true); - break; - case TYPE_GOTIFY: - setGotify(senderModel, true); - break; - default: - ToastUtils.delayedShow(R.string.invalid_sender, 3000); - SenderUtil.delSender(senderModel.getId()); - initSenders(); - adapter.del(senderModels); - break; - } - }); - - //添加AlertDialog.Builder对象的setNegativeButton()方法 - builder.setNegativeButton(R.string.cancel, (dialog, which) -> { - - }); - - builder.create().show(); - return true; - }); - - - //计算浮动按钮位置 - FloatingActionButton btnFloat = findViewById(R.id.btnAddSender); - CommonUtils.calcMarginBottom(this, btnFloat, listView, null); - - //添加发送通道 - btnFloat.setOnClickListener(v -> { - - @SuppressLint("InflateParams") View dialog_menu = LayoutInflater.from(SenderActivity.this).inflate(R.layout.alert_dialog_menu, null); - // 设置style 控制默认dialog带来的边距问题 - final Dialog dialog = new Dialog(this, R.style.dialog_menu); - dialog.setContentView(dialog_menu); - dialog.show(); - - GridView gridview = dialog.findViewById(R.id.MemuGridView); - final List> item = getMenuData(); - SimpleAdapter simpleAdapter = new SimpleAdapter(this, item, R.layout.item_menu, new String[]{"ItemImageView", "ItemTextView"}, new int[]{R.id.ItemImageView, R.id.ItemTextView}); - gridview.setAdapter(simpleAdapter); - - // 添加点击事件 - gridview.setOnItemClickListener((arg0, arg1, position, arg3) -> { - dialog.dismiss(); - - switch (position) { - case TYPE_DINGDING: - setDingDing(null, false); - break; - case TYPE_EMAIL: - setEmail(null, false); - break; - case TYPE_BARK: - setBark(null, false); - break; - case TYPE_WEB_NOTIFY: - setWebNotify(null, false); - break; - case TYPE_QYWX_GROUP_ROBOT: - setQYWXGroupRobot(null, false); - break; - case TYPE_QYWX_APP: - setQYWXApp(null, false); - break; - case TYPE_SERVER_CHAN: - setServerChan(null, false); - break; - case TYPE_TELEGRAM: - setTelegram(null, false); - break; - case TYPE_SMS: - setSms(null, false); - break; - case TYPE_FEISHU: - setFeiShu(null, false); - break; - case TYPE_PUSHPLUS: - setPushPlus(null, false); - break; - case TYPE_GOTIFY: - setGotify(null, false); - break; - default: - ToastUtils.delayedShow(R.string.not_supported, 3000); - break; - } - }); - }); - - //步骤完成状态校验 - StepBar stepBar = findViewById(R.id.stepBar); - stepBar.setHighlight(); - } - - @Override - protected void onDestroy() { - Log.d(TAG, "onDestroy"); - super.onDestroy(); - } - - @Override - protected void onPause() { - overridePendingTransition(0, 0); - super.onPause(); - } - - // 初始化数据 - private void initSenders() { - senderModels = SenderUtil.getSender(null, null); - } - - // 获取发送通道菜单 - private List> getMenuData() { - //定义图标数组 - int[] imageRes = { - R.mipmap.dingding, - R.mipmap.email, - R.mipmap.bark, - R.mipmap.webhook, - R.mipmap.qywx, - R.mipmap.qywxapp, - R.mipmap.serverchan, - R.mipmap.telegram, - R.mipmap.sms, - R.mipmap.feishu, - R.mipmap.pushplus, - R.mipmap.gotify, - }; - //定义标题数组 - String[] itemName = { - getString(R.string.dingding), - getString(R.string.email), - getString(R.string.bark), - getString(R.string.webhook), - getString(R.string.qywx), - getString(R.string.qywxapp), - getString(R.string.serverchan), - getString(R.string.telegram), - getString(R.string.sms_menu), - getString(R.string.feishu), - getString(R.string.pushplus), - getString(R.string.gotify), - }; - List> data = new ArrayList<>(); - int length = itemName.length; - for (int i = 0; i < length; i++) { - HashMap map = new HashMap<>(); - map.put("ItemImageView", imageRes[i]); - map.put("ItemTextView", itemName[i]); - data.add(map); - } - return data; - } - - //钉钉机器人 - @SuppressLint({"SimpleDateFormat", "SetTextI18n"}) - private void setDingDing(final SenderModel senderModel, final boolean isClone) { - DingDingSettingVo dingDingSettingVo = null; - if (senderModel != null) { - String jsonSettingStr = senderModel.getJsonSetting(); - if (jsonSettingStr != null) { - dingDingSettingVo = JSON.parseObject(jsonSettingStr, DingDingSettingVo.class); - } - } - final AlertDialog.Builder alertDialog71 = new AlertDialog.Builder(SenderActivity.this); - View view1 = View.inflate(SenderActivity.this, R.layout.alert_dialog_setview_dingding, null); - - final EditText editTextDingdingName = view1.findViewById(R.id.editTextDingdingName); - @SuppressLint("UseSwitchCompatOrMaterialCode") final Switch switchDingdingEnable = view1.findViewById(R.id.switchDingdingEnable); - if (senderModel != null) { - editTextDingdingName.setText(senderModel.getName()); - switchDingdingEnable.setChecked(senderModel.getStatusChecked()); - } - - final ClearEditText editTextDingdingToken = view1.findViewById(R.id.editTextDingdingToken); - final ClearEditText editTextDingdingSecret = view1.findViewById(R.id.editTextDingdingSecret); - final EditText editTextDingdingAtMobiles = view1.findViewById(R.id.editTextDingdingAtMobiles); - @SuppressLint("UseSwitchCompatOrMaterialCode") final Switch switchDingdingAtAll = view1.findViewById(R.id.switchDingdingAtAll); - final LinearLayout linearLayoutDingdingAtMobiles = view1.findViewById(R.id.linearLayoutDingdingAtMobiles); - if (dingDingSettingVo != null) { - editTextDingdingToken.setText(dingDingSettingVo.getToken()); - editTextDingdingSecret.setText(dingDingSettingVo.getSecret()); - editTextDingdingAtMobiles.setText(dingDingSettingVo.getAtMobiles()); - if (dingDingSettingVo.getAtAll() != null) { - switchDingdingAtAll.setChecked(dingDingSettingVo.getAtAll()); - linearLayoutDingdingAtMobiles.setVisibility(dingDingSettingVo.getAtAll() ? View.GONE : View.VISIBLE); - } - } - - switchDingdingAtAll.setOnCheckedChangeListener((buttonView, isChecked) -> { - if (isChecked) { - linearLayoutDingdingAtMobiles.setVisibility(View.GONE); - editTextDingdingAtMobiles.setText("@all"); - } else { - linearLayoutDingdingAtMobiles.setVisibility(View.VISIBLE); - editTextDingdingAtMobiles.setText(""); - } - Log.d(TAG, "onCheckedChanged:" + isChecked); - }); - - Button buttonOk = view1.findViewById(R.id.buttonOk); - Button buttonDel = view1.findViewById(R.id.buttonDel); - buttonDel.setText(senderModel != null ? R.string.del : R.string.cancel); - Button buttonTest = view1.findViewById(R.id.buttonTest); - alertDialog71 - .setTitle(R.string.setdingdingtitle) - .setIcon(R.mipmap.dingding) - .setView(view1) - .create(); - final AlertDialog show = alertDialog71.show(); - buttonOk.setOnClickListener(view -> { - String senderName = editTextDingdingName.getText().toString().trim(); - int senderStatus = switchDingdingEnable.isChecked() ? STATUS_ON : STATUS_OFF; - String token = editTextDingdingToken.getText().trim(); - String secret = editTextDingdingSecret.getText().trim(); - String atMobiles = editTextDingdingAtMobiles.getText().toString().trim(); - Boolean atAll = switchDingdingAtAll.isChecked(); - - if (TextUtils.isEmpty(senderName)) { - ToastUtils.delayedShow(R.string.invalid_name, 3000); - return; - } - if (CommonUtils.checkUrl(token, true)) { - ToastUtils.delayedShow(R.string.invalid_token, 3000); - return; - } - - DingDingSettingVo dingDingSettingVoNew = new DingDingSettingVo(token, secret, atMobiles, atAll); - if (isClone || senderModel == null) { - SenderModel newSenderModel = new SenderModel(); - newSenderModel.setName(senderName); - newSenderModel.setType(TYPE_DINGDING); - newSenderModel.setStatus(senderStatus); - newSenderModel.setJsonSetting(JSON.toJSONString(dingDingSettingVoNew)); - SenderUtil.addSender(newSenderModel); - initSenders(); - adapter.add(senderModels); - } else { - senderModel.setName(senderName); - senderModel.setType(TYPE_DINGDING); - senderModel.setStatus(senderStatus); - senderModel.setJsonSetting(JSON.toJSONString(dingDingSettingVoNew)); - SenderUtil.updateSender(senderModel); - initSenders(); - adapter.update(senderModels); - } - show.dismiss(); - }); - - buttonDel.setOnClickListener(view -> { - if (senderModel != null) { - SenderUtil.delSender(senderModel.getId()); - initSenders(); - adapter.del(senderModels); - } - show.dismiss(); - }); - - buttonTest.setOnClickListener(view -> { - String token = editTextDingdingToken.getText().trim(); - if (CommonUtils.checkUrl(token, true)) { - ToastUtils.delayedShow(R.string.invalid_token, 3000); - return; - } - - String secret = editTextDingdingSecret.getText().trim(); - String atMobiles = editTextDingdingAtMobiles.getText().toString().trim(); - Boolean atAll = switchDingdingAtAll.isChecked(); - try { - SmsVo smsVo = new SmsVo(getString(R.string.test_phone_num), getString(R.string.test_sender_sms), new Date(), getString(R.string.test_sim_info)); - SenderDingdingMsg.sendMsg(0, handler, null, token, secret, atMobiles, atAll, smsVo.getSmsVoForSend()); - } catch (Exception e) { - ToastUtils.delayedShow(getString(R.string.failed_to_fwd) + e.getMessage(), 3000); - e.printStackTrace(); - } - }); - } - - //邮箱 - @SuppressLint("SimpleDateFormat") - private void setEmail(final SenderModel senderModel, final boolean isClone) { - final String[] MAIL_TYPE = getResources().getStringArray(R.array.MailType); - - EmailSettingVo emailSettingVo = null; - //try phrase json setting - if (senderModel != null) { - String jsonSettingStr = senderModel.getJsonSetting(); - if (jsonSettingStr != null) { - emailSettingVo = JSON.parseObject(jsonSettingStr, EmailSettingVo.class); - } - } - - final AlertDialog.Builder alertDialog71 = new AlertDialog.Builder(SenderActivity.this); - View view1 = View.inflate(SenderActivity.this, R.layout.alert_dialog_setview_email, null); - - final EditText editTextEmailName = view1.findViewById(R.id.editTextEmailName); - @SuppressLint("UseSwitchCompatOrMaterialCode") final Switch switchEmailEnable = view1.findViewById(R.id.switchEmailEnable); - if (senderModel != null) { - editTextEmailName.setText(senderModel.getName()); - switchEmailEnable.setChecked(senderModel.getStatusChecked()); - } - - final EditText editTextEmailHost = view1.findViewById(R.id.editTextEmailHost); - final EditText editTextEmailPort = view1.findViewById(R.id.editTextEmailPort); - @SuppressLint("UseSwitchCompatOrMaterialCode") final Switch switchEmailSSl = view1.findViewById(R.id.switchEmailSSl); - final EditText editTextEmailFromAdd = view1.findViewById(R.id.editTextEmailFromAdd); - final EditText editTextEmailNickname = view1.findViewById(R.id.editTextEmailNickname); - final ClearEditText editTextEmailPsw = view1.findViewById(R.id.editTextEmailPsw); - final EditText editTextEmailToAdd = view1.findViewById(R.id.editTextEmailToAdd); - final EditText editTextEmailTitle = view1.findViewById(R.id.editTextEmailTitle); - final Spinner spinnerEmailType = view1.findViewById(R.id.spinnerEmailType); - final LinearLayout layoutServiceSetting = view1.findViewById(R.id.layoutServiceSetting); - if (emailSettingVo != null) { - String mailType = emailSettingVo.getMailType(); - if (!TextUtils.isEmpty(mailType)) { - for (int i = 0; i < MAIL_TYPE.length; i++) { - if (mailType.equals(MAIL_TYPE[i])) { - spinnerEmailType.setSelection(i); - break; - } - } - } else { - spinnerEmailType.setSelection(MAIL_TYPE.length - 1); - } - editTextEmailHost.setText(emailSettingVo.getHost()); - editTextEmailPort.setText(emailSettingVo.getPort()); - switchEmailSSl.setChecked(emailSettingVo.getSsl()); - editTextEmailFromAdd.setText(emailSettingVo.getFromEmail()); - editTextEmailNickname.setText(emailSettingVo.getNickname()); - editTextEmailPsw.setText(emailSettingVo.getPwd()); - editTextEmailToAdd.setText(emailSettingVo.getToEmail()); - editTextEmailTitle.setText(emailSettingVo.getTitle()); - } - - Button buttonOk = view1.findViewById(R.id.buttonOk); - Button buttonDel = view1.findViewById(R.id.buttonDel); - buttonDel.setText(senderModel != null ? R.string.del : R.string.cancel); - Button buttonTest = view1.findViewById(R.id.buttonTest); - alertDialog71 - .setTitle(R.string.setemailtitle) - .setIcon(R.mipmap.email) - .setView(view1) - .create(); - final AlertDialog show = alertDialog71.show(); - - spinnerEmailType.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { - - @Override - public void onItemSelected(AdapterView parent, View view, int position, long id) { - String mailType = parent.getItemAtPosition(position).toString(); - //ToastUtils.show("选择的邮箱类型是:" + mailType); - - String hint; - if (mailType.equals(getString(R.string.other_mail_type))) { - hint = getString(R.string.hint_from_add_full); - layoutServiceSetting.setVisibility(View.VISIBLE); - ToastUtils.delayedShow(R.string.tips_other_mail_type, 3000); - } else { - hint = getString(R.string.hint_from_add); - layoutServiceSetting.setVisibility(View.GONE); - } - SpannableString ss = new SpannableString(hint);//定义hint的值 - AbsoluteSizeSpan ass = new AbsoluteSizeSpan(13, true);//设置字体大小 true表示单位是sp - ss.setSpan(ass, 0, ss.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); - editTextEmailFromAdd.setHint(new SpannedString(ss)); - } - - @Override - public void onNothingSelected(AdapterView parent) { - // TODO Auto-generated method stub - } - }); - - buttonOk.setOnClickListener(view -> { - String senderName = editTextEmailName.getText().toString().trim(); - int senderStatus = switchEmailEnable.isChecked() ? STATUS_ON : STATUS_OFF; - if (TextUtils.isEmpty(senderName)) { - ToastUtils.delayedShow(R.string.invalid_name, 3000); - return; - } - - String mailType = spinnerEmailType.getSelectedItem().toString(); - String host = editTextEmailHost.getText().toString().trim(); - String port = editTextEmailPort.getText().toString().trim(); - boolean ssl = switchEmailSSl.isChecked(); - String fromEmail = editTextEmailFromAdd.getText().toString().trim(); - String pwd = editTextEmailPsw.getText().trim(); - String toEmail = editTextEmailToAdd.getText().toString().trim(); - - String title = editTextEmailTitle.getText().toString().trim(); - if (title.isEmpty()) title = "SmsForwarder Title"; - - String nickname = editTextEmailNickname.getText().toString().trim(); - if (nickname.isEmpty()) nickname = "SmsForwarder"; - if (fromEmail.isEmpty() || pwd.isEmpty() || toEmail.isEmpty()) { - ToastUtils.delayedShow(R.string.invalid_email, 3000); - return; - } - - if (mailType.equals(getString(R.string.other_mail_type)) && (host.isEmpty() || port.isEmpty())) { - ToastUtils.delayedShow(R.string.tips_other_mail_type, 3000); - return; - } - - EmailSettingVo emailSettingVoNew = new EmailSettingVo(mailType, host, port, ssl, fromEmail, nickname, pwd, toEmail, title); - if (isClone || senderModel == null) { - SenderModel newSenderModel = new SenderModel(); - newSenderModel.setName(senderName); - newSenderModel.setType(TYPE_EMAIL); - newSenderModel.setStatus(senderStatus); - newSenderModel.setJsonSetting(JSON.toJSONString(emailSettingVoNew)); - SenderUtil.addSender(newSenderModel); - initSenders(); - adapter.add(senderModels); - } else { - senderModel.setName(senderName); - senderModel.setType(TYPE_EMAIL); - senderModel.setStatus(senderStatus); - senderModel.setJsonSetting(JSON.toJSONString(emailSettingVoNew)); - SenderUtil.updateSender(senderModel); - initSenders(); - adapter.update(senderModels); - } - show.dismiss(); - }); - - buttonDel.setOnClickListener(view -> { - if (senderModel != null) { - SenderUtil.delSender(senderModel.getId()); - initSenders(); - adapter.del(senderModels); - } - show.dismiss(); - }); - - buttonTest.setOnClickListener(view -> { - String mailType = spinnerEmailType.getSelectedItem().toString(); - String host = editTextEmailHost.getText().toString().trim(); - String port = editTextEmailPort.getText().toString().trim(); - boolean ssl = switchEmailSSl.isChecked(); - String fromEmail = editTextEmailFromAdd.getText().toString().trim(); - String pwd = editTextEmailPsw.getText().trim(); - String toEmail = editTextEmailToAdd.getText().toString().trim(); - - String title = editTextEmailTitle.getText().toString().trim(); - if (title.isEmpty()) title = "SmsForwarder Title"; - - String nickname = editTextEmailNickname.getText().toString().trim(); - if (nickname.isEmpty()) nickname = "SmsForwarder"; - - if (fromEmail.isEmpty() || pwd.isEmpty() || toEmail.isEmpty()) { - ToastUtils.delayedShow(R.string.invalid_email, 3000); - return; - } - - if (mailType.equals(getString(R.string.other_mail_type)) && (host.isEmpty() || port.isEmpty())) { - ToastUtils.delayedShow(R.string.tips_other_mail_type, 3000); - return; - } - - try { - EmailSettingVo emailSettingVoNew = new EmailSettingVo(mailType, host, port, ssl, fromEmail, nickname, pwd, toEmail, title); - SmsVo smsVo = new SmsVo(getString(R.string.test_phone_num), getString(R.string.test_sender_sms), new Date(), getString(R.string.test_sim_info)); - SenderMailMsg.sendEmail(0, handler, emailSettingVoNew, smsVo.getTitleForSend(title), smsVo.getSmsVoForSend()); - } catch (Exception e) { - ToastUtils.delayedShow(getString(R.string.failed_to_fwd) + e.getMessage(), 3000); - e.printStackTrace(); - } - }); - - - Button buttonInsertSender = view1.findViewById(R.id.bt_insert_sender); - buttonInsertSender.setOnClickListener(view -> { - editTextEmailTitle.setFocusable(true); - editTextEmailTitle.requestFocus(); - CommonUtils.insertOrReplaceText2Cursor(editTextEmailTitle, getString(R.string.tag_from)); - }); - - Button buttonInsertExtra = view1.findViewById(R.id.bt_insert_extra); - buttonInsertExtra.setOnClickListener(view -> { - editTextEmailTitle.setFocusable(true); - editTextEmailTitle.requestFocus(); - CommonUtils.insertOrReplaceText2Cursor(editTextEmailTitle, getString(R.string.tag_card_slot)); - }); - - Button buttonInsertTime = view1.findViewById(R.id.bt_insert_time); - buttonInsertTime.setOnClickListener(view -> { - editTextEmailTitle.setFocusable(true); - editTextEmailTitle.requestFocus(); - CommonUtils.insertOrReplaceText2Cursor(editTextEmailTitle, getString(R.string.tag_receive_time)); - }); - - Button buttonInsertDeviceName = view1.findViewById(R.id.bt_insert_device_name); - buttonInsertDeviceName.setOnClickListener(view -> { - editTextEmailTitle.setFocusable(true); - editTextEmailTitle.requestFocus(); - CommonUtils.insertOrReplaceText2Cursor(editTextEmailTitle, getString(R.string.tag_device_name)); - }); - - } - - //Bark - private void setBark(final SenderModel senderModel, final boolean isClone) { - BarkSettingVo barkSettingVo = null; - //try phrase json setting - if (senderModel != null) { - String jsonSettingStr = senderModel.getJsonSetting(); - if (jsonSettingStr != null) { - barkSettingVo = JSON.parseObject(jsonSettingStr, BarkSettingVo.class); - } - } - - final AlertDialog.Builder alertDialog71 = new AlertDialog.Builder(SenderActivity.this); - View view1 = View.inflate(SenderActivity.this, R.layout.alert_dialog_setview_bark, null); - - final EditText editTextBarkName = view1.findViewById(R.id.editTextBarkName); - @SuppressLint("UseSwitchCompatOrMaterialCode") final Switch switchBarkEnable = view1.findViewById(R.id.switchBarkEnable); - if (senderModel != null) { - editTextBarkName.setText(senderModel.getName()); - switchBarkEnable.setChecked(senderModel.getStatusChecked()); - } - - final ClearEditText editTextBarkServer = view1.findViewById(R.id.editTextBarkServer); - final EditText editTextBarkTitle = view1.findViewById(R.id.editTextBarkTitle); - final EditText editTextBarkIcon = view1.findViewById(R.id.editTextBarkIcon); - final EditText editTextBarkSound = view1.findViewById(R.id.editTextBarkSound); - final EditText editTextBarkBadge = view1.findViewById(R.id.editTextBarkBadge); - final EditText editTextBarkUrl = view1.findViewById(R.id.editTextBarkUrl); - final RadioGroup radioGroupBarkLevel = view1.findViewById(R.id.radioGroupBarkLevel); - if (barkSettingVo != null) { - editTextBarkServer.setText(barkSettingVo.getServer()); - editTextBarkTitle.setText(barkSettingVo.getTitle()); - editTextBarkIcon.setText(barkSettingVo.getIcon()); - editTextBarkSound.setText(barkSettingVo.getSound()); - editTextBarkBadge.setText(barkSettingVo.getBadge()); - editTextBarkUrl.setText(barkSettingVo.getUrl()); - radioGroupBarkLevel.check(barkSettingVo.getLevelId()); - } - - Button buttonOk = view1.findViewById(R.id.buttonOk); - Button buttonDel = view1.findViewById(R.id.buttonDel); - buttonDel.setText(senderModel != null ? R.string.del : R.string.cancel); - Button buttonTest = view1.findViewById(R.id.buttonTest); - alertDialog71 - .setTitle(R.string.setbarktitle) - .setIcon(R.mipmap.bark) - .setView(view1) - .create(); - final AlertDialog show = alertDialog71.show(); - - buttonOk.setOnClickListener(view -> { - String senderName = editTextBarkName.getText().toString().trim(); - int senderStatus = switchBarkEnable.isChecked() ? STATUS_ON : STATUS_OFF; - if (TextUtils.isEmpty(senderName)) { - ToastUtils.delayedShow(R.string.invalid_name, 3000); - return; - } - - //推送地址 - String barkServer = editTextBarkServer.getText().trim(); - if (!CommonUtils.checkUrl(barkServer, false)) { - ToastUtils.delayedShow(R.string.invalid_bark_server, 3000); - return; - } - - String icon = editTextBarkIcon.getText().toString().trim(); //消息图标 - String title = editTextBarkTitle.getText().toString().trim(); //标题模板 - int levelId = radioGroupBarkLevel.getCheckedRadioButtonId(); //时效性 - String sound = editTextBarkSound.getText().toString().trim(); //声音 - String badge = editTextBarkBadge.getText().toString().trim(); //角标 - String url = editTextBarkUrl.getText().toString().trim(); //链接 - BarkSettingVo barkSettingVoNew = new BarkSettingVo(barkServer, icon, title, levelId, sound, badge, url); - if (isClone || senderModel == null) { - SenderModel newSenderModel = new SenderModel(); - newSenderModel.setName(senderName); - newSenderModel.setType(TYPE_BARK); - newSenderModel.setStatus(senderStatus); - newSenderModel.setJsonSetting(JSON.toJSONString(barkSettingVoNew)); - SenderUtil.addSender(newSenderModel); - initSenders(); - adapter.add(senderModels); - } else { - senderModel.setName(senderName); - senderModel.setType(TYPE_BARK); - senderModel.setStatus(senderStatus); - senderModel.setJsonSetting(JSON.toJSONString(barkSettingVoNew)); - SenderUtil.updateSender(senderModel); - initSenders(); - adapter.update(senderModels); - } - show.dismiss(); - }); - - buttonDel.setOnClickListener(view -> { - if (senderModel != null) { - SenderUtil.delSender(senderModel.getId()); - initSenders(); - adapter.del(senderModels); - } - show.dismiss(); - }); - - buttonTest.setOnClickListener(view -> { - String barkServer = editTextBarkServer.getText().trim(); - String icon = editTextBarkIcon.getText().toString().trim(); //消息图标 - String title = editTextBarkTitle.getText().toString().trim(); //标题模板 - int levelId = radioGroupBarkLevel.getCheckedRadioButtonId(); //时效性 - String sound = editTextBarkSound.getText().toString().trim(); //声音 - String badge = editTextBarkBadge.getText().toString().trim(); //角标 - String url = editTextBarkUrl.getText().toString().trim(); //链接 - BarkSettingVo barkSettingVoNew = new BarkSettingVo(barkServer, icon, title, levelId, sound, badge, url); - if (CommonUtils.checkUrl(barkServer, false)) { - try { - SmsVo smsVo = new SmsVo(getString(R.string.test_phone_num), getString(R.string.test_sender_sms), new Date(), getString(R.string.test_sim_info)); - SenderBarkMsg.sendMsg(0, handler, null, barkSettingVoNew, smsVo.getTitleForSend(title), smsVo.getSmsVoForSend(), getString(R.string.test_group_name)); - } catch (Exception e) { - ToastUtils.delayedShow(getString(R.string.failed_to_fwd) + e.getMessage(), 3000); - e.printStackTrace(); - } - } else { - ToastUtils.delayedShow(R.string.invalid_bark_server, 3000); - } - }); - - - Button buttonInsertSender = view1.findViewById(R.id.bt_insert_sender); - buttonInsertSender.setOnClickListener(view -> { - editTextBarkTitle.setFocusable(true); - editTextBarkTitle.requestFocus(); - CommonUtils.insertOrReplaceText2Cursor(editTextBarkTitle, getString(R.string.tag_from)); - }); - - Button buttonInsertExtra = view1.findViewById(R.id.bt_insert_extra); - buttonInsertExtra.setOnClickListener(view -> { - editTextBarkTitle.setFocusable(true); - editTextBarkTitle.requestFocus(); - CommonUtils.insertOrReplaceText2Cursor(editTextBarkTitle, getString(R.string.tag_card_slot)); - }); - - Button buttonInsertTime = view1.findViewById(R.id.bt_insert_time); - buttonInsertTime.setOnClickListener(view -> { - editTextBarkTitle.setFocusable(true); - editTextBarkTitle.requestFocus(); - CommonUtils.insertOrReplaceText2Cursor(editTextBarkTitle, getString(R.string.tag_receive_time)); - }); - - Button buttonInsertDeviceName = view1.findViewById(R.id.bt_insert_device_name); - buttonInsertDeviceName.setOnClickListener(view -> { - editTextBarkTitle.setFocusable(true); - editTextBarkTitle.requestFocus(); - CommonUtils.insertOrReplaceText2Cursor(editTextBarkTitle, getString(R.string.tag_device_name)); - }); - - } - - //header序号 - int headerItemId = 0; - - /** - * 动态增删header - * - * @param headerItemMap 管理item的map,用于删除指定header - * @param linearLayoutWebNotifyHeaders 需要挂载item的LinearLayout - * @param key header的key,为空则不设置 - * @param value header的value,为空则不设置 - */ - private void addHeaderItemLinearLayout(Map headerItemMap, final LinearLayout linearLayoutWebNotifyHeaders, String key, String value) { - LinearLayout linearLayoutItemAddHeader = (LinearLayout) View.inflate(this, R.layout.item_add_header, null); - ImageView imageViewRemoveHeader = linearLayoutItemAddHeader.findViewById(R.id.imageViewRemoveHeader); - - if (key != null && value != null) { - EditText editTextHeaderKey = linearLayoutItemAddHeader.findViewById(R.id.editTextHeaderKey); - EditText editTextHeaderValue = linearLayoutItemAddHeader.findViewById(R.id.editTextHeaderValue); - editTextHeaderKey.setText(key); - editTextHeaderValue.setText(value); - } - - imageViewRemoveHeader.setTag(headerItemId); - imageViewRemoveHeader.setOnClickListener(view2 -> { - int itemId = (int) view2.getTag(); - linearLayoutWebNotifyHeaders.removeView(headerItemMap.get(itemId)); - headerItemMap.remove(itemId); - }); - linearLayoutWebNotifyHeaders.addView(linearLayoutItemAddHeader); - headerItemMap.put(headerItemId, linearLayoutItemAddHeader); - headerItemId++; - } - - /** - * 从EditText控件中获取全部headers - * - * @param headerItemMap 管理item的map - * @return 全部headers - */ - private Map getHeadersFromHeaderItemMap(Map headerItemMap) { - Map headers = new HashMap<>(); - for (LinearLayout headerItem : headerItemMap.values()) { - EditText editTextHeaderKey = headerItem.findViewById(R.id.editTextHeaderKey); - EditText editTextHeaderValue = headerItem.findViewById(R.id.editTextHeaderValue); - String key = editTextHeaderKey.getText().toString().trim(); - String value = editTextHeaderValue.getText().toString().trim(); - headers.put(key, value); - } - return headers; - } - - //webhook - @SuppressLint("SimpleDateFormat") - private void setWebNotify(final SenderModel senderModel, final boolean isClone) { - WebNotifySettingVo webNotifySettingVo = null; - //try phrase json setting - if (senderModel != null) { - String jsonSettingStr = senderModel.getJsonSetting(); - if (jsonSettingStr != null) { - webNotifySettingVo = JSON.parseObject(jsonSettingStr, WebNotifySettingVo.class); - } - } - - final AlertDialog.Builder alertDialog71 = new AlertDialog.Builder(SenderActivity.this); - View view1 = View.inflate(SenderActivity.this, R.layout.alert_dialog_setview_webnotify, null); - - final EditText editTextWebNotifyName = view1.findViewById(R.id.editTextWebNotifyName); - @SuppressLint("UseSwitchCompatOrMaterialCode") final Switch switchWebNotifyEnable = view1.findViewById(R.id.switchWebNotifyEnable); - if (senderModel != null) { - editTextWebNotifyName.setText(senderModel.getName()); - switchWebNotifyEnable.setChecked(senderModel.getStatusChecked()); - } - - final EditText editTextWebNotifyWebServer = view1.findViewById(R.id.editTextWebNotifyWebServer); - final EditText editTextWebNotifyWebParams = view1.findViewById(R.id.editTextWebNotifyWebParams); - final ClearEditText editTextWebNotifySecret = view1.findViewById(R.id.editTextWebNotifySecret); - final RadioGroup radioGroupWebNotifyMethod = view1.findViewById(R.id.radioGroupWebNotifyMethod); - - Map headerItemMap = new HashMap<>(2); - final LinearLayout linearLayoutWebNotifyHeaders = view1.findViewById(R.id.linearLayoutWebNotifyHeaders); - final ImageView imageViewWebNotifyAddHeader = view1.findViewById(R.id.imageViewWebNotifyAddHeader); - - if (webNotifySettingVo != null) { - editTextWebNotifyWebServer.setText(webNotifySettingVo.getWebServer()); - editTextWebNotifyWebParams.setText(webNotifySettingVo.getWebParams()); - editTextWebNotifySecret.setText(webNotifySettingVo.getSecret()); - radioGroupWebNotifyMethod.check(webNotifySettingVo.getWebNotifyMethodCheckId()); - //set header - Map headers = webNotifySettingVo.getHeaders(); - if (headers != null) { - for (Map.Entry header : headers.entrySet()) { - addHeaderItemLinearLayout(headerItemMap, linearLayoutWebNotifyHeaders, header.getKey(), header.getValue()); - } - } - } - - //add header - imageViewWebNotifyAddHeader.setOnClickListener(view -> addHeaderItemLinearLayout(headerItemMap, linearLayoutWebNotifyHeaders, null, null)); - - Button buttonOk = view1.findViewById(R.id.buttonOk); - Button buttonDel = view1.findViewById(R.id.buttonDel); - buttonDel.setText(senderModel != null ? R.string.del : R.string.cancel); - Button buttonTest = view1.findViewById(R.id.buttonTest); - alertDialog71 - .setTitle(R.string.setwebnotifytitle) - .setIcon(R.mipmap.webhook) - .setView(view1) - .create(); - final AlertDialog show = alertDialog71.show(); - - buttonOk.setOnClickListener(view -> { - String senderName = editTextWebNotifyName.getText().toString().trim(); - int senderStatus = switchWebNotifyEnable.isChecked() ? STATUS_ON : STATUS_OFF; - if (TextUtils.isEmpty(senderName)) { - ToastUtils.delayedShow(R.string.invalid_name, 3000); - return; - } - - String webServer = editTextWebNotifyWebServer.getText().toString().trim(); - String secret = editTextWebNotifySecret.getText().trim(); - String method = radioGroupWebNotifyMethod.getCheckedRadioButtonId() == R.id.radioWebNotifyMethodGet ? "GET" : "POST"; - String webParams = editTextWebNotifyWebParams.getText().toString().trim(); - Map headers = getHeadersFromHeaderItemMap(headerItemMap); - - if (!CommonUtils.checkUrl(webServer, false)) { - ToastUtils.delayedShow(R.string.invalid_webserver, 3000); - return; - } - - WebNotifySettingVo webNotifySettingVoNew = new WebNotifySettingVo(webServer, secret, method, webParams, headers); - if (isClone || senderModel == null) { - SenderModel newSenderModel = new SenderModel(); - newSenderModel.setName(senderName); - newSenderModel.setType(TYPE_WEB_NOTIFY); - newSenderModel.setStatus(senderStatus); - newSenderModel.setJsonSetting(JSON.toJSONString(webNotifySettingVoNew)); - SenderUtil.addSender(newSenderModel); - } else { - senderModel.setName(senderName); - senderModel.setType(TYPE_WEB_NOTIFY); - senderModel.setStatus(senderStatus); - senderModel.setJsonSetting(JSON.toJSONString(webNotifySettingVoNew)); - SenderUtil.updateSender(senderModel); - } - initSenders(); - adapter.update(senderModels); - show.dismiss(); - }); - - buttonDel.setOnClickListener(view -> { - if (senderModel != null) { - SenderUtil.delSender(senderModel.getId()); - initSenders(); - adapter.del(senderModels); - } - show.dismiss(); - }); - - buttonTest.setOnClickListener(view -> { - String webServer = editTextWebNotifyWebServer.getText().toString().trim(); - String secret = editTextWebNotifySecret.getText().trim(); - String method = radioGroupWebNotifyMethod.getCheckedRadioButtonId() == R.id.radioWebNotifyMethodGet ? "GET" : "POST"; - String webParams = editTextWebNotifyWebParams.getText().toString().trim(); - Map headers = getHeadersFromHeaderItemMap(headerItemMap); - - if (!CommonUtils.checkUrl(webServer, false)) { - ToastUtils.delayedShow(R.string.invalid_webserver, 3000); - return; - } - - try { - SmsVo smsVo = new SmsVo(getString(R.string.test_phone_num), getString(R.string.test_sender_sms), new Date(), getString(R.string.test_sim_info)); - SenderWebNotifyMsg.sendMsg(0, handler, null, webServer, webParams, secret, method, headers, smsVo, "", ""); - } catch (Exception e) { - ToastUtils.delayedShow(getString(R.string.failed_to_fwd) + e.getMessage(), 3000); - e.printStackTrace(); - } - }); - } - - //企业微信群机器人 - @SuppressLint("SimpleDateFormat") - private void setQYWXGroupRobot(final SenderModel senderModel, final boolean isClone) { - QYWXGroupRobotSettingVo qywxGroupRobotSettingVo = null; - //try phrase json setting - if (senderModel != null) { - String jsonSettingStr = senderModel.getJsonSetting(); - if (jsonSettingStr != null) { - qywxGroupRobotSettingVo = JSON.parseObject(jsonSettingStr, QYWXGroupRobotSettingVo.class); - } - } - - final AlertDialog.Builder alertDialog71 = new AlertDialog.Builder(SenderActivity.this); - View view1 = View.inflate(SenderActivity.this, R.layout.alert_dialog_setview_qywxgrouprobot, null); - - final EditText editTextQYWXGroupRobotName = view1.findViewById(R.id.editTextQYWXGroupRobotName); - @SuppressLint("UseSwitchCompatOrMaterialCode") final Switch switchQYWXGroupRobotEnable = view1.findViewById(R.id.switchQYWXGroupRobotEnable); - if (senderModel != null) { - editTextQYWXGroupRobotName.setText(senderModel.getName()); - switchQYWXGroupRobotEnable.setChecked(senderModel.getStatusChecked()); - } - - final ClearEditText editTextQYWXGroupRobotWebHook = view1.findViewById(R.id.editTextQYWXGroupRobotWebHook); - if (qywxGroupRobotSettingVo != null) { - editTextQYWXGroupRobotWebHook.setText(qywxGroupRobotSettingVo.getWebHook()); - } - - Button buttonOk = view1.findViewById(R.id.buttonOk); - Button buttonDel = view1.findViewById(R.id.buttonDel); - buttonDel.setText(senderModel != null ? R.string.del : R.string.cancel); - Button buttonTest = view1.findViewById(R.id.buttonTest); - alertDialog71 - .setTitle(R.string.setqywxgrouprobottitle) - .setIcon(R.mipmap.qywx) - .setView(view1) - .create(); - final AlertDialog show = alertDialog71.show(); - - buttonOk.setOnClickListener(view -> { - String senderName = editTextQYWXGroupRobotName.getText().toString().trim(); - int senderStatus = switchQYWXGroupRobotEnable.isChecked() ? STATUS_ON : STATUS_OFF; - if (TextUtils.isEmpty(senderName)) { - ToastUtils.delayedShow(R.string.invalid_name, 3000); - return; - } - - String webHook = editTextQYWXGroupRobotWebHook.getText().trim(); - if (!CommonUtils.checkUrl(webHook, false)) { - ToastUtils.delayedShow(R.string.invalid_webhook, 3000); - return; - } - - QYWXGroupRobotSettingVo qywxGroupRobotSettingVoNew = new QYWXGroupRobotSettingVo(webHook); - if (isClone || senderModel == null) { - SenderModel newSenderModel = new SenderModel(); - newSenderModel.setName(senderName); - newSenderModel.setType(TYPE_QYWX_GROUP_ROBOT); - newSenderModel.setStatus(senderStatus); - newSenderModel.setJsonSetting(JSON.toJSONString(qywxGroupRobotSettingVoNew)); - SenderUtil.addSender(newSenderModel); - initSenders(); - adapter.add(senderModels); - } else { - senderModel.setName(senderName); - senderModel.setType(TYPE_QYWX_GROUP_ROBOT); - senderModel.setStatus(senderStatus); - senderModel.setJsonSetting(JSON.toJSONString(qywxGroupRobotSettingVoNew)); - SenderUtil.updateSender(senderModel); - initSenders(); - adapter.update(senderModels); - } - show.dismiss(); - }); - - buttonDel.setOnClickListener(view -> { - if (senderModel != null) { - SenderUtil.delSender(senderModel.getId()); - initSenders(); - adapter.del(senderModels); - } - show.dismiss(); - }); - - buttonTest.setOnClickListener(view -> { - String webHook = editTextQYWXGroupRobotWebHook.getText().trim(); - if (!CommonUtils.checkUrl(webHook, false)) { - ToastUtils.delayedShow(R.string.invalid_webhook, 3000); - return; - } - - try { - SmsVo smsVo = new SmsVo(getString(R.string.test_phone_num), getString(R.string.test_sender_sms), new Date(), getString(R.string.test_sim_info)); - SenderQyWxGroupRobotMsg.sendMsg(0, handler, null, webHook, smsVo.getMobile(), smsVo.getSmsVoForSend()); - } catch (Exception e) { - ToastUtils.delayedShow(getString(R.string.failed_to_fwd) + e.getMessage(), 3000); - e.printStackTrace(); - } - }); - } - - //企业微信应用 - @SuppressLint({"SimpleDateFormat", "SetTextI18n"}) - private void setQYWXApp(final SenderModel senderModel, final boolean isClone) { - QYWXAppSettingVo QYWXAppSettingVo = null; - //try phrase json setting - if (senderModel != null) { - String jsonSettingStr = senderModel.getJsonSetting(); - if (jsonSettingStr != null) { - QYWXAppSettingVo = JSON.parseObject(jsonSettingStr, QYWXAppSettingVo.class); - } - } - final AlertDialog.Builder alertDialog71 = new AlertDialog.Builder(SenderActivity.this); - View view1 = View.inflate(SenderActivity.this, R.layout.alert_dialog_setview_qywxapp, null); - - final EditText editTextQYWXAppName = view1.findViewById(R.id.editTextQYWXAppName); - @SuppressLint("UseSwitchCompatOrMaterialCode") final Switch switchQYWXAppEnable = view1.findViewById(R.id.switchQYWXAppEnable); - if (senderModel != null) { - editTextQYWXAppName.setText(senderModel.getName()); - switchQYWXAppEnable.setChecked(senderModel.getStatusChecked()); - } - - final EditText editTextQYWXAppCorpID = view1.findViewById(R.id.editTextQYWXAppCorpID); - final EditText editTextQYWXAppAgentID = view1.findViewById(R.id.editTextQYWXAppAgentID); - final ClearEditText editTextQYWXAppSecret = view1.findViewById(R.id.editTextQYWXAppSecret); - final LinearLayout linearLayoutQYWXAppToUser = view1.findViewById(R.id.linearLayoutQYWXAppToUser); - final EditText editTextQYWXAppToUser = view1.findViewById(R.id.editTextQYWXAppToUser); - @SuppressLint("UseSwitchCompatOrMaterialCode") final Switch switchQYWXAppAtAll = view1.findViewById(R.id.switchQYWXAppAtAll); - if (QYWXAppSettingVo != null) { - editTextQYWXAppCorpID.setText(QYWXAppSettingVo.getCorpID()); - editTextQYWXAppAgentID.setText(QYWXAppSettingVo.getAgentID()); - editTextQYWXAppSecret.setText(QYWXAppSettingVo.getSecret()); - editTextQYWXAppToUser.setText(QYWXAppSettingVo.getToUser()); - switchQYWXAppAtAll.setChecked(QYWXAppSettingVo.getAtAll()); - linearLayoutQYWXAppToUser.setVisibility(QYWXAppSettingVo.getAtAll() ? View.GONE : View.VISIBLE); - } - switchQYWXAppAtAll.setOnCheckedChangeListener((buttonView, isChecked) -> { - if (isChecked) { - linearLayoutQYWXAppToUser.setVisibility(View.GONE); - editTextQYWXAppToUser.setText("@all"); - } else { - linearLayoutQYWXAppToUser.setVisibility(View.VISIBLE); - editTextQYWXAppToUser.setText(""); - } - Log.d(TAG, "onCheckedChanged:" + isChecked); - }); - - Button buttonOk = view1.findViewById(R.id.buttonOk); - Button buttonDel = view1.findViewById(R.id.buttonDel); - buttonDel.setText(senderModel != null ? R.string.del : R.string.cancel); - Button buttonTest = view1.findViewById(R.id.buttonTest); - alertDialog71 - .setTitle(R.string.setqywxapptitle) - .setIcon(R.mipmap.qywxapp) - .setView(view1) - .create(); - final AlertDialog show = alertDialog71.show(); - buttonOk.setOnClickListener(view -> { - String senderName = editTextQYWXAppName.getText().toString().trim(); - int senderStatus = switchQYWXAppEnable.isChecked() ? STATUS_ON : STATUS_OFF; - if (TextUtils.isEmpty(senderName)) { - ToastUtils.delayedShow(R.string.invalid_name, 3000); - return; - } - - String toUser = editTextQYWXAppToUser.getText().toString().trim(); - if (toUser.isEmpty()) { - ToastUtils.delayedShow(R.string.invalid_at_mobiles, 3000); - editTextQYWXAppToUser.setFocusable(true); - editTextQYWXAppToUser.requestFocus(); - return; - } - - QYWXAppSettingVo QYWXAppSettingVoNew = new QYWXAppSettingVo( - editTextQYWXAppCorpID.getText().toString().trim(), - editTextQYWXAppAgentID.getText().toString().trim(), - editTextQYWXAppSecret.getText().trim(), - editTextQYWXAppToUser.getText().toString().trim(), - switchQYWXAppAtAll.isChecked()); - if (!QYWXAppSettingVoNew.checkParms()) { - ToastUtils.delayedShow(R.string.invalid_webcom_app_parm, 3000); - return; - } - - if (isClone || senderModel == null) { - SenderModel newSenderModel = new SenderModel(); - newSenderModel.setName(senderName); - newSenderModel.setType(TYPE_QYWX_APP); - newSenderModel.setStatus(senderStatus); - newSenderModel.setJsonSetting(JSON.toJSONString(QYWXAppSettingVoNew)); - SenderUtil.addSender(newSenderModel); - initSenders(); - adapter.add(senderModels); - } else { - senderModel.setName(senderName); - senderModel.setType(TYPE_QYWX_APP); - senderModel.setStatus(senderStatus); - senderModel.setJsonSetting(JSON.toJSONString(QYWXAppSettingVoNew)); - SenderUtil.updateSender(senderModel); - initSenders(); - adapter.update(senderModels); - } - show.dismiss(); - }); - - buttonDel.setOnClickListener(view -> { - if (senderModel != null) { - SenderUtil.delSender(senderModel.getId()); - initSenders(); - adapter.del(senderModels); - } - show.dismiss(); - }); - - buttonTest.setOnClickListener(view -> { - QYWXAppSettingVo QYWXAppSettingVoNew = new QYWXAppSettingVo( - editTextQYWXAppCorpID.getText().toString().trim(), - editTextQYWXAppAgentID.getText().toString().trim(), - editTextQYWXAppSecret.getText().trim(), - editTextQYWXAppToUser.getText().toString().trim(), - switchQYWXAppAtAll.isChecked()); - if (!QYWXAppSettingVoNew.checkParms()) { - ToastUtils.delayedShow(R.string.invalid_webcom_app_parm, 3000); - return; - } - if (QYWXAppSettingVoNew.getToUser().isEmpty()) { - ToastUtils.delayedShow(R.string.invalid_at_mobiles, 3000); - return; - } - - try { - SmsVo smsVo = new SmsVo(getString(R.string.test_phone_num), getString(R.string.test_sender_sms), new Date(), getString(R.string.test_sim_info)); - SenderQyWxAppMsg.sendMsg(0, handler, null, senderModel, QYWXAppSettingVoNew, smsVo.getSmsVoForSend()); - } catch (Exception e) { - ToastUtils.delayedShow(getString(R.string.failed_to_fwd) + e.getMessage(), 3000); - e.printStackTrace(); - } - }); - } - - //Server酱·Turbo版 - private void setServerChan(final SenderModel senderModel, final boolean isClone) { - ServerChanSettingVo serverchanSettingVo = null; - //try phrase json setting - if (senderModel != null) { - String jsonSettingStr = senderModel.getJsonSetting(); - if (jsonSettingStr != null) { - serverchanSettingVo = JSON.parseObject(jsonSettingStr, ServerChanSettingVo.class); - } - } - - final AlertDialog.Builder alertDialog71 = new AlertDialog.Builder(SenderActivity.this); - View view1 = View.inflate(SenderActivity.this, R.layout.alert_dialog_setview_serverchan, null); - - final EditText editTextServerChanName = view1.findViewById(R.id.editTextServerChanName); - @SuppressLint("UseSwitchCompatOrMaterialCode") final Switch switchServerChanEnable = view1.findViewById(R.id.switchServerChanEnable); - if (senderModel != null) { - editTextServerChanName.setText(senderModel.getName()); - switchServerChanEnable.setChecked(senderModel.getStatusChecked()); - } - - final ClearEditText editTextServerChanSendKey = view1.findViewById(R.id.editTextServerChanSendKey); - if (serverchanSettingVo != null) - editTextServerChanSendKey.setText(serverchanSettingVo.getSendKey()); - - Button buttonOk = view1.findViewById(R.id.buttonOk); - Button buttonDel = view1.findViewById(R.id.buttonDel); - buttonDel.setText(senderModel != null ? R.string.del : R.string.cancel); - Button buttonTest = view1.findViewById(R.id.buttonTest); - alertDialog71 - .setTitle(R.string.setserverchantitle) - .setIcon(R.mipmap.serverchan) - .setView(view1) - .create(); - final AlertDialog show = alertDialog71.show(); - - buttonOk.setOnClickListener(view -> { - String senderName = editTextServerChanName.getText().toString().trim(); - int senderStatus = switchServerChanEnable.isChecked() ? STATUS_ON : STATUS_OFF; - if (TextUtils.isEmpty(senderName)) { - ToastUtils.delayedShow(R.string.invalid_name, 3000); - return; - } - - String serverChanServer = editTextServerChanSendKey.getText().trim(); - if (TextUtils.isEmpty(serverChanServer)) { - ToastUtils.delayedShow(R.string.invalid_sendkey, 3000); - return; - } - ServerChanSettingVo serverChanSettingVoNew = new ServerChanSettingVo(serverChanServer); - - if (isClone || senderModel == null) { - SenderModel newSenderModel = new SenderModel(); - newSenderModel.setName(senderName); - newSenderModel.setType(TYPE_SERVER_CHAN); - newSenderModel.setStatus(senderStatus); - newSenderModel.setJsonSetting(JSON.toJSONString(serverChanSettingVoNew)); - SenderUtil.addSender(newSenderModel); - initSenders(); - adapter.add(senderModels); - } else { - senderModel.setName(senderName); - senderModel.setType(TYPE_SERVER_CHAN); - senderModel.setStatus(senderStatus); - senderModel.setJsonSetting(JSON.toJSONString(serverChanSettingVoNew)); - SenderUtil.updateSender(senderModel); - initSenders(); - adapter.update(senderModels); - } - show.dismiss(); - }); - - buttonDel.setOnClickListener(view -> { - if (senderModel != null) { - SenderUtil.delSender(senderModel.getId()); - initSenders(); - adapter.del(senderModels); - } - show.dismiss(); - }); - - buttonTest.setOnClickListener(view -> { - String serverChanServer = editTextServerChanSendKey.getText().trim(); - if (TextUtils.isEmpty(serverChanServer)) { - ToastUtils.delayedShow(R.string.invalid_sendkey, 3000); - return; - } - - try { - SmsVo smsVo = new SmsVo(getString(R.string.test_phone_num), getString(R.string.test_sender_sms), new Date(), getString(R.string.test_sim_info)); - SenderServerChanMsg.sendMsg(0, handler, null, serverChanServer, smsVo.getMobile(), smsVo.getSmsVoForSend()); - } catch (Exception e) { - ToastUtils.delayedShow(getString(R.string.failed_to_fwd) + e.getMessage(), 3000); - e.printStackTrace(); - } - }); - } - - //Telegram机器人 - private void setTelegram(final SenderModel senderModel, final boolean isClone) { - TelegramSettingVo telegramSettingVo = null; - //try phrase json setting - if (senderModel != null) { - String jsonSettingStr = senderModel.getJsonSetting(); - if (jsonSettingStr != null) { - telegramSettingVo = JSON.parseObject(jsonSettingStr, TelegramSettingVo.class); - } - } - - final AlertDialog.Builder alertDialog71 = new AlertDialog.Builder(SenderActivity.this); - View view1 = View.inflate(SenderActivity.this, R.layout.alert_dialog_setview_telegram, null); - - final EditText editTextTelegramName = view1.findViewById(R.id.editTextTelegramName); - @SuppressLint("UseSwitchCompatOrMaterialCode") final Switch switchTelegramEnable = view1.findViewById(R.id.switchTelegramEnable); - if (senderModel != null) { - editTextTelegramName.setText(senderModel.getName()); - switchTelegramEnable.setChecked(senderModel.getStatusChecked()); - } - - final ClearEditText editTextTelegramApiToken = view1.findViewById(R.id.editTextTelegramApiToken); - final EditText editTextTelegramChatId = view1.findViewById(R.id.editTextTelegramChatId); - final RadioGroup radioGroupTelegramMethod = view1.findViewById(R.id.radioGroupTelegramMethod); - - final RadioGroup radioGroupProxyType = view1.findViewById(R.id.radioGroupProxyType); - final EditText editTextProxyHost = view1.findViewById(R.id.editTextProxyHost); - final EditText editTextProxyPort = view1.findViewById(R.id.editTextProxyPort); - - @SuppressLint("UseSwitchCompatOrMaterialCode") final Switch switchProxyAuthenticator = view1.findViewById(R.id.switchProxyAuthenticator); - final EditText editTextProxyUsername = view1.findViewById(R.id.editTextProxyUsername); - final ClearEditText editTextProxyPassword = view1.findViewById(R.id.editTextProxyPassword); - - final LinearLayout layoutProxyHost = view1.findViewById(R.id.layoutProxyHost); - final LinearLayout layoutProxyPort = view1.findViewById(R.id.layoutProxyPort); - final LinearLayout layoutProxyAuthenticator = view1.findViewById(R.id.layoutProxyAuthenticator); - - switchProxyAuthenticator.setOnCheckedChangeListener((buttonView, isChecked) -> { - Log.d(TAG, "onCheckedChanged:" + isChecked); - layoutProxyAuthenticator.setVisibility(isChecked ? View.VISIBLE : View.GONE); - }); - - radioGroupProxyType.setOnCheckedChangeListener((group, checkedId) -> { - if (group != null && checkedId > 0) { - if (checkedId == R.id.btnProxyNone) { - layoutProxyHost.setVisibility(View.GONE); - layoutProxyPort.setVisibility(View.GONE); - layoutProxyAuthenticator.setVisibility(View.GONE); - } else { - layoutProxyHost.setVisibility(View.VISIBLE); - layoutProxyPort.setVisibility(View.VISIBLE); - layoutProxyAuthenticator.setVisibility(switchProxyAuthenticator.isChecked() ? View.VISIBLE : View.GONE); - } - group.check(checkedId); - } - }); - - if (telegramSettingVo != null) { - editTextTelegramApiToken.setText(telegramSettingVo.getApiToken()); - editTextTelegramChatId.setText(telegramSettingVo.getChatId()); - radioGroupTelegramMethod.check(telegramSettingVo.getMethodCheckId()); - - radioGroupProxyType.check(telegramSettingVo.getProxyTypeCheckId()); - layoutProxyAuthenticator.setVisibility(telegramSettingVo.getProxyAuthenticator() ? View.VISIBLE : View.GONE); - - switchProxyAuthenticator.setChecked(telegramSettingVo.getProxyAuthenticator()); - if (Proxy.Type.DIRECT == telegramSettingVo.getProxyType()) { - layoutProxyHost.setVisibility(View.GONE); - layoutProxyPort.setVisibility(View.GONE); - } else { - layoutProxyHost.setVisibility(View.VISIBLE); - layoutProxyPort.setVisibility(View.VISIBLE); - } - editTextProxyHost.setText(telegramSettingVo.getProxyHost()); - editTextProxyPort.setText(telegramSettingVo.getProxyPort()); - - editTextProxyUsername.setText(telegramSettingVo.getProxyUsername()); - editTextProxyPassword.setText(telegramSettingVo.getProxyPassword()); - } - - Button buttonOk = view1.findViewById(R.id.buttonOk); - Button buttonDel = view1.findViewById(R.id.buttonDel); - buttonDel.setText(senderModel != null ? R.string.del : R.string.cancel); - Button buttonTest = view1.findViewById(R.id.buttonTest); - alertDialog71 - .setTitle(R.string.settelegramtitle) - .setIcon(R.mipmap.telegram) - .setView(view1) - .create(); - final AlertDialog show = alertDialog71.show(); - - buttonOk.setOnClickListener(view -> { - String senderName = editTextTelegramName.getText().toString().trim(); - int senderStatus = switchTelegramEnable.isChecked() ? STATUS_ON : STATUS_OFF; - if (TextUtils.isEmpty(senderName)) { - ToastUtils.delayedShow(R.string.invalid_name, 3000); - return; - } - - String apiToken = editTextTelegramApiToken.getText().trim(); - String chatId = editTextTelegramChatId.getText().toString().trim(); - if (apiToken.isEmpty() || chatId.isEmpty()) { - ToastUtils.delayedShow(R.string.invalid_apiToken_or_chatId, 3000); - return; - } - - int proxyTypeId = radioGroupProxyType.getCheckedRadioButtonId(); - String proxyHost = editTextProxyHost.getText().toString().trim(); - String proxyPort = editTextProxyPort.getText().toString().trim(); - if (proxyTypeId != R.id.btnProxyNone && (TextUtils.isEmpty(proxyHost) || TextUtils.isEmpty(proxyPort))) { - ToastUtils.delayedShow(R.string.invalid_host_or_port, 3000); - return; - } - - boolean proxyAuthenticator = switchProxyAuthenticator.isChecked(); - String proxyUsername = editTextProxyUsername.getText().toString().trim(); - String proxyPassword = editTextProxyPassword.getText().trim(); - if (proxyAuthenticator && TextUtils.isEmpty(proxyUsername) && TextUtils.isEmpty(proxyPassword)) { - ToastUtils.delayedShow(R.string.invalid_username_or_password, 3000); - return; - } - - String method = radioGroupTelegramMethod.getCheckedRadioButtonId() == R.id.radioTelegramMethodGet ? "GET" : "POST"; - TelegramSettingVo telegramSettingVoNew = new TelegramSettingVo(apiToken, chatId, proxyTypeId, proxyHost, proxyPort, proxyAuthenticator, proxyUsername, proxyPassword, method); - - if (isClone || senderModel == null) { - SenderModel newSenderModel = new SenderModel(); - newSenderModel.setName(senderName); - newSenderModel.setType(TYPE_TELEGRAM); - newSenderModel.setStatus(senderStatus); - newSenderModel.setJsonSetting(JSON.toJSONString(telegramSettingVoNew)); - SenderUtil.addSender(newSenderModel); - initSenders(); - adapter.add(senderModels); - } else { - senderModel.setName(senderName); - senderModel.setType(TYPE_TELEGRAM); - senderModel.setStatus(senderStatus); - senderModel.setJsonSetting(JSON.toJSONString(telegramSettingVoNew)); - SenderUtil.updateSender(senderModel); - initSenders(); - adapter.update(senderModels); - } - show.dismiss(); - }); - - buttonDel.setOnClickListener(view -> { - if (senderModel != null) { - SenderUtil.delSender(senderModel.getId()); - initSenders(); - adapter.del(senderModels); - } - show.dismiss(); - }); - - buttonTest.setOnClickListener(view -> { - String apiToken = editTextTelegramApiToken.getText().trim(); - String chatId = editTextTelegramChatId.getText().toString().trim(); - if (apiToken.isEmpty() || chatId.isEmpty()) { - ToastUtils.delayedShow(R.string.invalid_apiToken_or_chatId, 3000); - return; - } - - int proxyTypeId = radioGroupProxyType.getCheckedRadioButtonId(); - String proxyHost = editTextProxyHost.getText().toString().trim(); - String proxyPort = editTextProxyPort.getText().toString().trim(); - if (proxyTypeId != R.id.btnProxyNone && (TextUtils.isEmpty(proxyHost) || TextUtils.isEmpty(proxyPort))) { - ToastUtils.delayedShow(R.string.invalid_host_or_port, 3000); - return; - } - - boolean proxyAuthenticator = switchProxyAuthenticator.isChecked(); - String proxyUsername = editTextProxyUsername.getText().toString().trim(); - String proxyPassword = editTextProxyPassword.getText().trim(); - if (proxyAuthenticator && TextUtils.isEmpty(proxyUsername) && TextUtils.isEmpty(proxyPassword)) { - ToastUtils.delayedShow(R.string.invalid_username_or_password, 3000); - return; - } - - String method = radioGroupTelegramMethod.getCheckedRadioButtonId() == R.id.radioTelegramMethodGet ? "GET" : "POST"; - - try { - SmsVo smsVo = new SmsVo(getString(R.string.test_phone_num), getString(R.string.test_sender_sms), new Date(), getString(R.string.test_sim_info)); - TelegramSettingVo telegramSettingVoNew = new TelegramSettingVo(apiToken, chatId, proxyTypeId, proxyHost, proxyPort, proxyAuthenticator, proxyUsername, proxyPassword, method); - SenderTelegramMsg.sendMsg(0, handler, null, telegramSettingVoNew, smsVo.getMobile(), smsVo.getSmsVoForSend(), telegramSettingVoNew.getMethod()); - } catch (Exception e) { - ToastUtils.delayedShow(getString(R.string.failed_to_fwd) + e.getMessage(), 3000); - e.printStackTrace(); - } - }); - } - - //短信 - private void setSms(final SenderModel senderModel, final boolean isClone) { - if (!isClone) { - XXPermissions.with(this) - // 接收短信 - .permission(Permission.RECEIVE_SMS) - // 发送短信 - .permission(Permission.SEND_SMS) - // 读取短信 - .permission(Permission.READ_SMS) - .request(new OnPermissionCallback() { - - @Override - public void onGranted(List permissions, boolean all) { - if (!all) { - ToastUtils.show(R.string.toast_granted_part); - } - } - - @Override - public void onDenied(List permissions, boolean never) { - if (never) { - ToastUtils.show(R.string.toast_denied_never); - // 如果是被永久拒绝就跳转到应用权限系统设置页面 - XXPermissions.startPermissionActivity(SenderActivity.this, permissions); - } else { - ToastUtils.show(R.string.toast_denied); - } - } - }); - } - - SmsSettingVo smsSettingVo = null; - //try phrase json setting - if (senderModel != null) { - String jsonSettingStr = senderModel.getJsonSetting(); - Log.d(TAG, "jsonSettingStr = " + jsonSettingStr); - if (jsonSettingStr != null) { - smsSettingVo = JSON.parseObject(jsonSettingStr, SmsSettingVo.class); - } - } - - final AlertDialog.Builder alertDialog71 = new AlertDialog.Builder(SenderActivity.this); - View view1 = View.inflate(SenderActivity.this, R.layout.alert_dialog_setview_sms, null); - - final EditText editTextSmsName = view1.findViewById(R.id.editTextSmsName); - @SuppressLint("UseSwitchCompatOrMaterialCode") final Switch switchSmsEnable = view1.findViewById(R.id.switchSmsEnable); - if (senderModel != null) { - editTextSmsName.setText(senderModel.getName()); - switchSmsEnable.setChecked(senderModel.getStatusChecked()); - } - - final RadioGroup radioGroupSmsSimSlot = view1.findViewById(R.id.radioGroupSmsSimSlot); - final EditText editTextSmsMobiles = view1.findViewById(R.id.editTextSmsMobiles); - @SuppressLint("UseSwitchCompatOrMaterialCode") final Switch switchSmsOnlyNoNetwork = view1.findViewById(R.id.switchSmsOnlyNoNetwork); - if (smsSettingVo != null) { - radioGroupSmsSimSlot.check(smsSettingVo.getSmsSimSlotCheckId()); - editTextSmsMobiles.setText(smsSettingVo.getMobiles()); - switchSmsOnlyNoNetwork.setChecked(smsSettingVo.getOnlyNoNetwork()); - } - - Button buttonOk = view1.findViewById(R.id.buttonOk); - Button buttonDel = view1.findViewById(R.id.buttonDel); - buttonDel.setText(senderModel != null ? R.string.del : R.string.cancel); - Button buttonTest = view1.findViewById(R.id.buttonTest); - alertDialog71 - .setTitle(R.string.setsmstitle) - .setIcon(R.mipmap.sms) - .setView(view1) - .create(); - final AlertDialog show = alertDialog71.show(); - - buttonOk.setOnClickListener(view -> { - String senderName = editTextSmsName.getText().toString().trim(); - int senderStatus = switchSmsEnable.isChecked() ? STATUS_ON : STATUS_OFF; - if (TextUtils.isEmpty(senderName)) { - ToastUtils.delayedShow(R.string.invalid_name, 3000); - return; - } - - Boolean onlyNoNetwork = switchSmsOnlyNoNetwork.isChecked(); - String mobiles = editTextSmsMobiles.getText().toString().trim(); - if (TextUtils.isEmpty(mobiles)) { - ToastUtils.delayedShow(R.string.invalid_phone_num, 3000); - return; - } - - if (isClone || senderModel == null) { - SenderModel newSenderModel = new SenderModel(); - newSenderModel.setName(senderName); - newSenderModel.setType(TYPE_SMS); - newSenderModel.setStatus(senderStatus); - SmsSettingVo smsSettingVoNew = new SmsSettingVo( - newSenderModel.getSmsSimSlotId(radioGroupSmsSimSlot.getCheckedRadioButtonId()), - mobiles, - onlyNoNetwork - ); - newSenderModel.setJsonSetting(JSON.toJSONString(smsSettingVoNew)); - SenderUtil.addSender(newSenderModel); - initSenders(); - adapter.add(senderModels); - } else { - senderModel.setName(senderName); - senderModel.setType(TYPE_SMS); - senderModel.setStatus(senderStatus); - SmsSettingVo smsSettingVoNew = new SmsSettingVo( - senderModel.getSmsSimSlotId(radioGroupSmsSimSlot.getCheckedRadioButtonId()), - mobiles, - onlyNoNetwork - ); - senderModel.setJsonSetting(JSON.toJSONString(smsSettingVoNew)); - SenderUtil.updateSender(senderModel); - initSenders(); - adapter.update(senderModels); - } - show.dismiss(); - }); - - buttonDel.setOnClickListener(view -> { - if (senderModel != null) { - SenderUtil.delSender(senderModel.getId()); - initSenders(); - adapter.del(senderModels); - } - show.dismiss(); - }); - - buttonTest.setOnClickListener(view -> { - int simSlot = R.id.btnSmsSimSlot2 == radioGroupSmsSimSlot.getCheckedRadioButtonId() ? 1 : 0; - Boolean onlyNoNetwork = switchSmsOnlyNoNetwork.isChecked(); - String mobiles = editTextSmsMobiles.getText().toString().trim(); - if (TextUtils.isEmpty(mobiles)) { - ToastUtils.delayedShow(R.string.invalid_phone_num, 3000); - return; - } - - try { - SmsVo smsVo = new SmsVo(getString(R.string.test_phone_num), getString(R.string.test_sender_sms), new Date(), getString(R.string.test_sim_info)); - SenderSmsMsg.sendMsg(0, handler, simSlot, mobiles, onlyNoNetwork, smsVo.getMobile(), smsVo.getSmsVoForSend()); - } catch (Exception e) { - ToastUtils.delayedShow(getString(R.string.failed_to_fwd) + e.getMessage(), 3000); - e.printStackTrace(); - } - }); - } - - //飞书机器人 - @SuppressLint("SimpleDateFormat") - private void setFeiShu(final SenderModel senderModel, final boolean isClone) { - FeiShuSettingVo feiShuSettingVo = null; - //try phrase json setting - if (senderModel != null) { - String jsonSettingStr = senderModel.getJsonSetting(); - if (jsonSettingStr != null) { - feiShuSettingVo = JSON.parseObject(jsonSettingStr, FeiShuSettingVo.class); - } - } - final AlertDialog.Builder alertDialog71 = new AlertDialog.Builder(SenderActivity.this); - View view1 = View.inflate(SenderActivity.this, R.layout.alert_dialog_setview_feishu, null); - - final EditText editTextFeishuName = view1.findViewById(R.id.editTextFeishuName); - @SuppressLint("UseSwitchCompatOrMaterialCode") final Switch switchFeishuEnable = view1.findViewById(R.id.switchFeishuEnable); - if (senderModel != null) { - editTextFeishuName.setText(senderModel.getName()); - switchFeishuEnable.setChecked(senderModel.getStatusChecked()); - } - - final ClearEditText editTextFeishuWebhook = view1.findViewById(R.id.editTextFeishuWebhook); - final ClearEditText editTextFeishuSecret = view1.findViewById(R.id.editTextFeishuSecret); - final RadioGroup radioGroupFeishuMsgType = view1.findViewById(R.id.radioGroupFeishuMsgType); - final EditText editTextFeishuTitle = view1.findViewById(R.id.editTextFeishuTitle); - final LinearLayout layoutTitleTemplate = view1.findViewById(R.id.layoutTitleTemplate); - - if (feiShuSettingVo != null) { - editTextFeishuWebhook.setText(feiShuSettingVo.getWebhook()); - editTextFeishuSecret.setText(feiShuSettingVo.getSecret()); - radioGroupFeishuMsgType.check(feiShuSettingVo.getMsgTypeCheckId()); - editTextFeishuTitle.setText(feiShuSettingVo.getTitleTemplate()); - if ("text".equals(feiShuSettingVo.getMsgType())) { - layoutTitleTemplate.setVisibility(View.GONE); - } else { - layoutTitleTemplate.setVisibility(View.VISIBLE); - } - } - - radioGroupFeishuMsgType.setOnCheckedChangeListener((group, checkedId) -> { - if (group != null && checkedId > 0) { - layoutTitleTemplate.setVisibility(checkedId == R.id.radioFeishuMsgTypeText ? View.GONE : View.VISIBLE); - group.check(checkedId); - } - }); - - Button buttonOk = view1.findViewById(R.id.buttonOk); - Button buttonDel = view1.findViewById(R.id.buttonDel); - buttonDel.setText(senderModel != null ? R.string.del : R.string.cancel); - Button buttonTest = view1.findViewById(R.id.buttonTest); - alertDialog71 - .setTitle(R.string.setfeishutitle) - .setIcon(R.mipmap.feishu) - .setView(view1) - .create(); - final AlertDialog show = alertDialog71.show(); - buttonOk.setOnClickListener(view -> { - String senderName = editTextFeishuName.getText().toString().trim(); - int senderStatus = switchFeishuEnable.isChecked() ? STATUS_ON : STATUS_OFF; - if (TextUtils.isEmpty(senderName)) { - ToastUtils.delayedShow(R.string.invalid_name, 3000); - return; - } - - String webHook = editTextFeishuWebhook.getText().trim(); - String secret = editTextFeishuSecret.getText().trim(); - String msgType = radioGroupFeishuMsgType.getCheckedRadioButtonId() == R.id.radioFeishuMsgTypeText ? "text" : "interactive"; - String titleTemplate = editTextFeishuTitle.getText().toString().trim(); - if (!CommonUtils.checkUrl(webHook, false)) { - ToastUtils.delayedShow(R.string.invalid_webhook, 3000); - return; - } - - if (TextUtils.isEmpty(titleTemplate)) titleTemplate = "【{{设备名称}}】来自{{来源号码}}的通知"; - - FeiShuSettingVo feiShuSettingVoNew = new FeiShuSettingVo(webHook, secret, msgType, titleTemplate); - if (isClone || senderModel == null) { - SenderModel newSenderModel = new SenderModel(); - newSenderModel.setName(senderName); - newSenderModel.setType(TYPE_FEISHU); - newSenderModel.setStatus(senderStatus); - newSenderModel.setJsonSetting(JSON.toJSONString(feiShuSettingVoNew)); - SenderUtil.addSender(newSenderModel); - initSenders(); - adapter.add(senderModels); - } else { - senderModel.setName(senderName); - senderModel.setType(TYPE_FEISHU); - senderModel.setStatus(senderStatus); - senderModel.setJsonSetting(JSON.toJSONString(feiShuSettingVoNew)); - SenderUtil.updateSender(senderModel); - initSenders(); - adapter.update(senderModels); - } - show.dismiss(); - }); - - buttonDel.setOnClickListener(view -> { - if (senderModel != null) { - SenderUtil.delSender(senderModel.getId()); - initSenders(); - adapter.del(senderModels); - } - show.dismiss(); - }); - - buttonTest.setOnClickListener(view -> { - String webHook = editTextFeishuWebhook.getText().trim(); - String secret = editTextFeishuSecret.getText().trim(); - String msgType = radioGroupFeishuMsgType.getCheckedRadioButtonId() == R.id.radioFeishuMsgTypeText ? "text" : "interactive"; - String titleTemplate = editTextFeishuTitle.getText().toString().trim(); - if (!CommonUtils.checkUrl(webHook, false)) { - ToastUtils.delayedShow(R.string.invalid_webhook, 3000); - return; - } - - try { - SmsVo smsVo = new SmsVo(getString(R.string.test_phone_num), getString(R.string.test_sender_sms), new Date(), getString(R.string.test_sim_info)); - SenderFeishuMsg.sendMsg(0, handler, null, webHook, secret, msgType, smsVo.getMobile(), new Date(), smsVo.getTitleForSend(titleTemplate), smsVo.getSmsVoForSend()); - } catch (Exception e) { - ToastUtils.delayedShow(getString(R.string.failed_to_fwd) + e.getMessage(), 3000); - e.printStackTrace(); - } - }); - - Button buttonInsertSender = view1.findViewById(R.id.bt_insert_sender); - buttonInsertSender.setOnClickListener(view -> { - editTextFeishuTitle.setFocusable(true); - editTextFeishuTitle.requestFocus(); - CommonUtils.insertOrReplaceText2Cursor(editTextFeishuTitle, getString(R.string.tag_from)); - }); - - Button buttonInsertExtra = view1.findViewById(R.id.bt_insert_extra); - buttonInsertExtra.setOnClickListener(view -> { - editTextFeishuTitle.setFocusable(true); - editTextFeishuTitle.requestFocus(); - CommonUtils.insertOrReplaceText2Cursor(editTextFeishuTitle, getString(R.string.tag_card_slot)); - }); - - Button buttonInsertTime = view1.findViewById(R.id.bt_insert_time); - buttonInsertTime.setOnClickListener(view -> { - editTextFeishuTitle.setFocusable(true); - editTextFeishuTitle.requestFocus(); - CommonUtils.insertOrReplaceText2Cursor(editTextFeishuTitle, getString(R.string.tag_receive_time)); - }); - - Button buttonInsertDeviceName = view1.findViewById(R.id.bt_insert_device_name); - buttonInsertDeviceName.setOnClickListener(view -> { - editTextFeishuTitle.setFocusable(true); - editTextFeishuTitle.requestFocus(); - CommonUtils.insertOrReplaceText2Cursor(editTextFeishuTitle, getString(R.string.tag_device_name)); - }); - } - - //推送加 - @SuppressLint("SimpleDateFormat") - private void setPushPlus(final SenderModel senderModel, final boolean isClone) { - PushPlusSettingVo pushPlusSettingVo = null; - //try phrase json setting - if (senderModel != null) { - String jsonSettingStr = senderModel.getJsonSetting(); - if (jsonSettingStr != null) { - pushPlusSettingVo = JSON.parseObject(jsonSettingStr, PushPlusSettingVo.class); - } - } - final AlertDialog.Builder alertDialog71 = new AlertDialog.Builder(SenderActivity.this); - View view1 = View.inflate(SenderActivity.this, R.layout.alert_dialog_setview_pushplus, null); - - final EditText editTextPushPlusName = view1.findViewById(R.id.editTextPushPlusName); - @SuppressLint("UseSwitchCompatOrMaterialCode") final Switch switchPushPlusEnable = view1.findViewById(R.id.switchPushPlusEnable); - if (senderModel != null) { - editTextPushPlusName.setText(senderModel.getName()); - switchPushPlusEnable.setChecked(senderModel.getStatusChecked()); - } - - final ClearEditText editTextPushPlusToken = view1.findViewById(R.id.editTextPushPlusToken); - final EditText editTextPushPlusTopic = view1.findViewById(R.id.editTextPushPlusTopic); - final EditText editTextPushPlusTemplate = view1.findViewById(R.id.editTextPushPlusTemplate); - final EditText editTextPushPlusChannel = view1.findViewById(R.id.editTextPushPlusChannel); - final EditText editTextPushPlusWebhook = view1.findViewById(R.id.editTextPushPlusWebhook); - final EditText editTextPushPlusCallbackUrl = view1.findViewById(R.id.editTextPushPlusCallbackUrl); - final EditText editTextPushPlusValidTime = view1.findViewById(R.id.editTextPushPlusValidTime); - final EditText editTextPushPlusTitle = view1.findViewById(R.id.editTextPushPlusTitle); - - if (pushPlusSettingVo != null) { - editTextPushPlusToken.setText(pushPlusSettingVo.getToken()); - editTextPushPlusTopic.setText(pushPlusSettingVo.getTopic()); - editTextPushPlusTemplate.setText(pushPlusSettingVo.getTemplate()); - editTextPushPlusChannel.setText(pushPlusSettingVo.getChannel()); - editTextPushPlusWebhook.setText(pushPlusSettingVo.getWebhook()); - editTextPushPlusCallbackUrl.setText(pushPlusSettingVo.getCallbackUrl()); - editTextPushPlusValidTime.setText(pushPlusSettingVo.getValidTime()); - editTextPushPlusTitle.setText(pushPlusSettingVo.getTitleTemplate()); - } - - Button buttonOk = view1.findViewById(R.id.buttonOk); - Button buttonDel = view1.findViewById(R.id.buttonDel); - buttonDel.setText(senderModel != null ? R.string.del : R.string.cancel); - Button buttonTest = view1.findViewById(R.id.buttonTest); - alertDialog71 - .setTitle(R.string.setpushplustitle) - .setIcon(R.mipmap.pushplus) - .setView(view1) - .create(); - final AlertDialog show = alertDialog71.show(); - buttonOk.setOnClickListener(view -> { - String senderName = editTextPushPlusName.getText().toString().trim(); - int senderStatus = switchPushPlusEnable.isChecked() ? STATUS_ON : STATUS_OFF; - if (TextUtils.isEmpty(senderName)) { - ToastUtils.delayedShow(R.string.invalid_name, 3000); - return; - } - - PushPlusSettingVo pushPlusSettingVoNew = new PushPlusSettingVo( - editTextPushPlusToken.getText().trim(), - editTextPushPlusTopic.getText().toString().trim(), - editTextPushPlusTemplate.getText().toString().trim(), - editTextPushPlusChannel.getText().toString().trim(), - editTextPushPlusWebhook.getText().toString().trim(), - editTextPushPlusCallbackUrl.getText().toString().trim(), - editTextPushPlusValidTime.getText().toString().trim(), - editTextPushPlusTitle.getText().toString().trim() - ); - if (TextUtils.isEmpty(pushPlusSettingVoNew.getToken())) { - ToastUtils.delayedShow(R.string.invalid_token, 3000); - return; - } - - if (isClone || senderModel == null) { - SenderModel newSenderModel = new SenderModel(); - newSenderModel.setName(senderName); - newSenderModel.setType(TYPE_PUSHPLUS); - newSenderModel.setStatus(senderStatus); - - newSenderModel.setJsonSetting(JSON.toJSONString(pushPlusSettingVoNew)); - SenderUtil.addSender(newSenderModel); - initSenders(); - adapter.add(senderModels); - } else { - senderModel.setName(senderName); - senderModel.setType(TYPE_PUSHPLUS); - senderModel.setStatus(senderStatus); - senderModel.setJsonSetting(JSON.toJSONString(pushPlusSettingVoNew)); - SenderUtil.updateSender(senderModel); - initSenders(); - adapter.update(senderModels); - } - show.dismiss(); - }); - - buttonDel.setOnClickListener(view -> { - if (senderModel != null) { - SenderUtil.delSender(senderModel.getId()); - initSenders(); - adapter.del(senderModels); - } - show.dismiss(); - }); - - buttonTest.setOnClickListener(view -> { - - String title = editTextPushPlusTitle.getText().toString().trim(); - if (title.isEmpty()) title = "SmsForwarder Title"; - - PushPlusSettingVo pushPlusSettingVoNew = new PushPlusSettingVo( - editTextPushPlusToken.getText().trim(), - editTextPushPlusTopic.getText().toString().trim(), - editTextPushPlusTemplate.getText().toString().trim(), - editTextPushPlusChannel.getText().toString().trim(), - editTextPushPlusWebhook.getText().toString().trim(), - editTextPushPlusCallbackUrl.getText().toString().trim(), - editTextPushPlusValidTime.getText().toString().trim(), - title - ); - - if (TextUtils.isEmpty(pushPlusSettingVoNew.getToken())) { - ToastUtils.delayedShow(R.string.invalid_token, 3000); - return; - } - - try { - SmsVo smsVo = new SmsVo(getString(R.string.test_phone_num), getString(R.string.test_sender_sms), new Date(), getString(R.string.test_sim_info)); - SenderPushPlusMsg.sendMsg(0, handler, null, pushPlusSettingVoNew, smsVo.getTitleForSend(title), smsVo.getSmsVoForSend()); - } catch (Exception e) { - ToastUtils.delayedShow(getString(R.string.failed_to_fwd) + e.getMessage(), 3000); - e.printStackTrace(); - } - }); - - Button buttonInsertSender = view1.findViewById(R.id.bt_insert_sender); - buttonInsertSender.setOnClickListener(view -> { - editTextPushPlusTitle.setFocusable(true); - editTextPushPlusTitle.requestFocus(); - CommonUtils.insertOrReplaceText2Cursor(editTextPushPlusTitle, getString(R.string.tag_from)); - }); - - Button buttonInsertExtra = view1.findViewById(R.id.bt_insert_extra); - buttonInsertExtra.setOnClickListener(view -> { - editTextPushPlusTitle.setFocusable(true); - editTextPushPlusTitle.requestFocus(); - CommonUtils.insertOrReplaceText2Cursor(editTextPushPlusTitle, getString(R.string.tag_card_slot)); - }); - - Button buttonInsertTime = view1.findViewById(R.id.bt_insert_time); - buttonInsertTime.setOnClickListener(view -> { - editTextPushPlusTitle.setFocusable(true); - editTextPushPlusTitle.requestFocus(); - CommonUtils.insertOrReplaceText2Cursor(editTextPushPlusTitle, getString(R.string.tag_receive_time)); - }); - - Button buttonInsertDeviceName = view1.findViewById(R.id.bt_insert_device_name); - buttonInsertDeviceName.setOnClickListener(view -> { - editTextPushPlusTitle.setFocusable(true); - editTextPushPlusTitle.requestFocus(); - CommonUtils.insertOrReplaceText2Cursor(editTextPushPlusTitle, getString(R.string.tag_device_name)); - }); - } - - //Gotify - @SuppressLint("SimpleDateFormat") - private void setGotify(final SenderModel senderModel, final boolean isClone) { - GotifySettingVo gotifySettingVo = null; - //try phrase json setting - if (senderModel != null) { - String jsonSettingStr = senderModel.getJsonSetting(); - if (jsonSettingStr != null) { - gotifySettingVo = JSON.parseObject(jsonSettingStr, GotifySettingVo.class); - } - } - - final AlertDialog.Builder alertDialog71 = new AlertDialog.Builder(SenderActivity.this); - View view1 = View.inflate(SenderActivity.this, R.layout.alert_dialog_setview_gotify, null); - - final EditText editTextGotifyName = view1.findViewById(R.id.editTextGotifyName); - @SuppressLint("UseSwitchCompatOrMaterialCode") final Switch switchGotifyEnable = view1.findViewById(R.id.switchGotifyEnable); - if (senderModel != null) { - editTextGotifyName.setText(senderModel.getName()); - switchGotifyEnable.setChecked(senderModel.getStatusChecked()); - } - - final ClearEditText editTextGotifyWebServer = view1.findViewById(R.id.editTextGotifyWebServer); - final EditText editTextGotifyTitle = view1.findViewById(R.id.editTextGotifyTitle); - final EditText editTextGotifyPriority = view1.findViewById(R.id.editTextGotifyPriority); - if (gotifySettingVo != null) { - editTextGotifyWebServer.setText(gotifySettingVo.getWebServer()); - editTextGotifyTitle.setText(gotifySettingVo.getTitle()); - editTextGotifyPriority.setText(gotifySettingVo.getPriority()); - } - - Button buttonOk = view1.findViewById(R.id.buttonOk); - Button buttonDel = view1.findViewById(R.id.buttonDel); - buttonDel.setText(senderModel != null ? R.string.del : R.string.cancel); - Button buttonTest = view1.findViewById(R.id.buttonTest); - alertDialog71 - .setTitle(R.string.setgotifytitle) - .setIcon(R.mipmap.gotify) - .setView(view1) - .create(); - final AlertDialog show = alertDialog71.show(); - - buttonOk.setOnClickListener(view -> { - String senderName = editTextGotifyName.getText().toString().trim(); - int senderStatus = switchGotifyEnable.isChecked() ? STATUS_ON : STATUS_OFF; - if (TextUtils.isEmpty(senderName)) { - ToastUtils.delayedShow(R.string.invalid_name, 3000); - return; - } - - String webServer = editTextGotifyWebServer.getText().trim(); - if (!CommonUtils.checkUrl(webServer, false)) { - ToastUtils.delayedShow(R.string.invalid_webserver, 3000); - return; - } - - String title = editTextGotifyTitle.getText().toString().trim(); - if (title.isEmpty()) title = "SmsForwarder Title"; - - String priority = editTextGotifyPriority.getText().toString().trim(); - - GotifySettingVo gotifySettingVoNew = new GotifySettingVo(webServer, title, priority); - - if (isClone || senderModel == null) { - SenderModel newSenderModel = new SenderModel(); - newSenderModel.setName(senderName); - newSenderModel.setType(TYPE_GOTIFY); - newSenderModel.setStatus(senderStatus); - newSenderModel.setJsonSetting(JSON.toJSONString(gotifySettingVoNew)); - SenderUtil.addSender(newSenderModel); - initSenders(); - adapter.add(senderModels); - } else { - senderModel.setName(senderName); - senderModel.setType(TYPE_GOTIFY); - senderModel.setStatus(senderStatus); - senderModel.setJsonSetting(JSON.toJSONString(gotifySettingVoNew)); - SenderUtil.updateSender(senderModel); - initSenders(); - adapter.update(senderModels); - } - show.dismiss(); - }); - - buttonDel.setOnClickListener(view -> { - if (senderModel != null) { - SenderUtil.delSender(senderModel.getId()); - initSenders(); - adapter.del(senderModels); - } - show.dismiss(); - }); - - buttonTest.setOnClickListener(view -> { - String webServer = editTextGotifyWebServer.getText().trim(); - if (!CommonUtils.checkUrl(webServer, false)) { - ToastUtils.delayedShow(R.string.invalid_webserver, 3000); - return; - } - - String title = editTextGotifyTitle.getText().toString().trim(); - if (title.isEmpty()) title = "SmsForwarder Title"; - - String priority = editTextGotifyPriority.getText().toString().trim(); - - GotifySettingVo gotifySettingVoNew = new GotifySettingVo(webServer, title, priority); - - try { - SmsVo smsVo = new SmsVo(getString(R.string.test_phone_num), getString(R.string.test_sender_sms), new Date(), getString(R.string.test_sim_info)); - SenderGotifyMsg.sendMsg(0, handler, null, gotifySettingVoNew, smsVo.getTitleForSend(title), smsVo.getSmsVoForSend()); - } catch (Exception e) { - ToastUtils.delayedShow(getString(R.string.failed_to_fwd) + e.getMessage(), 3000); - e.printStackTrace(); - } - - }); - - Button buttonInsertSender = view1.findViewById(R.id.bt_insert_sender); - buttonInsertSender.setOnClickListener(view -> { - editTextGotifyTitle.setFocusable(true); - editTextGotifyTitle.requestFocus(); - CommonUtils.insertOrReplaceText2Cursor(editTextGotifyTitle, getString(R.string.tag_from)); - }); - - Button buttonInsertExtra = view1.findViewById(R.id.bt_insert_extra); - buttonInsertExtra.setOnClickListener(view -> { - editTextGotifyTitle.setFocusable(true); - editTextGotifyTitle.requestFocus(); - CommonUtils.insertOrReplaceText2Cursor(editTextGotifyTitle, getString(R.string.tag_card_slot)); - }); - - Button buttonInsertTime = view1.findViewById(R.id.bt_insert_time); - buttonInsertTime.setOnClickListener(view -> { - editTextGotifyTitle.setFocusable(true); - editTextGotifyTitle.requestFocus(); - CommonUtils.insertOrReplaceText2Cursor(editTextGotifyTitle, getString(R.string.tag_receive_time)); - }); - - Button buttonInsertDeviceName = view1.findViewById(R.id.bt_insert_device_name); - buttonInsertDeviceName.setOnClickListener(view -> { - editTextGotifyTitle.setFocusable(true); - editTextGotifyTitle.requestFocus(); - CommonUtils.insertOrReplaceText2Cursor(editTextGotifyTitle, getString(R.string.tag_device_name)); - }); - - } - -} diff --git a/app/src/main/java/com/idormy/sms/forwarder/SettingActivity.java b/app/src/main/java/com/idormy/sms/forwarder/SettingActivity.java deleted file mode 100644 index b087931d..00000000 --- a/app/src/main/java/com/idormy/sms/forwarder/SettingActivity.java +++ /dev/null @@ -1,1041 +0,0 @@ -package com.idormy.sms.forwarder; - -import android.annotation.SuppressLint; -import android.app.ActivityManager; -import android.app.TimePickerDialog; -import android.content.ComponentName; -import android.content.Context; -import android.content.Intent; -import android.content.SharedPreferences; -import android.content.pm.PackageManager; -import android.database.sqlite.SQLiteDatabase; -import android.net.Uri; -import android.os.Build; -import android.os.Bundle; -import android.preference.PreferenceManager; -import android.provider.Settings; -import android.text.Editable; -import android.text.TextWatcher; -import android.util.Log; -import android.view.View; -import android.widget.CheckBox; -import android.widget.EditText; -import android.widget.LinearLayout; -import android.widget.ScrollView; -import android.widget.Switch; -import android.widget.TextView; - -import androidx.annotation.RequiresApi; -import androidx.appcompat.app.AlertDialog; - -import com.hjq.permissions.OnPermissionCallback; -import com.hjq.permissions.Permission; -import com.hjq.permissions.XXPermissions; -import com.hjq.toast.ToastUtils; -import com.idormy.sms.forwarder.receiver.RebootBroadcastReceiver; -import com.idormy.sms.forwarder.sender.BatteryReportCronTask; -import com.idormy.sms.forwarder.sender.HttpServer; -import com.idormy.sms.forwarder.sender.SenderUtil; -import com.idormy.sms.forwarder.service.MusicService; -import com.idormy.sms.forwarder.utils.CommonUtils; -import com.idormy.sms.forwarder.utils.DbHelper; -import com.idormy.sms.forwarder.utils.Define; -import com.idormy.sms.forwarder.utils.HttpUtils; -import com.idormy.sms.forwarder.utils.KeepAliveUtils; -import com.idormy.sms.forwarder.utils.LogUtils; -import com.idormy.sms.forwarder.utils.OnePixelManager; -import com.idormy.sms.forwarder.utils.RuleUtils; -import com.idormy.sms.forwarder.utils.SettingUtils; -import com.idormy.sms.forwarder.view.StepBar; - -import java.util.Arrays; -import java.util.Calendar; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; - -public class SettingActivity extends BaseActivity { - private final String TAG = "SettingActivity"; - private Context context; - private boolean isIgnoreBatteryOptimization; - - @Override - public void onCreate(Bundle savedInstanceState) { - Log.d(TAG, "onCreate"); - super.onCreate(savedInstanceState); - context = SettingActivity.this; - setContentView(R.layout.activity_setting); - - LogUtils.init(this); - RuleUtils.init(this); - SenderUtil.init(this); - } - - @SuppressLint("NewApi") - @Override - protected void onStart() { - super.onStart(); - Log.d(TAG, "onStart"); - - //是否关闭页面提示 - ScrollView scrollView = findViewById(R.id.scrollView); - CommonUtils.calcMarginBottom(this, null, null, scrollView); - - //转发短信广播 - switchEnableSms(findViewById(R.id.switch_enable_sms)); - //转发通话记录 - switchEnablePhone(findViewById(R.id.switch_enable_phone), findViewById(R.id.cbCallType1), findViewById(R.id.cbCallType2), findViewById(R.id.cbCallType3)); - //转发应用通知 & 自动关闭通知 - switchEnableAppNotify(findViewById(R.id.switch_enable_app_notify), findViewById(R.id.checkbox_cancel_app_notify), findViewById(R.id.checkbox_not_user_present)); - - //HttpServer - switchEnableHttpServer(findViewById(R.id.switch_enable_http_server)); - - //监听电池状态变化 - switchBatteryReceiver(findViewById(R.id.switch_battery_receiver)); - //电量预警 - editBatteryLevelAlarm(findViewById(R.id.et_battery_level_alarm_min), findViewById(R.id.et_battery_level_alarm_max), findViewById(R.id.cb_battery_level_alarm_once)); - //定时推送电池状态 - switchBatteryCron(findViewById(R.id.switch_battery_cron)); - //设置推送电池状态时机 - editBatteryCronTiming(findViewById(R.id.et_battery_cron_start_time), findViewById(R.id.et_battery_cron_interval)); - - //开机启动 - checkWithReboot(findViewById(R.id.switch_with_reboot), findViewById(R.id.tv_auto_startup)); - //电池优化设置 - batterySetting(findViewById(R.id.layout_battery_setting), findViewById(R.id.switch_battery_setting)); - //不在最近任务列表中显示 - switchExcludeFromRecents(findViewById(R.id.switch_exclude_from_recents)); - //后台播放无声音乐 - switchPlaySilenceMusic(findViewById(R.id.switch_play_silence_music)); - //1像素透明Activity保活 - switchOnePixelActivity(findViewById(R.id.switch_one_pixel_activity)); - //接口请求失败重试时间间隔 - editRetryDelayTime(findViewById(R.id.et_retry_times), findViewById(R.id.et_delay_time)); - - //设备备注 - editAddExtraDeviceMark(findViewById(R.id.et_add_extra_device_mark)); - //SIM1备注 - editAddExtraSim1(findViewById(R.id.et_add_extra_sim1)); - //SIM2备注 - editAddExtraSim2(findViewById(R.id.et_add_extra_sim2)); - //启用自定义模版 - switchSmsTemplate(findViewById(R.id.switch_sms_template)); - //自定义模板 - editSmsTemplate(findViewById(R.id.text_sms_template)); - - //帮助提示 - SwitchHelpTip(findViewById(R.id.switch_help_tip)); - - //步骤完成状态校验 - StepBar stepBar = findViewById(R.id.stepBar); - stepBar.setHighlight(); - } - - @Override - protected void onPause() { - overridePendingTransition(0, 0); - super.onPause(); - } - - //设置转发短信 - @SuppressLint("UseSwitchCompatOrMaterialCode") - private void switchEnableSms(Switch switch_enable_sms) { - switch_enable_sms.setChecked(SettingUtils.getSwitchEnableSms()); - - switch_enable_sms.setOnCheckedChangeListener((buttonView, isChecked) -> { - Log.d(TAG, "switchEnableSms:" + isChecked); - if (isChecked) { - //检查权限是否获取 - PackageManager pm = getPackageManager(); - CommonUtils.CheckPermission(pm, this); - XXPermissions.with(this) - // 接收短信 - .permission(Permission.RECEIVE_SMS) - // 发送短信 - //.permission(Permission.SEND_SMS) - // 读取短信 - .permission(Permission.READ_SMS) - .request(new OnPermissionCallback() { - - @Override - public void onGranted(List permissions, boolean all) { - if (all) { - ToastUtils.show(R.string.toast_granted_all); - } else { - ToastUtils.show(R.string.toast_granted_part); - } - SettingUtils.switchEnableSms(true); - } - - @Override - public void onDenied(List permissions, boolean never) { - if (never) { - ToastUtils.show(R.string.toast_denied_never); - // 如果是被永久拒绝就跳转到应用权限系统设置页面 - XXPermissions.startPermissionActivity(SettingActivity.this, permissions); - } else { - ToastUtils.show(R.string.toast_denied); - } - SettingUtils.switchEnableSms(false); - } - }); - } else { - SettingUtils.switchEnableSms(false); - } - }); - } - - //转发通话记录 - @SuppressLint("UseSwitchCompatOrMaterialCode") - private void switchEnablePhone(Switch switch_enable_phone, CheckBox check_box_call_type_1, CheckBox check_box_call_type_2, CheckBox check_box_call_type_3) { - switch_enable_phone.setChecked(SettingUtils.getSwitchEnablePhone()); - check_box_call_type_1.setChecked(SettingUtils.getSwitchCallType1()); - check_box_call_type_2.setChecked(SettingUtils.getSwitchCallType2()); - check_box_call_type_3.setChecked(SettingUtils.getSwitchCallType3()); - - switch_enable_phone.setOnCheckedChangeListener((buttonView, isChecked) -> { - if (isChecked && !SettingUtils.getSwitchCallType1() && !SettingUtils.getSwitchCallType2() && !SettingUtils.getSwitchCallType3()) { - ToastUtils.show(R.string.enable_phone_fw_tips); - SettingUtils.switchEnablePhone(false); - return; - } - - Log.d(TAG, "switchEnablePhone:" + isChecked); - if (isChecked) { - //检查权限是否获取 - PackageManager pm = getPackageManager(); - CommonUtils.CheckPermission(pm, this); - XXPermissions.with(this) - // 读取电话状态 - .permission(Permission.READ_PHONE_STATE) - // 读取手机号码 - .permission(Permission.READ_PHONE_NUMBERS) - // 读取通话记录 - .permission(Permission.READ_CALL_LOG) - // 读取联系人 - .permission(Permission.READ_CONTACTS) - .request(new OnPermissionCallback() { - - @Override - public void onGranted(List permissions, boolean all) { - if (all) { - ToastUtils.show(R.string.toast_granted_all); - } else { - ToastUtils.show(R.string.toast_granted_part); - } - SettingUtils.switchEnablePhone(true); - } - - @Override - public void onDenied(List permissions, boolean never) { - if (never) { - ToastUtils.show(R.string.toast_denied_never); - // 如果是被永久拒绝就跳转到应用权限系统设置页面 - XXPermissions.startPermissionActivity(SettingActivity.this, permissions); - } else { - ToastUtils.show(R.string.toast_denied); - } - SettingUtils.switchEnablePhone(false); - } - }); - } else { - SettingUtils.switchEnablePhone(false); - } - }); - - check_box_call_type_1.setOnCheckedChangeListener((buttonView, isChecked) -> { - SettingUtils.switchCallType1(isChecked); - if (!isChecked && !SettingUtils.getSwitchCallType1() && !SettingUtils.getSwitchCallType2() && !SettingUtils.getSwitchCallType3()) { - ToastUtils.show(R.string.enable_phone_fw_tips); - SettingUtils.switchEnablePhone(false); - } - }); - - check_box_call_type_2.setOnCheckedChangeListener((buttonView, isChecked) -> { - SettingUtils.switchCallType2(isChecked); - if (!isChecked && !SettingUtils.getSwitchCallType1() && !SettingUtils.getSwitchCallType2() && !SettingUtils.getSwitchCallType3()) { - ToastUtils.show(R.string.enable_phone_fw_tips); - SettingUtils.switchEnablePhone(false); - } - }); - - check_box_call_type_3.setOnCheckedChangeListener((buttonView, isChecked) -> { - SettingUtils.switchCallType3(isChecked); - if (!isChecked && !SettingUtils.getSwitchCallType1() && !SettingUtils.getSwitchCallType2() && !SettingUtils.getSwitchCallType3()) { - ToastUtils.show(R.string.enable_phone_fw_tips); - SettingUtils.switchEnablePhone(false); - } - }); - } - - //转发应用通知 & 自动关闭通知 - @SuppressLint("UseSwitchCompatOrMaterialCode") - private void switchEnableAppNotify(Switch switch_enable_app_notify, CheckBox checkbox_cancel_app_notify, CheckBox checkbox_not_user_present) { - final LinearLayout layout_cancel_app_notify = findViewById(R.id.layout_cancel_app_notify); - boolean isEnable = SettingUtils.getSwitchEnableAppNotify(); - switch_enable_app_notify.setChecked(isEnable); - layout_cancel_app_notify.setVisibility(isEnable ? View.VISIBLE : View.GONE); - - switch_enable_app_notify.setOnCheckedChangeListener((buttonView, isChecked) -> { - layout_cancel_app_notify.setVisibility(isChecked ? View.VISIBLE : View.GONE); - //TODO:校验使用APP通知转发必备的权限 - if (isChecked) { - if (!CommonUtils.isNotificationListenerServiceEnabled(this)) { - CommonUtils.openNotificationAccess(this); - ToastUtils.delayedShow(R.string.tips_notification_listener, 3000); - return; - } else { - ToastUtils.delayedShow(R.string.notification_service_is_on, 3000); - CommonUtils.toggleNotificationListenerService(this); - } - } - SettingUtils.switchEnableAppNotify(isChecked); - Log.d(TAG, "switchEnableAppNotify:" + isChecked); - }); - - checkbox_cancel_app_notify.setChecked(SettingUtils.getSwitchCancelAppNotify()); - checkbox_cancel_app_notify.setOnCheckedChangeListener((buttonView, isChecked) -> { - SettingUtils.switchCancelAppNotify(isChecked); - Log.d(TAG, "switchCancelAppNotify:" + isChecked); - }); - - checkbox_not_user_present.setChecked(SettingUtils.getSwitchNotUserPresent()); - checkbox_not_user_present.setOnCheckedChangeListener((buttonView, isChecked) -> { - SettingUtils.switchNotUserPresent(isChecked); - Log.d(TAG, "switchNotUserPresent:" + isChecked); - - //1像素透明Activity保活 or 仅锁屏状态转发APP通知 - OnePixelManager onePixelManager = new OnePixelManager(); - if (SettingUtils.getOnePixelActivity() || SettingUtils.getSwitchNotUserPresent()) { - onePixelManager.registerOnePixelReceiver(this);//注册广播接收者 - } else { - onePixelManager.unregisterOnePixelReceiver(this); - } - }); - } - - //请求通知使用权限 - public void requestNotificationPermission(View view) { - if (!CommonUtils.isNotificationListenerServiceEnabled(this)) { - CommonUtils.openNotificationAccess(this); - } else { - ToastUtils.show(R.string.notification_listener_service_enabled); - CommonUtils.toggleNotificationListenerService(this); - } - } - - @Override - protected void onActivityResult(int requestCode, int resultCode, Intent data) { - super.onActivityResult(requestCode, resultCode, data); - if (requestCode == CommonUtils.NOTIFICATION_REQUEST_CODE) { - if (CommonUtils.isNotificationListenerServiceEnabled(this)) { - ToastUtils.show(R.string.notification_listener_service_enabled); - CommonUtils.toggleNotificationListenerService(this); - SettingUtils.switchEnableAppNotify(true); - } else { - ToastUtils.show(R.string.notification_listener_service_disabled); - SettingUtils.switchEnableAppNotify(false); - } - - @SuppressLint("UseSwitchCompatOrMaterialCode") Switch switch_enable_app_notify = findViewById(R.id.switch_enable_app_notify); - switch_enable_app_notify.setChecked(SettingUtils.getSwitchEnableAppNotify()); - } - } - - //HttpServer - @SuppressLint("UseSwitchCompatOrMaterialCode") - private void switchEnableHttpServer(Switch switch_enable_http_server) { - switch_enable_http_server.setChecked(SettingUtils.getSwitchEnableHttpServer()); - - switch_enable_http_server.setOnCheckedChangeListener((buttonView, isChecked) -> { - SettingUtils.switchEnableHttpServer(isChecked); - Log.d(TAG, "switchEnableHttpServer:" + isChecked); - - HttpUtils.init(this); - HttpServer.init(this); - HttpServer.update(); - }); - } - - //监听电池状态变化 - @SuppressLint("UseSwitchCompatOrMaterialCode") - private void switchBatteryReceiver(Switch switch_battery_receiver) { - switch_battery_receiver.setChecked(SettingUtils.getSwitchEnableBatteryReceiver()); - - switch_battery_receiver.setOnCheckedChangeListener((buttonView, isChecked) -> { - //TODO:校验使用来电转发必备的权限 - SettingUtils.switchEnableBatteryReceiver(isChecked); - Log.d(TAG, "switchEnablePhone:" + isChecked); - }); - } - - //设置低电量报警 - private void editBatteryLevelAlarm(final EditText et_battery_level_alarm_min, final EditText et_battery_level_alarm_max, CheckBox cb_battery_level_alarm_once) { - et_battery_level_alarm_min.setText(String.valueOf(SettingUtils.getBatteryLevelAlarmMin())); - et_battery_level_alarm_min.addTextChangedListener(new TextWatcher() { - @Override - public void beforeTextChanged(CharSequence s, int start, int count, int after) { - } - - @Override - public void onTextChanged(CharSequence s, int start, int before, int count) { - } - - @Override - public void afterTextChanged(Editable s) { - String batteryLevel = et_battery_level_alarm_min.getText().toString().trim(); - if (!batteryLevel.isEmpty()) { - SettingUtils.setBatteryLevelAlarmMin(Integer.parseInt(batteryLevel)); - } else { - SettingUtils.setBatteryLevelAlarmMin(0); - } - } - }); - - et_battery_level_alarm_max.setText(String.valueOf(SettingUtils.getBatteryLevelAlarmMax())); - et_battery_level_alarm_max.addTextChangedListener(new TextWatcher() { - @Override - public void beforeTextChanged(CharSequence s, int start, int count, int after) { - } - - @Override - public void onTextChanged(CharSequence s, int start, int before, int count) { - } - - @Override - public void afterTextChanged(Editable s) { - String batteryLevel = et_battery_level_alarm_max.getText().toString().trim(); - if (!batteryLevel.isEmpty()) { - SettingUtils.setBatteryLevelAlarmMax(Integer.parseInt(batteryLevel)); - } else { - SettingUtils.setBatteryLevelAlarmMax(0); - } - } - }); - - cb_battery_level_alarm_once.setChecked(SettingUtils.getBatteryLevelAlarmOnce()); - cb_battery_level_alarm_once.setOnCheckedChangeListener((buttonView, isChecked) -> { - SettingUtils.switchBatteryLevelAlarmOnce(isChecked); - if (isChecked && 0 == SettingUtils.getBatteryLevelAlarmMin() && 0 == SettingUtils.getBatteryLevelAlarmMax()) { - ToastUtils.show(R.string.tips_battery_level_alarm_once); - SettingUtils.switchEnablePhone(false); - } - }); - } - - //定时推送电池状态 - @SuppressLint("UseSwitchCompatOrMaterialCode") - private void switchBatteryCron(Switch switch_battery_cron) { - boolean isOn = SettingUtils.getSwitchEnableBatteryCron(); - switch_battery_cron.setChecked(isOn); - - final LinearLayout layout_battery_cron = findViewById(R.id.layout_battery_cron); - layout_battery_cron.setVisibility(isOn ? View.VISIBLE : View.GONE); - - switch_battery_cron.setOnCheckedChangeListener((buttonView, isChecked) -> { - Log.d(TAG, "onCheckedChanged:" + isChecked); - layout_battery_cron.setVisibility(isChecked ? View.VISIBLE : View.GONE); - SettingUtils.switchEnableBatteryCron(isChecked); - BatteryReportCronTask.getSingleton().updateTimer(); - }); - } - - //设置推送电池状态时机 - private void editBatteryCronTiming(final EditText et_battery_cron_start_time, final EditText et_battery_cron_interval) { - et_battery_cron_start_time.setText(SettingUtils.getBatteryCronStartTime()); - - Calendar calendar = Calendar.getInstance(); - et_battery_cron_start_time.setOnClickListener(view -> { - TimePickerDialog dialog = new TimePickerDialog(SettingActivity.this, (timePicker, hourOfDay, minute) -> { - StringBuilder sb = new StringBuilder(); - if (hourOfDay < 10) { - sb.append("0"); - } - sb.append(hourOfDay); - sb.append(":"); - if (minute < 10) { - sb.append("0"); - } - sb.append(minute); - String startTime = sb.toString(); - et_battery_cron_start_time.setText(startTime); - SettingUtils.setBatteryCronStartTime(startTime); - BatteryReportCronTask.getSingleton().updateTimer(); - }, calendar.get(Calendar.HOUR_OF_DAY) + 1, 0, true); - dialog.show(); - }); - - et_battery_cron_interval.setText(String.valueOf(SettingUtils.getBatteryCronInterval())); - et_battery_cron_interval.addTextChangedListener(new TextWatcher() { - @Override - public void beforeTextChanged(CharSequence s, int start, int count, int after) { - } - - @Override - public void onTextChanged(CharSequence s, int start, int before, int count) { - } - - @Override - public void afterTextChanged(Editable s) { - String interval = et_battery_cron_interval.getText().toString().trim(); - if (!interval.isEmpty() && Integer.parseInt(interval) > 0) { - SettingUtils.setBatteryCronInterval(Integer.parseInt(interval)); - BatteryReportCronTask.getSingleton().updateTimer(); - } else { - SettingUtils.setBatteryCronInterval(60); - } - } - }); - } - - //开机启动 - private void checkWithReboot(@SuppressLint("UseSwitchCompatOrMaterialCode") Switch withrebootSwitch, TextView tvAutoStartup) { - tvAutoStartup.setText(getAutoStartTips()); - - //获取组件 - final ComponentName cm = new ComponentName(this.getPackageName(), RebootBroadcastReceiver.class.getName()); - - final PackageManager pm = getPackageManager(); - int state = pm.getComponentEnabledSetting(cm); - withrebootSwitch.setChecked(state != PackageManager.COMPONENT_ENABLED_STATE_DISABLED - && state != PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER); - withrebootSwitch.setOnCheckedChangeListener((buttonView, isChecked) -> { - int newState = isChecked ? PackageManager.COMPONENT_ENABLED_STATE_ENABLED - : PackageManager.COMPONENT_ENABLED_STATE_DISABLED; - pm.setComponentEnabledSetting(cm, newState, PackageManager.DONT_KILL_APP); - Log.d(TAG, "onCheckedChanged:" + isChecked); - - if (isChecked) startToAutoStartSetting(this); - }); - } - - //Intent跳转到[自启动]页面全网最全适配机型解决方案 - private static final HashMap> hashMap = new HashMap<>() { - { - put("Xiaomi", Arrays.asList( - "com.miui.securitycenter/com.miui.permcenter.autostart.AutoStartManagementActivity",//MIUI10_9.8.1(9.0) - "com.miui.securitycenter" - )); - - put("samsung", Arrays.asList( - "com.samsung.android.sm_cn/com.samsung.android.sm.ui.ram.AutoRunActivity", - "com.samsung.android.sm_cn/com.samsung.android.sm.ui.appmanagement.AppManagementActivity", - "com.samsung.android.sm_cn/com.samsung.android.sm.ui.cstyleboard.SmartManagerDashBoardActivity", - "com.samsung.android.sm_cn/.ui.ram.RamActivity", - "com.samsung.android.sm_cn/.app.dashboard.SmartManagerDashBoardActivity", - - "com.samsung.android.sm/com.samsung.android.sm.ui.ram.AutoRunActivity", - "com.samsung.android.sm/com.samsung.android.sm.ui.appmanagement.AppManagementActivity", - "com.samsung.android.sm/com.samsung.android.sm.ui.cstyleboard.SmartManagerDashBoardActivity", - "com.samsung.android.sm/.ui.ram.RamActivity", - "com.samsung.android.sm/.app.dashboard.SmartManagerDashBoardActivity", - - "com.samsung.android.lool/com.samsung.android.sm.ui.battery.BatteryActivity", - "com.samsung.android.sm_cn", - "com.samsung.android.sm" - )); - - put("HUAWEI", Arrays.asList( - "com.huawei.systemmanager/.startupmgr.ui.StartupNormalAppListActivity",//EMUI9.1.0(方舟,9.0) - "com.huawei.systemmanager/.appcontrol.activity.StartupAppControlActivity", - "com.huawei.systemmanager/.optimize.process.ProtectActivity", - "com.huawei.systemmanager/.optimize.bootstart.BootStartActivity", - "com.huawei.systemmanager"//最后一行可以写包名, 这样如果签名的类路径在某些新版本的ROM中没找到 就直接跳转到对应的安全中心/手机管家 首页. - )); - - put("vivo", Arrays.asList( - "com.iqoo.secure/.ui.phoneoptimize.BgStartUpManager", - "com.iqoo.secure/.safeguard.PurviewTabActivity", - "com.vivo.permissionmanager/.activity.BgStartUpManagerActivity", - //"com.iqoo.secure/.ui.phoneoptimize.AddWhiteListActivity", //这是白名单, 不是自启动 - "com.iqoo.secure", - "com.vivo.permissionmanager" - )); - - put("Meizu", Arrays.asList( - "com.meizu.safe/.permission.SmartBGActivity",//Flyme7.3.0(7.1.2) - "com.meizu.safe/.permission.PermissionMainActivity",//网上的 - "com.meizu.safe" - )); - - put("OPPO", Arrays.asList( - "com.coloros.safecenter/.startupapp.StartupAppListActivity", - "com.coloros.safecenter/.permission.startup.StartupAppListActivity", - "com.oppo.safe/.permission.startup.StartupAppListActivity", - "com.coloros.oppoguardelf/com.coloros.powermanager.fuelgaue.PowerUsageModelActivity", - "com.coloros.safecenter/com.coloros.privacypermissionsentry.PermissionTopActivity", - "com.coloros.safecenter", - "com.oppo.safe", - "com.coloros.oppoguardelf" - )); - - put("oneplus", Arrays.asList( - "com.oneplus.security/.chainlaunch.view.ChainLaunchAppListActivity", - "com.oneplus.security" - )); - - put("letv", Arrays.asList( - "com.letv.android.letvsafe/.AutobootManageActivity", - "com.letv.android.letvsafe/.BackgroundAppManageActivity",//应用保护 - "com.letv.android.letvsafe" - )); - - put("zte", Arrays.asList( - "com.zte.heartyservice/.autorun.AppAutoRunManager", - "com.zte.heartyservice" - )); - - //金立 - put("F", Arrays.asList( - "com.gionee.softmanager/.MainActivity", - "com.gionee.softmanager" - )); - - //以下为未确定(厂商名也不确定) - put("smartisanos", Arrays.asList( - "com.smartisanos.security/.invokeHistory.InvokeHistoryActivity", - "com.smartisanos.security" - )); - - //360 - put("360", Arrays.asList( - "com.yulong.android.coolsafe/.ui.activity.autorun.AutoRunListActivity", - "com.yulong.android.coolsafe" - )); - - //360 - put("ulong", Arrays.asList( - "com.yulong.android.coolsafe/.ui.activity.autorun.AutoRunListActivity", - "com.yulong.android.coolsafe" - )); - - //酷派 - put("coolpad"/*厂商名称不确定是否正确*/, Arrays.asList( - "com.yulong.android.security/com.yulong.android.seccenter.tabbarmain", - "com.yulong.android.security" - )); - - //联想 - put("lenovo"/*厂商名称不确定是否正确*/, Arrays.asList( - "com.lenovo.security/.purebackground.PureBackgroundActivity", - "com.lenovo.security" - )); - - put("htc"/*厂商名称不确定是否正确*/, Arrays.asList( - "com.htc.pitroad/.landingpage.activity.LandingPageActivity", - "com.htc.pitroad" - )); - - //华硕 - put("asus"/*厂商名称不确定是否正确*/, Arrays.asList( - "com.asus.mobilemanager/.MainActivity", - "com.asus.mobilemanager" - )); - - } - }; - - //跳转自启动页面 - public static void startToAutoStartSetting(Context context) { - Log.e("Util", "******************The current phone model is:" + Build.MANUFACTURER); - - Set>> entries = hashMap.entrySet(); - boolean has = false; - for (Map.Entry> entry : entries) { - String manufacturer = entry.getKey(); - List actCompatList = entry.getValue(); - if (Build.MANUFACTURER.equalsIgnoreCase(manufacturer)) { - for (String act : actCompatList) { - try { - Intent intent; - if (act.contains("/")) { - intent = new Intent(); - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - ComponentName componentName = ComponentName.unflattenFromString(act); - intent.setComponent(componentName); - } else { - //找不到? 网上的做法都是跳转到设置... 这基本上是没意义的 基本上自启动这个功能是第三方厂商自己写的安全管家类app - //所以我是直接跳转到对应的安全管家/安全中心 - intent = context.getPackageManager().getLaunchIntentForPackage(act); - } - context.startActivity(intent); - has = true; - break; - } catch (Exception e) { - e.printStackTrace(); - } - } - } - } - if (!has) { - ToastUtils.show(R.string.tips_compatible_solution); - try { - Intent intent = new Intent(); - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - intent.setAction("android.settings.APPLICATION_DETAILS_SETTINGS"); - intent.setData(Uri.fromParts("package", context.getPackageName(), null)); - context.startActivity(intent); - } catch (Exception e) { - e.printStackTrace(); - Intent intent = new Intent(Settings.ACTION_SETTINGS); - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - context.startActivity(intent); - } - } - } - - //电池优化设置 - @RequiresApi(api = Build.VERSION_CODES.M) - @SuppressLint("UseSwitchCompatOrMaterialCode") - public void batterySetting(LinearLayout layout_battery_setting, Switch switch_battery_setting) { - //安卓6.0以下没有忽略电池优化 - if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.M) { - layout_battery_setting.setVisibility(View.GONE); - return; - } - - isIgnoreBatteryOptimization = KeepAliveUtils.isIgnoreBatteryOptimization(this); - switch_battery_setting.setChecked(isIgnoreBatteryOptimization); - - switch_battery_setting.setOnCheckedChangeListener((buttonView, isChecked) -> { - if (isChecked && !isIgnoreBatteryOptimization) { - KeepAliveUtils.ignoreBatteryOptimization(this); - } else if (isChecked) { - ToastUtils.show(R.string.isIgnored); - switch_battery_setting.setChecked(isIgnoreBatteryOptimization); - } else { - ToastUtils.show(R.string.isIgnored2); - switch_battery_setting.setChecked(isIgnoreBatteryOptimization); - } - }); - } - - //不在最近任务列表中显示 - @SuppressLint("ObsoleteSdkInt,UseSwitchCompatOrMaterialCode") - private void switchExcludeFromRecents(Switch switch_exclude_from_recents) { - switch_exclude_from_recents.setChecked(SettingUtils.getExcludeFromRecents()); - - switch_exclude_from_recents.setOnCheckedChangeListener((buttonView, isChecked) -> { - SettingUtils.switchExcludeFromRecents(isChecked); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - ActivityManager am = (ActivityManager) this.getSystemService(Context.ACTIVITY_SERVICE); - if (am != null) { - List appTasks = am.getAppTasks(); - if (appTasks != null && !appTasks.isEmpty()) { - appTasks.get(0).setExcludeFromRecents(isChecked); - } - } - } - Log.d(TAG, "onCheckedChanged:" + isChecked); - }); - } - - //后台播放无声音乐 - @SuppressLint("ObsoleteSdkInt,UseSwitchCompatOrMaterialCode") - private void switchPlaySilenceMusic(Switch switch_play_silence_music) { - switch_play_silence_music.setChecked(SettingUtils.getPlaySilenceMusic()); - - switch_play_silence_music.setOnCheckedChangeListener((buttonView, isChecked) -> { - SettingUtils.switchPlaySilenceMusic(isChecked); - - if (isChecked) { - startService(new Intent(context, MusicService.class)); - } else { - stopService(new Intent(context, MusicService.class)); - } - Log.d(TAG, "onCheckedChanged:" + isChecked); - }); - } - - //1像素透明Activity保活 - @SuppressLint("ObsoleteSdkInt,UseSwitchCompatOrMaterialCode") - private void switchOnePixelActivity(Switch switch_one_pixel_activity) { - switch_one_pixel_activity.setChecked(SettingUtils.getOnePixelActivity()); - - switch_one_pixel_activity.setOnCheckedChangeListener((buttonView, isChecked) -> { - SettingUtils.switchOnePixelActivity(isChecked); - Log.d(TAG, "onCheckedChanged:" + isChecked); - - //1像素透明Activity保活 or 仅锁屏状态转发APP通知 - OnePixelManager onePixelManager = new OnePixelManager(); - if (SettingUtils.getOnePixelActivity() || SettingUtils.getSwitchNotUserPresent()) { - onePixelManager.registerOnePixelReceiver(this);//注册广播接收者 - } else { - onePixelManager.unregisterOnePixelReceiver(this); - } - }); - } - - //接口请求失败重试时间间隔 - private void editRetryDelayTime(final EditText et_retry_times, final EditText et_delay_time) { - et_retry_times.setText(String.valueOf(SettingUtils.getRetryTimes())); - et_retry_times.addTextChangedListener(new TextWatcher() { - @Override - public void beforeTextChanged(CharSequence s, int start, int count, int after) { - } - - @Override - public void onTextChanged(CharSequence s, int start, int before, int count) { - } - - @Override - public void afterTextChanged(Editable s) { - String retryTimes = et_retry_times.getText().toString().trim(); - if (!retryTimes.isEmpty()) { - SettingUtils.setRetryTimes(Integer.parseInt(retryTimes)); - } else { - SettingUtils.setRetryTimes(0); - } - } - }); - - et_delay_time.setText(String.valueOf(SettingUtils.getDelayTime())); - et_delay_time.addTextChangedListener(new TextWatcher() { - @Override - public void beforeTextChanged(CharSequence s, int start, int count, int after) { - } - - @Override - public void onTextChanged(CharSequence s, int start, int before, int count) { - } - - @Override - public void afterTextChanged(Editable s) { - String delayTime = et_delay_time.getText().toString().trim(); - if (!delayTime.isEmpty()) { - SettingUtils.setDelayTime(Integer.parseInt(delayTime)); - } else { - SettingUtils.setDelayTime(1); - } - } - }); - } - - //设置设备名称 - private void editAddExtraDeviceMark(final EditText et_add_extra_device_mark) { - et_add_extra_device_mark.setText(SettingUtils.getAddExtraDeviceMark()); - - et_add_extra_device_mark.addTextChangedListener(new TextWatcher() { - @Override - public void beforeTextChanged(CharSequence s, int start, int count, int after) { - } - - @Override - public void onTextChanged(CharSequence s, int start, int before, int count) { - } - - @Override - public void afterTextChanged(Editable s) { - SettingUtils.setAddExtraDeviceMark(et_add_extra_device_mark.getText().toString().trim()); - } - }); - } - - //设置SIM1备注 - private void editAddExtraSim1(final EditText et_add_extra_sim1) { - et_add_extra_sim1.setText(SettingUtils.getAddExtraSim1()); - - et_add_extra_sim1.addTextChangedListener(new TextWatcher() { - @Override - public void beforeTextChanged(CharSequence s, int start, int count, int after) { - } - - @Override - public void onTextChanged(CharSequence s, int start, int before, int count) { - } - - @Override - public void afterTextChanged(Editable s) { - SettingUtils.setAddExtraSim1(et_add_extra_sim1.getText().toString().trim()); - } - }); - } - - //设置SIM2备注 - private void editAddExtraSim2(final EditText et_add_extra_sim2) { - et_add_extra_sim2.setText(SettingUtils.getAddExtraSim2()); - - et_add_extra_sim2.addTextChangedListener(new TextWatcher() { - @Override - public void beforeTextChanged(CharSequence s, int start, int count, int after) { - } - - @Override - public void onTextChanged(CharSequence s, int start, int before, int count) { - } - - @Override - public void afterTextChanged(Editable s) { - SettingUtils.setAddExtraSim2(et_add_extra_sim2.getText().toString().trim()); - } - }); - } - - //设置转发时启用自定义模版 - @SuppressLint({"UseSwitchCompatOrMaterialCode", "SetTextI18n"}) - private void switchSmsTemplate(Switch switch_sms_template) { - boolean isOn = SettingUtils.getSwitchSmsTemplate(); - switch_sms_template.setChecked(isOn); - - final LinearLayout layout_sms_template = findViewById(R.id.layout_sms_template); - layout_sms_template.setVisibility(isOn ? View.VISIBLE : View.GONE); - final EditText textSmsTemplate = findViewById(R.id.text_sms_template); - - switch_sms_template.setOnCheckedChangeListener((buttonView, isChecked) -> { - Log.d(TAG, "onCheckedChanged:" + isChecked); - layout_sms_template.setVisibility(isChecked ? View.VISIBLE : View.GONE); - SettingUtils.switchSmsTemplate(isChecked); - if (!isChecked) { - textSmsTemplate.setText(getString(R.string.tag_from) + "\n" + - getString(R.string.tag_sms) + "\n" + - getString(R.string.tag_card_slot) + "\n" + - getString(R.string.tag_receive_time) + "\n" + - getString(R.string.tag_device_name)); - } - }); - } - - //设置转发信息模版 - private void editSmsTemplate(final EditText textSmsTemplate) { - textSmsTemplate.setText(SettingUtils.getSmsTemplate()); - - textSmsTemplate.addTextChangedListener(new TextWatcher() { - @Override - public void beforeTextChanged(CharSequence s, int start, int count, int after) { - - } - - @Override - public void onTextChanged(CharSequence s, int start, int before, int count) { - - } - - @Override - public void afterTextChanged(Editable s) { - SettingUtils.setSmsTemplate(textSmsTemplate.getText().toString().trim()); - } - }); - } - - //插入标签 - @SuppressLint("NonConstantResourceId") - public void toInsertLabel(View v) { - EditText textSmsTemplate = findViewById(R.id.text_sms_template); - textSmsTemplate.setFocusable(true); - textSmsTemplate.requestFocus(); - switch (v.getId()) { - case R.id.bt_insert_sender: - CommonUtils.insertOrReplaceText2Cursor(textSmsTemplate, getString(R.string.tag_from)); - return; - case R.id.bt_insert_content: - CommonUtils.insertOrReplaceText2Cursor(textSmsTemplate, getString(R.string.tag_sms)); - return; - case R.id.bt_insert_extra: - CommonUtils.insertOrReplaceText2Cursor(textSmsTemplate, getString(R.string.tag_card_slot)); - return; - case R.id.bt_insert_time: - CommonUtils.insertOrReplaceText2Cursor(textSmsTemplate, getString(R.string.tag_receive_time)); - return; - case R.id.bt_insert_device_name: - CommonUtils.insertOrReplaceText2Cursor(textSmsTemplate, getString(R.string.tag_device_name)); - return; - default: - } - } - - //页面帮助提示 - private void SwitchHelpTip(@SuppressLint("UseSwitchCompatOrMaterialCode") Switch switchHelpTip) { - switchHelpTip.setChecked(MyApplication.showHelpTip); - - switchHelpTip.setOnCheckedChangeListener((buttonView, isChecked) -> { - MyApplication.showHelpTip = isChecked; - SharedPreferences sp = context.getSharedPreferences(Define.SP_CONFIG, Context.MODE_PRIVATE); - sp.edit().putBoolean(Define.SP_CONFIG_SWITCH_HELP_TIP, isChecked).apply(); - Log.d(TAG, "onCheckedChanged:" + isChecked); - - StepBar stepBar = findViewById(R.id.stepBar); - stepBar.setHighlight(); - CommonUtils.calcMarginBottom(this, null, null, findViewById(R.id.scrollView)); - }); - } - - //恢复初始化配置 - public void initSetting(View view) { - - AlertDialog.Builder builder = new AlertDialog.Builder(SettingActivity.this); - builder.setTitle(R.string.init_setting); - builder.setMessage(R.string.init_setting_tips); - - //添加AlertDialog.Builder对象的setPositiveButton()方法 - builder.setPositiveButton(R.string.confirm, (dialog, which) -> { - //初始化配置 - SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this); - SharedPreferences.Editor editor = preferences.edit(); - editor.clear(); - editor.apply(); - - //初始化数据库 - DbHelper dbHelper = new DbHelper(this); - SQLiteDatabase db = dbHelper.getReadableDatabase(); - dbHelper.delCreateTable(db); - dbHelper.onCreate(db); - - Intent intent = new Intent(this, MainActivity.class); - startActivity(intent); - }); - - //添加AlertDialog.Builder对象的setNegativeButton()方法 - builder.setNegativeButton(R.string.cancel, (dialog, which) -> { - - }); - - builder.create().show(); - } - - /** - * 获取当前手机品牌 - * - * @return 手机品牌 - */ - public static String getAutoStartTips() { - String brand = Build.BRAND.toLowerCase(); - String tips; - - switch (brand) { - case "huawei": - tips = "华为手机:应用启动管理 -> 关闭应用开关 -> 打开允许自启动"; - break; - case "honor": - tips = "荣耀手机:应用启动管理 -> 关闭应用开关 -> 打开允许自启动"; - break; - case "xiaomi": - tips = "小米手机:授权管理 -> 自启动管理 -> 允许应用自启动"; - break; - case "oppo": - tips = "OPPO手机:权限隐私 -> 自启动管理 -> 允许应用自启动"; - break; - case "vivo": - tips = "vivo手机:权限管理 -> 自启动 -> 允许应用自启动"; - break; - case "meizu": - tips = "魅族手机:权限管理 -> 后台管理 -> 点击应用 -> 允许后台运行"; - break; - case "samsung": - tips = "三星手机:自动运行应用程序 -> 打开应用开关 -> 电池管理 -> 未监视的应用程序 -> 添加应用"; - break; - case "letv": - tips = "乐视手机:自启动管理 -> 允许应用自启动"; - break; - case "smartisan": - tips = "锤子手机:权限管理 -> 自启动权限管理 -> 点击应用 -> 允许被系统启动"; - break; - default: - tips = "未知手机品牌:需要自主查看设置操作"; - break; - } - - return tips; - } -} diff --git a/app/src/main/java/com/idormy/sms/forwarder/activity/LoginActivity.kt b/app/src/main/java/com/idormy/sms/forwarder/activity/LoginActivity.kt new file mode 100644 index 00000000..153744a4 --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/activity/LoginActivity.kt @@ -0,0 +1,28 @@ +package com.idormy.sms.forwarder.activity + +import android.os.Bundle +import android.view.KeyEvent +import androidx.viewbinding.ViewBinding +import com.idormy.sms.forwarder.core.BaseActivity +import com.idormy.sms.forwarder.fragment.LoginFragment +import com.xuexiang.xui.utils.KeyboardUtils +import com.xuexiang.xui.utils.StatusBarUtils +import com.xuexiang.xutil.display.Colors + +class LoginActivity : BaseActivity() { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + openPage(LoginFragment::class.java, intent.extras) + } + + override val isSupportSlideBack: Boolean + get() = false + + override fun initStatusBarStyle() { + StatusBarUtils.initStatusBarStyle(this, false, Colors.WHITE) + } + + override fun onKeyDown(keyCode: Int, event: KeyEvent): Boolean { + return KeyboardUtils.onDisableBackKeyDown(keyCode) && super.onKeyDown(keyCode, event) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/activity/MainActivity.kt b/app/src/main/java/com/idormy/sms/forwarder/activity/MainActivity.kt new file mode 100644 index 00000000..9446787a --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/activity/MainActivity.kt @@ -0,0 +1,402 @@ +package com.idormy.sms.forwarder.activity + +import android.annotation.SuppressLint +import android.app.ActivityManager +import android.content.Context +import android.content.Intent +import android.os.Build +import android.os.Bundle +import android.util.Log +import android.view.LayoutInflater +import android.view.MenuItem +import android.view.View +import android.widget.TextView +import androidx.appcompat.app.ActionBarDrawerToggle +import androidx.appcompat.widget.Toolbar +import androidx.recyclerview.widget.RecyclerView +import androidx.viewpager.widget.ViewPager +import com.google.android.material.bottomnavigation.BottomNavigationView +import com.google.android.material.bottomsheet.BottomSheetDialog +import com.gyf.cactus.ext.cactusUpdateNotification +import com.idormy.sms.forwarder.App +import com.idormy.sms.forwarder.R +import com.idormy.sms.forwarder.adapter.WidgetItemAdapter +import com.idormy.sms.forwarder.core.BaseActivity +import com.idormy.sms.forwarder.core.webview.AgentWebActivity +import com.idormy.sms.forwarder.database.AppDatabase +import com.idormy.sms.forwarder.databinding.ActivityMainBinding +import com.idormy.sms.forwarder.fragment.* +import com.idormy.sms.forwarder.utils.* +import com.idormy.sms.forwarder.widget.GuideTipsDialog.Companion.showTips +import com.idormy.sms.forwarder.widget.GuideTipsDialog.Companion.showTipsForce +import com.jeremyliao.liveeventbus.LiveEventBus +import com.xuexiang.xaop.annotation.SingleClick +import com.xuexiang.xpage.base.XPageFragment +import com.xuexiang.xpage.core.PageOption +import com.xuexiang.xpage.model.PageInfo +import com.xuexiang.xui.adapter.FragmentAdapter +import com.xuexiang.xui.adapter.recyclerview.RecyclerViewHolder +import com.xuexiang.xui.utils.DensityUtils +import com.xuexiang.xui.utils.ResUtils +import com.xuexiang.xui.utils.WidgetUtils +import com.xuexiang.xui.widget.dialog.materialdialog.DialogAction +import com.xuexiang.xui.widget.dialog.materialdialog.GravityEnum +import com.xuexiang.xui.widget.dialog.materialdialog.MaterialDialog +import com.xuexiang.xupdate.XUpdate +import com.xuexiang.xupdate.service.OnFileDownloadListener +import com.xuexiang.xutil.common.CollectionUtils +import com.xuexiang.xutil.file.FileUtils +import frpclib.Frpclib +import io.reactivex.CompletableObserver +import io.reactivex.android.schedulers.AndroidSchedulers +import io.reactivex.disposables.Disposable +import io.reactivex.schedulers.Schedulers +import java.io.File +import kotlin.math.roundToInt + + +@Suppress("DEPRECATION", "PrivatePropertyName") +class MainActivity : BaseActivity(), + View.OnClickListener, + BottomNavigationView.OnNavigationItemSelectedListener, + Toolbar.OnMenuItemClickListener, + RecyclerViewHolder.OnItemClickListener { + + private val TAG: String = MainActivity::class.java.simpleName + private lateinit var mTitles: Array + private var logsType: String = "sms" + private var ruleType: String = "sms" + + override fun viewBindingInflate(inflater: LayoutInflater?): ActivityMainBinding { + return ActivityMainBinding.inflate(inflater!!) + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + initViews() + initData() + initListeners() + + //不在最近任务列表中显示 + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && SettingUtils.enableExcludeFromRecents) { + val am = App.context.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager + am.let { + val tasks = it.appTasks + if (!tasks.isNullOrEmpty()) { + tasks[0].setExcludeFromRecents(true) + } + } + } + } + + override val isSupportSlideBack: Boolean + get() = false + + private fun initViews() { + WidgetUtils.clearActivityBackground(this) + mTitles = ResUtils.getStringArray(R.array.home_titles) + binding!!.includeMain.toolbar.title = mTitles[0] + binding!!.includeMain.toolbar.inflateMenu(R.menu.menu_logs) + binding!!.includeMain.toolbar.setOnMenuItemClickListener(this) + + //主页内容填充 + val fragments = arrayOf( + LogsFragment(), + RulesFragment(), + SendersFragment(), + SettingsFragment() + ) + val adapter = FragmentAdapter(supportFragmentManager, fragments) + binding!!.includeMain.viewPager.offscreenPageLimit = mTitles.size - 1 + binding!!.includeMain.viewPager.adapter = adapter + + if (!SettingUtils.enableHelpTip) { + val headerView = binding!!.navView.getHeaderView(0) + val tvSlogan = headerView.findViewById(R.id.tv_slogan) + tvSlogan.visibility = View.GONE + } + } + + private fun initData() { + showTips(this) + //XUpdateInit.checkUpdate(this, true) + } + + fun initListeners() { + val toggle = ActionBarDrawerToggle( + this, + binding!!.drawerLayout, + binding!!.includeMain.toolbar, + R.string.navigation_drawer_open, + R.string.navigation_drawer_close + ) + binding!!.drawerLayout.addDrawerListener(toggle) + toggle.syncState() + + //侧边栏点击事件 + binding!!.navView.setNavigationItemSelectedListener { menuItem: MenuItem -> + if (menuItem.isCheckable) { + binding!!.drawerLayout.closeDrawers() + return@setNavigationItemSelectedListener handleNavigationItemSelected(menuItem) + } else { + when (menuItem.itemId) { + R.id.nav_server -> openNewPage(ServerFragment::class.java) + R.id.nav_client -> openNewPage(ClientFragment::class.java) + R.id.nav_frpc -> { + if (FileUtils.isFileExists(filesDir.absolutePath + "/libs/libgojni.so")) { + if (FRPC_LIB_VERSION == Frpclib.getVersion()) { + openNewPage(FrpcFragment::class.java) + } else { + XToastUtils.error(getString(R.string.frpclib_version_mismatch)) + downloadFrpcLib() + } + } else { + downloadFrpcLib() + } + } + R.id.nav_app_list -> openNewPage(AppListFragment::class.java) + R.id.nav_logcat -> openNewPage(LogcatFragment::class.java) + R.id.nav_help -> AgentWebActivity.goWeb(this, getString(R.string.url_help)) + R.id.nav_about -> openNewPage(AboutFragment::class.java) + else -> XToastUtils.toast("点击了:" + menuItem.title) + } + } + true + } + + //主页事件监听 + binding!!.includeMain.viewPager.addOnPageChangeListener(object : + ViewPager.OnPageChangeListener { + override fun onPageScrolled( + position: Int, + positionOffset: Float, + positionOffsetPixels: Int, + ) { + } + + override fun onPageSelected(position: Int) { + val item = binding!!.includeMain.bottomNavigation.menu.getItem(position) + binding!!.includeMain.toolbar.title = item.title + binding!!.includeMain.toolbar.menu.clear() + when { + item.title.equals(getString(R.string.menu_rules)) -> binding!!.includeMain.toolbar.inflateMenu(R.menu.menu_rules) + item.title.equals(getString(R.string.menu_senders)) -> binding!!.includeMain.toolbar.inflateMenu(R.menu.menu_senders) + item.title.equals(getString(R.string.menu_settings)) -> binding!!.includeMain.toolbar.inflateMenu(R.menu.menu_settings) + else -> binding!!.includeMain.toolbar.inflateMenu(R.menu.menu_logs) + } + item.isChecked = true + updateSideNavStatus(item) + } + + override fun onPageScrollStateChanged(state: Int) {} + }) + binding!!.includeMain.bottomNavigation.setOnNavigationItemSelectedListener(this) + + //tabBar分类切换 + LiveEventBus.get(EVENT_UPDATE_LOGS_TYPE, String::class.java).observe(this) { type: String -> + logsType = type + } + LiveEventBus.get(EVENT_UPDATE_RULE_TYPE, String::class.java).observe(this) { type: String -> + ruleType = type + } + + //更新通知栏文案 + LiveEventBus.get(EVENT_UPDATE_NOTIFY, String::class.java).observe(this) { notify: String -> + cactusUpdateNotification { + setContent(notify) + } + } + } + + /** + * 处理侧边栏点击事件 + * + * @param menuItem + * @return + */ + private fun handleNavigationItemSelected(menuItem: MenuItem): Boolean { + val index = CollectionUtils.arrayIndexOf(mTitles, menuItem.title) + if (index != -1) { + binding!!.includeMain.toolbar.title = menuItem.title + binding!!.includeMain.viewPager.setCurrentItem(index, false) + return true + } + return false + } + + @SuppressLint("InflateParams") + override fun onMenuItemClick(item: MenuItem): Boolean { + when (item.itemId) { + R.id.action_notifications -> { + showTipsForce(this) + } + R.id.action_clear_logs -> { + MaterialDialog.Builder(this) + .content(R.string.delete_type_log_tips) + .positiveText(R.string.lab_yes) + .negativeText(R.string.lab_no) + .onPositive { _: MaterialDialog?, _: DialogAction? -> + AppDatabase.getInstance(this) + .logsDao() + .deleteAll(logsType) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(object : CompletableObserver { + override fun onSubscribe(d: Disposable) {} + override fun onComplete() { + XToastUtils.success(R.string.delete_type_log_toast) + } + + override fun onError(e: Throwable) { + e.message?.let { XToastUtils.error(it) } + } + }) + } + .show() + } + R.id.action_add_sender -> { + val dialog = BottomSheetDialog(this) + val view: View = LayoutInflater.from(this).inflate(R.layout.dialog_sender_bottom_sheet, null) + val recyclerView: RecyclerView = view.findViewById(R.id.recyclerView) + + WidgetUtils.initGridRecyclerView(recyclerView, 4, DensityUtils.dp2px(1f)) + val widgetItemAdapter = WidgetItemAdapter(SENDER_FRAGMENT_LIST) + widgetItemAdapter.setOnItemClickListener(this) + recyclerView.adapter = widgetItemAdapter + + dialog.setContentView(view) + dialog.setCancelable(true) + dialog.setCanceledOnTouchOutside(true) + dialog.show() + WidgetUtils.transparentBottomSheetDialogBackground(dialog) + } + R.id.action_add_rule -> { + PageOption.to(RulesEditFragment::class.java) + .putString(KEY_RULE_TYPE, ruleType) + .setNewActivity(true) + .open(this) + } + /*R.id.action_restore_settings -> { + XToastUtils.success(logsType) + }*/ + } + return false + } + + @SingleClick + override fun onClick(v: View) { + } + + //================Navigation================// + /** + * 底部导航栏点击事件 + * + * @param menuItem + * @return + */ + override fun onNavigationItemSelected(menuItem: MenuItem): Boolean { + val index = CollectionUtils.arrayIndexOf(mTitles, menuItem.title) + if (index != -1) { + binding!!.includeMain.toolbar.title = menuItem.title + binding!!.includeMain.viewPager.setCurrentItem(index, false) + updateSideNavStatus(menuItem) + return true + } + return false + } + + /** + * 更新侧边栏菜单选中状态 + * + * @param menuItem + */ + private fun updateSideNavStatus(menuItem: MenuItem) { + val side = binding!!.navView.menu.findItem(menuItem.itemId) + if (side != null) { + side.isChecked = true + } + } + + //按返回键不退出回到桌面 + override fun onBackPressed() { + val intent = Intent(Intent.ACTION_MAIN) + intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK + intent.addCategory(Intent.CATEGORY_HOME) + startActivity(intent) + } + + @SingleClick + override fun onItemClick(itemView: View, widgetInfo: PageInfo, pos: Int) { + try { + @Suppress("UNCHECKED_CAST") + PageOption.to(Class.forName(widgetInfo.classPath) as Class) //跳转的fragment + .setNewActivity(true) + .putInt(KEY_SENDER_TYPE, pos) //TODO:需要注意这里,目前刚好是这个顺序而已 + .open(this) + } catch (e: Exception) { + e.printStackTrace() + XToastUtils.error(e.message.toString()) + } + } + + //动态加载FrpcLib + private fun downloadFrpcLib() { + val cpuAbi = when (Build.CPU_ABI) { + "x86" -> "x86" + "x86_64" -> "x86_64" + "arm64-v8a" -> "arm64-v8a" + else -> "armeabi-v7a" + } + + val libPath = filesDir.absolutePath + "/libs" + val soFile = File(libPath) + if (!soFile.exists()) soFile.mkdirs() + val downloadUrl = "https://xupdate.bms.ink/uploads/$FRPC_LIB_VERSION/$cpuAbi/libgojni.so" + val mContext = this + val dialog: MaterialDialog = MaterialDialog.Builder(mContext) + .title(getString(R.string.frpclib_download_title)) + .content(getString(R.string.frpclib_download_content)) + .contentGravity(GravityEnum.CENTER) + .progress(false, 0, true) + .progressNumberFormat("%2dMB/%1dMB") + .build() + XUpdate.newBuild(mContext) + .apkCacheDir(cacheDir.absolutePath) //设置下载缓存的根目录 + .build() + .download(downloadUrl, object : OnFileDownloadListener { + + override fun onStart() { + dialog.show() + } + + override fun onProgress(progress: Float, total: Long) { + Log.d(TAG, "onProgress: progress=$progress, total=$total") + + val max = (total / 1024F / 1024F).roundToInt() + dialog.maxProgress = max + dialog.setProgress((progress * max).roundToInt()) + } + + override fun onCompleted(srcFile: File): Boolean { + dialog.dismiss() + Log.d(TAG, srcFile.path) + + val destFile = File("$libPath/libgojni.so") + FileUtils.moveFile(srcFile, destFile, null) + + val intent: Intent? = packageManager.getLaunchIntentForPackage(packageName) + intent?.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP) + startActivity(intent) + android.os.Process.killProcess(android.os.Process.myPid()) //杀掉以前进程 + + return false + } + + override fun onError(throwable: Throwable) { + dialog.dismiss() + XToastUtils.error(throwable.message!!) + } + }) + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/activity/SplashActivity.kt b/app/src/main/java/com/idormy/sms/forwarder/activity/SplashActivity.kt new file mode 100644 index 00000000..4c1cac6b --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/activity/SplashActivity.kt @@ -0,0 +1,79 @@ +package com.idormy.sms.forwarder.activity + +import android.annotation.SuppressLint +import android.util.Log +import android.view.KeyEvent +import com.idormy.sms.forwarder.R +import com.idormy.sms.forwarder.utils.CommonUtils.Companion.showPrivacyDialog +import com.idormy.sms.forwarder.utils.MMKVUtils +import com.idormy.sms.forwarder.utils.SettingUtils.Companion.isAgreePrivacy +import com.idormy.sms.forwarder.utils.SettingUtils.Companion.isFirstOpen +import com.xuexiang.xui.utils.KeyboardUtils +import com.xuexiang.xui.widget.activity.BaseSplashActivity +import com.xuexiang.xui.widget.dialog.materialdialog.DialogAction +import com.xuexiang.xui.widget.dialog.materialdialog.MaterialDialog +import com.xuexiang.xutil.app.ActivityUtils +import me.jessyan.autosize.internal.CancelAdapt + +@Suppress("PropertyName") +@SuppressLint("CustomSplashScreen") +class SplashActivity : BaseSplashActivity(), CancelAdapt { + + val TAG: String = SplashActivity::class.java.simpleName + + override fun getSplashDurationMillis(): Long { + return 500 + } + + /** + * activity启动后的初始化 + */ + override fun onCreateActivity() { + initSplashView(R.drawable.xui_config_bg_splash) + startSplash(false) + } + + /** + * 启动页结束后的动作 + */ + override fun onSplashFinished() { + if (isFirstOpen) { + isFirstOpen = false + Log.d(TAG, "从SP迁移数据") + MMKVUtils.importSharedPreferences(this) + } + + if (isAgreePrivacy) { + loginOrGoMainPage() + } else { + showPrivacyDialog(this) { dialog: MaterialDialog, _: DialogAction? -> + dialog.dismiss() + isAgreePrivacy = true + loginOrGoMainPage() + } + } + } + + private fun loginOrGoMainPage() { + /*if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && SettingUtils.enableExcludeFromRecents) { + val intent = Intent(App.context, if (hasToken()) MainActivity::class.java else LoginActivity::class.java) + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS) + App.context.startActivity(intent) + } else { + if (hasToken()) { + ActivityUtils.startActivity(MainActivity::class.java) + } else { + ActivityUtils.startActivity(LoginActivity::class.java) + } + }*/ + ActivityUtils.startActivity(MainActivity::class.java) + finish() + } + + /** + * 菜单、返回键响应 + */ + override fun onKeyDown(keyCode: Int, event: KeyEvent): Boolean { + return KeyboardUtils.onDisableBackKeyDown(keyCode) && super.onKeyDown(keyCode, event) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/adapter/AppAdapter.java b/app/src/main/java/com/idormy/sms/forwarder/adapter/AppAdapter.java deleted file mode 100644 index 5bc17325..00000000 --- a/app/src/main/java/com/idormy/sms/forwarder/adapter/AppAdapter.java +++ /dev/null @@ -1,113 +0,0 @@ -package com.idormy.sms.forwarder.adapter; - -import android.annotation.SuppressLint; -import android.content.Context; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.ArrayAdapter; -import android.widget.ImageView; -import android.widget.TextView; - -import com.idormy.sms.forwarder.R; -import com.idormy.sms.forwarder.model.AppInfo; - -import java.util.List; - -public class AppAdapter extends ArrayAdapter { - private final int resourceId; - private List list; - - // 适配器的构造函数,把要适配的数据传入这里 - public AppAdapter(Context context, int textViewResourceId, List objects) { - super(context, textViewResourceId, objects); - resourceId = textViewResourceId; - list = objects; - } - - @Override - public int getCount() { - return list.size(); - } - - @Override - public AppInfo getItem(int position) { - return list.get(position); - } - - @Override - public long getItemId(int position) { - AppInfo item = list.get(position); - return 0; - } - - @SuppressLint("SetTextI18n") - @Override - public View getView(int position, View convertView, ViewGroup parent) { - AppInfo appInfo = getItem(position); //获取当前项的TLog实例 - - // 加个判断,以免ListView每次滚动时都要重新加载布局,以提高运行效率 - View view; - AppAdapter.ViewHolder viewHolder; - if (convertView == null) { - - // 避免ListView每次滚动时都要重新加载布局,以提高运行效率 - view = LayoutInflater.from(getContext()).inflate(resourceId, parent, false); - - // 避免每次调用getView()时都要重新获取控件实例 - viewHolder = new AppAdapter.ViewHolder(); - viewHolder.appName = view.findViewById(R.id.appName); - viewHolder.pkgName = view.findViewById(R.id.pkgName); - viewHolder.appIcon = view.findViewById(R.id.appIcon); - viewHolder.verName = view.findViewById(R.id.verName); - viewHolder.verCode = view.findViewById(R.id.verCode); - - // 将ViewHolder存储在View中(即将控件的实例存储在其中) - view.setTag(viewHolder); - } else { - view = convertView; - viewHolder = (AppAdapter.ViewHolder) view.getTag(); - } - - // 获取控件实例,并调用set...方法使其显示出来 - if (appInfo != null) { - viewHolder.appName.setText(appInfo.getAppName()); - viewHolder.pkgName.setText(appInfo.getPkgName()); - viewHolder.appIcon.setBackground(appInfo.getAppIcon()); - viewHolder.verName.setText(appInfo.getVerName()); - viewHolder.verCode.setText(appInfo.getVerCode() + ""); - } - - return view; - } - - public void add(List appModels) { - if (list != null) { - list = appModels; - notifyDataSetChanged(); - } - } - - public void del(List appModels) { - if (list != null) { - list = appModels; - notifyDataSetChanged(); - } - } - - public void update(List appModels) { - if (list != null) { - list = appModels; - notifyDataSetChanged(); - } - } - - // 定义一个内部类,用于对控件的实例进行缓存 - static class ViewHolder { - TextView appName; - TextView pkgName; - ImageView appIcon; - TextView verName; - TextView verCode; - } -} diff --git a/app/src/main/java/com/idormy/sms/forwarder/adapter/AppListAdapter.kt b/app/src/main/java/com/idormy/sms/forwarder/adapter/AppListAdapter.kt new file mode 100644 index 00000000..8436bdfd --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/adapter/AppListAdapter.kt @@ -0,0 +1,65 @@ +package com.idormy.sms.forwarder.adapter + +import android.widget.ImageView +import com.idormy.sms.forwarder.R +import com.idormy.sms.forwarder.adapter.base.broccoli.BroccoliRecyclerAdapter +import com.idormy.sms.forwarder.utils.PlaceholderHelper +import com.xuexiang.xui.adapter.recyclerview.RecyclerViewHolder +import com.xuexiang.xui.widget.imageview.ImageLoader +import com.xuexiang.xutil.app.AppUtils +import com.xuexiang.xutil.app.AppUtils.AppInfo +import me.samlss.broccoli.Broccoli + +class AppListAdapter( + /** + * 是否是加载占位 + */ + private val mIsAnim: Boolean, +) : BroccoliRecyclerAdapter(AppUtils.getAppsInfo()) { + + override fun getItemLayoutId(viewType: Int): Int { + return R.layout.adapter_app_list_item + } + + /** + * 绑定控件 + * + * @param holder + * @param model + * @param position + */ + override fun onBindData(holder: RecyclerViewHolder?, model: AppInfo?, position: Int) { + if (holder == null || model == null) return + val ivAppIcon = holder.findViewById(R.id.iv_app_icon) + ImageLoader.get().loadImage(ivAppIcon, model.icon) + holder.text(R.id.tv_app_name, model.name) + holder.text(R.id.tv_pkg_name, model.packageName) + holder.text(R.id.tv_ver_name, model.versionName) + //holder.text(R.id.tv_ver_code, model.versionCode) + } + + /** + * 绑定占位控件 + * + * @param holder + * @param broccoli + */ + override fun onBindBroccoli(holder: RecyclerViewHolder?, broccoli: Broccoli?) { + if (holder == null || broccoli == null) return + if (mIsAnim) { + broccoli.addPlaceholder(PlaceholderHelper.getParameter(holder.findView(R.id.iv_app_icon))) + .addPlaceholder(PlaceholderHelper.getParameter(holder.findView(R.id.tv_app_name))) + .addPlaceholder(PlaceholderHelper.getParameter(holder.findView(R.id.tv_pkg_name))) + .addPlaceholder(PlaceholderHelper.getParameter(holder.findView(R.id.tv_ver_name))) + //.addPlaceholder(PlaceholderHelper.getParameter(holder.findView(R.id.tv_ver_code))) + } else { + broccoli.addPlaceholders( + holder.findView(R.id.iv_app_icon), + holder.findView(R.id.tv_app_name), + holder.findView(R.id.tv_pkg_name), + holder.findView(R.id.tv_ver_name), + //holder.findView(R.id.tv_ver_code) + ) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/adapter/FrpcPagingAdapter.kt b/app/src/main/java/com/idormy/sms/forwarder/adapter/FrpcPagingAdapter.kt new file mode 100644 index 00000000..499c6af4 --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/adapter/FrpcPagingAdapter.kt @@ -0,0 +1,77 @@ +package com.idormy.sms.forwarder.adapter + +import android.annotation.SuppressLint +import android.os.Build +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.paging.PagingDataAdapter +import androidx.recyclerview.widget.DiffUtil +import androidx.recyclerview.widget.RecyclerView +import com.idormy.sms.forwarder.R +import com.idormy.sms.forwarder.adapter.FrpcPagingAdapter.MyViewHolder +import com.idormy.sms.forwarder.database.entity.Frpc +import com.idormy.sms.forwarder.databinding.AdapterFrpcsCardViewListItemBinding +import com.xuexiang.xutil.resource.ResUtils.getColors +import frpclib.Frpclib + +class FrpcPagingAdapter(private val itemClickListener: OnItemClickListener) : PagingDataAdapter(diffCallback) { + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder { + val binding = AdapterFrpcsCardViewListItemBinding.inflate(LayoutInflater.from(parent.context), parent, false) + return MyViewHolder(binding) + } + + override fun onBindViewHolder(holder: MyViewHolder, position: Int) { + val item = getItem(position) + if (item != null) { + holder.binding.ivImage.setImageResource(R.drawable.ic_menu_frpc) + holder.binding.ivAutorun.setImageResource(item.autorunImageId) + holder.binding.tvName.text = item.name + + if (item.connecting || Frpclib.isRunning(item.uid)) { + holder.binding.ivPlay.setImageResource(R.drawable.ic_stop) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + holder.binding.ivPlay.imageTintList = getColors(R.color.colorStop) + } + } else { + holder.binding.ivPlay.setImageResource(R.drawable.ic_start) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + holder.binding.ivPlay.imageTintList = getColors(R.color.colorStart) + } + } + + holder.binding.ivEdit.setImageResource(R.drawable.ic_edit) + holder.binding.ivDelete.setImageResource(R.drawable.ic_delete) + + holder.binding.ivPlay.setOnClickListener { view: View? -> + itemClickListener.onItemClicked(view, item) + } + holder.binding.ivEdit.setOnClickListener { view: View? -> + itemClickListener.onItemClicked(view, item) + } + holder.binding.ivDelete.setOnClickListener { view: View? -> + itemClickListener.onItemClicked(view, item) + } + } + } + + class MyViewHolder(val binding: AdapterFrpcsCardViewListItemBinding) : RecyclerView.ViewHolder(binding.root) + interface OnItemClickListener { + fun onItemClicked(view: View?, item: Frpc) + fun onItemRemove(view: View?, id: Int) + } + + companion object { + var diffCallback: DiffUtil.ItemCallback = object : DiffUtil.ItemCallback() { + override fun areItemsTheSame(oldItem: Frpc, newItem: Frpc): Boolean { + return oldItem.uid == newItem.uid + } + + @SuppressLint("DiffUtilEquals") + override fun areContentsTheSame(oldItem: Frpc, newItem: Frpc): Boolean { + return oldItem === newItem + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/adapter/LogAdapter.java b/app/src/main/java/com/idormy/sms/forwarder/adapter/LogAdapter.java deleted file mode 100644 index 8661f4c9..00000000 --- a/app/src/main/java/com/idormy/sms/forwarder/adapter/LogAdapter.java +++ /dev/null @@ -1,124 +0,0 @@ -package com.idormy.sms.forwarder.adapter; - -import android.content.Context; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.ArrayAdapter; -import android.widget.ImageView; -import android.widget.TextView; - -import com.idormy.sms.forwarder.R; -import com.idormy.sms.forwarder.model.vo.LogVo; -import com.idormy.sms.forwarder.utils.TimeUtils; - -import java.util.List; - -@SuppressWarnings("unused") -public class LogAdapter extends ArrayAdapter { - private final int resourceId; - private List list; - - // 适配器的构造函数,把要适配的数据传入这里 - public LogAdapter(Context context, int textViewResourceId, List objects) { - super(context, textViewResourceId, objects); - resourceId = textViewResourceId; - list = objects; - } - - @Override - public int getCount() { - return list.size(); - } - - @Override - public LogVo getItem(int position) { - return list.get(position); - } - - @Override - public long getItemId(int position) { - return 0; - } - - // convertView 参数用于将之前加载好的布局进行缓存 - @Override - public View getView(int position, View convertView, ViewGroup parent) { - LogVo logVo = getItem(position); //获取当前项的TLog实例 - - // 加个判断,以免ListView每次滚动时都要重新加载布局,以提高运行效率 - View view; - ViewHolder viewHolder; - if (convertView == null) { - - // 避免ListView每次滚动时都要重新加载布局,以提高运行效率 - view = LayoutInflater.from(getContext()).inflate(resourceId, parent, false); - - // 避免每次调用getView()时都要重新获取控件实例 - viewHolder = new ViewHolder(); - viewHolder.tLogFrom = view.findViewById(R.id.tlog_from); - viewHolder.tLogContent = view.findViewById(R.id.tlog_content); - viewHolder.tLogRule = view.findViewById(R.id.tlog_rule); - viewHolder.tLogTime = view.findViewById(R.id.tlog_time); - viewHolder.senderImage = view.findViewById(R.id.tlog_sender_image); - viewHolder.statusImage = view.findViewById(R.id.tlog_status_image); - viewHolder.simImage = view.findViewById(R.id.tlog_sim_image); - - // 将ViewHolder存储在View中(即将控件的实例存储在其中) - view.setTag(viewHolder); - } else { - view = convertView; - viewHolder = (ViewHolder) view.getTag(); - } - - // 获取控件实例,并调用set...方法使其显示出来 - if (logVo != null) { - viewHolder.tLogFrom.setText(logVo.getFrom()); - viewHolder.tLogContent.setText(logVo.getContent()); - viewHolder.tLogRule.setText(logVo.getRule()); - viewHolder.tLogTime.setText(TimeUtils.friendlyTime(logVo.getTime())); - viewHolder.senderImage.setImageResource(logVo.getSenderImageId()); - viewHolder.simImage.setImageResource(logVo.getSimImageId()); - viewHolder.statusImage.setImageResource(logVo.getStatusImageId()); - } - - return view; - } - - public void add(List logVos) { - if (list != null) { - list = logVos; - notifyDataSetChanged(); - } - } - - public void del(List logVos) { - if (list != null) { - list = logVos; - notifyDataSetChanged(); - } - } - - public void update(List logVos) { - if (list != null) { - list = logVos; - notifyDataSetChanged(); - } - } - - public void onDateChange(List logVos) { - list = logVos; - notifyDataSetChanged(); - } - - // 定义一个内部类,用于对控件的实例进行缓存 - static class ViewHolder { - TextView tLogFrom; - TextView tLogContent; - TextView tLogRule; - TextView tLogTime; - ImageView senderImage; - ImageView simImage; - ImageView statusImage; - } -} \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/adapter/LogsPagingAdapter.kt b/app/src/main/java/com/idormy/sms/forwarder/adapter/LogsPagingAdapter.kt new file mode 100644 index 00000000..1eadb6ac --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/adapter/LogsPagingAdapter.kt @@ -0,0 +1,57 @@ +package com.idormy.sms.forwarder.adapter + +import android.annotation.SuppressLint +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.paging.PagingDataAdapter +import androidx.recyclerview.widget.DiffUtil +import androidx.recyclerview.widget.RecyclerView +import com.idormy.sms.forwarder.adapter.LogsPagingAdapter.MyViewHolder +import com.idormy.sms.forwarder.database.entity.LogsAndRuleAndSender +import com.idormy.sms.forwarder.database.entity.Sender +import com.idormy.sms.forwarder.databinding.AdapterLogsCardViewListItemBinding +import com.xuexiang.xutil.data.DateUtils + +class LogsPagingAdapter(private val itemClickListener: OnItemClickListener) : PagingDataAdapter(diffCallback) { + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder { + val binding = AdapterLogsCardViewListItemBinding.inflate(LayoutInflater.from(parent.context), parent, false) + return MyViewHolder(binding) + } + + override fun onBindViewHolder(holder: MyViewHolder, position: Int) { + val item = getItem(position) + if (item != null) { + holder.binding.tvFrom.text = item.logs.from + holder.binding.tvTime.text = DateUtils.getFriendlyTimeSpanByNow(item.logs.time) + holder.binding.tvContent.text = item.logs.content + holder.binding.ivSenderImage.setImageResource(Sender.getImageId(item.relation.sender.type)) + holder.binding.ivStatusImage.setImageResource(item.logs.statusImageId) + holder.binding.ivSimImage.setImageResource(item.logs.simImageId) + + holder.binding.cardView.setOnClickListener { view: View? -> + itemClickListener.onItemClicked(view, item) + } + } + } + + class MyViewHolder(val binding: AdapterLogsCardViewListItemBinding) : RecyclerView.ViewHolder(binding.root) + interface OnItemClickListener { + fun onItemClicked(view: View?, item: LogsAndRuleAndSender) + fun onItemRemove(view: View?, id: Int) + } + + companion object { + var diffCallback: DiffUtil.ItemCallback = object : DiffUtil.ItemCallback() { + override fun areItemsTheSame(oldItem: LogsAndRuleAndSender, newItem: LogsAndRuleAndSender): Boolean { + return oldItem.logs.id == newItem.logs.id + } + + @SuppressLint("DiffUtilEquals") + override fun areContentsTheSame(oldItem: LogsAndRuleAndSender, newItem: LogsAndRuleAndSender): Boolean { + return oldItem.logs === newItem.logs + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/adapter/RuleAdapter.java b/app/src/main/java/com/idormy/sms/forwarder/adapter/RuleAdapter.java deleted file mode 100644 index 7db40969..00000000 --- a/app/src/main/java/com/idormy/sms/forwarder/adapter/RuleAdapter.java +++ /dev/null @@ -1,127 +0,0 @@ -package com.idormy.sms.forwarder.adapter; - -import android.content.Context; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.ArrayAdapter; -import android.widget.ImageView; -import android.widget.TextView; - -import com.idormy.sms.forwarder.R; -import com.idormy.sms.forwarder.model.RuleModel; -import com.idormy.sms.forwarder.model.SenderModel; -import com.idormy.sms.forwarder.sender.SenderUtil; - -import java.util.List; - -public class RuleAdapter extends ArrayAdapter { - private final int resourceId; - private List list; - - // 适配器的构造函数,把要适配的数据传入这里 - public RuleAdapter(Context context, int textViewResourceId, List objects) { - super(context, textViewResourceId, objects); - resourceId = textViewResourceId; - list = objects; - } - - @Override - public int getCount() { - return list.size(); - } - - @Override - public RuleModel getItem(int position) { - return list.get(position); - } - - @Override - public long getItemId(int position) { - RuleModel item = list.get(position); - if (item == null) { - return 0; - } - return item.getId(); - } - - // convertView 参数用于将之前加载好的布局进行缓存 - @Override - public View getView(int position, View convertView, ViewGroup parent) { - RuleModel ruleModel = getItem(position); //获取当前项的TLog实例 - - // 加个判断,以免ListView每次滚动时都要重新加载布局,以提高运行效率 - View view; - ViewHolder viewHolder; - if (convertView == null) { - - // 避免ListView每次滚动时都要重新加载布局,以提高运行效率 - view = LayoutInflater.from(getContext()).inflate(resourceId, parent, false); - - // 避免每次调用getView()时都要重新获取控件实例 - viewHolder = new ViewHolder(); - viewHolder.ruleMatch = view.findViewById(R.id.rule_match); - viewHolder.ruleSender = view.findViewById(R.id.rule_sender); - viewHolder.ruleImage = view.findViewById(R.id.rule_image); - viewHolder.ruleStatus = view.findViewById(R.id.rule_status); - viewHolder.ruleSenderImage = view.findViewById(R.id.rule_sender_image); - viewHolder.ruleSenderStatus = view.findViewById(R.id.rule_sender_status); - - // 将ViewHolder存储在View中(即将控件的实例存储在其中) - view.setTag(viewHolder); - } else { - view = convertView; - viewHolder = (ViewHolder) view.getTag(); - } - - // 获取控件实例,并调用set...方法使其显示出来 - if (ruleModel != null) { - viewHolder.ruleImage.setImageResource(ruleModel.getImageId()); - viewHolder.ruleStatus.setImageResource(ruleModel.getStatusImageId()); - - List senderModel = SenderUtil.getSender(ruleModel.getSenderId(), null); - viewHolder.ruleMatch.setText(ruleModel.getRuleMatch()); - if (!senderModel.isEmpty()) { - viewHolder.ruleSender.setText(senderModel.get(0).getName()); - viewHolder.ruleSenderImage.setImageResource(senderModel.get(0).getImageId()); - viewHolder.ruleSenderStatus.setImageResource(senderModel.get(0).getStatusImageId()); - } else { - viewHolder.ruleSender.setText(""); - } - } - - return view; - } - - public void add(List ruleModels) { - if (list != null) { - list = ruleModels; - notifyDataSetChanged(); - } - } - - public void del(List ruleModels) { - if (list != null) { - list = ruleModels; - notifyDataSetChanged(); - } - } - - public void update(List ruleModels) { - if (list != null) { - list = ruleModels; - notifyDataSetChanged(); - } - } - - // 定义一个内部类,用于对控件的实例进行缓存 - static class ViewHolder { - TextView ruleMatch; - TextView ruleSender; - ImageView ruleImage; - ImageView ruleStatus; - ImageView ruleSenderImage; - ImageView ruleSenderStatus; - } - -} \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/adapter/RulePagingAdapter.kt b/app/src/main/java/com/idormy/sms/forwarder/adapter/RulePagingAdapter.kt new file mode 100644 index 00000000..f4ae8c74 --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/adapter/RulePagingAdapter.kt @@ -0,0 +1,68 @@ +package com.idormy.sms.forwarder.adapter + +import android.annotation.SuppressLint +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.paging.PagingDataAdapter +import androidx.recyclerview.widget.DiffUtil +import androidx.recyclerview.widget.RecyclerView +import com.idormy.sms.forwarder.R +import com.idormy.sms.forwarder.adapter.RulePagingAdapter.MyViewHolder +import com.idormy.sms.forwarder.database.entity.RuleAndSender +import com.idormy.sms.forwarder.databinding.AdapterRulesCardViewListItemBinding + +class RulePagingAdapter(private val itemClickListener: OnItemClickListener) : PagingDataAdapter(diffCallback) { + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder { + val binding = AdapterRulesCardViewListItemBinding.inflate(LayoutInflater.from(parent.context), parent, false) + return MyViewHolder(binding) + } + + override fun onBindViewHolder(holder: MyViewHolder, position: Int) { + val item = getItem(position) + if (item != null) { + holder.binding.ivRuleImage.setImageResource(item.rule.imageId) + holder.binding.ivRuleStatus.setImageResource(item.rule.statusImageId) + holder.binding.tvRuleMatch.text = item.rule.ruleMatch + holder.binding.ivSenderImage.setImageResource(item.sender.imageId) + holder.binding.ivSenderStatus.setImageResource(item.sender.statusImageId) + holder.binding.tvSenderName.text = item.sender.name + + /*holder.binding.cardView.setOnClickListener { view: View? -> + itemClickListener.onItemClicked(view, item) + }*/ + holder.binding.ivCopy.setImageResource(R.drawable.ic_copy) + holder.binding.ivEdit.setImageResource(R.drawable.ic_edit) + holder.binding.ivDelete.setImageResource(R.drawable.ic_delete) + holder.binding.ivCopy.setOnClickListener { view: View? -> + itemClickListener.onItemClicked(view, item) + } + holder.binding.ivEdit.setOnClickListener { view: View? -> + itemClickListener.onItemClicked(view, item) + } + holder.binding.ivDelete.setOnClickListener { view: View? -> + itemClickListener.onItemClicked(view, item) + } + } + } + + class MyViewHolder(val binding: AdapterRulesCardViewListItemBinding) : RecyclerView.ViewHolder(binding.root) + interface OnItemClickListener { + fun onItemClicked(view: View?, item: RuleAndSender) + fun onItemRemove(view: View?, id: Int) + } + + companion object { + var diffCallback: DiffUtil.ItemCallback = object : DiffUtil.ItemCallback() { + override fun areItemsTheSame(oldItem: RuleAndSender, newItem: RuleAndSender): Boolean { + return oldItem.rule.id == newItem.rule.id + } + + @SuppressLint("DiffUtilEquals") + override fun areContentsTheSame(oldItem: RuleAndSender, newItem: RuleAndSender): Boolean { + return oldItem.rule === newItem.rule + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/adapter/SenderAdapter.java b/app/src/main/java/com/idormy/sms/forwarder/adapter/SenderAdapter.java deleted file mode 100644 index cee01407..00000000 --- a/app/src/main/java/com/idormy/sms/forwarder/adapter/SenderAdapter.java +++ /dev/null @@ -1,132 +0,0 @@ -package com.idormy.sms.forwarder.adapter; - -import android.content.Context; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.ArrayAdapter; -import android.widget.ImageView; -import android.widget.TextView; - -import com.idormy.sms.forwarder.R; -import com.idormy.sms.forwarder.model.SenderModel; - -import java.util.List; - -@SuppressWarnings("unused") -public class SenderAdapter extends ArrayAdapter { - private final int resourceId; - private List list; - - // 适配器的构造函数,把要适配的数据传入这里 - public SenderAdapter(Context context, int textViewResourceId, List objects) { - super(context, textViewResourceId, objects); - resourceId = textViewResourceId; - list = objects; - } - - @Override - public int getCount() { - return list.size(); - } - - @Override - public SenderModel getItem(int position) { - return list.get(position); - } - - @Override - public long getItemId(int position) { - SenderModel item = list.get(position); - if (item == null) { - return 0; - } - return item.getId(); - } - - // convertView 参数用于将之前加载好的布局进行缓存 - @Override - public View getView(int position, View convertView, ViewGroup parent) { - SenderModel senderModel = getItem(position); //获取当前项的TLog实例 - - // 加个判断,以免ListView每次滚动时都要重新加载布局,以提高运行效率 - View view; - ViewHolder viewHolder; - if (convertView == null) { - - // 避免ListView每次滚动时都要重新加载布局,以提高运行效率 - view = LayoutInflater.from(getContext()).inflate(resourceId, parent, false); - - // 避免每次调用getView()时都要重新获取控件实例 - viewHolder = new ViewHolder(); - viewHolder.senderImage = view.findViewById(R.id.sender_image); - viewHolder.senderStatus = view.findViewById(R.id.sender_status); - viewHolder.senderName = view.findViewById(R.id.sender_name); - - // 将ViewHolder存储在View中(即将控件的实例存储在其中) - view.setTag(viewHolder); - } else { - view = convertView; - viewHolder = (ViewHolder) view.getTag(); - } - - // 获取控件实例,并调用set...方法使其显示出来 - if (senderModel != null) { - viewHolder.senderImage.setImageResource(senderModel.getImageId()); - viewHolder.senderStatus.setImageResource(senderModel.getStatusImageId()); - viewHolder.senderName.setText(senderModel.getName()); - } - - return view; - } - - public void add(SenderModel senderModel) { - if (list != null) { - list.add(senderModel); - notifyDataSetChanged(); - } - } - - public void del(int position) { - if (list != null) { - list.remove(position); - notifyDataSetChanged(); - } - } - - public void update(SenderModel senderModel, int position) { - if (list != null) { - list.set(position, senderModel); - notifyDataSetChanged(); - } - } - - public void add(List senderModels) { - if (list != null) { - list = senderModels; - notifyDataSetChanged(); - } - } - - public void del(List senderModels) { - if (list != null) { - list = senderModels; - notifyDataSetChanged(); - } - } - - public void update(List senderModels) { - if (list != null) { - list = senderModels; - notifyDataSetChanged(); - } - } - - // 定义一个内部类,用于对控件的实例进行缓存 - static class ViewHolder { - ImageView senderImage; - ImageView senderStatus; - TextView senderName; - } - -} \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/adapter/SenderPagingAdapter.kt b/app/src/main/java/com/idormy/sms/forwarder/adapter/SenderPagingAdapter.kt new file mode 100644 index 00000000..1f0757b3 --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/adapter/SenderPagingAdapter.kt @@ -0,0 +1,65 @@ +package com.idormy.sms.forwarder.adapter + +import android.annotation.SuppressLint +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.paging.PagingDataAdapter +import androidx.recyclerview.widget.DiffUtil +import androidx.recyclerview.widget.RecyclerView +import com.idormy.sms.forwarder.R +import com.idormy.sms.forwarder.adapter.SenderPagingAdapter.MyViewHolder +import com.idormy.sms.forwarder.database.entity.Sender +import com.idormy.sms.forwarder.databinding.AdapterSendersCardViewListItemBinding + +class SenderPagingAdapter(private val itemClickListener: OnItemClickListener) : PagingDataAdapter(diffCallback) { + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder { + val binding = AdapterSendersCardViewListItemBinding.inflate(LayoutInflater.from(parent.context), parent, false) + return MyViewHolder(binding) + } + + override fun onBindViewHolder(holder: MyViewHolder, position: Int) { + val item = getItem(position) + if (item != null) { + holder.binding.ivImage.setImageResource(item.imageId) + holder.binding.ivStatus.setImageResource(item.statusImageId) + holder.binding.tvName.text = item.name + + /*holder.binding.cardView.setOnClickListener { view: View? -> + itemClickListener.onItemClicked(view, item) + }*/ + holder.binding.ivCopy.setImageResource(R.drawable.ic_copy) + holder.binding.ivEdit.setImageResource(R.drawable.ic_edit) + holder.binding.ivDelete.setImageResource(R.drawable.ic_delete) + holder.binding.ivCopy.setOnClickListener { view: View? -> + itemClickListener.onItemClicked(view, item) + } + holder.binding.ivEdit.setOnClickListener { view: View? -> + itemClickListener.onItemClicked(view, item) + } + holder.binding.ivDelete.setOnClickListener { view: View? -> + itemClickListener.onItemClicked(view, item) + } + } + } + + class MyViewHolder(val binding: AdapterSendersCardViewListItemBinding) : RecyclerView.ViewHolder(binding.root) + interface OnItemClickListener { + fun onItemClicked(view: View?, item: Sender) + fun onItemRemove(view: View?, id: Int) + } + + companion object { + var diffCallback: DiffUtil.ItemCallback = object : DiffUtil.ItemCallback() { + override fun areItemsTheSame(oldItem: Sender, newItem: Sender): Boolean { + return oldItem.id == newItem.id + } + + @SuppressLint("DiffUtilEquals") + override fun areContentsTheSame(oldItem: Sender, newItem: Sender): Boolean { + return oldItem === newItem + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/adapter/WidgetItemAdapter.kt b/app/src/main/java/com/idormy/sms/forwarder/adapter/WidgetItemAdapter.kt new file mode 100644 index 00000000..39364d7c --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/adapter/WidgetItemAdapter.kt @@ -0,0 +1,20 @@ +package com.idormy.sms.forwarder.adapter + +import com.idormy.sms.forwarder.R +import com.xuexiang.xpage.model.PageInfo +import com.xuexiang.xui.adapter.recyclerview.BaseRecyclerAdapter +import com.xuexiang.xui.adapter.recyclerview.RecyclerViewHolder + +class WidgetItemAdapter(list: List) : BaseRecyclerAdapter(list) { + + public override fun getItemLayoutId(viewType: Int): Int { + return R.layout.layout_widget_item + } + + override fun bindData(holder: RecyclerViewHolder, position: Int, item: PageInfo) { + holder.text(R.id.item_name, item.name) + if (item.extra != 0) { + holder.image(R.id.item_icon, item.extra) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/adapter/base/broccoli/BroccoliRecyclerAdapter.kt b/app/src/main/java/com/idormy/sms/forwarder/adapter/base/broccoli/BroccoliRecyclerAdapter.kt new file mode 100644 index 00000000..bd943c0b --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/adapter/base/broccoli/BroccoliRecyclerAdapter.kt @@ -0,0 +1,67 @@ +package com.idormy.sms.forwarder.adapter.base.broccoli + +import android.view.View +import com.xuexiang.xui.adapter.recyclerview.BaseRecyclerAdapter +import com.xuexiang.xui.adapter.recyclerview.RecyclerViewHolder +import com.xuexiang.xui.adapter.recyclerview.XRecyclerAdapter +import me.samlss.broccoli.Broccoli + +/** + * 使用Broccoli占位的基础适配器 + * + * @author XUE + * @since 2019/4/8 16:33 + */ +abstract class BroccoliRecyclerAdapter(collection: Collection?) : + BaseRecyclerAdapter(collection) { + /** + * 是否已经加载成功 + */ + private var mHasLoad = false + private val mBroccoliMap: MutableMap = HashMap() + override fun bindData(holder: RecyclerViewHolder, position: Int, item: T) { + var broccoli = mBroccoliMap[holder.itemView] + if (broccoli == null) { + broccoli = Broccoli() + mBroccoliMap[holder.itemView] = broccoli + } + if (mHasLoad) { + broccoli.removeAllPlaceholders() + onBindData(holder, item, position) + } else { + onBindBroccoli(holder, broccoli) + broccoli.show() + } + } + + /** + * 绑定控件 + * + * @param holder + * @param model + * @param position + */ + protected abstract fun onBindData(holder: RecyclerViewHolder?, model: T, position: Int) + + /** + * 绑定占位控件 + * + * @param broccoli + */ + protected abstract fun onBindBroccoli(holder: RecyclerViewHolder?, broccoli: Broccoli?) + override fun refresh(collection: Collection): XRecyclerAdapter<*, *> { + mHasLoad = true + return super.refresh(collection) + } + + /** + * 资源释放,防止内存泄漏 + */ + fun recycle() { + for (broccoli in mBroccoliMap.values) { + broccoli.removeAllPlaceholders() + } + mBroccoliMap.clear() + clear() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/adapter/base/broccoli/BroccoliSimpleDelegateAdapter.kt b/app/src/main/java/com/idormy/sms/forwarder/adapter/base/broccoli/BroccoliSimpleDelegateAdapter.kt new file mode 100644 index 00000000..94a6d4c5 --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/adapter/base/broccoli/BroccoliSimpleDelegateAdapter.kt @@ -0,0 +1,85 @@ +@file:Suppress("unused") + +package com.idormy.sms.forwarder.adapter.base.broccoli + +import android.view.View +import com.alibaba.android.vlayout.LayoutHelper +import com.idormy.sms.forwarder.adapter.base.delegate.SimpleDelegateAdapter +import com.idormy.sms.forwarder.adapter.base.delegate.XDelegateAdapter +import com.xuexiang.xui.adapter.recyclerview.RecyclerViewHolder +import me.samlss.broccoli.Broccoli + +/** + * 使用Broccoli占位的基础适配器 + * + * @author xuexiang + * @since 2021/1/9 4:52 PM + */ +abstract class BroccoliSimpleDelegateAdapter : SimpleDelegateAdapter { + /** + * 是否已经加载成功 + */ + private var mHasLoad = false + private val mBroccoliMap: MutableMap = HashMap() + + constructor(layoutId: Int, layoutHelper: LayoutHelper) : super(layoutId, layoutHelper) + constructor(layoutId: Int, layoutHelper: LayoutHelper, list: Collection?) : super( + layoutId, + layoutHelper, + list + ) + + constructor(layoutId: Int, layoutHelper: LayoutHelper?, data: Array?) : super( + layoutId, + layoutHelper!!, + data + ) + + override fun bindData(holder: RecyclerViewHolder, position: Int, item: T) { + var broccoli = mBroccoliMap[holder.itemView] + if (broccoli == null) { + broccoli = Broccoli() + mBroccoliMap[holder.itemView] = broccoli + } + if (mHasLoad) { + broccoli.removeAllPlaceholders() + onBindData(holder, item, position) + } else { + onBindBroccoli(holder, broccoli) + broccoli.show() + } + } + + /** + * 绑定控件 + * + * @param holder + * @param model + * @param position + */ + protected abstract fun onBindData(holder: RecyclerViewHolder, model: T, position: Int) + + /** + * 绑定占位控件 + * + * @param holder + * @param broccoli + */ + protected abstract fun onBindBroccoli(holder: RecyclerViewHolder, broccoli: Broccoli) + + override fun refresh(collection: Collection?): XDelegateAdapter<*, *> { + mHasLoad = true + return super.refresh(collection) + } + + /** + * 资源释放,防止内存泄漏 + */ + fun recycle() { + for (broccoli in mBroccoliMap.values) { + broccoli.removeAllPlaceholders() + } + mBroccoliMap.clear() + clear() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/adapter/base/delegate/BaseDelegateAdapter.kt b/app/src/main/java/com/idormy/sms/forwarder/adapter/base/delegate/BaseDelegateAdapter.kt new file mode 100644 index 00000000..27561ea1 --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/adapter/base/delegate/BaseDelegateAdapter.kt @@ -0,0 +1,27 @@ +package com.idormy.sms.forwarder.adapter.base.delegate + +import android.view.ViewGroup +import com.xuexiang.xui.adapter.recyclerview.RecyclerViewHolder + +/** + * 通用的DelegateAdapter适配器 + * + * @author xuexiang + * @since 2020/3/20 12:44 AM + */ +abstract class BaseDelegateAdapter : XDelegateAdapter { + constructor() : super() + constructor(list: Collection?) : super(list) + constructor(data: Array?) : super(data) + + /** + * 适配的布局 + * + * @param viewType + * @return + */ + protected abstract fun getItemLayoutId(viewType: Int): Int + override fun getViewHolder(parent: ViewGroup, viewType: Int): RecyclerViewHolder { + return RecyclerViewHolder(inflateView(parent, getItemLayoutId(viewType))) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/adapter/base/delegate/SimpleDelegateAdapter.kt b/app/src/main/java/com/idormy/sms/forwarder/adapter/base/delegate/SimpleDelegateAdapter.kt new file mode 100644 index 00000000..ef03b98b --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/adapter/base/delegate/SimpleDelegateAdapter.kt @@ -0,0 +1,37 @@ +package com.idormy.sms.forwarder.adapter.base.delegate + +import com.alibaba.android.vlayout.LayoutHelper + +/** + * 简易DelegateAdapter适配器 + * + * @author xuexiang + * @since 2020/3/20 12:55 AM + */ +abstract class SimpleDelegateAdapter : BaseDelegateAdapter { + private var mLayoutId: Int + private var mLayoutHelper: LayoutHelper + + constructor(layoutId: Int, layoutHelper: LayoutHelper) : super() { + mLayoutId = layoutId + mLayoutHelper = layoutHelper + } + + constructor(layoutId: Int, layoutHelper: LayoutHelper, list: Collection?) : super(list) { + mLayoutId = layoutId + mLayoutHelper = layoutHelper + } + + constructor(layoutId: Int, layoutHelper: LayoutHelper, data: Array?) : super(data) { + mLayoutId = layoutId + mLayoutHelper = layoutHelper + } + + override fun getItemLayoutId(viewType: Int): Int { + return mLayoutId + } + + override fun onCreateLayoutHelper(): LayoutHelper { + return mLayoutHelper + } +} \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/adapter/base/delegate/XDelegateAdapter.kt b/app/src/main/java/com/idormy/sms/forwarder/adapter/base/delegate/XDelegateAdapter.kt new file mode 100644 index 00000000..89e13353 --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/adapter/base/delegate/XDelegateAdapter.kt @@ -0,0 +1,269 @@ +package com.idormy.sms.forwarder.adapter.base.delegate + +import android.annotation.SuppressLint +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.annotation.LayoutRes +import androidx.recyclerview.widget.RecyclerView +import com.alibaba.android.vlayout.DelegateAdapter + +/** + * 基础DelegateAdapter + * + * @author xuexiang + * @since 2020/3/20 12:17 AM + */ +@Suppress("unused") +abstract class XDelegateAdapter : DelegateAdapter.Adapter { + /** + * 数据源 + */ + private val mData: MutableList = ArrayList() + /** + * @return 当前列表的选中项 + */ + /** + * 当前点击的条目 + */ + private var selectPosition = -1 + + constructor() + constructor(list: Collection?) { + if (list != null) { + mData.addAll(list) + } + } + + constructor(data: Array?) { + if (data != null && data.isNotEmpty()) { + mData.addAll(listOf(*data)) + } + } + + /** + * 构建自定义的ViewHolder + * + * @param parent + * @param viewType + * @return + */ + protected abstract fun getViewHolder(parent: ViewGroup, viewType: Int): V + + /** + * 绑定数据 + * + * @param holder + * @param position 索引 + * @param item 列表项 + */ + protected abstract fun bindData(holder: V, position: Int, item: T) + + /** + * 加载布局获取控件 + * + * @param parent 父布局 + * @param layoutId 布局ID + * @return + */ + protected fun inflateView(parent: ViewGroup, @LayoutRes layoutId: Int): View { + return LayoutInflater.from(parent.context).inflate(layoutId, parent, false) + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): V { + return getViewHolder(parent, viewType) + } + + override fun onBindViewHolder(holder: V, position: Int) { + bindData(holder, position, mData[position]) + } + + /** + * 获取列表项 + * + * @param position + * @return + */ + private fun getItem(position: Int): T? { + return if (checkPosition(position)) mData[position] else null + } + + private fun checkPosition(position: Int): Boolean { + return position >= 0 && position <= mData.size - 1 + } + + val isEmpty: Boolean + get() = itemCount == 0 + + override fun getItemCount(): Int { + return mData.size + } + + /** + * @return 数据源 + */ + val data: List + get() = mData + + /** + * 给指定位置添加一项 + * + * @param pos + * @param item + * @return + */ + fun add(pos: Int, item: T): XDelegateAdapter<*, *> { + mData.add(pos, item) + notifyItemInserted(pos) + return this + } + + /** + * 在列表末端增加一项 + * + * @param item + * @return + */ + fun add(item: T): XDelegateAdapter<*, *> { + mData.add(item) + notifyItemInserted(mData.size - 1) + return this + } + + /** + * 删除列表中指定索引的数据 + * + * @param pos + * @return + */ + fun delete(pos: Int): XDelegateAdapter<*, *> { + mData.removeAt(pos) + notifyItemRemoved(pos) + return this + } + + /** + * 刷新列表中指定位置的数据 + * + * @param pos + * @param item + * @return + */ + fun refresh(pos: Int, item: T): XDelegateAdapter<*, *> { + mData[pos] = item + notifyItemChanged(pos) + return this + } + + /** + * 刷新列表数据 + * + * @param collection + * @return + */ + @SuppressLint("NotifyDataSetChanged") + open fun refresh(collection: Collection?): XDelegateAdapter<*, *> { + if (collection != null) { + mData.clear() + mData.addAll(collection) + selectPosition = -1 + notifyDataSetChanged() + } + return this + } + + /** + * 刷新列表数据 + * + * @param array + * @return + */ + @SuppressLint("NotifyDataSetChanged") + fun refresh(array: Array?): XDelegateAdapter<*, *> { + if (array != null && array.isNotEmpty()) { + mData.clear() + mData.addAll(listOf(*array)) + selectPosition = -1 + notifyDataSetChanged() + } + return this + } + + /** + * 加载更多 + * + * @param collection + * @return + */ + @SuppressLint("NotifyDataSetChanged") + fun loadMore(collection: Collection?): XDelegateAdapter<*, *> { + if (collection != null) { + mData.addAll(collection) + notifyDataSetChanged() + } + return this + } + + /** + * 加载更多 + * + * @param array + * @return + */ + @SuppressLint("NotifyDataSetChanged") + fun loadMore(array: Array?): XDelegateAdapter<*, *> { + if (array != null && array.isNotEmpty()) { + mData.addAll(listOf(*array)) + notifyDataSetChanged() + } + return this + } + + /** + * 添加一个 + * + * @param item + * @return + */ + @SuppressLint("NotifyDataSetChanged") + fun load(item: T?): XDelegateAdapter<*, *> { + if (item != null) { + mData.add(item) + notifyDataSetChanged() + } + return this + } + + /** + * 设置当前列表的选中项 + * + * @param selectPosition + * @return + */ + @SuppressLint("NotifyDataSetChanged") + fun setSelectPosition(selectPosition: Int): XDelegateAdapter<*, *> { + this.selectPosition = selectPosition + notifyDataSetChanged() + return this + } + + /** + * 获取当前列表选中项 + * + * @return 当前列表选中项 + */ + val selectItem: T? + get() = getItem(selectPosition) + + /** + * 清除数据 + */ + @SuppressLint("NotifyDataSetChanged") + fun clear() { + if (!isEmpty) { + mData.clear() + selectPosition = -1 + notifyDataSetChanged() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/adapter/spinner/AppListAdapterItem.kt b/app/src/main/java/com/idormy/sms/forwarder/adapter/spinner/AppListAdapterItem.kt new file mode 100644 index 00000000..aeee8a37 --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/adapter/spinner/AppListAdapterItem.kt @@ -0,0 +1,45 @@ +package com.idormy.sms.forwarder.adapter.spinner + +import android.graphics.drawable.Drawable +import com.xuexiang.xui.utils.ResUtils + +@Suppress("unused") +class AppListAdapterItem { + + var name: String = "" + var icon: Drawable? = null + var packageName: String? = null + //var packagePath: String? = null + //var versionName: String? = null + //var versionCode: Int = 0 + //var isSystem: Boolean = false + + + constructor(name: String, icon: Drawable?, packageName: String?) { + this.name = name + this.icon = icon + this.packageName = packageName + } + + constructor(name: String) : this(name, null, null) + constructor(name: String, drawableId: Int, packageName: String) : this(name, ResUtils.getDrawable(drawableId), packageName) + + //TODO:注意自定义实体,需要重写对象的toString方法 + override fun toString(): String { + return name + } + + companion object { + fun of(name: String): AppListAdapterItem { + return AppListAdapterItem(name) + } + + fun arrayof(title: Array): Array { + val array = arrayOfNulls(title.size) + for (i in array.indices) { + array[i] = AppListAdapterItem(title[i]) + } + return array + } + } +} diff --git a/app/src/main/java/com/idormy/sms/forwarder/adapter/spinner/AppListSpinnerAdapter.kt b/app/src/main/java/com/idormy/sms/forwarder/adapter/spinner/AppListSpinnerAdapter.kt new file mode 100644 index 00000000..90028aa2 --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/adapter/spinner/AppListSpinnerAdapter.kt @@ -0,0 +1,153 @@ +package com.idormy.sms.forwarder.adapter.spinner + +import android.annotation.SuppressLint +import android.os.Build +import android.text.Html +import android.text.TextUtils +import android.util.Log +import android.util.TypedValue +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.ImageView +import android.widget.TextView +import androidx.annotation.ColorInt +import androidx.annotation.DrawableRes +import com.idormy.sms.forwarder.R +import com.xuexiang.xui.utils.CollectionUtils +import com.xuexiang.xui.widget.spinner.editspinner.BaseEditSpinnerAdapter +import com.xuexiang.xui.widget.spinner.editspinner.EditSpinnerFilter + +@Suppress("unused", "NAME_SHADOWING", "SENSELESS_COMPARISON", "DEPRECATION") +class AppListSpinnerAdapter : BaseEditSpinnerAdapter, EditSpinnerFilter { + /** + * 选项的文字颜色 + */ + private var mTextColor = 0 + + /** + * 选项的文字大小 + */ + private var mTextSize = 0f + + /** + * 背景颜色 + */ + private var mBackgroundSelector = 0 + + /** + * 过滤关键词的选中颜色 + */ + private var mFilterColor = "#F15C58" + private var mIsFilterKey = false + + /** + * 构造方法 + * + * @param data 选项数据 + */ + constructor(data: List?) : super(data) + + /** + * 构造方法 + * + * @param data 选项数据 + */ + constructor(data: Array?) : super(data) + + override fun getEditSpinnerFilter(): EditSpinnerFilter { + return this + } + + override fun getView(position: Int, convertView: View?, parent: ViewGroup): View? { + var convertView = convertView + val holder: ViewHolder + if (convertView == null) { + convertView = LayoutInflater.from(parent.context).inflate(R.layout.item_spinner_with_icon, parent, false) + holder = ViewHolder(convertView, mTextColor, mTextSize, mBackgroundSelector) + convertView.tag = holder + } else { + holder = convertView.tag as ViewHolder + } + val item = CollectionUtils.getListItem(mDataSource, mIndexs[position]) as AppListAdapterItem + holder.iconView.setImageDrawable(item.icon) + //holder.titleView.text = Html.fromHtml(item.toString()) + holder.titleView.text = Html.fromHtml(getItem(position)) + return convertView + } + + override fun onFilter(keyword: String): Boolean { + mDisplayData.clear() + Log.d("AppListSpinnerAdapter", "keyword = $keyword") + Log.d("AppListSpinnerAdapter", "mIndexs.indices = ${mIndexs.indices}") + if (TextUtils.isEmpty(keyword)) { + initDisplayData(mDataSource) + for (i in mIndexs.indices) { + mIndexs[i] = i + } + } else { + try { + for (i in mDataSource.indices) { + if (getDataSourceString(i).contains(keyword, ignoreCase = true)) { + mIndexs[mDisplayData.size] = i + if (mIsFilterKey) { + mDisplayData.add(getDataSourceString(i).replaceFirst(keyword.toRegex(), "$keyword")) + } else { + mDisplayData.add(getDataSourceString(i)) + } + } + } + } catch (e: Exception) { + e.printStackTrace() + } + } + Log.d("AppListSpinnerAdapter", "mDisplayData = $mDisplayData") + notifyDataSetChanged() + return mDisplayData.size > 0 + } + + fun setTextColor(@ColorInt textColor: Int): AppListSpinnerAdapter<*> { + mTextColor = textColor + return this + } + + fun setTextSize(textSize: Float): AppListSpinnerAdapter<*> { + mTextSize = textSize + return this + } + + fun setBackgroundSelector(@DrawableRes backgroundSelector: Int): AppListSpinnerAdapter<*> { + mBackgroundSelector = backgroundSelector + return this + } + + fun setFilterColor(filterColor: String): AppListSpinnerAdapter<*> { + mFilterColor = filterColor + return this + } + + fun setIsFilterKey(isFilterKey: Boolean): AppListSpinnerAdapter<*> { + mIsFilterKey = isFilterKey + return this + } + + @Suppress("DEPRECATION") + @SuppressLint("ObsoleteSdkInt") + private class ViewHolder(convertView: View, @ColorInt textColor: Int, textSize: Float, @DrawableRes backgroundSelector: Int) { + val iconView: ImageView = convertView.findViewById(R.id.iv_icon) + val statusView: ImageView = convertView.findViewById(R.id.iv_status) + val titleView: TextView = convertView.findViewById(R.id.tv_title) + + init { + if (textColor > 0) titleView.setTextColor(textColor) + if (textSize > 0F) titleView.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize) + if (backgroundSelector != 0) titleView.setBackgroundResource(backgroundSelector) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { + val config = convertView.resources.configuration + if (config.layoutDirection == View.LAYOUT_DIRECTION_RTL) { + titleView.textDirection = View.TEXT_DIRECTION_RTL + } + } + } + } +} diff --git a/app/src/main/java/com/idormy/sms/forwarder/adapter/spinner/SenderAdapterItem.kt b/app/src/main/java/com/idormy/sms/forwarder/adapter/spinner/SenderAdapterItem.kt new file mode 100644 index 00000000..d9561af9 --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/adapter/spinner/SenderAdapterItem.kt @@ -0,0 +1,92 @@ +package com.idormy.sms.forwarder.adapter.spinner + +import android.content.Context +import android.graphics.drawable.Drawable +import com.xuexiang.xui.utils.ResUtils + +@Suppress("unused") +class SenderAdapterItem { + + //标题内容 + var title: CharSequence + + //图标 + var icon: Drawable? = null + + //ID + var id: Long? = 0L + + //状态 + var status: Int? = 1 + + constructor(title: CharSequence) { + this.title = title + } + + constructor(title: CharSequence, icon: Drawable?) { + this.title = title + this.icon = icon + } + + constructor(title: CharSequence, icon: Drawable?, id: Long?) { + this.title = title + this.icon = icon + this.id = id + } + + constructor(title: CharSequence, icon: Drawable?, id: Long?, status: Int?) { + this.title = title + this.icon = icon + this.id = id + this.status = status + } + + constructor(title: CharSequence, drawableId: Int) : this(title, ResUtils.getDrawable(drawableId)) + constructor(title: CharSequence, drawableId: Int, id: Long) : this(title, ResUtils.getDrawable(drawableId), id) + constructor(title: CharSequence, drawableId: Int, id: Long, status: Int) : this(title, ResUtils.getDrawable(drawableId), id, status) + constructor(context: Context?, titleId: Int, drawableId: Int) : this(ResUtils.getString(titleId), ResUtils.getDrawable(context, drawableId)) + constructor(context: Context?, titleId: Int, drawableId: Int, id: Long) : this(ResUtils.getString(titleId), ResUtils.getDrawable(context, drawableId), id) + constructor(context: Context?, titleId: Int, drawableId: Int, id: Long, status: Int) : this(ResUtils.getString(titleId), ResUtils.getDrawable(context, drawableId), id, status) + constructor(context: Context?, title: CharSequence, drawableId: Int) : this(title, ResUtils.getDrawable(context, drawableId)) + constructor(context: Context?, title: CharSequence, drawableId: Int, id: Long) : this(title, ResUtils.getDrawable(context, drawableId), id) + constructor(context: Context?, title: CharSequence, drawableId: Int, id: Long, status: Int) : this(title, ResUtils.getDrawable(context, drawableId), id, status) + + fun setStatus(status: Int): SenderAdapterItem { + this.status = status + return this + } + + fun setId(id: Long): SenderAdapterItem { + this.id = id + return this + } + + fun setTitle(title: CharSequence): SenderAdapterItem { + this.title = title + return this + } + + fun setIcon(icon: Drawable?): SenderAdapterItem { + this.icon = icon + return this + } + + //TODO:注意自定义实体,需要重写对象的toString方法 + override fun toString(): String { + return title.toString() + } + + companion object { + fun of(title: CharSequence): SenderAdapterItem { + return SenderAdapterItem(title) + } + + fun arrayof(title: Array): Array { + val array = arrayOfNulls(title.size) + for (i in array.indices) { + array[i] = SenderAdapterItem(title[i]) + } + return array + } + } +} diff --git a/app/src/main/java/com/idormy/sms/forwarder/adapter/spinner/SenderSpinnerAdapter.kt b/app/src/main/java/com/idormy/sms/forwarder/adapter/spinner/SenderSpinnerAdapter.kt new file mode 100644 index 00000000..a0ff255d --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/adapter/spinner/SenderSpinnerAdapter.kt @@ -0,0 +1,163 @@ +package com.idormy.sms.forwarder.adapter.spinner + +import android.annotation.SuppressLint +import android.os.Build +import android.text.Html +import android.text.TextUtils +import android.util.Log +import android.util.TypedValue +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.ImageView +import android.widget.TextView +import androidx.annotation.ColorInt +import androidx.annotation.DrawableRes +import com.idormy.sms.forwarder.R +import com.idormy.sms.forwarder.utils.STATUS_OFF +import com.xuexiang.xui.utils.CollectionUtils +import com.xuexiang.xui.utils.ResUtils +import com.xuexiang.xui.widget.spinner.editspinner.BaseEditSpinnerAdapter +import com.xuexiang.xui.widget.spinner.editspinner.EditSpinnerFilter + +@Suppress("unused", "NAME_SHADOWING", "SENSELESS_COMPARISON", "DEPRECATION") +class SenderSpinnerAdapter : BaseEditSpinnerAdapter, EditSpinnerFilter { + /** + * 选项的文字颜色 + */ + private var mTextColor = 0 + + /** + * 选项的文字大小 + */ + private var mTextSize = 0f + + /** + * 背景颜色 + */ + private var mBackgroundSelector = 0 + + /** + * 过滤关键词的选中颜色 + */ + private var mFilterColor = "#F15C58" + private var mIsFilterKey = false + + /** + * 构造方法 + * + * @param data 选项数据 + */ + constructor(data: List?) : super(data) + + /** + * 构造方法 + * + * @param data 选项数据 + */ + constructor(data: Array?) : super(data) + + override fun getEditSpinnerFilter(): EditSpinnerFilter { + return this + } + + override fun getView(position: Int, convertView: View?, parent: ViewGroup): View? { + var convertView = convertView + val holder: ViewHolder + if (convertView == null) { + convertView = LayoutInflater.from(parent.context).inflate(R.layout.item_spinner_with_icon, parent, false) + holder = ViewHolder(convertView, mTextColor, mTextSize, mBackgroundSelector) + convertView.tag = holder + } else { + holder = convertView.tag as ViewHolder + } + val item = CollectionUtils.getListItem(mDataSource, mIndexs[position]) as SenderAdapterItem + holder.iconView.setImageDrawable(item.icon) + holder.statusView.setImageDrawable( + ResUtils.getDrawable( + when (item.status) { + STATUS_OFF -> R.drawable.icon_off + else -> R.drawable.icon_on + } + ) + ) + //holder.titleView.text = Html.fromHtml(item.toString()) + holder.titleView.text = Html.fromHtml(getItem(position)) + return convertView + } + + override fun onFilter(keyword: String): Boolean { + mDisplayData.clear() + Log.d("SenderSpinnerAdapter", "keyword = $keyword") + Log.d("SenderSpinnerAdapter", "mIndexs.indices = ${mIndexs.indices}") + if (TextUtils.isEmpty(keyword)) { + initDisplayData(mDataSource) + for (i in mIndexs.indices) { + mIndexs[i] = i + } + } else { + try { + for (i in mDataSource.indices) { + if (getDataSourceString(i).contains(keyword, ignoreCase = true)) { + mIndexs[mDisplayData.size] = i + if (mIsFilterKey) { + mDisplayData.add(getDataSourceString(i).replaceFirst(keyword.toRegex(), "$keyword")) + } else { + mDisplayData.add(getDataSourceString(i)) + } + } + } + } catch (e: Exception) { + e.printStackTrace() + } + } + Log.d("SenderSpinnerAdapter", "mDisplayData = $mDisplayData") + notifyDataSetChanged() + return mDisplayData.size > 0 + } + + fun setTextColor(@ColorInt textColor: Int): SenderSpinnerAdapter<*> { + mTextColor = textColor + return this + } + + fun setTextSize(textSize: Float): SenderSpinnerAdapter<*> { + mTextSize = textSize + return this + } + + fun setBackgroundSelector(@DrawableRes backgroundSelector: Int): SenderSpinnerAdapter<*> { + mBackgroundSelector = backgroundSelector + return this + } + + fun setFilterColor(filterColor: String): SenderSpinnerAdapter<*> { + mFilterColor = filterColor + return this + } + + fun setIsFilterKey(isFilterKey: Boolean): SenderSpinnerAdapter<*> { + mIsFilterKey = isFilterKey + return this + } + + @Suppress("DEPRECATION") + @SuppressLint("ObsoleteSdkInt") + private class ViewHolder(convertView: View, @ColorInt textColor: Int, textSize: Float, @DrawableRes backgroundSelector: Int) { + val iconView: ImageView = convertView.findViewById(R.id.iv_icon) + val statusView: ImageView = convertView.findViewById(R.id.iv_status) + val titleView: TextView = convertView.findViewById(R.id.tv_title) + + init { + if (textColor > 0) titleView.setTextColor(textColor) + if (textSize > 0F) titleView.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize) + if (backgroundSelector != 0) titleView.setBackgroundResource(backgroundSelector) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { + val config = convertView.resources.configuration + if (config.layoutDirection == View.LAYOUT_DIRECTION_RTL) { + titleView.textDirection = View.TEXT_DIRECTION_RTL + } + } + } + } +} diff --git a/app/src/main/java/com/idormy/sms/forwarder/core/BaseActivity.kt b/app/src/main/java/com/idormy/sms/forwarder/core/BaseActivity.kt new file mode 100644 index 00000000..a36e7205 --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/core/BaseActivity.kt @@ -0,0 +1,157 @@ +package com.idormy.sms.forwarder.core + +import android.content.Context +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import androidx.viewbinding.ViewBinding +import com.xuexiang.xpage.base.XPageActivity +import com.xuexiang.xpage.base.XPageFragment +import com.xuexiang.xpage.core.CoreSwitchBean +import com.xuexiang.xrouter.facade.service.SerializationService +import com.xuexiang.xrouter.launcher.XRouter +import com.xuexiang.xui.utils.ResUtils +import com.xuexiang.xui.widget.slideback.SlideBack +import io.github.inflationx.viewpump.ViewPumpContextWrapper + +/** + * 基础容器Activity + * + * @author XUE + * @since 2019/3/22 11:21 + */ +@Suppress("MemberVisibilityCanBePrivate", "UNCHECKED_CAST") +open class BaseActivity : XPageActivity() { + /** + * 获取Binding + * + * @return Binding + */ + /** + * ViewBinding + */ + var binding: Binding? = null + protected set + + override fun attachBaseContext(newBase: Context) { + //注入字体 + super.attachBaseContext(ViewPumpContextWrapper.wrap(newBase)) + } + + override fun getCustomRootView(): View? { + binding = viewBindingInflate(layoutInflater) + return if (binding != null) binding!!.root else null + } + + override fun onCreate(savedInstanceState: Bundle?) { + initStatusBarStyle() + super.onCreate(savedInstanceState) + registerSlideBack() + } + + /** + * 构建ViewBinding + * + * @param inflater inflater + * @return ViewBinding + */ + protected open fun viewBindingInflate(inflater: LayoutInflater?): Binding? { + return null + } + + /** + * 初始化状态栏的样式 + */ + protected open fun initStatusBarStyle() {} + + /** + * 打开fragment + * + * @param clazz 页面类 + * @param addToBackStack 是否添加到栈中 + * @return 打开的fragment对象 + */ + fun openPage(clazz: Class?, addToBackStack: Boolean): T { + val page = CoreSwitchBean(clazz) + .setAddToBackStack(addToBackStack) + return openPage(page) as T + } + + /** + * 打开fragment + * + * @return 打开的fragment对象 + */ + fun openNewPage(clazz: Class?): T { + val page = CoreSwitchBean(clazz) + .setNewActivity(true) + return openPage(page) as T + } + + /** + * 切换fragment + * + * @param clazz 页面类 + * @return 打开的fragment对象 + */ + fun switchPage(clazz: Class?): T { + return openPage(clazz, false) + } + + /** + * 序列化对象 + * + * @param object + * @return + */ + fun serializeObject(`object`: Any?): String { + return XRouter.getInstance().navigation(SerializationService::class.java) + .object2Json(`object`) + } + + override fun onRelease() { + unregisterSlideBack() + super.onRelease() + } + + /** + * 注册侧滑回调 + */ + protected fun registerSlideBack() { + if (isSupportSlideBack) { + SlideBack.with(this) + .haveScroll(true) + .edgeMode(if (ResUtils.isRtl()) SlideBack.EDGE_RIGHT else SlideBack.EDGE_LEFT) + .callBack { popPage() } + .register() + } + } + + /** + * 注销侧滑回调 + */ + protected fun unregisterSlideBack() { + if (isSupportSlideBack) { + SlideBack.unregister(this) + } + } + + /** + * @return 是否支持侧滑返回 + */ + protected open val isSupportSlideBack: Boolean + get() { + val page: CoreSwitchBean? = intent.getParcelableExtra(CoreSwitchBean.KEY_SWITCH_BEAN) + return page == null || page.bundle == null || page.bundle.getBoolean( + KEY_SUPPORT_SLIDE_BACK, + true + ) + } + + companion object { + /** + * 是否支持侧滑返回 + */ + const val KEY_SUPPORT_SLIDE_BACK = "key_support_slide_back" + } +} \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/core/BaseContainerFragment.kt b/app/src/main/java/com/idormy/sms/forwarder/core/BaseContainerFragment.kt new file mode 100644 index 00000000..053eae9d --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/core/BaseContainerFragment.kt @@ -0,0 +1,87 @@ +package com.idormy.sms.forwarder.core + +import android.content.res.Configuration +import android.view.View +import android.view.ViewGroup +import android.widget.AdapterView +import com.umeng.analytics.MobclickAgent +import com.xuexiang.xaop.annotation.SingleClick +import com.xuexiang.xpage.base.XPageContainerListFragment +import com.xuexiang.xui.widget.actionbar.TitleBar +import com.xuexiang.xui.widget.actionbar.TitleUtils + +/** + * 修改列表样式为主副标题显示 + * + * @author xuexiang + * @since 2018/11/22 上午11:26 + */ +@Suppress("unused") +abstract class BaseContainerFragment : XPageContainerListFragment() { + override fun initPage() { + initTitle() + initViews() + initListeners() + } + + protected fun initTitle(): TitleBar { + return TitleUtils.addTitleBarDynamic( + rootView as ViewGroup, + pageTitle + ) { popToBack() } + } + + override fun initData() { + mSimpleData = initSimpleData(mSimpleData) + val data: MutableList?> = ArrayList() + for (content in mSimpleData) { + val item: MutableMap = HashMap() + val index = content.indexOf("\n") + if (index > 0) { + item[SimpleListAdapter.KEY_TITLE] = content.subSequence(0, index).toString() + item[SimpleListAdapter.KEY_SUB_TITLE] = + content.subSequence(index + 1, content.length).toString() + } else { + item[SimpleListAdapter.KEY_TITLE] = content + item[SimpleListAdapter.KEY_SUB_TITLE] = "" + } + data.add(item) + } + listView.adapter = SimpleListAdapter(context, data) + initSimply() + } + + override fun onItemClick(adapterView: AdapterView<*>?, view: View, position: Int, id: Long) { + onItemClick(view, position) + } + + @SingleClick + private fun onItemClick(view: View, position: Int) { + onItemClick(position) + } + + override fun onDestroyView() { + listView.onItemClickListener = null + super.onDestroyView() + } + + override fun onConfigurationChanged(newConfig: Configuration) { + //屏幕旋转时刷新一下title + super.onConfigurationChanged(newConfig) + val root = rootView as ViewGroup + if (root.getChildAt(0) is TitleBar) { + root.removeViewAt(0) + initTitle() + } + } + + override fun onResume() { + super.onResume() + MobclickAgent.onPageStart(pageName) + } + + override fun onPause() { + super.onPause() + MobclickAgent.onPageEnd(pageName) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/core/BaseFragment.kt b/app/src/main/java/com/idormy/sms/forwarder/core/BaseFragment.kt new file mode 100644 index 00000000..465ef3b7 --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/core/BaseFragment.kt @@ -0,0 +1,391 @@ +package com.idormy.sms.forwarder.core + +import android.app.Activity +import android.content.Context +import android.content.res.Configuration +import android.os.Parcelable +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.Fragment +import androidx.viewbinding.ViewBinding +import com.idormy.sms.forwarder.App +import com.idormy.sms.forwarder.core.http.loader.ProgressLoader +import com.umeng.analytics.MobclickAgent +import com.xuexiang.xhttp2.subsciber.impl.IProgressLoader +import com.xuexiang.xpage.base.XPageActivity +import com.xuexiang.xpage.base.XPageFragment +import com.xuexiang.xpage.core.PageOption +import com.xuexiang.xpage.enums.CoreAnim +import com.xuexiang.xpage.utils.Utils +import com.xuexiang.xrouter.facade.service.SerializationService +import com.xuexiang.xrouter.launcher.XRouter +import com.xuexiang.xui.widget.actionbar.TitleBar +import com.xuexiang.xui.widget.actionbar.TitleUtils +import java.io.Serializable +import java.lang.reflect.Type + + +/** + * 基础fragment,使用XPage框架搭建 + * + * + * 具体使用参见:https://github.com/xuexiangjys/XPage/wiki + * + * @author xuexiang + * @since 2018/5/25 下午3:44 + */ +@Suppress("MemberVisibilityCanBePrivate") +abstract class BaseFragment : XPageFragment() { + private var mIProgressLoader: IProgressLoader? = null + + /** + * ViewBinding + */ + var binding: Binding? = null + protected set + + override fun inflateView(inflater: LayoutInflater, container: ViewGroup): View { + binding = viewBindingInflate(inflater, container) + return binding!!.root + } + + /** + * 构建ViewBinding + * + * @param inflater inflater + * @param container 容器 + * @return ViewBinding + */ + protected abstract fun viewBindingInflate( + inflater: LayoutInflater, + container: ViewGroup, + ): Binding + + private var activity: Activity? = null + + override fun getContext(): Context? { + return if (activity == null) App.context else activity + } + + override fun initPage() { + activity = getActivity() + initTitle() + initViews() + initListeners() + } + + protected open fun initTitle(): TitleBar? { + return TitleUtils.addTitleBarDynamic( + rootView as ViewGroup, + pageTitle + ) { popToBack() } + } + + override fun initListeners() {} + + /** + * 获取进度条加载者 + * + * @return 进度条加载者 + */ + val progressLoader: IProgressLoader? + get() { + if (mIProgressLoader == null) { + mIProgressLoader = ProgressLoader.create(context) + } + return mIProgressLoader + } + + /** + * 获取进度条加载者 + * + * @param message 提示信息 + * @return 进度条加载者 + */ + fun getProgressLoader(message: String?): IProgressLoader? { + if (mIProgressLoader == null) { + mIProgressLoader = ProgressLoader.create(context, message) + } else { + mIProgressLoader!!.updateMessage(message) + } + return mIProgressLoader + } + + override fun onConfigurationChanged(newConfig: Configuration) { + //屏幕旋转时刷新一下title + super.onConfigurationChanged(newConfig) + val root = rootView as ViewGroup + if (root.getChildAt(0) is TitleBar) { + root.removeViewAt(0) + initTitle() + } + } + + override fun onDestroyView() { + if (mIProgressLoader != null) { + mIProgressLoader!!.dismissLoading() + } + super.onDestroyView() + binding = null + } + + override fun onResume() { + super.onResume() + MobclickAgent.onPageStart(pageName) + } + + override fun onPause() { + super.onPause() + MobclickAgent.onPageEnd(pageName) + } + //==============================页面跳转api===================================// + /** + * 打开一个新的页面【建议只在主tab页使用】 + * + * @param clazz 页面的类 + * @param + * @return + */ + fun openNewPage(clazz: Class): Fragment? { + return PageOption.to(clazz) + .setNewActivity(true) + .open(this) + } + + /** + * 打开一个新的页面【建议只在主tab页使用】 + * + * @param pageName 页面名 + * @param + * @return + */ + fun openNewPage(pageName: String): Fragment? { + return PageOption.to(pageName) + .setAnim(CoreAnim.slide) + .setNewActivity(true) + .open(this) + } + + /** + * 打开一个新的页面【建议只在主tab页使用】 + * + * @param clazz 页面的类 + * @param containActivityClazz 页面容器 + * @param + * @return + */ + fun openNewPage( + clazz: Class, + containActivityClazz: Class, + ): Fragment? { + return PageOption.to(clazz) + .setNewActivity(true) + .setContainActivityClazz(containActivityClazz) + .open(this) + } + + /** + * 打开一个新的页面【建议只在主tab页使用】 + * + * @param clazz 页面的类 + * @param key 入参的键 + * @param value 入参的值 + * @param + * @return + */ + fun openNewPage(clazz: Class, key: String, value: Any?): Fragment? { + val option = PageOption.to(clazz).setNewActivity(true) + return openPage(option, key, value) + } + + private fun openPage(option: PageOption, key: String?, value: Any?): Fragment? { + when (value) { + is Int -> { + option.putInt(key, value) + } + is Float -> { + option.putFloat(key, value) + } + is String -> { + option.putString(key, value) + } + is Boolean -> { + option.putBoolean(key, value) + } + is Long -> { + option.putLong(key, value) + } + is Double -> { + option.putDouble(key, value) + } + is Parcelable -> { + option.putParcelable(key, value) + } + is Serializable -> { + option.putSerializable(key, value) + } + else -> { + option.putString(key, serializeObject(value)) + } + } + return option.open(this) + } + + /** + * 打开页面 + * + * @param clazz 页面的类 + * @param addToBackStack 是否加入回退栈 + * @param key 入参的键 + * @param value 入参的值 + * @param + * @return + */ + fun openPage( + clazz: Class?, + addToBackStack: Boolean, + key: String?, + value: String?, + ): Fragment? { + return PageOption(clazz) + .setAddToBackStack(addToBackStack) + .putString(key, value) + .open(this) + } + + /** + * 打开页面 + * + * @param clazz 页面的类 + * @param key 入参的键 + * @param value 入参的值 + * @param + * @return + */ + fun openPage(clazz: Class?, key: String?, value: Any?): Fragment? { + return openPage(clazz, true, key, value) + } + + /** + * 打开页面 + * + * @param clazz 页面的类 + * @param addToBackStack 是否加入回退栈 + * @param key 入参的键 + * @param value 入参的值 + * @param + * @return + */ + fun openPage( + clazz: Class?, + addToBackStack: Boolean, + key: String?, + value: Any?, + ): Fragment? { + val option = PageOption(clazz).setAddToBackStack(addToBackStack) + return openPage(option, key, value) + } + + /** + * 打开页面 + * + * @param clazz 页面的类 + * @param key 入参的键 + * @param value 入参的值 + * @param + * @return + */ + fun openPage(clazz: Class?, key: String?, value: String?): Fragment? { + return PageOption(clazz) + .putString(key, value) + .open(this) + } + + /** + * 打开页面,需要结果返回 + * + * @param clazz 页面的类 + * @param key 入参的键 + * @param value 入参的值 + * @param requestCode 请求码 + * @param + * @return + */ + fun openPageForResult( + clazz: Class?, + key: String?, + value: Any?, + requestCode: Int, + ): Fragment? { + val option = PageOption(clazz).setRequestCode(requestCode) + return openPage(option, key, value) + } + + /** + * 打开页面,需要结果返回 + * + * @param clazz 页面的类 + * @param key 入参的键 + * @param value 入参的值 + * @param requestCode 请求码 + * @param + * @return + */ + fun openPageForResult( + clazz: Class?, + key: String?, + value: String?, + requestCode: Int, + ): Fragment? { + return PageOption(clazz) + .setRequestCode(requestCode) + .putString(key, value) + .open(this) + } + + /** + * 打开页面,需要结果返回 + * + * @param clazz 页面的类 + * @param requestCode 请求码 + * @param + * @return + */ + fun openPageForResult(clazz: Class?, requestCode: Int): Fragment? { + return PageOption(clazz) + .setRequestCode(requestCode) + .open(this) + } + + /** + * 序列化对象 + * + * @param object 需要序列化的对象 + * @return 序列化结果 + */ + fun serializeObject(`object`: Any?): String { + return XRouter.getInstance().navigation(SerializationService::class.java) + .object2Json(`object`) + } + + /** + * 反序列化对象 + * + * @param input 反序列化的内容 + * @param clazz 类型 + * @return 反序列化结果 + */ + fun deserializeObject(input: String?, clazz: Type?): T { + return XRouter.getInstance().navigation(SerializationService::class.java) + .parseObject(input, clazz) + } + + override fun hideCurrentPageSoftInput() { + if (activity == null) { + return + } + // 记住,要在xml的父布局加上android:focusable="true" 和 android:focusableInTouchMode="true" + Utils.hideSoftInputClearFocus(requireActivity().currentFocus) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/core/BaseSimpleListFragment.kt b/app/src/main/java/com/idormy/sms/forwarder/core/BaseSimpleListFragment.kt new file mode 100644 index 00000000..694a0bcc --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/core/BaseSimpleListFragment.kt @@ -0,0 +1,286 @@ +package com.idormy.sms.forwarder.core + +import android.content.res.Configuration +import android.os.Parcelable +import android.view.ViewGroup +import androidx.fragment.app.Fragment +import com.umeng.analytics.MobclickAgent +import com.xuexiang.xpage.base.XPageActivity +import com.xuexiang.xpage.base.XPageFragment +import com.xuexiang.xpage.base.XPageSimpleListFragment +import com.xuexiang.xpage.core.PageOption +import com.xuexiang.xpage.enums.CoreAnim +import com.xuexiang.xrouter.facade.service.SerializationService +import com.xuexiang.xrouter.launcher.XRouter +import com.xuexiang.xui.widget.actionbar.TitleBar +import com.xuexiang.xui.widget.actionbar.TitleUtils +import java.io.Serializable + +/** + * @author xuexiang + * @since 2018/12/29 下午12:41 + */ +@Suppress("unused", "MemberVisibilityCanBePrivate") +abstract class BaseSimpleListFragment : XPageSimpleListFragment() { + override fun initPage() { + initTitle() + initViews() + initListeners() + } + + protected fun initTitle(): TitleBar { + return TitleUtils.addTitleBarDynamic( + rootView as ViewGroup, + pageTitle + ) { popToBack() } + } + + override fun onConfigurationChanged(newConfig: Configuration) { + //屏幕旋转时刷新一下title + super.onConfigurationChanged(newConfig) + val root = rootView as ViewGroup + if (root.getChildAt(0) is TitleBar) { + root.removeViewAt(0) + initTitle() + } + } + + override fun onResume() { + super.onResume() + MobclickAgent.onPageStart(pageName) + } + + override fun onPause() { + super.onPause() + MobclickAgent.onPageEnd(pageName) + } + //==============================页面跳转api===================================// + /** + * 打开一个新的页面【建议只在主tab页使用】 + * + * @param clazz 页面的类 + * @param + * @return + */ + fun openNewPage(clazz: Class?): Fragment? { + return PageOption(clazz) + .setNewActivity(true) + .open(this) + } + + /** + * 打开一个新的页面【建议只在主tab页使用】 + * + * @param pageName 页面名 + * @param + * @return + */ + fun openNewPage(pageName: String?): Fragment? { + return PageOption(pageName) + .setAnim(CoreAnim.slide) + .setNewActivity(true) + .open(this) + } + + /** + * 打开一个新的页面【建议只在主tab页使用】 + * + * @param clazz 页面的类 + * @param containActivityClazz 页面容器 + * @param + * @return + */ + fun openNewPage( + clazz: Class?, + containActivityClazz: Class, + ): Fragment? { + return PageOption(clazz) + .setNewActivity(true) + .setContainActivityClazz(containActivityClazz) + .open(this) + } + + /** + * 打开一个新的页面【建议只在主tab页使用】 + * + * @param clazz 页面的类 + * @param key 入参的键 + * @param value 入参的值 + * @param + * @return + */ + fun openNewPage(clazz: Class?, key: String?, value: Any?): Fragment? { + val option = PageOption(clazz).setNewActivity(true) + return openPage(option, key, value) + } + + private fun openPage(option: PageOption, key: String?, value: Any?): Fragment? { + when (value) { + is Int -> { + option.putInt(key, value) + } + is Float -> { + option.putFloat(key, value) + } + is String -> { + option.putString(key, value) + } + is Boolean -> { + option.putBoolean(key, value) + } + is Long -> { + option.putLong(key, value) + } + is Double -> { + option.putDouble(key, value) + } + is Parcelable -> { + option.putParcelable(key, value) + } + is Serializable -> { + option.putSerializable(key, value) + } + else -> { + option.putString(key, serializeObject(value)) + } + } + return option.open(this) + } + + /** + * 打开页面 + * + * @param clazz 页面的类 + * @param addToBackStack 是否加入回退栈 + * @param key 入参的键 + * @param value 入参的值 + * @param + * @return + */ + fun openPage( + clazz: Class?, + addToBackStack: Boolean, + key: String?, + value: String?, + ): Fragment? { + return PageOption(clazz) + .setAddToBackStack(addToBackStack) + .putString(key, value) + .open(this) + } + + /** + * 打开页面 + * + * @param clazz 页面的类 + * @param key 入参的键 + * @param value 入参的值 + * @param + * @return + */ + fun openPage(clazz: Class?, key: String?, value: Any?): Fragment? { + return openPage(clazz, true, key, value) + } + + /** + * 打开页面 + * + * @param clazz 页面的类 + * @param addToBackStack 是否加入回退栈 + * @param key 入参的键 + * @param value 入参的值 + * @param + * @return + */ + fun openPage( + clazz: Class?, + addToBackStack: Boolean, + key: String?, + value: Any?, + ): Fragment? { + val option = PageOption(clazz).setAddToBackStack(addToBackStack) + return openPage(option, key, value) + } + + /** + * 打开页面 + * + * @param clazz 页面的类 + * @param key 入参的键 + * @param value 入参的值 + * @param + * @return + */ + fun openPage(clazz: Class?, key: String?, value: String?): Fragment? { + return PageOption(clazz) + .putString(key, value) + .open(this) + } + + /** + * 打开页面,需要结果返回 + * + * @param clazz 页面的类 + * @param key 入参的键 + * @param value 入参的值 + * @param requestCode 请求码 + * @param + * @return + */ + fun openPageForResult( + clazz: Class?, + key: String?, + value: Any?, + requestCode: Int, + ): Fragment? { + val option = PageOption(clazz).setRequestCode(requestCode) + return openPage(option, key, value) + } + + /** + * 打开页面,需要结果返回 + * + * @param clazz 页面的类 + * @param key 入参的键 + * @param value 入参的值 + * @param requestCode 请求码 + * @param + * @return + */ + fun openPageForResult( + clazz: Class?, + key: String?, + value: String?, + requestCode: Int, + ): Fragment? { + return PageOption(clazz) + .setRequestCode(requestCode) + .putString(key, value) + .open(this) + } + + /** + * 打开页面,需要结果返回 + * + * @param clazz 页面的类 + * @param requestCode 请求码 + * @param + * @return + */ + fun openPageForResult(clazz: Class?, requestCode: Int): Fragment? { + return PageOption(clazz) + .setRequestCode(requestCode) + .open(this) + } + + /** + * 序列化对象 + * + * @param object 需要序列化的对象 + * @return 序列化结果 + */ + fun serializeObject(`object`: Any?): String { + return XRouter.getInstance().navigation(SerializationService::class.java) + .object2Json(`object`) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/core/Core.kt b/app/src/main/java/com/idormy/sms/forwarder/core/Core.kt new file mode 100644 index 00000000..ff57f754 --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/core/Core.kt @@ -0,0 +1,60 @@ +package com.idormy.sms.forwarder.core + +import android.app.Application +import android.content.Intent +import android.util.Log +import androidx.core.content.ContextCompat +import androidx.work.Configuration +import com.idormy.sms.forwarder.App +import com.idormy.sms.forwarder.BuildConfig +import com.idormy.sms.forwarder.database.repository.FrpcRepository +import com.idormy.sms.forwarder.database.repository.LogsRepository +import com.idormy.sms.forwarder.database.repository.RuleRepository +import com.idormy.sms.forwarder.database.repository.SenderRepository +import com.idormy.sms.forwarder.service.ForegroundService +import kotlinx.coroutines.launch + +object Core : Configuration.Provider { + lateinit var app: Application + val frpc: FrpcRepository by lazy { (app as App).frpcRepository } + val logs: LogsRepository by lazy { (app as App).logsRepository } + val rule: RuleRepository by lazy { (app as App).ruleRepository } + val sender: SenderRepository by lazy { (app as App).senderRepository } + /* + val telephonyManager: TelephonyManager by lazy { app.getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager } + val smsManager: SmsManager by lazy { app.getSystemService(SmsManager::class.java) } + val subscriptionManager: SubscriptionManager by lazy { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) { + SubscriptionManager.from(app) + } else { + app.getSystemService(SubscriptionManager::class.java) + } + } + val user by lazy { app.getSystemService()!! }*/ + + + /*val directBootAware: Boolean get() = directBootSupported && dataStore.canToggleLocked + val directBootSupported by lazy { + Build.VERSION.SDK_INT >= 24 && try { + app.getSystemService()?.storageEncryptionStatus == + DevicePolicyManager.ENCRYPTION_STATUS_ACTIVE_PER_USER + } catch (_: RuntimeException) { + false + } + }*/ + + fun init(app: Application) { + this.app = app + } + + override fun getWorkManagerConfiguration(): Configuration { + return Configuration.Builder().apply { + setDefaultProcessName(app.packageName + ":bg") + setMinimumLoggingLevel(if (BuildConfig.DEBUG) Log.VERBOSE else Log.INFO) + setExecutor { (app as App).applicationScope.launch { it.run() } } + setTaskExecutor { (app as App).applicationScope.launch { it.run() } } + }.build() + } + + fun startService() = ContextCompat.startForegroundService(app, Intent(app, ForegroundService::class.java)) +} diff --git a/app/src/main/java/com/idormy/sms/forwarder/core/SimpleListAdapter.kt b/app/src/main/java/com/idormy/sms/forwarder/core/SimpleListAdapter.kt new file mode 100644 index 00000000..7289f83a --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/core/SimpleListAdapter.kt @@ -0,0 +1,57 @@ +package com.idormy.sms.forwarder.core + +import android.content.Context +import android.view.View +import android.widget.TextView +import com.idormy.sms.forwarder.R +import com.xuexiang.xui.adapter.listview.BaseListAdapter +import com.xuexiang.xutil.common.StringUtils + +/** + * 主副标题显示适配器 + * + * @author xuexiang + * @since 2018/12/19 上午12:19 + */ +class SimpleListAdapter(context: Context?, data: List?>?) : + BaseListAdapter, SimpleListAdapter.ViewHolder>(context, data) { + override fun newViewHolder(convertView: View): ViewHolder { + val holder = ViewHolder() + holder.mTvTitle = convertView.findViewById(R.id.tv_title) + holder.mTvSubTitle = convertView.findViewById(R.id.tv_sub_title) + return holder + } + + override fun getLayoutId(): Int { + return R.layout.adapter_item_simple_list + } + + override fun convert(holder: ViewHolder, item: Map, position: Int) { + holder.mTvTitle!!.text = + item[KEY_TITLE] + if (!StringUtils.isEmpty(item[KEY_SUB_TITLE])) { + holder.mTvSubTitle!!.text = + item[KEY_SUB_TITLE] + holder.mTvSubTitle!!.visibility = View.VISIBLE + } else { + holder.mTvSubTitle!!.visibility = View.GONE + } + } + + class ViewHolder { + /** + * 标题 + */ + var mTvTitle: TextView? = null + + /** + * 副标题 + */ + var mTvSubTitle: TextView? = null + } + + companion object { + const val KEY_TITLE = "key_title" + const val KEY_SUB_TITLE = "key_sub_title" + } +} \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/core/XPageTransferActivity.kt b/app/src/main/java/com/idormy/sms/forwarder/core/XPageTransferActivity.kt new file mode 100644 index 00000000..0c22845f --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/core/XPageTransferActivity.kt @@ -0,0 +1,39 @@ +package com.idormy.sms.forwarder.core + +import android.os.Bundle +import androidx.viewbinding.ViewBinding +import com.idormy.sms.forwarder.R +import com.idormy.sms.forwarder.utils.XToastUtils +import com.xuexiang.xrouter.annotation.AutoWired +import com.xuexiang.xrouter.annotation.Router +import com.xuexiang.xrouter.launcher.XRouter +import com.xuexiang.xutil.common.StringUtils + +/** + * https://xuexiangjys.club/xpage/transfer?pageName=xxxxx&.... + * applink的中转 + * + * @author xuexiang + * @since 2019-07-06 9:37 + */ +@Router(path = "/xpage/transfer") +class XPageTransferActivity : BaseActivity() { + + @JvmField + @AutoWired(name = "pageName") + var pageName: Nothing? = null + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + XRouter.getInstance().inject(this) + if (!StringUtils.isEmpty(pageName)) { + if (openPage(pageName, intent.extras) == null) { + XToastUtils.error(getString(R.string.page_not_found)) + finish() + } + } else { + XToastUtils.error(getString(R.string.page_not_found)) + finish() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/core/http/api/ApiService.kt b/app/src/main/java/com/idormy/sms/forwarder/core/http/api/ApiService.kt new file mode 100644 index 00000000..396965b3 --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/core/http/api/ApiService.kt @@ -0,0 +1,24 @@ +package com.idormy.sms.forwarder.core.http.api + +import com.idormy.sms.forwarder.core.http.entity.TipInfo +import com.xuexiang.xhttp2.model.ApiResult +import io.reactivex.Observable +import retrofit2.http.GET + +/** + * @author xuexiang + * @since 2021/1/9 7:01 PM + */ +@Suppress("unused") +class ApiService { + /** + * 使用的是retrofit的接口定义 + */ + interface IGetService { + /** + * 获得小贴士 + */ + @get:GET("/pp/SmsForwarder.wiki/raw/master/tips.json") + val tips: Observable?>> + } +} \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/core/http/callback/NoTipCallBack.kt b/app/src/main/java/com/idormy/sms/forwarder/core/http/callback/NoTipCallBack.kt new file mode 100644 index 00000000..02b8422f --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/core/http/callback/NoTipCallBack.kt @@ -0,0 +1,35 @@ +package com.idormy.sms.forwarder.core.http.callback + +import com.xuexiang.xhttp2.callback.SimpleCallBack +import com.xuexiang.xhttp2.exception.ApiException +import com.xuexiang.xhttp2.model.XHttpRequest +import com.xuexiang.xutil.common.StringUtils +import com.xuexiang.xutil.common.logger.Logger + +/** + * 不带错误提示的网络请求回调 + * + * @author xuexiang + * @since 2019-11-18 23:02 + */ +@Suppress("unused") +abstract class NoTipCallBack : SimpleCallBack { + /** + * 记录一下请求的url,确定出错的请求是哪个请求 + */ + private var mUrl: String? = null + + constructor() + constructor(req: XHttpRequest) : this(req.url) + constructor(url: String?) { + mUrl = url + } + + override fun onError(e: ApiException) { + if (!StringUtils.isEmpty(mUrl)) { + Logger.e("网络请求的url:$mUrl", e) + } else { + Logger.e(e) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/core/http/callback/TipCallBack.kt b/app/src/main/java/com/idormy/sms/forwarder/core/http/callback/TipCallBack.kt new file mode 100644 index 00000000..59de6b0a --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/core/http/callback/TipCallBack.kt @@ -0,0 +1,37 @@ +package com.idormy.sms.forwarder.core.http.callback + +import com.idormy.sms.forwarder.utils.XToastUtils +import com.xuexiang.xhttp2.callback.SimpleCallBack +import com.xuexiang.xhttp2.exception.ApiException +import com.xuexiang.xhttp2.model.XHttpRequest +import com.xuexiang.xutil.common.StringUtils +import com.xuexiang.xutil.common.logger.Logger + +/** + * 带错误toast提示的网络请求回调 + * + * @author xuexiang + * @since 2019-11-18 23:02 + */ +@Suppress("unused") +abstract class TipCallBack : SimpleCallBack { + /** + * 记录一下请求的url,确定出错的请求是哪个请求 + */ + private var mUrl: String? = null + + constructor() + constructor(req: XHttpRequest) : this(req.url) + constructor(url: String?) { + mUrl = url + } + + override fun onError(e: ApiException) { + XToastUtils.error(e) + if (!StringUtils.isEmpty(mUrl)) { + Logger.e("网络请求的url:$mUrl", e) + } else { + Logger.e(e) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/core/http/callback/TipProgressLoadingCallBack.kt b/app/src/main/java/com/idormy/sms/forwarder/core/http/callback/TipProgressLoadingCallBack.kt new file mode 100644 index 00000000..1e1b8b54 --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/core/http/callback/TipProgressLoadingCallBack.kt @@ -0,0 +1,45 @@ +package com.idormy.sms.forwarder.core.http.callback + +import com.idormy.sms.forwarder.core.BaseFragment +import com.idormy.sms.forwarder.utils.XToastUtils +import com.xuexiang.xhttp2.callback.ProgressLoadingCallBack +import com.xuexiang.xhttp2.exception.ApiException +import com.xuexiang.xhttp2.model.XHttpRequest +import com.xuexiang.xhttp2.subsciber.impl.IProgressLoader +import com.xuexiang.xutil.common.StringUtils +import com.xuexiang.xutil.common.logger.Logger + +/** + * 带错误toast提示和加载进度条的网络请求回调 + * + * @author xuexiang + * @since 2019-11-18 23:16 + */ +@Suppress("unused") +abstract class TipProgressLoadingCallBack : ProgressLoadingCallBack { + /** + * 记录一下请求的url,确定出错的请求是哪个请求 + */ + private var mUrl: String? = null + + constructor(fragment: BaseFragment<*>) : super(fragment.progressLoader) + constructor(iProgressLoader: IProgressLoader?) : super(iProgressLoader) + constructor(req: XHttpRequest, iProgressLoader: IProgressLoader?) : this( + req.url, + iProgressLoader + ) + + constructor(url: String?, iProgressLoader: IProgressLoader?) : super(iProgressLoader) { + mUrl = url + } + + override fun onError(e: ApiException) { + super.onError(e) + XToastUtils.error(e) + if (!StringUtils.isEmpty(mUrl)) { + Logger.e("网络请求的url:$mUrl", e) + } else { + Logger.e(e) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/core/http/entity/TipInfo.kt b/app/src/main/java/com/idormy/sms/forwarder/core/http/entity/TipInfo.kt new file mode 100644 index 00000000..d8475da7 --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/core/http/entity/TipInfo.kt @@ -0,0 +1,27 @@ +package com.idormy.sms.forwarder.core.http.entity + +import androidx.annotation.Keep + +/** + * @author xuexiang + * @since 2019-08-28 15:35 + */ +@Keep +class TipInfo { + /** + * title : 小贴士3 + * content : + * + *欢迎关注我的微信公众号:我的Android开源之旅。 + * + *

+ */ + var title: String? = null + var content: String? = null + override fun toString(): String { + return "TipInfo{" + + "title='" + title + '\'' + + ", content='" + content + '\'' + + '}' + } +} \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/core/http/loader/IProgressLoaderFactory.kt b/app/src/main/java/com/idormy/sms/forwarder/core/http/loader/IProgressLoaderFactory.kt new file mode 100644 index 00000000..9b68b8e7 --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/core/http/loader/IProgressLoaderFactory.kt @@ -0,0 +1,29 @@ +package com.idormy.sms.forwarder.core.http.loader + +import android.content.Context +import com.xuexiang.xhttp2.subsciber.impl.IProgressLoader + +/** + * IProgressLoader的创建工厂实现接口 + * + * @author xuexiang + * @since 2019-11-18 23:17 + */ +interface IProgressLoaderFactory { + /** + * 创建进度加载者 + * + * @param context + * @return + */ + fun create(context: Context?): IProgressLoader? + + /** + * 创建进度加载者 + * + * @param context + * @param message 默认提示 + * @return + */ + fun create(context: Context?, message: String?): IProgressLoader? +} \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/core/http/loader/MiniLoadingDialogLoader.kt b/app/src/main/java/com/idormy/sms/forwarder/core/http/loader/MiniLoadingDialogLoader.kt new file mode 100644 index 00000000..842ba897 --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/core/http/loader/MiniLoadingDialogLoader.kt @@ -0,0 +1,65 @@ +package com.idormy.sms.forwarder.core.http.loader + +import android.content.Context +import com.xuexiang.xhttp2.subsciber.impl.IProgressLoader +import com.xuexiang.xhttp2.subsciber.impl.OnProgressCancelListener +import com.xuexiang.xui.widget.dialog.MiniLoadingDialog + +/** + * 默认进度加载 + * + * @author xuexiang + * @since 2019-11-18 23:07 + */ +class MiniLoadingDialogLoader @JvmOverloads constructor( + context: Context?, + msg: String? = "请求中...", +) : IProgressLoader { + /** + * 进度loading弹窗 + */ + private val mDialog: MiniLoadingDialog? + + /** + * 进度框取消监听 + */ + private var mOnProgressCancelListener: OnProgressCancelListener? = null + override fun isLoading(): Boolean { + return mDialog != null && mDialog.isShowing + } + + override fun updateMessage(msg: String) { + mDialog?.updateMessage(msg) + } + + override fun showLoading() { + if (mDialog != null && !mDialog.isShowing) { + mDialog.show() + } + } + + override fun dismissLoading() { + if (mDialog != null && mDialog.isShowing) { + mDialog.dismiss() + } + } + + override fun setCancelable(flag: Boolean) { + mDialog!!.setCancelable(flag) + if (flag) { + mDialog.setOnCancelListener { + if (mOnProgressCancelListener != null) { + mOnProgressCancelListener!!.onCancelProgress() + } + } + } + } + + override fun setOnProgressCancelListener(listener: OnProgressCancelListener) { + mOnProgressCancelListener = listener + } + + init { + mDialog = MiniLoadingDialog(context, msg) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/core/http/loader/MiniProgressLoaderFactory.kt b/app/src/main/java/com/idormy/sms/forwarder/core/http/loader/MiniProgressLoaderFactory.kt new file mode 100644 index 00000000..9c421923 --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/core/http/loader/MiniProgressLoaderFactory.kt @@ -0,0 +1,20 @@ +package com.idormy.sms.forwarder.core.http.loader + +import android.content.Context +import com.xuexiang.xhttp2.subsciber.impl.IProgressLoader + +/** + * 迷你加载框创建工厂 + * + * @author xuexiang + * @since 2019-11-18 23:23 + */ +class MiniProgressLoaderFactory : IProgressLoaderFactory { + override fun create(context: Context?): IProgressLoader { + return MiniLoadingDialogLoader(context) + } + + override fun create(context: Context?, message: String?): IProgressLoader { + return MiniLoadingDialogLoader(context, message) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/core/http/loader/ProgressLoader.kt b/app/src/main/java/com/idormy/sms/forwarder/core/http/loader/ProgressLoader.kt new file mode 100644 index 00000000..d4ce9e5d --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/core/http/loader/ProgressLoader.kt @@ -0,0 +1,45 @@ +package com.idormy.sms.forwarder.core.http.loader + +import android.content.Context +import com.xuexiang.xhttp2.subsciber.impl.IProgressLoader + +/** + * 创建进度加载者 + * + * @author xuexiang + * @since 2019-07-02 12:51 + */ +@Suppress("unused") +class ProgressLoader private constructor() { + companion object { + private var sIProgressLoaderFactory: IProgressLoaderFactory = MiniProgressLoaderFactory() + fun setIProgressLoaderFactory(sIProgressLoaderFactory: IProgressLoaderFactory) { + Companion.sIProgressLoaderFactory = sIProgressLoaderFactory + } + + /** + * 创建进度加载者 + * + * @param context + * @return + */ + fun create(context: Context?): IProgressLoader? { + return sIProgressLoaderFactory.create(context) + } + + /** + * 创建进度加载者 + * + * @param context + * @param message 默认提示信息 + * @return + */ + fun create(context: Context?, message: String?): IProgressLoader? { + return sIProgressLoaderFactory.create(context, message) + } + } + + init { + throw UnsupportedOperationException("u can't instantiate me...") + } +} \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/core/http/subscriber/NoTipRequestSubscriber.kt b/app/src/main/java/com/idormy/sms/forwarder/core/http/subscriber/NoTipRequestSubscriber.kt new file mode 100644 index 00000000..df52f486 --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/core/http/subscriber/NoTipRequestSubscriber.kt @@ -0,0 +1,35 @@ +package com.idormy.sms.forwarder.core.http.subscriber + +import com.xuexiang.xhttp2.exception.ApiException +import com.xuexiang.xhttp2.model.XHttpRequest +import com.xuexiang.xhttp2.subsciber.BaseSubscriber +import com.xuexiang.xutil.common.StringUtils +import com.xuexiang.xutil.common.logger.Logger + +/** + * 不带错误toast提示的网络请求订阅,只存储错误的日志 + * + * @author xuexiang + * @since 2019-11-18 23:11 + */ +@Suppress("unused") +abstract class NoTipRequestSubscriber : BaseSubscriber { + /** + * 记录一下请求的url,确定出错的请求是哪个请求 + */ + private var mUrl: String? = null + + constructor() + constructor(req: XHttpRequest) : this(req.url) + constructor(url: String?) { + mUrl = url + } + + public override fun onError(e: ApiException) { + if (!StringUtils.isEmpty(mUrl)) { + Logger.e("网络请求的url:$mUrl", e) + } else { + Logger.e(e) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/core/http/subscriber/TipProgressLoadingSubscriber.kt b/app/src/main/java/com/idormy/sms/forwarder/core/http/subscriber/TipProgressLoadingSubscriber.kt new file mode 100644 index 00000000..42570440 --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/core/http/subscriber/TipProgressLoadingSubscriber.kt @@ -0,0 +1,42 @@ +package com.idormy.sms.forwarder.core.http.subscriber + +import com.idormy.sms.forwarder.core.BaseFragment +import com.idormy.sms.forwarder.utils.XToastUtils +import com.xuexiang.xhttp2.exception.ApiException +import com.xuexiang.xhttp2.model.XHttpRequest +import com.xuexiang.xhttp2.subsciber.ProgressLoadingSubscriber +import com.xuexiang.xhttp2.subsciber.impl.IProgressLoader +import com.xuexiang.xutil.common.StringUtils +import com.xuexiang.xutil.common.logger.Logger + +/** + * 带错误toast提示和加载进度条的网络请求订阅 + * + * @author xuexiang + * @since 2019-11-18 23:11 + */ +@Suppress("unused") +abstract class TipProgressLoadingSubscriber : ProgressLoadingSubscriber { + /** + * 记录一下请求的url,确定出错的请求是哪个请求 + */ + private var mUrl: String? = null + + constructor() : super() + constructor(fragment: BaseFragment<*>) : super(fragment.progressLoader) + constructor(iProgressLoader: IProgressLoader?) : super(iProgressLoader) + constructor(req: XHttpRequest) : this(req.url) + constructor(url: String?) : super() { + mUrl = url + } + + override fun onError(e: ApiException) { + super.onError(e) + XToastUtils.error(e) + if (!StringUtils.isEmpty(mUrl)) { + Logger.e("网络请求的url:$mUrl", e) + } else { + Logger.e(e) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/core/http/subscriber/TipRequestSubscriber.kt b/app/src/main/java/com/idormy/sms/forwarder/core/http/subscriber/TipRequestSubscriber.kt new file mode 100644 index 00000000..e7b7d071 --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/core/http/subscriber/TipRequestSubscriber.kt @@ -0,0 +1,37 @@ +package com.idormy.sms.forwarder.core.http.subscriber + +import com.idormy.sms.forwarder.utils.XToastUtils +import com.xuexiang.xhttp2.exception.ApiException +import com.xuexiang.xhttp2.model.XHttpRequest +import com.xuexiang.xhttp2.subsciber.BaseSubscriber +import com.xuexiang.xutil.common.StringUtils +import com.xuexiang.xutil.common.logger.Logger + +/** + * 带错误toast提示的网络请求订阅 + * + * @author xuexiang + * @since 2019-11-18 23:10 + */ +@Suppress("unused") +abstract class TipRequestSubscriber : BaseSubscriber { + /** + * 记录一下请求的url,确定出错的请求是哪个请求 + */ + private var mUrl: String? = null + + constructor() + constructor(req: XHttpRequest) : this(req.url) + constructor(url: String?) { + mUrl = url + } + + public override fun onError(e: ApiException) { + XToastUtils.error(e) + if (!StringUtils.isEmpty(mUrl)) { + Logger.e("网络请求的url:$mUrl", e) + } else { + Logger.e(e) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/core/webview/AgentWebActivity.kt b/app/src/main/java/com/idormy/sms/forwarder/core/webview/AgentWebActivity.kt new file mode 100644 index 00000000..306cdc35 --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/core/webview/AgentWebActivity.kt @@ -0,0 +1,96 @@ +package com.idormy.sms.forwarder.core.webview + +import android.content.Context +import android.content.Intent +import android.os.Bundle +import android.view.KeyEvent +import androidx.appcompat.app.AppCompatActivity +import com.idormy.sms.forwarder.R +import com.idormy.sms.forwarder.utils.XToastUtils +import com.xuexiang.xrouter.facade.Postcard +import com.xuexiang.xrouter.facade.callback.NavCallback +import com.xuexiang.xrouter.launcher.XRouter +import com.xuexiang.xui.widget.slideback.SlideBack + +/** + * 壳浏览器 + * + * @author xuexiang + * @since 2019/1/5 上午12:15 + */ +class AgentWebActivity : AppCompatActivity() { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_agent_web) + SlideBack.with(this) + .haveScroll(true) + .callBack { finish() } + .register() + val uri = intent.data + if (uri != null) { + XRouter.getInstance().build(uri).navigation(this, object : NavCallback() { + override fun onArrival(postcard: Postcard) { + finish() + } + + override fun onLost(postcard: Postcard) { + loadUrl(uri.toString()) + } + }) + } else { + val url = intent.getStringExtra(AgentWebFragment.KEY_URL) + loadUrl(url) + } + } + + private fun loadUrl(url: String?) { + if (url != null) { + openFragment(url) + } else { + XToastUtils.error(getString(R.string.data_error)) + finish() + } + } + + private var mAgentWebFragment: AgentWebFragment? = null + private fun openFragment(url: String) { + val ft = supportFragmentManager.beginTransaction() + ft.add( + R.id.container_frame_layout, + AgentWebFragment.getInstance(url).also { mAgentWebFragment = it }) + ft.commit() + } + + /*override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { + super.onActivityResult(requestCode, resultCode, data) + }*/ + + override fun onKeyDown(keyCode: Int, event: KeyEvent): Boolean { + val agentWebFragment = mAgentWebFragment + return if (agentWebFragment != null) { + if ((agentWebFragment as FragmentKeyDown).onFragmentKeyDown(keyCode, event)) { + true + } else { + super.onKeyDown(keyCode, event) + } + } else super.onKeyDown(keyCode, event) + } + + override fun onDestroy() { + SlideBack.unregister(this) + super.onDestroy() + } + + companion object { + /** + * 请求浏览器 + * + * @param url + */ + fun goWeb(context: Context?, url: String?) { + val intent = Intent(context, AgentWebActivity::class.java) + intent.putExtra(AgentWebFragment.KEY_URL, url) + context?.startActivity(intent) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/core/webview/AgentWebFragment.kt b/app/src/main/java/com/idormy/sms/forwarder/core/webview/AgentWebFragment.kt new file mode 100644 index 00000000..1f7df011 --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/core/webview/AgentWebFragment.kt @@ -0,0 +1,574 @@ +package com.idormy.sms.forwarder.core.webview + +import android.content.ClipData +import android.content.ClipboardManager +import android.content.Context +import android.content.Intent +import android.graphics.Bitmap +import android.graphics.Color +import android.net.Uri +import android.os.Build +import android.os.Bundle +import android.text.TextUtils +import android.util.Log +import android.view.* +import android.webkit.* +import android.widget.FrameLayout +import android.widget.ImageView +import android.widget.LinearLayout +import android.widget.TextView +import androidx.annotation.RequiresApi +import androidx.appcompat.widget.PopupMenu +import androidx.fragment.app.Fragment +import com.idormy.sms.forwarder.R +import com.idormy.sms.forwarder.utils.XToastUtils +import com.just.agentweb.action.PermissionInterceptor +import com.just.agentweb.core.AgentWeb +import com.just.agentweb.core.client.MiddlewareWebChromeBase +import com.just.agentweb.core.client.MiddlewareWebClientBase +import com.just.agentweb.core.client.WebListenerManager +import com.just.agentweb.core.web.AbsAgentWebSettings +import com.just.agentweb.core.web.AgentWebConfig +import com.just.agentweb.core.web.IAgentWebSettings +import com.just.agentweb.download.AgentWebDownloader.Extra +import com.just.agentweb.download.DefaultDownloadImpl +import com.just.agentweb.download.DownloadListenerAdapter +import com.just.agentweb.download.DownloadingService +import com.just.agentweb.utils.LogUtils +import com.just.agentweb.widget.IWebLayout +import com.xuexiang.xutil.net.JsonUtil + +/** + * 通用WebView页面 + * + * @author xuexiang + * @since 2019/1/4 下午11:13 + */ +@Suppress("unused", "MemberVisibilityCanBePrivate", "ProtectedInFinal", "NAME_SHADOWING", "UNUSED_PARAMETER", "OVERRIDE_DEPRECATION") +class AgentWebFragment : Fragment(), FragmentKeyDown { + private var mBackImageView: ImageView? = null + private var mLineView: View? = null + private var mFinishImageView: ImageView? = null + private var mTitleTextView: TextView? = null + private var mAgentWeb: AgentWeb? = null + private var mMoreImageView: ImageView? = null + private var mPopupMenu: PopupMenu? = null + private var mDownloadingService: DownloadingService? = null + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle?, + ): View? { + return inflater.inflate(R.layout.fragment_agentweb, container, false) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + mAgentWeb = AgentWeb.with(this) //传入AgentWeb的父控件。 + .setAgentWebParent( + (view as LinearLayout), + -1, + LinearLayout.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.MATCH_PARENT + ) + ) //设置进度条颜色与高度,-1为默认值,高度为2,单位为dp。 + .useDefaultIndicator(-1, 3) //设置 IAgentWebSettings。 + .setAgentWebWebSettings(settings) //WebViewClient , 与 WebView 使用一致 ,但是请勿获取WebView调用setWebViewClient(xx)方法了,会覆盖AgentWeb DefaultWebClient,同时相应的中间件也会失效。 + .setWebViewClient(mWebViewClient) //WebChromeClient + .setWebChromeClient(mWebChromeClient) //设置WebChromeClient中间件,支持多个WebChromeClient,AgentWeb 3.0.0 加入。 + .useMiddlewareWebChrome(middlewareWebChrome) //设置WebViewClient中间件,支持多个WebViewClient, AgentWeb 3.0.0 加入。 + .useMiddlewareWebClient(middlewareWebClient) //权限拦截 2.0.0 加入。 + .setPermissionInterceptor(mPermissionInterceptor) //严格模式 Android 4.2.2 以下会放弃注入对象 ,使用AgentWebView没影响。 + .setSecurityType(AgentWeb.SecurityType.STRICT_CHECK) //自定义UI AgentWeb3.0.0 加入。 + .setAgentWebUIController(UIController(requireActivity())) //参数1是错误显示的布局,参数2点击刷新控件ID -1表示点击整个布局都刷新, AgentWeb 3.0.0 加入。 + .setMainFrameErrorView(R.layout.agentweb_error_page, -1) + .setWebLayout(webLayout) + .interceptUnkownUrl() //创建AgentWeb。 + .createAgentWeb() + .ready() //设置 WebSettings。 + //WebView载入该url地址的页面并显示。 + .go(url) + if (com.idormy.sms.forwarder.App.isDebug) { + AgentWebConfig.debug() + } + + // 得到 AgentWeb 最底层的控件 + addBackgroundChild(mAgentWeb!!.webCreator.webParentLayout) + initView(view) + + // AgentWeb 没有把WebView的功能全面覆盖 ,所以某些设置 AgentWeb 没有提供,请从WebView方面入手设置。 + mAgentWeb!!.webCreator.webView.overScrollMode = WebView.OVER_SCROLL_NEVER + } + + protected val webLayout: IWebLayout<*, *> + get() = WebLayout(activity) + + protected fun initView(view: View) { + mBackImageView = view.findViewById(R.id.iv_back) + mLineView = view.findViewById(R.id.view_line) + mFinishImageView = view.findViewById(R.id.iv_finish) + mTitleTextView = view.findViewById(R.id.toolbar_title) + mBackImageView?.setOnClickListener(mOnClickListener) + mFinishImageView?.setOnClickListener(mOnClickListener) + mMoreImageView = view.findViewById(R.id.iv_more) + mMoreImageView?.setOnClickListener(mOnClickListener) + pageNavigator(View.GONE) + } + + protected fun addBackgroundChild(frameLayout: FrameLayout) { + val textView = TextView(frameLayout.context) + textView.text = getString(R.string.provided_by_agentweb) + textView.textSize = 16f + textView.setTextColor(Color.parseColor("#727779")) + frameLayout.setBackgroundColor(Color.parseColor("#272b2d")) + val params = FrameLayout.LayoutParams(-2, -2) + params.gravity = Gravity.CENTER_HORIZONTAL + val scale = frameLayout.context.resources.displayMetrics.density + params.topMargin = (15 * scale + 0.5f).toInt() + frameLayout.addView(textView, 0, params) + } + + private fun pageNavigator(tag: Int) { + mBackImageView!!.visibility = tag + mLineView!!.visibility = tag + } + + private val mOnClickListener = View.OnClickListener { v -> + when (v.id) { + R.id.iv_back -> // true表示AgentWeb处理了该事件 + if (!mAgentWeb!!.back()) { + this.requireActivity().finish() + } + R.id.iv_finish -> this.requireActivity().finish() + R.id.iv_more -> showPoPup(v) + else -> {} + } + } + //========================================// + /** + * 权限申请拦截器 + */ + protected var mPermissionInterceptor = PermissionInterceptor { url, permissions, action -> + + /** + * PermissionInterceptor 能达到 url1 允许授权, url2 拒绝授权的效果。 + * @param url + * @param permissions + * @param action + * @return true 该Url对应页面请求权限进行拦截 ,false 表示不拦截。 + */ + /** + * PermissionInterceptor 能达到 url1 允许授权, url2 拒绝授权的效果。 + * @param url + * @param permissions + * @param action + * @return true 该Url对应页面请求权限进行拦截 ,false 表示不拦截。 + */ + Log.i( + TAG, + "mUrl:" + url + " permission:" + JsonUtil.toJson(permissions) + " action:" + action + ) + false + } + //=====================下载============================// + /** + * 更新于 AgentWeb 4.0.0,下载监听 + */ + protected var mDownloadListenerAdapter: DownloadListenerAdapter = + object : DownloadListenerAdapter() { + /** + * + * @param url 下载链接 + * @param userAgent UserAgent + * @param contentDisposition ContentDisposition + * @param mimetype 资源的媒体类型 + * @param contentLength 文件长度 + * @param extra 下载配置 , 用户可以通过 Extra 修改下载icon , 关闭进度条 , 是否强制下载。 + * @return true 表示用户处理了该下载事件 , false 交给 AgentWeb 下载 + */ + override fun onStart( + url: String, + userAgent: String, + contentDisposition: String, + mimetype: String, + contentLength: Long, + extra: Extra, + ): Boolean { + LogUtils.i(TAG, "onStart:$url") + // 是否开启断点续传 + extra.setOpenBreakPointDownload(true) //下载通知的icon + .setIcon(R.drawable.ic_file_download_black_24dp) // 连接的超时时间 + .setConnectTimeOut(6000) // 以8KB位单位,默认60s ,如果60s内无法从网络流中读满8KB数据,则抛出异常 + .setBlockMaxTime(10 * 60 * 1000) // 下载的超时时间 + .setDownloadTimeOut(Long.MAX_VALUE) // 串行下载更节省资源哦 + .setParallelDownload(false) // false 关闭进度通知 + .setEnableIndicator(true) // 自定义请求头 + .addHeader("Cookie", "xx") // 下载完成自动打开 + .setAutoOpen(true).isForceDownload = true + return false + } + + /** + * + * 不需要暂停或者停止下载该方法可以不必实现 + * @param url + * @param downloadingService 用户可以通过 DownloadingService#shutdownNow 终止下载 + */ + override fun onBindService(url: String, downloadingService: DownloadingService) { + super.onBindService(url, downloadingService) + mDownloadingService = downloadingService + LogUtils.i(TAG, "onBindService:$url DownloadingService:$downloadingService") + } + + /** + * 回调onUnbindService方法,让用户释放掉 DownloadingService。 + * @param url + * @param downloadingService + */ + override fun onUnbindService(url: String, downloadingService: DownloadingService) { + super.onUnbindService(url, downloadingService) + mDownloadingService = null + LogUtils.i(TAG, "onUnbindService:$url") + } + + /** + * + * @param url 下载链接 + * @param loaded 已经下载的长度 + * @param length 文件的总大小 + * @param usedTime 耗时 ,单位ms + * 注意该方法回调在子线程 ,线程名 AsyncTask #XX 或者 AgentWeb # XX + */ + override fun onProgress(url: String, loaded: Long, length: Long, usedTime: Long) { + val mProgress = (loaded / java.lang.Float.valueOf(length.toFloat()) * 100).toInt() + LogUtils.i(TAG, "onProgress:$mProgress") + super.onProgress(url, loaded, length, usedTime) + } + + /** + * + * @param path 文件的绝对路径 + * @param url 下载地址 + * @param throwable 如果异常,返回给用户异常 + * @return true 表示用户处理了下载完成后续的事件 ,false 默认交给AgentWeb 处理 + */ + override fun onResult(path: String, url: String, throwable: Throwable): Boolean { + //下载成功 + //if (null == throwable) { + //do you work + //} else { //下载失败 + //} + // true 不会发出下载完成的通知 , 或者打开文件 + return false + } + } + /** + * AgentWeb 4.0.0 内部删除了 DownloadListener 监听 ,以及相关API ,将 Download 部分完全抽离出来独立一个库, + * 如果你需要使用 AgentWeb Download 部分 , 请依赖上 compile 'com.just.agentweb:download:4.0.0 , + * 如果你需要监听下载结果,请自定义 AgentWebSetting , New 出 DefaultDownloadImpl,传入DownloadListenerAdapter + * 实现进度或者结果监听,例如下面这个例子,如果你不需要监听进度,或者下载结果,下面 setDownloader 的例子可以忽略。 + * @return WebListenerManager + */ + /** + * @return IAgentWebSettings + */ + val settings: IAgentWebSettings<*> + get() = object : AbsAgentWebSettings() { + private val mAgentWeb: AgentWeb? = null + override fun bindAgentWebSupport(agentWeb: AgentWeb) { + this.mAgentWeb = agentWeb + } + + /** + * AgentWeb 4.0.0 内部删除了 DownloadListener 监听 ,以及相关API ,将 Download 部分完全抽离出来独立一个库, + * 如果你需要使用 AgentWeb Download 部分 , 请依赖上 compile 'com.just.agentweb:download:4.0.0 , + * 如果你需要监听下载结果,请自定义 AgentWebSetting , New 出 DefaultDownloadImpl,传入DownloadListenerAdapter + * 实现进度或者结果监听,例如下面这个例子,如果你不需要监听进度,或者下载结果,下面 setDownloader 的例子可以忽略。 + * @return WebListenerManager + */ + override fun setDownloader( + webView: WebView, + downloadListener: DownloadListener?, + ): WebListenerManager { + return super.setDownloader( + webView, + DefaultDownloadImpl + .create( + requireActivity(), + webView, + mDownloadListenerAdapter, + mDownloadListenerAdapter, + this.mAgentWeb.permissionInterceptor + ) + ) + } + } + //===================WebChromeClient 和 WebViewClient===========================// + /** + * 页面空白,请检查scheme是否加上, scheme://host:port/path?query&query 。 + * + * @return mUrl + */ + val url: String + get() { + var target = "" + val bundle = arguments + if (bundle != null) { + target = bundle.getString(KEY_URL).toString() + } + if (TextUtils.isEmpty(target)) { + target = "https://github.com/xuexiangjys" + } + return target + } + protected var mWebChromeClient: WebChromeClient = object : WebChromeClient() { + override fun onProgressChanged(view: WebView, newProgress: Int) { + Log.i(TAG, "onProgressChanged:$newProgress view:$view") + } + + override fun onReceivedTitle(view: WebView, title: String) { + var title = title + super.onReceivedTitle(view, title) + if (mTitleTextView != null && !TextUtils.isEmpty(title)) { + if (title.length > 10) { + title = title.substring(0, 10) + "..." + } + mTitleTextView!!.text = title + } + } + } + + @Suppress("DEPRECATION") + protected var mWebViewClient: WebViewClient = object : WebViewClient() { + private val timer = HashMap() + override fun onReceivedError( + view: WebView, + request: WebResourceRequest, + error: WebResourceError, + ) { + super.onReceivedError(view, request, error) + } + + @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) + override fun shouldOverrideUrlLoading(view: WebView, request: WebResourceRequest): Boolean { + return shouldOverrideUrlLoading(view, request.url.toString() + "") + } + + override fun shouldInterceptRequest( + view: WebView, + request: WebResourceRequest, + ): WebResourceResponse? { + return super.shouldInterceptRequest(view, request) + } + + override fun shouldOverrideUrlLoading(view: WebView, url: String): Boolean { + //intent:// scheme的处理 如果返回false , 则交给 DefaultWebClient 处理 , 默认会打开该Activity , 如果Activity不存在则跳到应用市场上去. true 表示拦截 + //例如优酷视频播放 ,intent://play?...package=com.youku.phone;end; + //优酷想唤起自己应用播放该视频 , 下面拦截地址返回 true 则会在应用内 H5 播放 ,禁止优酷唤起播放该视频, 如果返回 false , DefaultWebClient 会根据intent 协议处理 该地址 , 首先匹配该应用存不存在 ,如果存在 , 唤起该应用播放 , 如果不存在 , 则跳到应用市场下载该应用 . + return url.startsWith("intent://") && url.contains("com.youku.phone") + } + + override fun onPageStarted(view: WebView, url: String, favicon: Bitmap?) { + Log.i(TAG, "mUrl:$url onPageStarted target:$url") + timer[url] = System.currentTimeMillis() + if (url == url) { + pageNavigator(View.GONE) + } else { + pageNavigator(View.VISIBLE) + } + } + + override fun onPageFinished(view: WebView, url: String) { + super.onPageFinished(view, url) + if (timer[url] != null) { + val overTime = System.currentTimeMillis() + val startTime = timer[url] + Log.i(TAG, " page mUrl:" + url + " used time:" + (overTime - startTime!!)) + } + } + + override fun onReceivedHttpError( + view: WebView, + request: WebResourceRequest, + errorResponse: WebResourceResponse, + ) { + super.onReceivedHttpError(view, request, errorResponse) + } + + override fun onReceivedError( + view: WebView, + errorCode: Int, + description: String, + failingUrl: String, + ) { + super.onReceivedError(view, errorCode, description, failingUrl) + } + } + + /*override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { + super.onActivityResult(requestCode, resultCode, data) + }*/ + //========================菜单功能================================// + /** + * 打开浏览器 + * + * @param targetUrl 外部浏览器打开的地址 + */ + private fun openBrowser(targetUrl: String) { + if (TextUtils.isEmpty(targetUrl) || targetUrl.startsWith("file://")) { + XToastUtils.toast(targetUrl + getString(R.string.cannot_open_with_browser)) + return + } + val intent = Intent() + intent.action = "android.intent.action.VIEW" + val uri = Uri.parse(targetUrl) + intent.data = uri + startActivity(intent) + } + + /** + * 显示更多菜单 + * + * @param view 菜单依附在该View下面 + */ + private fun showPoPup(view: View) { + if (mPopupMenu == null) { + mPopupMenu = PopupMenu(requireContext(), view) + mPopupMenu!!.inflate(R.menu.menu_toolbar_web) + mPopupMenu!!.setOnMenuItemClickListener(mOnMenuItemClickListener) + } + mPopupMenu!!.show() + } + + /** + * 菜单事件 + */ + private val mOnMenuItemClickListener = PopupMenu.OnMenuItemClickListener { item -> + when (item.itemId) { + R.id.refresh -> { + if (mAgentWeb != null) { + mAgentWeb!!.urlLoader.reload() // 刷新 + } + true + } + R.id.copy -> { + if (mAgentWeb != null) { + mAgentWeb!!.webCreator.webView.url?.let { toCopy(context, it) } + } + true + } + R.id.default_browser -> { + if (mAgentWeb != null) { + mAgentWeb!!.webCreator.webView.url?.let { openBrowser(it) } + } + true + } + R.id.share -> { + if (mAgentWeb != null) { + mAgentWeb!!.webCreator.webView.url?.let { shareWebUrl(it) } + } + true + } + else -> false + } + } + + /** + * 分享网页链接 + * + * @param url 网页链接 + */ + private fun shareWebUrl(url: String) { + val shareIntent = Intent() + shareIntent.action = Intent.ACTION_SEND + shareIntent.putExtra(Intent.EXTRA_TEXT, url) + shareIntent.type = "text/plain" + //设置分享列表的标题,并且每次都显示分享列表 + startActivity(Intent.createChooser(shareIntent, getString(R.string.share_to))) + } + + /** + * 复制字符串 + * + * @param context + * @param text + */ + private fun toCopy(context: Context?, text: String) { + val manager = requireContext().getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager + manager.setPrimaryClip(ClipData.newPlainText(null, text)) + } + + //===================生命周期管理===========================// + override fun onResume() { + mAgentWeb!!.webLifeCycle.onResume() //恢复 + super.onResume() + } + + override fun onPause() { + mAgentWeb!!.webLifeCycle.onPause() //暂停应用内所有WebView , 调用mWebView.resumeTimers();/mAgentWeb.getWebLifeCycle().onResume(); 恢复。 + super.onPause() + } + + override fun onFragmentKeyDown(keyCode: Int, event: KeyEvent?): Boolean { + return mAgentWeb!!.handleKeyEvent(keyCode, event) + } + + override fun onDestroyView() { + mAgentWeb!!.webLifeCycle.onDestroy() + super.onDestroyView() + } + //===================中间键===========================//// 拦截 url,不执行 DefaultWebClient#shouldOverrideUrlLoading + // 执行 DefaultWebClient#shouldOverrideUrlLoading + // do you work + /** + * MiddlewareWebClientBase 是 AgentWeb 3.0.0 提供一个强大的功能, + * 如果用户需要使用 AgentWeb 提供的功能, 不想重写 WebClientView方 + * 法覆盖AgentWeb提供的功能,那么 MiddlewareWebClientBase 是一个 + * 不错的选择 。 + * + * @return + */ + @Suppress("DEPRECATION") + protected val middlewareWebClient: MiddlewareWebClientBase + get() = object : MiddlewareWebViewClient() { + override fun shouldOverrideUrlLoading(view: WebView, url: String): Boolean { + // 拦截 url,不执行 DefaultWebClient#shouldOverrideUrlLoading + if (url.startsWith("agentweb")) { + Log.i(TAG, "agentweb scheme ~") + return true + } + // 执行 DefaultWebClient#shouldOverrideUrlLoading + return super.shouldOverrideUrlLoading(view, url) + // do you work + } + + @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) + override fun shouldOverrideUrlLoading( + view: WebView, + request: WebResourceRequest, + ): Boolean { + return super.shouldOverrideUrlLoading(view, request) + } + } + protected val middlewareWebChrome: MiddlewareWebChromeBase + get() = object : MiddlewareChromeClient() {} + + companion object { + const val KEY_URL = "com.xuexiang.xuidemo.base.webview.key_url" + val TAG: String = AgentWebFragment::class.java.simpleName + fun getInstance(url: String?): AgentWebFragment { + val bundle = Bundle() + bundle.putString(KEY_URL, url) + return getInstance(bundle) + } + + fun getInstance(bundle: Bundle?): AgentWebFragment { + val fragment = AgentWebFragment() + if (bundle != null) { + fragment.arguments = bundle + } + return fragment + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/core/webview/BaseWebViewFragment.kt b/app/src/main/java/com/idormy/sms/forwarder/core/webview/BaseWebViewFragment.kt new file mode 100644 index 00000000..612ad7e7 --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/core/webview/BaseWebViewFragment.kt @@ -0,0 +1,45 @@ +package com.idormy.sms.forwarder.core.webview + +import android.view.KeyEvent +import androidx.viewbinding.ViewBinding +import com.idormy.sms.forwarder.core.BaseFragment +import com.just.agentweb.core.AgentWeb + +/** + * 基础web + * + * @author xuexiang + * @since 2019/5/28 10:22 + */ +@Suppress("unused") +abstract class BaseWebViewFragment : BaseFragment() { + private var mAgentWeb: AgentWeb? = null + + //===================生命周期管理===========================// + override fun onResume() { + if (mAgentWeb != null) { + //恢复 + mAgentWeb!!.webLifeCycle.onResume() + } + super.onResume() + } + + override fun onPause() { + if (mAgentWeb != null) { + //暂停应用内所有WebView , 调用mWebView.resumeTimers();/mAgentWeb.getWebLifeCycle().onResume(); 恢复。 + mAgentWeb!!.webLifeCycle.onPause() + } + super.onPause() + } + + override fun onKeyDown(keyCode: Int, event: KeyEvent): Boolean { + return mAgentWeb != null && mAgentWeb!!.handleKeyEvent(keyCode, event) + } + + override fun onDestroyView() { + if (mAgentWeb != null) { + mAgentWeb!!.destroy() + } + super.onDestroyView() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/core/webview/FragmentKeyDown.kt b/app/src/main/java/com/idormy/sms/forwarder/core/webview/FragmentKeyDown.kt new file mode 100644 index 00000000..5c52a653 --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/core/webview/FragmentKeyDown.kt @@ -0,0 +1,19 @@ +package com.idormy.sms.forwarder.core.webview + +import android.view.KeyEvent + +/** + * + * + * @author xuexiang + * @since 2019/1/4 下午11:32 + */ +interface FragmentKeyDown { + /** + * fragment按键监听 + * @param keyCode + * @param event + * @return + */ + fun onFragmentKeyDown(keyCode: Int, event: KeyEvent?): Boolean +} \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/core/webview/LollipopFixedWebView.kt b/app/src/main/java/com/idormy/sms/forwarder/core/webview/LollipopFixedWebView.kt new file mode 100644 index 00000000..270a27d2 --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/core/webview/LollipopFixedWebView.kt @@ -0,0 +1,52 @@ +package com.idormy.sms.forwarder.core.webview + +import android.annotation.TargetApi +import android.content.Context +import android.content.res.Configuration +import android.os.Build +import android.util.AttributeSet +import android.webkit.WebView + +/** + * 修复 Android 5.0 & 5.1 打开 WebView 闪退问题: + * 参阅 https://stackoverflow.com/questions/41025200/android-view-inflateexception-error-inflating-class-android-webkit-webview + */ +@Suppress("unused") +class LollipopFixedWebView : WebView { + constructor(context: Context) : super(getFixedContext(context)) + constructor(context: Context, attrs: AttributeSet?) : super(getFixedContext(context), attrs) + constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super( + getFixedContext(context), attrs, defStyleAttr + ) + + @TargetApi(Build.VERSION_CODES.LOLLIPOP) + constructor( + context: Context, + attrs: AttributeSet?, + defStyleAttr: Int, + defStyleRes: Int, + ) : super( + getFixedContext(context), attrs, defStyleAttr, defStyleRes + ) + + constructor( + context: Context, + attrs: AttributeSet?, + defStyleAttr: Int, + privateBrowsing: Boolean, + ) : super( + getFixedContext(context), attrs, defStyleAttr, privateBrowsing + ) + + companion object { + fun getFixedContext(context: Context): Context { + return if (isLollipopWebViewBug) { + // Avoid crashing on Android 5 and 6 (API level 21 to 23) + context.createConfigurationContext(Configuration()) + } else context + } + + private val isLollipopWebViewBug: Boolean + get() = Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && Build.VERSION.SDK_INT < Build.VERSION_CODES.M + } +} \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/core/webview/MiddlewareChromeClient.kt b/app/src/main/java/com/idormy/sms/forwarder/core/webview/MiddlewareChromeClient.kt new file mode 100644 index 00000000..41b4c094 --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/core/webview/MiddlewareChromeClient.kt @@ -0,0 +1,24 @@ +package com.idormy.sms.forwarder.core.webview + +import android.util.Log +import android.webkit.JsResult +import android.webkit.WebView +import com.just.agentweb.core.client.MiddlewareWebChromeBase + +/** + * WebChrome(WebChromeClient主要辅助WebView处理JavaScript的对话框、网站图片、网站title、加载进度等)中间件 + * 【浏览器】 + * @author xuexiang + * @since 2019/1/4 下午11:31 + */ +open class MiddlewareChromeClient : MiddlewareWebChromeBase() { + override fun onJsAlert(view: WebView, url: String, message: String, result: JsResult): Boolean { + Log.i("Info", "onJsAlert:$url") + return super.onJsAlert(view, url, message, result) + } + + override fun onProgressChanged(view: WebView, newProgress: Int) { + super.onProgressChanged(view, newProgress) + Log.i("Info", "onProgressChanged:") + } +} \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/core/webview/MiddlewareWebViewClient.kt b/app/src/main/java/com/idormy/sms/forwarder/core/webview/MiddlewareWebViewClient.kt new file mode 100644 index 00000000..0d6174b8 --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/core/webview/MiddlewareWebViewClient.kt @@ -0,0 +1,132 @@ +package com.idormy.sms.forwarder.core.webview + +import android.net.Uri +import android.os.Build +import android.util.Log +import android.webkit.WebResourceRequest +import android.webkit.WebResourceResponse +import android.webkit.WebView +import androidx.annotation.RequiresApi +import com.idormy.sms.forwarder.R +import com.idormy.sms.forwarder.core.webview.WebViewInterceptDialog.Companion.show +import com.just.agentweb.core.client.MiddlewareWebClientBase +import com.xuexiang.xui.utils.ResUtils +import java.util.* + +/** + * 【网络请求、加载】 + * WebClient(WebViewClient 这个类主要帮助WebView处理各种通知、url加载,请求时间的)中间件 + * + * + * + * + * 方法的执行顺序,例如下面用了7个中间件一个 WebViewClient + * + * + * .useMiddlewareWebClient(getMiddlewareWebClient()) // 1 + * .useMiddlewareWebClient(getMiddlewareWebClient()) // 2 + * .useMiddlewareWebClient(getMiddlewareWebClient()) // 3 + * .useMiddlewareWebClient(getMiddlewareWebClient()) // 4 + * .useMiddlewareWebClient(getMiddlewareWebClient()) // 5 + * .useMiddlewareWebClient(getMiddlewareWebClient()) // 6 + * .useMiddlewareWebClient(getMiddlewareWebClient()) // 7 + * DefaultWebClient // 8 + * .setWebViewClient(mWebViewClient) // 9 + * + * + * + * + * 典型的洋葱模型 + * 对象内部的方法执行顺序: 1->2->3->4->5->6->7->8->9->8->7->6->5->4->3->2->1 + * + * + * + * + * 中断中间件的执行, 删除super.methodName(...) 这行即可 + * + * + * 这里主要是做去广告的工作 + */ +open class MiddlewareWebViewClient : MiddlewareWebClientBase() { + @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) + override fun shouldOverrideUrlLoading(view: WebView, request: WebResourceRequest): Boolean { + Log.i( + "Info", + "MiddlewareWebViewClient -- > shouldOverrideUrlLoading:" + request.url.toString() + " c:" + count++ + ) + return if (shouldOverrideUrlLoadingByApp(view, request.url.toString())) { + true + } else super.shouldOverrideUrlLoading(view, request) + } + + override fun shouldOverrideUrlLoading(view: WebView, url: String): Boolean { + Log.i( + "Info", + "MiddlewareWebViewClient -- > shouldOverrideUrlLoading:" + url + " c:" + count++ + ) + return if (shouldOverrideUrlLoadingByApp(view, url)) { + true + } else super.shouldOverrideUrlLoading(view, url) + } + + override fun shouldInterceptRequest(view: WebView, url: String): WebResourceResponse? { + val tUrl = url.lowercase(Locale.ROOT) + return if (!hasAdUrl(tUrl)) { + //正常加载 + super.shouldInterceptRequest(view, tUrl) + } else { + //含有广告资源屏蔽请求 + WebResourceResponse(null, null, null) + } + } + + @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) + override fun shouldInterceptRequest( + view: WebView, + request: WebResourceRequest, + ): WebResourceResponse? { + val url = request.url.toString().lowercase(Locale.ROOT) + return if (!hasAdUrl(url)) { + //正常加载 + super.shouldInterceptRequest(view, request) + } else { + //含有广告资源屏蔽请求 + WebResourceResponse(null, null, null) + } + } + + /** + * 根据url的scheme处理跳转第三方app的业务,true代表拦截,false代表不拦截 + */ + private fun shouldOverrideUrlLoadingByApp(webView: WebView, url: String): Boolean { + if (url.startsWith("http") || url.startsWith("https") || url.startsWith("ftp")) { + //不拦截http, https, ftp的请求 + val uri = Uri.parse(url) + if (uri != null && !(WebViewInterceptDialog.APP_LINK_HOST == uri.host && url.contains("xpage"))) { + return false + } + } + show(url) + return true + } + + companion object { + private var count = 1 + + /** + * 判断是否存在广告的链接 + * + * @param url + * @return + */ + private fun hasAdUrl(url: String): Boolean { + val adUrls = ResUtils.getStringArray(R.array.adBlockUrl) + for (adUrl in adUrls) { + if (url.contains(adUrl)) { + return true + } + } + return false + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/core/webview/UIController.kt b/app/src/main/java/com/idormy/sms/forwarder/core/webview/UIController.kt new file mode 100644 index 00000000..46d7942e --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/core/webview/UIController.kt @@ -0,0 +1,35 @@ +package com.idormy.sms.forwarder.core.webview + +import android.app.Activity +import android.os.Handler +import android.util.Log +import android.webkit.WebView +import com.just.agentweb.core.web.AgentWebUIControllerImplBase +import java.lang.ref.WeakReference + +/** + * 如果你需要修改某一个AgentWeb 内部的某一个弹窗 ,请看下面的例子 + * 注意写法一定要参照 DefaultUIController 的写法 ,因为UI自由定制,但是回调的方式是固定的,并且一定要回调。 + * + * @author xuexiang + * @since 2019-10-30 23:18 + */ +@Suppress("unused") +class UIController(activity: Activity) : AgentWebUIControllerImplBase() { + private val mActivity: WeakReference = WeakReference(activity) + override fun onShowMessage(message: String, from: String) { + super.onShowMessage(message, from) + Log.i(TAG, "message:$message") + } + + override fun onSelectItemsPrompt( + view: WebView, + url: String, + items: Array, + callback: Handler.Callback, + ) { + // 使用默认的UI + super.onSelectItemsPrompt(view, url, items, callback) + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/core/webview/WebLayout.kt b/app/src/main/java/com/idormy/sms/forwarder/core/webview/WebLayout.kt new file mode 100644 index 00000000..21809cfe --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/core/webview/WebLayout.kt @@ -0,0 +1,29 @@ +package com.idormy.sms.forwarder.core.webview + +import android.app.Activity +import android.view.LayoutInflater +import android.view.ViewGroup +import android.webkit.WebView +import com.idormy.sms.forwarder.R +import com.just.agentweb.widget.IWebLayout +import com.scwang.smartrefresh.layout.SmartRefreshLayout + +/** + * 定义支持下来回弹的WebView + * + * @author xuexiang + * @since 2019/1/5 上午2:01 + */ +class WebLayout(activity: Activity?) : IWebLayout { + private val mSmartRefreshLayout: SmartRefreshLayout = LayoutInflater.from(activity) + .inflate(R.layout.fragment_pulldown_web, null) as SmartRefreshLayout + private val mWebView: WebView = mSmartRefreshLayout.findViewById(R.id.webView) + override fun getLayout(): ViewGroup { + return mSmartRefreshLayout + } + + override fun getWebView(): WebView { + return mWebView + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/core/webview/WebViewInterceptDialog.kt b/app/src/main/java/com/idormy/sms/forwarder/core/webview/WebViewInterceptDialog.kt new file mode 100644 index 00000000..8fc4c10f --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/core/webview/WebViewInterceptDialog.kt @@ -0,0 +1,111 @@ +package com.idormy.sms.forwarder.core.webview + +import android.content.Context +import android.content.DialogInterface +import android.content.Intent +import android.net.Uri +import android.os.Bundle +import androidx.appcompat.app.AppCompatActivity +import com.idormy.sms.forwarder.R +import com.idormy.sms.forwarder.utils.XToastUtils +import com.xuexiang.xui.utils.ResUtils +import com.xuexiang.xui.widget.dialog.DialogLoader +import com.xuexiang.xutil.XUtil +import com.xuexiang.xutil.app.ActivityUtils +import java.net.URISyntaxException + +/** + * WebView拦截提示 + * + * @author xuexiang + * @since 2019-10-21 9:51 + */ +class WebViewInterceptDialog : AppCompatActivity(), DialogInterface.OnDismissListener { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + val url = intent.getStringExtra(KEY_INTERCEPT_URL).toString() + DialogLoader.getInstance().showConfirmDialog( + this, + getOpenTitle(url), + ResUtils.getString(R.string.lab_yes), + { dialog: DialogInterface, _: Int -> + dialog.dismiss() + if (isAppLink(url)) { + openAppLink(this, url) + } else { + openApp(url) + } + }, + ResUtils.getString(R.string.lab_no) + ) { dialog: DialogInterface, _: Int -> dialog.dismiss() }.setOnDismissListener(this) + } + + private fun getOpenTitle(url: String): String { + val scheme = getScheme(url) + return if ("mqqopensdkapi" == scheme) { + "是否允许页面打开\"QQ\"?" + } else { + ResUtils.getString(R.string.lab_open_third_app) + } + } + + private fun getScheme(url: String): String? { + try { + val intent = Intent.parseUri(url, Intent.URI_INTENT_SCHEME) + return intent.scheme + } catch (e: URISyntaxException) { + e.printStackTrace() + } + return "" + } + + private fun isAppLink(url: String): Boolean { + val uri = Uri.parse(url) + return uri != null && APP_LINK_HOST == uri.host && (url.startsWith("http") || url.startsWith( + "https" + )) + } + + private fun openApp(url: String) { + val intent: Intent + try { + intent = Intent.parseUri(url, Intent.URI_INTENT_SCHEME) + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_SINGLE_TOP) + XUtil.getContext().startActivity(intent) + } catch (e: Exception) { + XToastUtils.error(getString(R.string.third_party_app_not_installed)) + } + } + + private fun openAppLink(context: Context, url: String) { + try { + val intent = Intent(APP_LINK_ACTION) + intent.data = Uri.parse(url) + context.startActivity(intent) + } catch (e: Exception) { + XToastUtils.error(getString(R.string.third_party_app_not_installed)) + } + } + + override fun onDismiss(dialog: DialogInterface) { + finish() + } + + companion object { + private const val KEY_INTERCEPT_URL = "key_intercept_url" + + // TODO: 2019-10-30 这里修改你的applink + const val APP_LINK_HOST = "xuexiangjys.club" + const val APP_LINK_ACTION = "com.xuexiang.xui.applink" + + /** + * 显示WebView拦截提示 + * + * @param url 需要拦截处理的url + */ + @JvmStatic + fun show(url: String?) { + ActivityUtils.startActivity(WebViewInterceptDialog::class.java, KEY_INTERCEPT_URL, url) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/core/webview/XPageWebViewFragment.kt b/app/src/main/java/com/idormy/sms/forwarder/core/webview/XPageWebViewFragment.kt new file mode 100644 index 00000000..69de9112 --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/core/webview/XPageWebViewFragment.kt @@ -0,0 +1,595 @@ +package com.idormy.sms.forwarder.core.webview + +import android.content.ClipData +import android.content.ClipboardManager +import android.content.Context +import android.content.Intent +import android.graphics.Bitmap +import android.graphics.Color +import android.net.Uri +import android.os.Build +import android.text.TextUtils +import android.view.* +import android.webkit.* +import android.widget.FrameLayout +import android.widget.LinearLayout +import android.widget.TextView +import androidx.annotation.RequiresApi +import androidx.appcompat.widget.PopupMenu +import androidx.fragment.app.Fragment +import com.idormy.sms.forwarder.R +import com.idormy.sms.forwarder.core.BaseFragment +import com.idormy.sms.forwarder.databinding.FragmentAgentwebBinding +import com.idormy.sms.forwarder.utils.XToastUtils +import com.just.agentweb.action.PermissionInterceptor +import com.just.agentweb.core.AgentWeb +import com.just.agentweb.core.client.DefaultWebClient +import com.just.agentweb.core.client.MiddlewareWebChromeBase +import com.just.agentweb.core.client.MiddlewareWebClientBase +import com.just.agentweb.core.client.WebListenerManager +import com.just.agentweb.core.web.AbsAgentWebSettings +import com.just.agentweb.core.web.AgentWebConfig +import com.just.agentweb.core.web.IAgentWebSettings +import com.just.agentweb.download.AgentWebDownloader.Extra +import com.just.agentweb.download.DefaultDownloadImpl +import com.just.agentweb.download.DownloadListenerAdapter +import com.just.agentweb.download.DownloadingService +import com.just.agentweb.widget.IWebLayout +import com.xuexiang.xaop.annotation.SingleClick +import com.xuexiang.xpage.annotation.Page +import com.xuexiang.xpage.base.XPageActivity +import com.xuexiang.xpage.base.XPageFragment +import com.xuexiang.xpage.core.PageOption +import com.xuexiang.xui.widget.actionbar.TitleBar +import com.xuexiang.xutil.common.logger.Logger +import com.xuexiang.xutil.net.JsonUtil + +/** + * 使用XPageFragment + * + * @author xuexiang + * @since 2019-05-26 18:15 + */ +@Suppress("DEPRECATION", "unused", "UNUSED_PARAMETER", "NAME_SHADOWING", "OVERRIDE_DEPRECATION") +@Page(params = [AgentWebFragment.KEY_URL]) +class XPageWebViewFragment : BaseFragment(), View.OnClickListener { + private var mAgentWeb: AgentWeb? = null + private var mPopupMenu: PopupMenu? = null + private var mDownloadingService: DownloadingService? = null + override fun viewBindingInflate( + inflater: LayoutInflater, + container: ViewGroup, + ): FragmentAgentwebBinding { + return FragmentAgentwebBinding.inflate(inflater, container, false) + } + + override fun initTitle(): TitleBar? { + return null + } + + /** + * 初始化控件 + */ + override fun initViews() { + mAgentWeb = AgentWeb.with(this) //传入AgentWeb的父控件。 + .setAgentWebParent( + (rootView as LinearLayout), + -1, + LinearLayout.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.MATCH_PARENT + ) + ) //设置进度条颜色与高度,-1为默认值,高度为2,单位为dp。 + .useDefaultIndicator(-1, 3) //设置 IAgentWebSettings。 + .setAgentWebWebSettings(settings) //WebViewClient , 与 WebView 使用一致 ,但是请勿获取WebView调用setWebViewClient(xx)方法了,会覆盖AgentWeb DefaultWebClient,同时相应的中间件也会失效。 + .setWebViewClient(mWebViewClient) //WebChromeClient + .setWebChromeClient(mWebChromeClient) //设置WebChromeClient中间件,支持多个WebChromeClient,AgentWeb 3.0.0 加入。 + .useMiddlewareWebChrome(middlewareWebChrome) //设置WebViewClient中间件,支持多个WebViewClient, AgentWeb 3.0.0 加入。 + .useMiddlewareWebClient(middlewareWebClient) //权限拦截 2.0.0 加入。 + .setPermissionInterceptor(mPermissionInterceptor) //严格模式 Android 4.2.2 以下会放弃注入对象 ,使用AgentWebView没影响。 + .setSecurityType(AgentWeb.SecurityType.STRICT_CHECK) //自定义UI AgentWeb3.0.0 加入。 + .setAgentWebUIController(UIController(requireActivity())) //参数1是错误显示的布局,参数2点击刷新控件ID -1表示点击整个布局都刷新, AgentWeb 3.0.0 加入。 + .setMainFrameErrorView(R.layout.agentweb_error_page, -1) + .setWebLayout(webLayout) //打开其他页面时,弹窗质询用户前往其他应用 AgentWeb 3.0.0 加入。 + .setOpenOtherPageWays(DefaultWebClient.OpenOtherPageWays.DISALLOW) //拦截找不到相关页面的Url AgentWeb 3.0.0 加入。 + .interceptUnkownUrl() //创建AgentWeb。 + .createAgentWeb() + .ready() //设置 WebSettings。 + //WebView载入该url地址的页面并显示。 + .go(url) + if (com.idormy.sms.forwarder.App.isDebug) { + AgentWebConfig.debug() + } + pageNavigator(View.GONE) + // 得到 AgentWeb 最底层的控件 + addBackgroundChild(mAgentWeb!!.webCreator.webParentLayout) + + // AgentWeb 没有把WebView的功能全面覆盖 ,所以某些设置 AgentWeb 没有提供,请从WebView方面入手设置。 + mAgentWeb!!.webCreator.webView.overScrollMode = WebView.OVER_SCROLL_NEVER + } + + private val webLayout: IWebLayout<*, *> + get() = WebLayout(activity) + + private fun addBackgroundChild(frameLayout: FrameLayout) { + val textView = TextView(frameLayout.context) + textView.text = getString(R.string.provided_by_agentweb) + textView.textSize = 16f + textView.setTextColor(Color.parseColor("#727779")) + frameLayout.setBackgroundColor(Color.parseColor("#272b2d")) + val params = FrameLayout.LayoutParams(-2, -2) + params.gravity = Gravity.CENTER_HORIZONTAL + val scale = frameLayout.context.resources.displayMetrics.density + params.topMargin = (15 * scale + 0.5f).toInt() + frameLayout.addView(textView, 0, params) + } + + override fun initListeners() { + binding!!.includeTitle.ivBack.setOnClickListener(this) + binding!!.includeTitle.ivFinish.setOnClickListener(this) + binding!!.includeTitle.ivMore.setOnClickListener(this) + } + + private fun pageNavigator(tag: Int) { + //返回的导航按钮 + binding!!.includeTitle.ivBack.visibility = tag + binding!!.includeTitle.viewLine.visibility = tag + } + + @SingleClick + override fun onClick(view: View) { + val id = view.id + if (id == R.id.iv_back) { + // true表示AgentWeb处理了该事件 + if (!mAgentWeb!!.back()) { + popToBack() + } + } else if (id == R.id.iv_finish) { + popToBack() + } else if (id == R.id.iv_more) { + showPoPup(view) + } + } + //=====================下载============================// + /** + * 更新于 AgentWeb 4.0.0,下载监听 + */ + private var mDownloadListenerAdapter: DownloadListenerAdapter = + object : DownloadListenerAdapter() { + /** + * + * @param url 下载链接 + * @param userAgent UserAgent + * @param contentDisposition ContentDisposition + * @param mimeType 资源的媒体类型 + * @param contentLength 文件长度 + * @param extra 下载配置 , 用户可以通过 Extra 修改下载icon , 关闭进度条 , 是否强制下载。 + * @return true 表示用户处理了该下载事件 , false 交给 AgentWeb 下载 + */ + override fun onStart( + url: String, + userAgent: String, + contentDisposition: String, + mimeType: String, + contentLength: Long, + extra: Extra, + ): Boolean { + Logger.i("onStart:$url") + // 是否开启断点续传 + extra.setOpenBreakPointDownload(true) //下载通知的icon + .setIcon(R.drawable.ic_file_download_black_24dp) // 连接的超时时间 + .setConnectTimeOut(6000) // 以8KB位单位,默认60s ,如果60s内无法从网络流中读满8KB数据,则抛出异常 + .setBlockMaxTime(10 * 60 * 1000) // 下载的超时时间 + .setDownloadTimeOut(Long.MAX_VALUE) // 串行下载更节省资源哦 + .setParallelDownload(false) // false 关闭进度通知 + .setEnableIndicator(true) // 自定义请求头 + .addHeader("Cookie", "xx") // 下载完成自动打开 + .setAutoOpen(true).isForceDownload = true + return false + } + + /** + * + * 不需要暂停或者停止下载该方法可以不必实现 + * @param url + * @param downloadingService 用户可以通过 DownloadingService#shutdownNow 终止下载 + */ + override fun onBindService(url: String, downloadingService: DownloadingService) { + super.onBindService(url, downloadingService) + mDownloadingService = downloadingService + Logger.i("onBindService:$url DownloadingService:$downloadingService") + } + + /** + * 回调onUnbindService方法,让用户释放掉 DownloadingService。 + * @param url + * @param downloadingService + */ + override fun onUnbindService(url: String, downloadingService: DownloadingService) { + super.onUnbindService(url, downloadingService) + mDownloadingService = null + Logger.i("onUnbindService:$url") + } + + /** + * + * @param url 下载链接 + * @param loaded 已经下载的长度 + * @param length 文件的总大小 + * @param usedTime 耗时 ,单位ms + * 注意该方法回调在子线程 ,线程名 AsyncTask #XX 或者 AgentWeb # XX + */ + override fun onProgress(url: String, loaded: Long, length: Long, usedTime: Long) { + val mProgress = (loaded / length.toFloat() * 100).toInt() + Logger.i("onProgress:$mProgress") + super.onProgress(url, loaded, length, usedTime) + } + + /** + * + * @param path 文件的绝对路径 + * @param url 下载地址 + * @param throwable 如果异常,返回给用户异常 + * @return true 表示用户处理了下载完成后续的事件 ,false 默认交给AgentWeb 处理 + */ + override fun onResult(path: String, url: String, throwable: Throwable): Boolean { + //下载成功 + //if (null == throwable) { + //do you work + //} else { //下载失败 + //} + // true 不会发出下载完成的通知 , 或者打开文件 + return false + } + } + /** + * AgentWeb 4.0.0 内部删除了 DownloadListener 监听 ,以及相关API ,将 Download 部分完全抽离出来独立一个库, + * 如果你需要使用 AgentWeb Download 部分 , 请依赖上 compile 'com.just.agentweb:download:4.0.0 , + * 如果你需要监听下载结果,请自定义 AgentWebSetting , New 出 DefaultDownloadImpl,传入DownloadListenerAdapter + * 实现进度或者结果监听,例如下面这个例子,如果你不需要监听进度,或者下载结果,下面 setDownloader 的例子可以忽略。 + * @return WebListenerManager + */ + /** + * 下载服务设置 + * + * @return IAgentWebSettings + */ + val settings: IAgentWebSettings<*> + get() = object : AbsAgentWebSettings() { + private val mAgentWeb: AgentWeb? = null + override fun bindAgentWebSupport(agentWeb: AgentWeb) { + this.mAgentWeb = agentWeb + } + + /** + * AgentWeb 4.0.0 内部删除了 DownloadListener 监听 ,以及相关API ,将 Download 部分完全抽离出来独立一个库, + * 如果你需要使用 AgentWeb Download 部分 , 请依赖上 compile 'com.just.agentweb:download:4.0.0 , + * 如果你需要监听下载结果,请自定义 AgentWebSetting , New 出 DefaultDownloadImpl,传入DownloadListenerAdapter + * 实现进度或者结果监听,例如下面这个例子,如果你不需要监听进度,或者下载结果,下面 setDownloader 的例子可以忽略。 + * @return WebListenerManager + */ + override fun setDownloader( + webView: WebView, + downloadListener: DownloadListener, + ): WebListenerManager { + return super.setDownloader( + webView, + DefaultDownloadImpl + .create( + activity!!, + webView, + mDownloadListenerAdapter, + mDownloadListenerAdapter, + mAgentWeb.permissionInterceptor + ) + ) + } + } + //===================WebChromeClient 和 WebViewClient===========================// + /** + * 页面空白,请检查scheme是否加上, scheme://host:port/path?query&query 。 + * + * @return mUrl + */ + val url: String + get() { + var target = "" + val bundle = arguments + if (bundle != null) { + target = bundle.getString(AgentWebFragment.KEY_URL).toString() + } + if (TextUtils.isEmpty(target)) { + target = "https://github.com/xuexiangjys" + } + return target + } + + /** + * 和浏览器相关,包括和JS的交互 + */ + private var mWebChromeClient: WebChromeClient = object : WebChromeClient() { + override fun onProgressChanged(view: WebView, newProgress: Int) { + super.onProgressChanged(view, newProgress) + //网页加载进度 + } + + override fun onReceivedTitle(view: WebView, title: String) { + var title = title + super.onReceivedTitle(view, title) + if (!TextUtils.isEmpty(title)) { + if (title.length > 10) { + title = title.substring(0, 10) + "..." + } + binding!!.includeTitle.toolbarTitle.text = title + } + } + } + + /** + * 和网页url加载相关,统计加载时间 + */ + @Suppress("DEPRECATION") + private var mWebViewClient: WebViewClient = object : WebViewClient() { + private val mTimer = HashMap() + override fun onReceivedError( + view: WebView, + request: WebResourceRequest, + error: WebResourceError, + ) { + super.onReceivedError(view, request, error) + } + + @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) + override fun shouldOverrideUrlLoading(view: WebView, request: WebResourceRequest): Boolean { + return shouldOverrideUrlLoading(view, request.url.toString() + "") + } + + override fun shouldInterceptRequest( + view: WebView, + request: WebResourceRequest, + ): WebResourceResponse? { + return super.shouldInterceptRequest(view, request) + } + + override fun shouldOverrideUrlLoading(view: WebView, url: String): Boolean { + //intent:// scheme的处理 如果返回false , 则交给 DefaultWebClient 处理 , 默认会打开该Activity , 如果Activity不存在则跳到应用市场上去. true 表示拦截 + //例如优酷视频播放 ,intent://play?...package=com.youku.phone;end; + //优酷想唤起自己应用播放该视频 , 下面拦截地址返回 true 则会在应用内 H5 播放 ,禁止优酷唤起播放该视频, 如果返回 false , DefaultWebClient 会根据intent 协议处理 该地址 , 首先匹配该应用存不存在 ,如果存在 , 唤起该应用播放 , 如果不存在 , 则跳到应用市场下载该应用 . + return url.startsWith("intent://") && url.contains("com.youku.phone") + } + + override fun onPageStarted(view: WebView, url: String, favicon: Bitmap) { + mTimer[url] = System.currentTimeMillis() + if (url == url) { + pageNavigator(View.GONE) + } else { + pageNavigator(View.VISIBLE) + } + } + + override fun onPageFinished(view: WebView, url: String) { + super.onPageFinished(view, url) + if (mTimer[url] != null) { + val overTime = System.currentTimeMillis() + val startTime = mTimer[url] + //统计页面的使用时长 + Logger.i(" page mUrl:" + url + " used time:" + (overTime - startTime!!)) + } + } + + override fun onReceivedHttpError( + view: WebView, + request: WebResourceRequest, + errorResponse: WebResourceResponse, + ) { + super.onReceivedHttpError(view, request, errorResponse) + } + + override fun onReceivedError( + view: WebView, + errorCode: Int, + description: String, + failingUrl: String, + ) { + super.onReceivedError(view, errorCode, description, failingUrl) + } + } + //=====================菜单========================// + /** + * 显示更多菜单 + * + * @param view 菜单依附在该View下面 + */ + private fun showPoPup(view: View) { + if (mPopupMenu == null) { + mPopupMenu = PopupMenu(requireContext(), view) + mPopupMenu!!.inflate(R.menu.menu_toolbar_web) + mPopupMenu!!.setOnMenuItemClickListener(mOnMenuItemClickListener) + } + mPopupMenu!!.show() + } + + /** + * 菜单事件 + */ + private val mOnMenuItemClickListener = PopupMenu.OnMenuItemClickListener { item -> + when (item.itemId) { + R.id.refresh -> { + if (mAgentWeb != null) { + mAgentWeb!!.urlLoader.reload() // 刷新 + } + return@OnMenuItemClickListener true + } + R.id.copy -> { + if (mAgentWeb != null) { + mAgentWeb!!.webCreator.webView.url?.let { toCopy(context, it) } + } + return@OnMenuItemClickListener true + } + R.id.default_browser -> { + if (mAgentWeb != null) { + mAgentWeb!!.webCreator.webView.url?.let { openBrowser(it) } + } + return@OnMenuItemClickListener true + } + R.id.share -> { + if (mAgentWeb != null) { + mAgentWeb!!.webCreator.webView.url?.let { shareWebUrl(it) } + } + return@OnMenuItemClickListener true + } + else -> false + } + } + + /** + * 打开浏览器 + * + * @param targetUrl 外部浏览器打开的地址 + */ + private fun openBrowser(targetUrl: String) { + if (TextUtils.isEmpty(targetUrl) || targetUrl.startsWith("file://")) { + XToastUtils.toast(targetUrl + getString(R.string.cannot_open_with_browser)) + return + } + val intent = Intent() + intent.action = "android.intent.action.VIEW" + val uri = Uri.parse(targetUrl) + intent.data = uri + startActivity(intent) + } + + /** + * 分享网页链接 + * + * @param url 网页链接 + */ + private fun shareWebUrl(url: String) { + val shareIntent = Intent() + shareIntent.action = Intent.ACTION_SEND + shareIntent.putExtra(Intent.EXTRA_TEXT, url) + shareIntent.type = "text/plain" + //设置分享列表的标题,并且每次都显示分享列表 + startActivity(Intent.createChooser(shareIntent, getString(R.string.share_to))) + } + + /** + * 复制字符串 + * + * @param context + * @param text + */ + private fun toCopy(context: Context?, text: String) { + val manager = requireContext().getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager + manager.setPrimaryClip(ClipData.newPlainText(null, text)) + } + + //===================生命周期管理===========================// + override fun onResume() { + if (mAgentWeb != null) { + mAgentWeb!!.webLifeCycle.onResume() //恢复 + } + super.onResume() + } + + override fun onPause() { + if (mAgentWeb != null) { + mAgentWeb!!.webLifeCycle.onPause() //暂停应用内所有WebView , 调用mWebView.resumeTimers();/mAgentWeb.getWebLifeCycle().onResume(); 恢复。 + } + super.onPause() + } + + override fun onKeyDown(keyCode: Int, event: KeyEvent): Boolean { + return mAgentWeb != null && mAgentWeb!!.handleKeyEvent(keyCode, event) + } + + override fun onDestroyView() { + if (mAgentWeb != null) { + mAgentWeb!!.destroy() + } + super.onDestroyView() + } + //===================中间键===========================//// 拦截 url,不执行 DefaultWebClient#shouldOverrideUrlLoading + // 执行 DefaultWebClient#shouldOverrideUrlLoading + // do you work + /** + * MiddlewareWebClientBase 是 AgentWeb 3.0.0 提供一个强大的功能, + * 如果用户需要使用 AgentWeb 提供的功能, 不想重写 WebClientView方 + * 法覆盖AgentWeb提供的功能,那么 MiddlewareWebClientBase 是一个 + * 不错的选择 。 + */ + private val middlewareWebClient: MiddlewareWebClientBase + get() = object : MiddlewareWebViewClient() { + override fun shouldOverrideUrlLoading(view: WebView, url: String): Boolean { + // 拦截 url,不执行 DefaultWebClient#shouldOverrideUrlLoading + if (url.startsWith("agentweb")) { + return true + } + // 执行 DefaultWebClient#shouldOverrideUrlLoading + return super.shouldOverrideUrlLoading(view, url) + // do you work + } + + @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) + override fun shouldOverrideUrlLoading( + view: WebView, + request: WebResourceRequest, + ): Boolean { + return super.shouldOverrideUrlLoading(view, request) + } + } + private val middlewareWebChrome: MiddlewareWebChromeBase + get() = object : MiddlewareChromeClient() {} + + /** + * 权限申请拦截器 + */ + private var mPermissionInterceptor = PermissionInterceptor { url, permissions, action -> + + /** + * PermissionInterceptor 能达到 url1 允许授权, url2 拒绝授权的效果。 + * @param url + * @param permissions + * @param action + * @return true 该Url对应页面请求权限进行拦截 ,false 表示不拦截。 + */ + /** + * PermissionInterceptor 能达到 url1 允许授权, url2 拒绝授权的效果。 + * @param url + * @param permissions + * @param action + * @return true 该Url对应页面请求权限进行拦截 ,false 表示不拦截。 + */ + Logger.i("mUrl:" + url + " permission:" + JsonUtil.toJson(permissions) + " action:" + action) + false + } + + companion object { + /** + * 打开网页 + * + * @param xPageActivity + * @param url + * @return + */ + fun openUrl(xPageActivity: XPageActivity?, url: String?): Fragment { + return PageOption.to(XPageWebViewFragment::class.java) + .putString(AgentWebFragment.KEY_URL, url) + .open(xPageActivity!!) + } + + /** + * 打开网页 + * + * @param fragment + * @param url + * @return + */ + fun openUrl(fragment: XPageFragment?, url: String?): Fragment { + return PageOption.to(XPageWebViewFragment::class.java) + .setNewActivity(true) + .putString(AgentWebFragment.KEY_URL, url) + .open(fragment!!) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/database/AppDatabase.java b/app/src/main/java/com/idormy/sms/forwarder/database/AppDatabase.java deleted file mode 100644 index 531d13d9..00000000 --- a/app/src/main/java/com/idormy/sms/forwarder/database/AppDatabase.java +++ /dev/null @@ -1,25 +0,0 @@ -package com.idormy.sms.forwarder.database; - -import android.content.Context; - -import androidx.room.Database; -import androidx.room.Room; -import androidx.room.RoomDatabase; - -@Database(entities = {Config.class}, version = 2, exportSchema = false) -public abstract class AppDatabase extends RoomDatabase { - private static volatile AppDatabase instance; - - public abstract ConfigDao configDao(); - - public static AppDatabase getInstance(Context context) { - if (instance == null) { - synchronized (AppDatabase.class) { - if (instance == null) { - instance = Room.databaseBuilder(context, AppDatabase.class, "sms_forwarder.db").build(); - } - } - } - return instance; - } -} \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/database/AppDatabase.kt b/app/src/main/java/com/idormy/sms/forwarder/database/AppDatabase.kt new file mode 100644 index 00000000..f5d5d165 --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/database/AppDatabase.kt @@ -0,0 +1,263 @@ +package com.idormy.sms.forwarder.database + +import android.content.Context +import androidx.room.Database +import androidx.room.Room +import androidx.room.RoomDatabase +import androidx.room.TypeConverters +import androidx.room.migration.Migration +import androidx.sqlite.db.SupportSQLiteDatabase +import com.idormy.sms.forwarder.database.dao.FrpcDao +import com.idormy.sms.forwarder.database.dao.LogsDao +import com.idormy.sms.forwarder.database.dao.RuleDao +import com.idormy.sms.forwarder.database.dao.SenderDao +import com.idormy.sms.forwarder.database.entity.Frpc +import com.idormy.sms.forwarder.database.entity.Logs +import com.idormy.sms.forwarder.database.entity.Rule +import com.idormy.sms.forwarder.database.entity.Sender +import com.idormy.sms.forwarder.database.ext.Converters +import com.idormy.sms.forwarder.utils.DATABASE_NAME +import java.util.concurrent.Executors + +@Database( + entities = [Frpc::class, Logs::class, Rule::class, Sender::class], + version = 10, + exportSchema = false +) +@TypeConverters(Converters::class) +abstract class AppDatabase : RoomDatabase() { + + abstract fun frpcDao(): FrpcDao + abstract fun logsDao(): LogsDao + abstract fun ruleDao(): RuleDao + abstract fun senderDao(): SenderDao + + companion object { + @Volatile + private var instance: AppDatabase? = null + + fun getInstance(context: Context): AppDatabase { + return instance ?: synchronized(this) { + instance ?: buildDatabase(context).also { instance = it } + } + } + + private fun buildDatabase(context: Context): AppDatabase { + return Room.databaseBuilder(context.applicationContext, AppDatabase::class.java, DATABASE_NAME) + .allowMainThreadQueries() //TODO:允许主线程访问,后面再优化 + .addCallback(object : RoomDatabase.Callback() { + override fun onCreate(db: SupportSQLiteDatabase) { + //fillInDb(context.applicationContext) + db.execSQL( + """ +INSERT INTO "Frpc" VALUES ('830b0a0e-c2b3-4f95-b3c9-55db12923d2e', '远程控制SmsForwarder', '[common] +#frps服务端公网IP +server_addr = 88.88.88.88 +#frps服务端公网端口 +server_port = 8888 +#可选,建议启用 +token = 888888888 + +[SmsForwarder-TCP] +type = tcp +local_ip = 127.0.0.1 +local_port = 5000 +#只要修改下面这一行 +remote_port = 5000 + +[SmsForwarder-HTTP] +type = http +local_ip = 127.0.0.1 +local_port = 5000 +#只要修改下面这一行 +custom_domains = smsf.demo.com +', 0, '1651334400000') +""".trimIndent() + ) + } + }) + .addMigrations( + MIGRATION_1_2, + MIGRATION_2_3, + MIGRATION_3_4, + MIGRATION_4_5, + MIGRATION_5_6, + MIGRATION_6_7, + MIGRATION_7_8, + MIGRATION_8_9, + MIGRATION_9_10, + ) + .setQueryCallback({ sqlQuery, bindArgs -> + println("SQL_QUERY: $sqlQuery\nBIND_ARGS: $bindArgs") + }, Executors.newSingleThreadExecutor()) + .build() + } + + //转发日志添加SIM卡槽信息 + private val MIGRATION_1_2 = object : Migration(1, 2) { + override fun migrate(database: SupportSQLiteDatabase) { + database.execSQL("Alter table log add column sim_info TEXT ") + } + } + + //转发规则添加SIM卡槽信息 + private val MIGRATION_2_3 = object : Migration(2, 3) { + override fun migrate(database: SupportSQLiteDatabase) { + database.execSQL("Alter table rule add column sim_slot TEXT NOT NULL DEFAULT 'ALL' ") + } + } + + //转发日志添加转发状态与返回信息 + private val MIGRATION_3_4 = object : Migration(3, 4) { + override fun migrate(database: SupportSQLiteDatabase) { + database.execSQL("Alter table log add column forward_status INTEGER NOT NULL DEFAULT 1 ") + database.execSQL("Alter table log add column forward_response TEXT NOT NULL DEFAULT 'ok' ") + } + } + + //转发规则添加规则自定义信息模板 + private val MIGRATION_4_5 = object : Migration(4, 5) { + override fun migrate(database: SupportSQLiteDatabase) { + database.execSQL("Alter table rule add column sms_template TEXT NOT NULL DEFAULT '' ") + } + } + + //增加转发规则与日志的分类 + private val MIGRATION_5_6 = object : Migration(5, 6) { + override fun migrate(database: SupportSQLiteDatabase) { + database.execSQL("Alter table rule add column type TEXT NOT NULL DEFAULT 'sms' ") + database.execSQL("Alter table log add column type TEXT NOT NULL DEFAULT 'sms' ") + } + } + + //转发规则添加正则替换内容 + private val MIGRATION_6_7 = object : Migration(6, 7) { + override fun migrate(database: SupportSQLiteDatabase) { + database.execSQL("Alter table rule add column regex_replace TEXT NOT NULL DEFAULT '' ") + } + } + + //更新日志表状态:0=失败,1=待处理,2=成功 + private val MIGRATION_7_8 = object : Migration(7, 8) { + override fun migrate(database: SupportSQLiteDatabase) { + database.execSQL("update log set forward_status = 2 where forward_status = 1 ") + } + } + + //规则/通道状态:0=禁用,1=启用 + private val MIGRATION_8_9 = object : Migration(8, 9) { + override fun migrate(database: SupportSQLiteDatabase) { + database.execSQL("Alter table rule add column status INTEGER NOT NULL DEFAULT 1 ") + database.execSQL("update sender set status = 1 ") + } + } + + //从SQLite迁移到 Room + private val MIGRATION_9_10 = object : Migration(9, 10) { + override fun migrate(database: SupportSQLiteDatabase) { + database.execSQL( + """ +CREATE TABLE "Frpc" ( + "uid" TEXT NOT NULL, + "name" TEXT NOT NULL, + "config" TEXT NOT NULL, + "autorun" INTEGER NOT NULL DEFAULT 0, + "time" INTEGER NOT NULL, + PRIMARY KEY ("uid") +) +""".trimIndent() + ) + database.execSQL( + """ +INSERT INTO "Frpc" VALUES ('830b0a0e-c2b3-4f95-b3c9-55db12923d2e', '远程控制SmsForwarder', '[common] +#frps服务端公网IP +server_addr = 88.88.88.88 +#frps服务端公网端口 +server_port = 8888 +#可选,建议启用 +token = 888888888 + +[SmsForwarder-TCP] +type = tcp +local_ip = 127.0.0.1 +local_port = 5000 +#只要修改下面这一行 +remote_port = 5000 + +[SmsForwarder-HTTP] +type = http +local_ip = 127.0.0.1 +local_port = 5000 +#只要修改下面这一行 +custom_domains = smsf.demo.com +', 0, '1651334400000') +""".trimIndent() + ) + + database.execSQL("ALTER TABLE log RENAME TO old_log") + database.execSQL( + """ +CREATE TABLE "Logs" ( + "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + "type" TEXT NOT NULL DEFAULT 'sms', + "from" TEXT NOT NULL DEFAULT '', + "content" TEXT NOT NULL DEFAULT '', + "rule_id" INTEGER NOT NULL DEFAULT 0, + "sim_info" TEXT NOT NULL DEFAULT '', + "forward_status" INTEGER NOT NULL DEFAULT 1, + "forward_response" TEXT NOT NULL DEFAULT '', + "time" INTEGER NOT NULL, + FOREIGN KEY ("rule_id") REFERENCES "Rule" ("id") ON DELETE CASCADE ON UPDATE CASCADE +) +""".trimIndent() + ) + database.execSQL("CREATE UNIQUE INDEX \"index_Log_id\" ON \"Logs\" ( \"id\" ASC)") + database.execSQL("CREATE INDEX \"index_Log_rule_id\" ON \"Logs\" ( \"rule_id\" ASC)") + database.execSQL("INSERT INTO Logs (id,type,`from`,content,sim_info,rule_id,forward_status,forward_response,time) SELECT _id,type,l_from,content,sim_info,rule_id,forward_status,forward_response,strftime('%s000',time) FROM old_log") + database.execSQL("DROP TABLE old_log") + + database.execSQL("ALTER TABLE rule RENAME TO old_rule") + database.execSQL( + """ +CREATE TABLE "Rule" ( + "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + "type" TEXT NOT NULL DEFAULT 'sms', + "filed" TEXT NOT NULL DEFAULT 'transpond_all', + "check" TEXT NOT NULL DEFAULT 'is', + "value" TEXT NOT NULL DEFAULT '', + "sender_id" INTEGER NOT NULL DEFAULT 0, + "sms_template" TEXT NOT NULL DEFAULT '', + "regex_replace" TEXT NOT NULL DEFAULT '', + "sim_slot" TEXT NOT NULL DEFAULT 'ALL', + "status" INTEGER NOT NULL DEFAULT 1, + "time" INTEGER NOT NULL, + FOREIGN KEY ("sender_id") REFERENCES "Sender" ("id") ON DELETE CASCADE ON UPDATE CASCADE +) +""".trimIndent() + ) + database.execSQL("CREATE UNIQUE INDEX \"index_Rule_id\" ON \"Rule\" ( \"id\" ASC)") + database.execSQL("CREATE INDEX \"index_Rule_sender_id\" ON \"Rule\" ( \"sender_id\" ASC)") + database.execSQL("INSERT INTO Rule (id,type,filed,`check`,value,sender_id,time,sms_template,regex_replace,status,sim_slot) SELECT _id,type,filed,tcheck,value,sender_id,strftime('%s000',time),sms_template,regex_replace,status,sim_slot FROM old_rule") + database.execSQL("DROP TABLE old_rule") + + database.execSQL("ALTER TABLE sender RENAME TO old_sender") + database.execSQL( + """ +CREATE TABLE "Sender" ( + "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + "type" INTEGER NOT NULL DEFAULT 1, + "name" TEXT NOT NULL DEFAULT '', + "json_setting" TEXT NOT NULL DEFAULT '', + "status" INTEGER NOT NULL DEFAULT 1, + "time" INTEGER NOT NULL +) +""".trimIndent() + ) + database.execSQL("INSERT INTO Sender (id,name,status,type,json_setting,time) SELECT _id,name,status,type,json_setting,strftime('%s000',time) FROM old_sender") + database.execSQL("DROP TABLE old_sender") + } + } + + } + +} diff --git a/app/src/main/java/com/idormy/sms/forwarder/database/Config.java b/app/src/main/java/com/idormy/sms/forwarder/database/Config.java deleted file mode 100644 index 02bb63c6..00000000 --- a/app/src/main/java/com/idormy/sms/forwarder/database/Config.java +++ /dev/null @@ -1,94 +0,0 @@ -package com.idormy.sms.forwarder.database; - -import androidx.annotation.NonNull; -import androidx.room.Entity; -import androidx.room.Ignore; -import androidx.room.PrimaryKey; - -import java.util.Objects; - -@Entity - -public class Config { - - @PrimaryKey - @NonNull - private String uid; - private String name; - private String cfg; - @Ignore - private Boolean connecting; - - @Ignore - public Config() { - } - - @Ignore - public Config(String cfg) { - this.cfg = cfg; - } - - public Config(@NonNull String uid, String name, String cfg) { - this.uid = uid; - this.name = name; - this.cfg = cfg; - } - - @NonNull - public String getUid() { - return uid; - } - - public Config setUid(@NonNull String uid) { - this.uid = uid; - return this; - } - - public String getName() { - return name; - } - - public Config setName(String name) { - this.name = name; - return this; - } - - public Boolean getConnecting() { - return connecting; - } - - public Config setConnecting(Boolean connecting) { - this.connecting = connecting; - return this; - } - - public String getCfg() { - return cfg; - } - - public Config setCfg(String cfg) { - this.cfg = cfg; - return this; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - Config config = (Config) o; - return Objects.equals(uid, config.uid); - } - - @Override - public int hashCode() { - return Objects.hash(uid); - } - - @Override - public String toString() { - return "Config{" + - "uid='" + uid + '\'' + - ", cfg='" + cfg + '\'' + - '}'; - } -} diff --git a/app/src/main/java/com/idormy/sms/forwarder/database/ConfigDao.java b/app/src/main/java/com/idormy/sms/forwarder/database/ConfigDao.java deleted file mode 100644 index 9edf3dbd..00000000 --- a/app/src/main/java/com/idormy/sms/forwarder/database/ConfigDao.java +++ /dev/null @@ -1,30 +0,0 @@ -package com.idormy.sms.forwarder.database; - -import androidx.room.Dao; -import androidx.room.Delete; -import androidx.room.Insert; -import androidx.room.Query; -import androidx.room.Update; - -import java.util.List; - -import io.reactivex.Completable; -import io.reactivex.Single; - -@Dao -public interface ConfigDao { - @Query("SELECT * FROM config") - Single> getAll(); - - @Query("SELECT * FROM config where uid=:uid") - Single getConfigByUid(String uid); - - @Update - Completable update(Config config); - - @Insert - Completable insert(Config config); - - @Delete - Completable delete(Config config); -} \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/database/dao/FrpcDao.kt b/app/src/main/java/com/idormy/sms/forwarder/database/dao/FrpcDao.kt new file mode 100644 index 00000000..38203c6f --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/database/dao/FrpcDao.kt @@ -0,0 +1,33 @@ +package com.idormy.sms.forwarder.database.dao + +import androidx.paging.PagingSource +import androidx.room.* +import com.idormy.sms.forwarder.database.entity.Frpc +import io.reactivex.Single + +@Dao +interface FrpcDao { + + @Insert + fun insert(frpc: Frpc) + + @Delete + fun delete(frpc: Frpc) + + @Query("DELETE FROM Frpc where uid=:uid") + fun delete(uid: String) + + @Update + fun update(frpc: Frpc) + + @Query("SELECT * FROM Frpc where uid=:uid") + fun get(uid: String): Single + + //TODO:允许主线程访问,后面再优化 + @Query("SELECT * FROM Frpc where autorun=1") + fun getAutorun(): List + + @Query("SELECT * FROM Frpc ORDER BY time DESC") + fun pagingSource(): PagingSource + +} \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/database/dao/LogsDao.kt b/app/src/main/java/com/idormy/sms/forwarder/database/dao/LogsDao.kt new file mode 100644 index 00000000..101168b9 --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/database/dao/LogsDao.kt @@ -0,0 +1,47 @@ +package com.idormy.sms.forwarder.database.dao + +import androidx.paging.PagingSource +import androidx.room.* +import com.idormy.sms.forwarder.database.entity.Logs +import com.idormy.sms.forwarder.database.entity.LogsAndRuleAndSender +import io.reactivex.Completable +import io.reactivex.Single + +@Dao +interface LogsDao { + + @Insert(onConflict = OnConflictStrategy.IGNORE) + suspend fun insert(logs: Logs): Long + + @Delete + fun delete(logs: Logs): Completable + + @Query("DELETE FROM Logs where id=:id") + fun delete(id: Long) + + @Query("DELETE FROM Logs where type=:type") + fun deleteAll(type: String): Completable + + @Update + fun update(logs: Logs): Completable + + @Query("SELECT * FROM Logs where id=:id") + fun get(id: Long): Single + + @Query("SELECT count(*) FROM Logs where type=:type and forward_status=:forwardStatus") + fun count(type: String, forwardStatus: Int): Single + + @Transaction + @Query("SELECT * FROM Logs WHERE type = :type ORDER BY id DESC") + fun pagingSource(type: String): PagingSource + + @Query( + "UPDATE Logs SET forward_status=:status" + + ",forward_response=CASE WHEN (trim(forward_response) = '' or trim(forward_response) = 'ok')" + + " THEN :response" + + " ELSE forward_response || '\n--------------------\n' || :response" + + " END" + + " where id=:id" + ) + fun updateStatus(id: Long, status: Int, response: String): Int +} \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/database/dao/RuleDao.kt b/app/src/main/java/com/idormy/sms/forwarder/database/dao/RuleDao.kt new file mode 100644 index 00000000..f0e17ef3 --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/database/dao/RuleDao.kt @@ -0,0 +1,52 @@ +package com.idormy.sms.forwarder.database.dao + +import androidx.paging.PagingSource +import androidx.room.* +import com.idormy.sms.forwarder.database.entity.Rule +import com.idormy.sms.forwarder.database.entity.RuleAndSender +import io.reactivex.Completable +import io.reactivex.Single + +@Dao +interface RuleDao { + + @Insert + fun insert(rule: Rule) + + @Delete + fun delete(rule: Rule): Completable + + @Query("DELETE FROM Rule where id=:id") + fun delete(id: Long) + + @Update + fun update(rule: Rule) + + @Query("SELECT * FROM Rule where id=:id") + fun get(id: Long): Single + + @Query("SELECT count(*) FROM Rule where type=:type and status=:status") + fun count(type: String, status: Int): Single + + /*@Query( + "SELECT Rule.*," + + "Sender.name as sender_name,Sender.type as sender_type" + + " FROM Rule" + + " LEFT JOIN Sender ON Rule.sender_id = Sender.id" + + " where Rule.type=:type" + + " ORDER BY Rule.time DESC" + ) + fun pagingSource(type: String): PagingSource*/ + + @Transaction + @Query("SELECT * FROM Rule where type=:type ORDER BY id DESC") + fun pagingSource(type: String): PagingSource + + @Transaction + @Query("SELECT * FROM rule where type=:type and status=:status and (sim_slot='ALL' or sim_slot=:simSlot)") + suspend fun getRuleAndSender(type: String, status: Int, simSlot: String): List + + //TODO:允许主线程访问,后面再优化 + @Query("SELECT * FROM rule ORDER BY id ASC") + fun getAll(): List +} \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/database/dao/SenderDao.kt b/app/src/main/java/com/idormy/sms/forwarder/database/dao/SenderDao.kt new file mode 100644 index 00000000..09411eb8 --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/database/dao/SenderDao.kt @@ -0,0 +1,47 @@ +package com.idormy.sms.forwarder.database.dao + +import androidx.paging.PagingSource +import androidx.room.* +import com.idormy.sms.forwarder.database.entity.Sender +import io.reactivex.Completable +import io.reactivex.Single +import kotlinx.coroutines.flow.Flow + +@Dao +interface SenderDao { + + @Insert + fun insert(sender: Sender) + + @Delete + fun delete(sender: Sender): Completable + + @Query("DELETE FROM Sender where id=:id") + fun delete(id: Long) + + @Update + fun update(sender: Sender) + + @Query("SELECT * FROM Sender where id=:id") + fun get(id: Long): Single + + @Query("SELECT count(*) FROM Sender where type=:type and status=:status") + fun count(type: String, status: Int): Single + + @Query("SELECT * FROM Sender where status=:status ORDER BY id DESC") + fun pagingSource(status: Int): PagingSource + + @Query("SELECT * FROM sender ORDER BY id DESC") + fun getAll(): Single> + + @Query("SELECT COUNT(id) FROM sender WHERE status = 1") + fun getOnCount(): Flow + + //TODO:允许主线程访问,后面再优化 + @Query("SELECT * FROM sender ORDER BY id ASC") + fun getAll2(): List + + @Query("DELETE FROM Sender") + fun deleteAll() + +} \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/database/entity/Frpc.kt b/app/src/main/java/com/idormy/sms/forwarder/database/entity/Frpc.kt new file mode 100644 index 00000000..30ab7837 --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/database/entity/Frpc.kt @@ -0,0 +1,43 @@ +package com.idormy.sms.forwarder.database.entity + +import android.os.Parcelable +import androidx.room.ColumnInfo +import androidx.room.Entity +import androidx.room.Ignore +import androidx.room.PrimaryKey +import com.idormy.sms.forwarder.R +import com.idormy.sms.forwarder.utils.STATUS_ON +import kotlinx.parcelize.Parcelize +import java.util.* + +@Parcelize +@Entity(tableName = "Frpc") +data class Frpc( + @PrimaryKey + @ColumnInfo(name = "uid") var uid: String, + @ColumnInfo(name = "name") var name: String, + @ColumnInfo(name = "config") var config: String, + @ColumnInfo(name = "autorun", defaultValue = "0") var autorun: Int = 0, + @ColumnInfo(name = "time") var time: Date = Date(), + @Ignore var connecting: Boolean = false, +) : Parcelable { + constructor() : this("", "", "", 0, Date(), false) + + @Ignore + constructor(config: String) : this("", "", config, 0, Date(), false) + + @Ignore + constructor(uid: String, name: String, config: String) : this(uid, name, config, 0, Date(), false) + + fun setConnecting(connecting: Boolean): Frpc { + this.connecting = connecting + return this + } + + val autorunImageId: Int + get() = when (autorun) { + STATUS_ON -> R.drawable.ic_autorun + else -> R.drawable.ic_manual + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/database/entity/Logs.kt b/app/src/main/java/com/idormy/sms/forwarder/database/entity/Logs.kt new file mode 100644 index 00000000..444f8ded --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/database/entity/Logs.kt @@ -0,0 +1,61 @@ +package com.idormy.sms.forwarder.database.entity + +import android.os.Parcelable +import androidx.room.* +import com.idormy.sms.forwarder.R +import kotlinx.parcelize.Parcelize +import java.util.* + +@Parcelize +@Entity( + tableName = "Logs", + foreignKeys = [ + ForeignKey( + entity = Rule::class, + parentColumns = ["id"], + childColumns = ["rule_id"], + onDelete = ForeignKey.CASCADE, //级联操作 + onUpdate = ForeignKey.CASCADE //级联操作 + ) + ], + indices = [ + Index(value = ["id"], unique = true), + Index(value = ["rule_id"]) + ] +) +data class Logs( + @PrimaryKey(autoGenerate = true) + @ColumnInfo(name = "id") var id: Long, + @ColumnInfo(name = "type", defaultValue = "sms") var type: String, + @ColumnInfo(name = "from", defaultValue = "") var from: String, + @ColumnInfo(name = "content", defaultValue = "") var content: String, + @ColumnInfo(name = "rule_id", defaultValue = "0") var ruleId: Long = 0, + @ColumnInfo(name = "sim_info", defaultValue = "") var simInfo: String = "", + @ColumnInfo(name = "forward_status", defaultValue = "1") var forwardStatus: Int = 1, + @ColumnInfo(name = "forward_response", defaultValue = "") var forwardResponse: String = "", + @ColumnInfo(name = "time") var time: Date = Date(), +) : Parcelable { + + val simImageId: Int + get() { + if (simInfo.isNotEmpty()) { + if (simInfo.replace("-", "").startsWith("SIM2")) { + return R.drawable.ic_sim2 //mipmap + } else if (simInfo.replace("-", "").startsWith("SIM1")) { + return R.drawable.ic_sim1 + } + } + return R.drawable.ic_sim + } + + val statusImageId: Int + get() { + if (forwardStatus == 1) { + return R.drawable.ic_round_warning + } else if (forwardStatus == 2) { + return R.drawable.ic_round_check + } + return R.drawable.ic_round_cancel + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/database/entity/LogsAndRuleAndSender.kt b/app/src/main/java/com/idormy/sms/forwarder/database/entity/LogsAndRuleAndSender.kt new file mode 100644 index 00000000..1c2c492a --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/database/entity/LogsAndRuleAndSender.kt @@ -0,0 +1,18 @@ +package com.idormy.sms.forwarder.database.entity + +import android.os.Parcelable +import androidx.room.Embedded +import androidx.room.Relation +import kotlinx.parcelize.Parcelize + +@Parcelize +data class LogsAndRuleAndSender( + @Embedded val logs: Logs, + + @Relation( + entity = Rule::class, + parentColumn = "rule_id", + entityColumn = "id" + ) + val relation: RuleAndSender, +) : Parcelable diff --git a/app/src/main/java/com/idormy/sms/forwarder/database/entity/Rule.kt b/app/src/main/java/com/idormy/sms/forwarder/database/entity/Rule.kt new file mode 100644 index 00000000..5fcc552a --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/database/entity/Rule.kt @@ -0,0 +1,180 @@ +package com.idormy.sms.forwarder.database.entity + +import android.os.Parcelable +import android.util.Log +import androidx.room.* +import com.idormy.sms.forwarder.R +import com.idormy.sms.forwarder.entity.MsgInfo +import com.idormy.sms.forwarder.utils.* +import com.xuexiang.xui.utils.ResUtils.getString +import kotlinx.parcelize.Parcelize +import java.util.* +import java.util.regex.Pattern +import java.util.regex.PatternSyntaxException + +@Parcelize +@Entity( + tableName = "Rule", + foreignKeys = [ + ForeignKey( + entity = Sender::class, + parentColumns = ["id"], + childColumns = ["sender_id"], + onDelete = ForeignKey.CASCADE, //级联操作 + onUpdate = ForeignKey.CASCADE //级联操作 + ) + ], + indices = [ + Index(value = ["id"], unique = true), + Index(value = ["sender_id"]) + ] +) +data class Rule( + @PrimaryKey(autoGenerate = true) + @ColumnInfo(name = "id") var id: Long, + @ColumnInfo(name = "type", defaultValue = "sms") var type: String, + @ColumnInfo(name = "filed", defaultValue = "transpond_all") var filed: String, + @ColumnInfo(name = "check", defaultValue = "is") var check: String, + @ColumnInfo(name = "value", defaultValue = "") var value: String, + @ColumnInfo(name = "sender_id", defaultValue = "0") var senderId: Long = 0, + @ColumnInfo(name = "sms_template", defaultValue = "") var smsTemplate: String = "", + @ColumnInfo(name = "regex_replace", defaultValue = "") var regexReplace: String = "", + @ColumnInfo(name = "sim_slot", defaultValue = "ALL") var simSlot: String = "", + @ColumnInfo(name = "status", defaultValue = "1") var status: Int = 1, + @ColumnInfo(name = "time") var time: Date = Date(), +) : Parcelable { + + companion object { + val TAG: String = Rule::class.java.simpleName + + fun getRuleMatch(filed: String?, check: String?, value: String?, simSlot: String?): Any { + val sb = StringBuilder() + sb.append(SIM_SLOT_MAP[simSlot]).append(getString(R.string.rule_card)) + if (filed == null || filed == FILED_TRANSPOND_ALL) { + sb.append(getString(R.string.rule_all_fw_to)) + } else { + sb.append(getString(R.string.rule_when)).append(FILED_MAP[filed]).append(CHECK_MAP[check]).append(value).append(getString(R.string.rule_fw_to)) + } + return sb.toString() + } + + } + + val ruleMatch: String + get() { + val simStr = if ("app" == type) "" else SIM_SLOT_MAP[simSlot].toString() + getString(R.string.rule_card) + return if (filed == FILED_TRANSPOND_ALL) { + simStr + getString(R.string.rule_all_fw_to) + } else { + simStr + getString(R.string.rule_when) + FILED_MAP[filed] + CHECK_MAP[check] + value + getString(R.string.rule_fw_to) + } + } + + val statusChecked: Boolean + get() = status != STATUS_OFF + + val imageId: Int + get() = when (simSlot) { + CHECK_SIM_SLOT_1 -> R.drawable.ic_sim1 + CHECK_SIM_SLOT_2 -> R.drawable.ic_sim2 + CHECK_SIM_SLOT_ALL -> if (type == "app") R.drawable.ic_app else R.drawable.ic_sim + else -> if (type == "app") R.drawable.ic_app else R.drawable.ic_sim + } + + val statusImageId: Int + get() = when (status) { + STATUS_OFF -> R.drawable.icon_off + else -> R.drawable.icon_on + } + + fun getSimSlotCheckId(): Int { + return when (simSlot) { + CHECK_SIM_SLOT_1 -> R.id.rb_sim_slot_1 + CHECK_SIM_SLOT_2 -> R.id.rb_sim_slot_2 + else -> R.id.rb_sim_slot_all + } + } + + fun getFiledCheckId(): Int { + return when (filed) { + FILED_MSG_CONTENT -> R.id.rb_content + FILED_PHONE_NUM -> R.id.rb_phone + FILED_PACKAGE_NAME -> R.id.rb_package_name + FILED_INFORM_CONTENT -> R.id.rb_inform_content + FILED_MULTI_MATCH -> R.id.rb_multi_match + else -> R.id.rb_transpond_all + } + } + + fun getCheckCheckId(): Int { + return when (check) { + CHECK_CONTAIN -> R.id.rb_contain + CHECK_NOT_CONTAIN -> R.id.rb_not_contain + CHECK_START_WITH -> R.id.rb_start_with + CHECK_END_WITH -> R.id.rb_end_with + CHECK_REGEX -> R.id.rb_regex + else -> R.id.rb_is + } + } + + //字段分支 + @Throws(Exception::class) + fun checkMsg(msg: MsgInfo?): Boolean { + + //检查这一行和上一行合并的结果是否命中 + var mixChecked = false + if (msg != null) { + //先检查规则是否命中 + when (this.filed) { + FILED_TRANSPOND_ALL -> mixChecked = true + FILED_PHONE_NUM, FILED_PACKAGE_NAME -> mixChecked = checkValue(msg.from) + FILED_MSG_CONTENT, FILED_INFORM_CONTENT -> mixChecked = checkValue(msg.content) + FILED_MULTI_MATCH -> mixChecked = RuleLineUtils.checkRuleLines(msg, this.value) + else -> {} + } + } + Log.i(TAG, "rule:$this checkMsg:$msg checked:$mixChecked") + return mixChecked + } + + //内容分支 + private fun checkValue(msgValue: String?): Boolean { + var checked = false + when (this.check) { + CHECK_IS -> checked = this.value == msgValue + CHECK_NOT_IS -> checked = this.value != msgValue + CHECK_CONTAIN -> if (msgValue != null) { + checked = msgValue.contains(this.value) + } + CHECK_NOT_CONTAIN -> if (msgValue != null) { + checked = !msgValue.contains(this.value) + } + CHECK_START_WITH -> if (msgValue != null) { + checked = msgValue.startsWith(this.value) + } + CHECK_END_WITH -> if (msgValue != null) { + checked = msgValue.endsWith(this.value) + } + CHECK_REGEX -> if (msgValue != null) { + try { + //checked = Pattern.matches(this.value, msgValue); + val pattern = Pattern.compile(this.value, Pattern.CASE_INSENSITIVE) + val matcher = pattern.matcher(msgValue) + while (matcher.find()) { + checked = true + break + } + } catch (e: PatternSyntaxException) { + Log.d(TAG, "PatternSyntaxException: ") + Log.d(TAG, "Description: " + e.description) + Log.d(TAG, "Index: " + e.index) + Log.d(TAG, "Message: " + e.message) + Log.d(TAG, "Pattern: " + e.pattern) + } + } + else -> {} + } + Log.i(TAG, "checkValue " + msgValue + " " + this.check + " " + this.value + " checked:" + checked) + return checked + } +} \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/database/entity/RuleAndSender.kt b/app/src/main/java/com/idormy/sms/forwarder/database/entity/RuleAndSender.kt new file mode 100644 index 00000000..edb46106 --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/database/entity/RuleAndSender.kt @@ -0,0 +1,17 @@ +package com.idormy.sms.forwarder.database.entity + +import android.os.Parcelable +import androidx.room.Embedded +import androidx.room.Relation +import kotlinx.parcelize.Parcelize + +@Parcelize +data class RuleAndSender( + @Embedded val rule: Rule, + + @Relation( + parentColumn = "sender_id", + entityColumn = "id" + ) + val sender: Sender, +) : Parcelable diff --git a/app/src/main/java/com/idormy/sms/forwarder/database/entity/Sender.kt b/app/src/main/java/com/idormy/sms/forwarder/database/entity/Sender.kt new file mode 100644 index 00000000..bc4bd7c6 --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/database/entity/Sender.kt @@ -0,0 +1,66 @@ +package com.idormy.sms.forwarder.database.entity + +import android.os.Parcelable +import androidx.room.ColumnInfo +import androidx.room.Entity +import androidx.room.PrimaryKey +import com.idormy.sms.forwarder.R +import com.idormy.sms.forwarder.utils.* +import kotlinx.parcelize.Parcelize +import java.util.* + +@Parcelize +@Entity(tableName = "Sender") +data class Sender( + @PrimaryKey(autoGenerate = true) + @ColumnInfo(name = "id") var id: Long, + @ColumnInfo(name = "type", defaultValue = "1") var type: Int = 1, + @ColumnInfo(name = "name", defaultValue = "") var name: String, + @ColumnInfo(name = "json_setting", defaultValue = "") var jsonSetting: String, + @ColumnInfo(name = "status", defaultValue = "1") var status: Int = 1, + @ColumnInfo(name = "time") var time: Date = Date(), +) : Parcelable { + companion object { + + fun getImageId(type: Int): Int = when (type) { + TYPE_DINGDING -> R.drawable.icon_dingtalk + TYPE_EMAIL -> R.drawable.icon_email + TYPE_BARK -> R.drawable.icon_bark + TYPE_WEBHOOK -> R.drawable.icon_webhook + TYPE_WEWORK_ROBOT -> R.drawable.icon_wework_robot + TYPE_WEWORK_AGENT -> R.drawable.icon_wework_agent + TYPE_SERVERCHAN -> R.drawable.icon_serverchan + TYPE_TELEGRAM -> R.drawable.icon_telegram + TYPE_FEISHU -> R.drawable.icon_feishu + TYPE_PUSHPLUS -> R.drawable.icon_pushplus + TYPE_GOTIFY -> R.drawable.icon_gotify + TYPE_SMS -> R.drawable.icon_sms + else -> R.drawable.icon_sms + } + + } + + val imageId: Int + get() = when (type) { + TYPE_DINGDING -> R.drawable.icon_dingtalk + TYPE_EMAIL -> R.drawable.icon_email + TYPE_BARK -> R.drawable.icon_bark + TYPE_WEBHOOK -> R.drawable.icon_webhook + TYPE_WEWORK_ROBOT -> R.drawable.icon_wework_robot + TYPE_WEWORK_AGENT -> R.drawable.icon_wework_agent + TYPE_SERVERCHAN -> R.drawable.icon_serverchan + TYPE_TELEGRAM -> R.drawable.icon_telegram + TYPE_FEISHU -> R.drawable.icon_feishu + TYPE_PUSHPLUS -> R.drawable.icon_pushplus + TYPE_GOTIFY -> R.drawable.icon_gotify + TYPE_SMS -> R.drawable.icon_sms + else -> R.drawable.icon_sms + } + + val statusImageId: Int + get() = when (status) { + STATUS_OFF -> R.drawable.icon_off + else -> R.drawable.icon_on + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/database/ext/Converters.kt b/app/src/main/java/com/idormy/sms/forwarder/database/ext/Converters.kt new file mode 100644 index 00000000..c55006e5 --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/database/ext/Converters.kt @@ -0,0 +1,17 @@ +package com.idormy.sms.forwarder.database.ext + +import androidx.room.TypeConverter +import java.util.* + +@Suppress("unused") +class Converters { + @TypeConverter + fun fromTimestamp(value: Long?): Date? { + return value?.let { Date(it) } + } + + @TypeConverter + fun dateToTimestamp(date: Date?): Long? { + return date?.time + } +} \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/database/ext/Executors.kt b/app/src/main/java/com/idormy/sms/forwarder/database/ext/Executors.kt new file mode 100644 index 00000000..fdee9dc2 --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/database/ext/Executors.kt @@ -0,0 +1,12 @@ +package com.idormy.sms.forwarder.database.ext + +import java.util.concurrent.Executors + +private val IO_EXECUTOR = Executors.newSingleThreadExecutor() + +/** + * Utility method to run blocks on a dedicated background thread, used for io/database work. + */ +fun ioThread(f: () -> Unit) { + IO_EXECUTOR.execute(f) +} \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/database/ext/LifecyleExt.kt b/app/src/main/java/com/idormy/sms/forwarder/database/ext/LifecyleExt.kt new file mode 100644 index 00000000..bb2c702c --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/database/ext/LifecyleExt.kt @@ -0,0 +1,9 @@ +package com.idormy.sms.forwarder.database.ext + +import androidx.lifecycle.LifecycleOwner +import androidx.lifecycle.LiveData +import androidx.lifecycle.Observer + +@Suppress("unused") +fun LifecycleOwner.observe(liveData: LiveData?, observer: (T) -> Unit) = + liveData?.observe(this, Observer(observer)) \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/database/repository/FrpcRepository.kt b/app/src/main/java/com/idormy/sms/forwarder/database/repository/FrpcRepository.kt new file mode 100644 index 00000000..9669ccb5 --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/database/repository/FrpcRepository.kt @@ -0,0 +1,29 @@ +package com.idormy.sms.forwarder.database.repository + +import androidx.annotation.WorkerThread +import com.idormy.sms.forwarder.database.dao.FrpcDao +import com.idormy.sms.forwarder.database.entity.Frpc + +class FrpcRepository( + private val frpcDao: FrpcDao, +) { + + var listener: Listener? = null + + @WorkerThread + fun insert(frpc: Frpc) { + frpcDao.insert(frpc) + } + + @WorkerThread + fun delete(uid: String) { + frpcDao.delete(uid) + } + + @WorkerThread + fun get(uid: String) = frpcDao.get(uid) + + @WorkerThread + fun update(frpc: Frpc) = frpcDao.update(frpc) + +} \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/database/repository/Listener.kt b/app/src/main/java/com/idormy/sms/forwarder/database/repository/Listener.kt new file mode 100644 index 00000000..fb7ea10b --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/database/repository/Listener.kt @@ -0,0 +1,5 @@ +package com.idormy.sms.forwarder.database.repository + +interface Listener { + fun onDelete(id: Long) +} \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/database/repository/LogsRepository.kt b/app/src/main/java/com/idormy/sms/forwarder/database/repository/LogsRepository.kt new file mode 100644 index 00000000..003b2391 --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/database/repository/LogsRepository.kt @@ -0,0 +1,19 @@ +package com.idormy.sms.forwarder.database.repository + +import androidx.annotation.WorkerThread +import com.idormy.sms.forwarder.database.dao.LogsDao +import com.idormy.sms.forwarder.database.entity.Logs + +class LogsRepository(private val logsDao: LogsDao) { + + @WorkerThread + fun delete(id: Long) { + logsDao.delete(id) + } + + @WorkerThread + suspend fun insert(logs: Logs): Long = logsDao.insert(logs) + + @WorkerThread + fun updateStatus(id: Long, status: Int, response: String): Int = logsDao.updateStatus(id, status, response) +} \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/database/repository/RuleRepository.kt b/app/src/main/java/com/idormy/sms/forwarder/database/repository/RuleRepository.kt new file mode 100644 index 00000000..d53114f2 --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/database/repository/RuleRepository.kt @@ -0,0 +1,34 @@ +package com.idormy.sms.forwarder.database.repository + +import androidx.annotation.WorkerThread +import com.idormy.sms.forwarder.database.dao.RuleDao +import com.idormy.sms.forwarder.database.entity.Rule + +class RuleRepository( + private val ruleDao: RuleDao, +) { + + var listener: Listener? = null + + @WorkerThread + fun insert(rule: Rule) { + ruleDao.insert(rule) + } + + @WorkerThread + fun delete(id: Long) { + listener?.onDelete(id) + ruleDao.delete(id) + } + + @WorkerThread + fun get(id: Long) = ruleDao.get(id) + + suspend fun getRuleAndSender(type: String, status: Int, simSlot: String) = ruleDao.getRuleAndSender(type, status, simSlot) + + @WorkerThread + fun update(rule: Rule) = ruleDao.update(rule) + + //TODO:允许主线程访问,后面再优化 + val all: List = ruleDao.getAll() +} \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/database/repository/SenderRepository.kt b/app/src/main/java/com/idormy/sms/forwarder/database/repository/SenderRepository.kt new file mode 100644 index 00000000..7963e044 --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/database/repository/SenderRepository.kt @@ -0,0 +1,33 @@ +package com.idormy.sms.forwarder.database.repository + +import androidx.annotation.WorkerThread +import com.idormy.sms.forwarder.database.dao.SenderDao +import com.idormy.sms.forwarder.database.entity.Sender +import kotlinx.coroutines.flow.Flow + +class SenderRepository(private val senderDao: SenderDao) { + + var listener: Listener? = null + + @WorkerThread + fun insert(sender: Sender) = senderDao.insert(sender) + + @WorkerThread + fun delete(id: Long) { + listener?.onDelete(id) + senderDao.delete(id) + } + + fun get(id: Long) = senderDao.get(id) + + fun update(sender: Sender) = senderDao.update(sender) + + val count: Flow = senderDao.getOnCount() + + //TODO:允许主线程访问,后面再优化 + val all: List = senderDao.getAll2() + fun deleteAll() { + senderDao.deleteAll() + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/database/viewmodel/BaseViewModelFactory.kt b/app/src/main/java/com/idormy/sms/forwarder/database/viewmodel/BaseViewModelFactory.kt new file mode 100644 index 00000000..397dca27 --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/database/viewmodel/BaseViewModelFactory.kt @@ -0,0 +1,40 @@ +package com.idormy.sms.forwarder.database.viewmodel + +import android.content.Context +import androidx.lifecycle.ViewModel +import androidx.lifecycle.ViewModelProvider +import com.idormy.sms.forwarder.database.AppDatabase + +class BaseViewModelFactory(private val context: Context?) : ViewModelProvider.Factory { + + override fun create(modelClass: Class): T { + + if (context == null) throw IllegalArgumentException("Context CAN NOT BE null") + + when { + modelClass.isAssignableFrom(FrpcViewModel::class.java) -> { + val frpcDao = AppDatabase.getInstance(context).frpcDao() + @Suppress("UNCHECKED_CAST") + return FrpcViewModel(frpcDao) as T + } + modelClass.isAssignableFrom(LogsViewModel::class.java) -> { + val logDao = AppDatabase.getInstance(context).logsDao() + @Suppress("UNCHECKED_CAST") + return LogsViewModel(logDao) as T + } + modelClass.isAssignableFrom(RuleViewModel::class.java) -> { + val ruleDao = AppDatabase.getInstance(context).ruleDao() + @Suppress("UNCHECKED_CAST") + return RuleViewModel(ruleDao) as T + } + modelClass.isAssignableFrom(SenderViewModel::class.java) -> { + val senderDao = AppDatabase.getInstance(context).senderDao() + @Suppress("UNCHECKED_CAST") + return SenderViewModel(senderDao) as T + } + } + + throw IllegalArgumentException("Unknown ViewModel class") + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/database/viewmodel/FrpcViewModel.kt b/app/src/main/java/com/idormy/sms/forwarder/database/viewmodel/FrpcViewModel.kt new file mode 100644 index 00000000..39e35c46 --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/database/viewmodel/FrpcViewModel.kt @@ -0,0 +1,41 @@ +package com.idormy.sms.forwarder.database.viewmodel + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import androidx.paging.Pager +import androidx.paging.PagingConfig +import androidx.paging.PagingData +import androidx.paging.cachedIn +import com.idormy.sms.forwarder.database.dao.FrpcDao +import com.idormy.sms.forwarder.database.entity.Frpc +import com.idormy.sms.forwarder.database.ext.ioThread +import kotlinx.coroutines.flow.Flow +import java.util.* + +class FrpcViewModel(private val dao: FrpcDao) : ViewModel() { + + val allFrpc: Flow> = Pager( + config = PagingConfig( + pageSize = 10, + enablePlaceholders = false, + initialLoadSize = 10 + ) + ) { dao.pagingSource() }.flow.cachedIn(viewModelScope) + + fun insert(name: String, config: String, autorun: Int) = ioThread { + dao.insert(Frpc(uid = UUID.randomUUID().toString(), name = name, config = config, autorun = autorun)) + } + + fun insert(frpc: Frpc) = ioThread { + frpc.uid = UUID.randomUUID().toString() + dao.insert(frpc) + } + + fun update(frpc: Frpc) = ioThread { + dao.update(frpc) + } + + fun delete(frpc: Frpc) = ioThread { + dao.delete(frpc) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/database/viewmodel/LogsViewModel.kt b/app/src/main/java/com/idormy/sms/forwarder/database/viewmodel/LogsViewModel.kt new file mode 100644 index 00000000..c6e3c7a4 --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/database/viewmodel/LogsViewModel.kt @@ -0,0 +1,36 @@ +package com.idormy.sms.forwarder.database.viewmodel + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import androidx.paging.Pager +import androidx.paging.PagingConfig +import androidx.paging.PagingData +import androidx.paging.cachedIn +import com.idormy.sms.forwarder.database.dao.LogsDao +import com.idormy.sms.forwarder.database.entity.LogsAndRuleAndSender +import com.idormy.sms.forwarder.database.ext.ioThread +import kotlinx.coroutines.flow.Flow + +class LogsViewModel(private val dao: LogsDao) : ViewModel() { + private var type: String = "sms" + + fun setType(type: String): LogsViewModel { + this.type = type + return this + } + + val allLogs: Flow> = Pager( + config = PagingConfig( + pageSize = 10, + enablePlaceholders = false, + initialLoadSize = 10 + ) + ) { + dao.pagingSource(type) + }.flow.cachedIn(viewModelScope) + + fun delete(id: Long) = ioThread { + dao.delete(id) + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/database/viewmodel/RuleViewModel.kt b/app/src/main/java/com/idormy/sms/forwarder/database/viewmodel/RuleViewModel.kt new file mode 100644 index 00000000..550c4e2a --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/database/viewmodel/RuleViewModel.kt @@ -0,0 +1,38 @@ +package com.idormy.sms.forwarder.database.viewmodel + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import androidx.paging.Pager +import androidx.paging.PagingConfig +import androidx.paging.PagingData +import androidx.paging.cachedIn +import com.idormy.sms.forwarder.database.dao.RuleDao +import com.idormy.sms.forwarder.database.entity.Rule +import com.idormy.sms.forwarder.database.entity.RuleAndSender +import com.idormy.sms.forwarder.database.ext.ioThread +import kotlinx.coroutines.flow.Flow + +class RuleViewModel(private val dao: RuleDao) : ViewModel() { + private var type: String = "sms" + + fun setType(type: String): RuleViewModel { + this.type = type + return this + } + + val allRules: Flow> = Pager( + config = PagingConfig( + pageSize = 10, + enablePlaceholders = false, + initialLoadSize = 10 + ) + ) { dao.pagingSource(type) }.flow.cachedIn(viewModelScope) + + fun insertOrUpdate(rule: Rule) = ioThread { + if (rule.id > 0) dao.update(rule) else dao.insert(rule) + } + + fun delete(id: Long) = ioThread { + dao.delete(id) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/database/viewmodel/SenderViewModel.kt b/app/src/main/java/com/idormy/sms/forwarder/database/viewmodel/SenderViewModel.kt new file mode 100644 index 00000000..44081aae --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/database/viewmodel/SenderViewModel.kt @@ -0,0 +1,39 @@ +package com.idormy.sms.forwarder.database.viewmodel + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import androidx.paging.Pager +import androidx.paging.PagingConfig +import androidx.paging.PagingData +import androidx.paging.cachedIn +import com.idormy.sms.forwarder.database.dao.SenderDao +import com.idormy.sms.forwarder.database.entity.Sender +import com.idormy.sms.forwarder.database.ext.ioThread +import kotlinx.coroutines.flow.Flow + +class SenderViewModel(private val dao: SenderDao) : ViewModel() { + private var status: Int = 1 + + fun setStatus(status: Int): SenderViewModel { + this.status = status + return this + } + + val allSenders: Flow> = Pager( + config = PagingConfig( + pageSize = 10, + enablePlaceholders = false, + initialLoadSize = 10 + ) + ) { + dao.pagingSource(status) + }.flow.cachedIn(viewModelScope) + + fun insertOrUpdate(sender: Sender) = ioThread { + if (sender.id > 0) dao.update(sender) else dao.insert(sender) + } + + fun delete(id: Long) = ioThread { + dao.delete(id) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/entity/BatteryInfo.kt b/app/src/main/java/com/idormy/sms/forwarder/entity/BatteryInfo.kt new file mode 100644 index 00000000..4b2425d3 --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/entity/BatteryInfo.kt @@ -0,0 +1,27 @@ +package com.idormy.sms.forwarder.entity + +import com.idormy.sms.forwarder.R +import com.xuexiang.xui.utils.ResUtils +import java.io.Serializable + +data class BatteryInfo( + var level: String = "", + var scale: String = "", + var voltage: String = "", + var temperature: String = "", + var status: String = "", + var health: String = "", + var plugged: String = "", +) : Serializable { + override fun toString(): String { + var msg = "" + msg += "\n" + String.format(ResUtils.getString(R.string.battery_level), level) + if (scale != "") msg += "\n" + String.format(ResUtils.getString(R.string.battery_scale), scale) + if (voltage != "") msg += "\n" + String.format(ResUtils.getString(R.string.battery_voltage), voltage) + if (temperature != "") msg += "\n" + String.format(ResUtils.getString(R.string.battery_temperature), temperature) + msg += "\n" + String.format(ResUtils.getString(R.string.battery_status), status) + msg += "\n" + String.format(ResUtils.getString(R.string.battery_health), health) + msg += "\n" + String.format(ResUtils.getString(R.string.battery_plugged), plugged) + return msg + } +} diff --git a/app/src/main/java/com/idormy/sms/forwarder/entity/CallInfo.kt b/app/src/main/java/com/idormy/sms/forwarder/entity/CallInfo.kt new file mode 100644 index 00000000..f7410629 --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/entity/CallInfo.kt @@ -0,0 +1,55 @@ +package com.idormy.sms.forwarder.entity + +import com.google.gson.annotations.SerializedName +import com.idormy.sms.forwarder.R +import java.io.Serializable + +data class CallInfo( + //姓名 + var name: String = "", + //号码 + var number: String = "", + //获取通话日期 + var dateLong: Long = 0L, + //获取通话时长,值为多少秒 + var duration: Int = 0, + //通话类型:1=呼入, 2=呼出, 3=未接 + var type: Int = 1, + //被呼号码 + @SerializedName("via_number") + var viaNumber: String = "", + //卡槽ID: 1=Sim1, 2=Sim2, -1=获取失败 + @SerializedName("sim_id") + var simId: Int = -1, +) : Serializable { + + val typeImageId: Int + get() { + return when (type) { + 1 -> R.drawable.ic_phone_in + 2 -> R.drawable.ic_phone_out + else -> R.drawable.ic_phone_missed + } + } + + val simImageId: Int + get() { + return when (simId) { + 1 -> R.drawable.ic_sim1 + 2 -> R.drawable.ic_sim2 + else -> R.drawable.ic_sim + } + } + + override fun toString(): String { + return "CallInfo{" + + "name='" + name + '\'' + + ", number='" + number + '\'' + + ", dateLong=" + dateLong + + ", duration=" + duration + + ", type=" + type + + ", viaNumber=" + viaNumber + + ", simId=" + simId + + '}' + } +} \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/entity/CloneInfo.kt b/app/src/main/java/com/idormy/sms/forwarder/entity/CloneInfo.kt new file mode 100644 index 00000000..c97ef65e --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/entity/CloneInfo.kt @@ -0,0 +1,67 @@ +package com.idormy.sms.forwarder.entity + +import com.google.gson.annotations.SerializedName +import com.idormy.sms.forwarder.database.entity.Rule +import com.idormy.sms.forwarder.database.entity.Sender +import java.io.Serializable + +data class CloneInfo( + @SerializedName("version_code") + var versionCode: Int = 0, + @SerializedName("version_name") + var versionName: String? = null, + @SerializedName("enable_sms") + var enableSms: Boolean = false, + @SerializedName("enable_phone") + var enablePhone: Boolean = false, + @SerializedName("call_type1") + var callType1: Boolean = false, + @SerializedName("call_type2") + var callType2: Boolean = false, + @SerializedName("call_type3") + var callType3: Boolean = false, + @SerializedName("enable_app_notify") + var enableAppNotify: Boolean = false, + @SerializedName("cancel_app_notify") + var cancelAppNotify: Boolean = false, + @SerializedName("enable_not_user_present") + var enableNotUserPresent: Boolean = false, + @SerializedName("duplicate_messages_limits") + var duplicateMessagesLimits: Int = 0, + @SerializedName("enable_battery_receiver") + var enableBatteryReceiver: Boolean = false, + @SerializedName("battery_level_min") + var batteryLevelMin: Int = 0, + @SerializedName("battery_level_max") + var batteryLevelMax: Int = 0, + @SerializedName("battery_level_once") + var batteryLevelOnce: Boolean = false, + @SerializedName("enable_battery_cron") + var enableBatteryCron: Boolean = false, + @SerializedName("battery_cron_start_time") + var batteryCronStartTime: String? = null, + @SerializedName("battery_cron_interval") + var batteryCronInterval: Int = 0, + @SerializedName("enable_exclude_from_recents") + var enableExcludeFromRecents: Boolean = false, + @SerializedName("enable_play_silence_music") + var enablePlaySilenceMusic: Boolean = false, + @SerializedName("request_retry_times") + var requestRetryTimes: Int = 0, + @SerializedName("request_delay_time") + var requestDelayTime: Int = 0, + @SerializedName("request_timeout") + var requestTimeout: Int = 0, + @SerializedName("notify_content") + var notifyContent: String? = null, + @SerializedName("enable_sms_template") + var enableSmsTemplate: Boolean = false, + @SerializedName("sms_template") + var smsTemplate: String? = null, + @SerializedName("enable_help_tip") + var enableHelpTip: Boolean = false, + @SerializedName("sender_list") + var senderList: List? = null, + @SerializedName("rule_list") + var ruleList: List? = null, +) : Serializable \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/entity/ContactInfo.kt b/app/src/main/java/com/idormy/sms/forwarder/entity/ContactInfo.kt new file mode 100644 index 00000000..0a05fc40 --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/entity/ContactInfo.kt @@ -0,0 +1,22 @@ +package com.idormy.sms.forwarder.entity + +import android.util.Patterns +import com.google.gson.annotations.SerializedName +import java.io.Serializable +import java.util.* + +data class ContactInfo( + val name: String = "", + @SerializedName("phone_number") + val phoneNumber: String = "", +) : Serializable { + + val firstLetter: String + get() { + return if (name.matches(Patterns.PHONE.toRegex())) "#" else name[0].toString().uppercase(Locale.getDefault()) + } + + override fun toString(): String { + return String.format("姓名:%s\n号码:%s", name, phoneNumber) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/entity/ImageInfo.kt b/app/src/main/java/com/idormy/sms/forwarder/entity/ImageInfo.kt new file mode 100644 index 00000000..44f2dbbe --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/entity/ImageInfo.kt @@ -0,0 +1,88 @@ +package com.idormy.sms.forwarder.entity + +import android.graphics.Rect +import android.os.Parcel +import android.os.Parcelable.Creator +import com.idormy.sms.forwarder.R +import com.xuexiang.xui.utils.ResUtils +import com.xuexiang.xui.widget.imageview.preview.enitity.IPreviewInfo + +/** + * 图片预览实体类 + * + * @author xuexiang + * @since 2018/12/7 下午5:34 + */ +@Suppress("unused") +data class ImageInfo( + //图片地址 + var mUrl: String, + //记录坐标 + var mBounds: Rect? = null, + var mVideoUrl: String? = null, + var description: String? = ResUtils.getString(R.string.description), +) : IPreviewInfo { + + constructor(url: String) : this(mUrl = url) + + constructor(url: String, bounds: Rect?) : this(mUrl = url, mBounds = bounds) + + constructor(videoUrl: String?, url: String) : this(mUrl = url, mVideoUrl = videoUrl) + + override fun getUrl(): String { //将你的图片地址字段返回 + return mUrl + } + + fun setUrl(url: String) { + mUrl = url + } + + override fun getBounds(): Rect? { //将你的图片显示坐标字段返回 + return mBounds + } + + override fun getVideoUrl(): String? { + return mVideoUrl + } + + fun setBounds(bounds: Rect) { + mBounds = bounds + } + + fun setVideoUrl(videoUrl: String) { + mVideoUrl = videoUrl + } + + override fun describeContents(): Int { + return 0 + } + + override fun writeToParcel(dest: Parcel, flags: Int) { + dest.writeString(mUrl) + dest.writeParcelable(mBounds, flags) + dest.writeString(description) + dest.writeString(mVideoUrl) + } + + constructor(`in`: Parcel) : this( + mUrl = `in`.readString()!!, + mBounds = `in`.readParcelable(Rect::class.java.classLoader), + description = `in`.readString(), + mVideoUrl = `in`.readString() + ) + + companion object CREATOR : Creator { + + fun newInstance(url: String, bounds: Rect): List { + return listOf(ImageInfo(url, bounds)) + } + + override fun createFromParcel(parcel: Parcel): ImageInfo { + return ImageInfo(parcel) + } + + override fun newArray(size: Int): Array { + return arrayOfNulls(size) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/entity/MsgInfo.kt b/app/src/main/java/com/idormy/sms/forwarder/entity/MsgInfo.kt new file mode 100644 index 00000000..c9618627 --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/entity/MsgInfo.kt @@ -0,0 +1,120 @@ +package com.idormy.sms.forwarder.entity + +import android.annotation.SuppressLint +import android.text.TextUtils +import android.util.Log +import com.idormy.sms.forwarder.R +import com.idormy.sms.forwarder.utils.SettingUtils.Companion.enableSmsTemplate +import com.idormy.sms.forwarder.utils.SettingUtils.Companion.extraDeviceMark +import com.idormy.sms.forwarder.utils.SettingUtils.Companion.smsTemplate +import com.xuexiang.xui.utils.ResUtils.getString +import com.xuexiang.xutil.app.AppUtils +import java.io.Serializable +import java.text.SimpleDateFormat +import java.util.* + +@Suppress("unused") +data class MsgInfo( + var type: String = "sms", + var from: String, + var content: String, + var date: Date, + var simInfo: String, + var simSlot: Int = -1, //卡槽id:-1=获取失败、0=卡槽1、1=卡槽2 +) : Serializable { + + val titleForSend: String + get() = getTitleForSend("", "") + + fun getTitleForSend(titleTemplate: String): String { + return getTitleForSend(titleTemplate, "") + } + + @SuppressLint("SimpleDateFormat") + fun getTitleForSend(titleTemplate: String, regexReplace: String): String { + var template = titleTemplate.replace("null", "") + if (TextUtils.isEmpty(template)) template = getString(R.string.tag_from) + val deviceMark = extraDeviceMark!!.trim() + val versionName = AppUtils.getAppVersionName() + val titleForSend: String = template.replace(getString(R.string.tag_from), from) + .replace(getString(R.string.tag_package_name), from) + .replace(getString(R.string.tag_sms), content) + .replace(getString(R.string.tag_msg), content) + .replace(getString(R.string.tag_card_slot), simInfo) + .replace(getString(R.string.tag_title), simInfo) + .replace(getString(R.string.tag_receive_time), SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(date)) + .replace(getString(R.string.tag_device_name), deviceMark) + .replace(getString(R.string.tag_app_version), versionName) + .trim() + return regexReplace(regexReplace, titleForSend) + } + + val smsVoForSend: String + get() = getContentForSend("", "") + + fun getContentForSend(ruleSmsTemplate: String): String { + return getContentForSend(ruleSmsTemplate, "") + } + + @SuppressLint("SimpleDateFormat") + fun getContentForSend(ruleSmsTemplate: String, regexReplace: String): String { + val deviceMark = extraDeviceMark!!.trim() + var customSmsTemplate: String = getString(R.string.tag_from).toString() + "\n" + + getString(R.string.tag_sms) + "\n" + + getString(R.string.tag_card_slot) + "\n" + + getString(R.string.tag_receive_time) + "\n" + + getString(R.string.tag_device_name) + + //优先取转发规则的自定义模板,留空则取全局设置 + if (ruleSmsTemplate.isNotEmpty()) { + customSmsTemplate = ruleSmsTemplate.replace("null", "") + } else { + val switchSmsTemplate = enableSmsTemplate + val smsTemplate = smsTemplate.toString().trim() + if (switchSmsTemplate && smsTemplate.isNotEmpty()) { + customSmsTemplate = smsTemplate.replace("null", "") + } + } + val versionName = AppUtils.getAppVersionName() + val smsVoForSend: String = customSmsTemplate.replace(getString(R.string.tag_from), from) + .replace(getString(R.string.tag_package_name), from) + .replace(getString(R.string.tag_sms), content) + .replace(getString(R.string.tag_msg), content) + .replace(getString(R.string.tag_card_slot), simInfo) + .replace(getString(R.string.tag_title), simInfo) + .replace(getString(R.string.tag_receive_time), SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(date)) + .replace(getString(R.string.tag_device_name), deviceMark) + .replace(getString(R.string.tag_app_version), versionName) + .trim() + return regexReplace(regexReplace, smsVoForSend) + } + + //正则替换内容 + private fun regexReplace(regexReplace: String, Content: String): String { + return if (TextUtils.isEmpty(regexReplace)) Content else try { + var newContent = Content + val lineArray = regexReplace.split("\\n".toRegex()).toTypedArray() + for (line in lineArray) { + val lineSplit = line.split("===".toRegex()).toTypedArray() + if (lineSplit.isNotEmpty()) { + val regex = lineSplit[0] + val replacement = if (lineSplit.size >= 2) lineSplit[1] else "" + newContent = newContent.replace(regex.toRegex(), replacement) + } + } + newContent + } catch (e: Exception) { + Log.e("RegexReplace", "Failed to get the receiving phone number:" + e.message) + Content + } + } + + override fun toString(): String { + return "MsgInfo{" + + "mobile='" + from + '\'' + + ", content='" + content + '\'' + + ", date=" + date + + ", simInfo=" + simInfo + + '}' + } +} \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/entity/SimInfo.kt b/app/src/main/java/com/idormy/sms/forwarder/entity/SimInfo.kt new file mode 100644 index 00000000..e34b177c --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/entity/SimInfo.kt @@ -0,0 +1,30 @@ +package com.idormy.sms.forwarder.entity + +import java.io.Serializable + +//SIM卡信息 +data class SimInfo( + //运营商信息:中国移动 中国联通 中国电信 + var mCarrierName: CharSequence? = null, + //卡槽ID,SimSerialNumber + var mIccId: CharSequence? = null, + //卡槽id:-1=没插入、 0=卡槽1 、1=卡槽2 + var mSimSlotIndex: Int = 0, + //号码 + var mNumber: CharSequence? = null, + //城市 + var mCountryIso: CharSequence? = null, + //SIM的 Subscription Id (SIM插入顺序) + var mSubscriptionId: Int = 0, +) : Serializable { + override fun toString(): String { + return "SimInfo{" + + "mCarrierName=" + mCarrierName + + ", mIccId=" + mIccId + + ", mSimSlotIndex=" + mSimSlotIndex + + ", mNumber=" + mNumber + + ", mCountryIso=" + mCountryIso + + ", mSubscriptionId=" + mSubscriptionId + + '}' + } +} diff --git a/app/src/main/java/com/idormy/sms/forwarder/entity/SmsInfo.kt b/app/src/main/java/com/idormy/sms/forwarder/entity/SmsInfo.kt new file mode 100644 index 00000000..22336cbb --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/entity/SmsInfo.kt @@ -0,0 +1,33 @@ +package com.idormy.sms.forwarder.entity + +import com.google.gson.annotations.SerializedName +import com.idormy.sms.forwarder.R +import java.io.Serializable + +data class SmsInfo( + // 联系人姓名 + var name: String = "", + // 联系人号码 + var number: String = "", + // 短信内容 + var content: String = "", + // 短信时间 + var date: Long = 0L, + // 短信类型: 1=接收, 2=发送 + var type: Int = 1, + // 卡槽ID: 1=Sim1, 2=Sim2, -1=获取失败 + @SerializedName("sim_id") + var simId: Int = -1, +) : Serializable { + + val typeImageId: Int = R.drawable.ic_sms + + val simImageId: Int + get() { + return when (simId) { + 1 -> R.drawable.ic_sim1 + 2 -> R.drawable.ic_sim2 + else -> R.drawable.ic_sim + } + } +} diff --git a/app/src/main/java/com/idormy/sms/forwarder/entity/result/BarkResult.kt b/app/src/main/java/com/idormy/sms/forwarder/entity/result/BarkResult.kt new file mode 100644 index 00000000..ba7a65da --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/entity/result/BarkResult.kt @@ -0,0 +1,7 @@ +package com.idormy.sms.forwarder.entity.result + +data class BarkResult( + var code: Long, + var message: String, + var timestamp: Long?, +) \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/entity/result/DingtalkResult.kt b/app/src/main/java/com/idormy/sms/forwarder/entity/result/DingtalkResult.kt new file mode 100644 index 00000000..6d7d7bbf --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/entity/result/DingtalkResult.kt @@ -0,0 +1,6 @@ +package com.idormy.sms.forwarder.entity.result + +data class DingtalkResult( + var errcode: Long, + var errmsg: String, +) \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/entity/result/FeishuResult.kt b/app/src/main/java/com/idormy/sms/forwarder/entity/result/FeishuResult.kt new file mode 100644 index 00000000..09ddc38d --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/entity/result/FeishuResult.kt @@ -0,0 +1,7 @@ +package com.idormy.sms.forwarder.entity.result + +data class FeishuResult( + var code: Long, + var msg: String, + var data: Any?, +) \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/entity/result/GotifyResult.kt b/app/src/main/java/com/idormy/sms/forwarder/entity/result/GotifyResult.kt new file mode 100644 index 00000000..f7706312 --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/entity/result/GotifyResult.kt @@ -0,0 +1,15 @@ +package com.idormy.sms.forwarder.entity.result + +data class GotifyResult( + //失败返回 + var errorCode: Long?, + var error: String?, + var errorDescription: String?, + //成功返回 + var id: Long?, + var appid: Long?, + var title: String?, + var message: String?, + var priority: Long?, + var date: String?, +) \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/entity/result/PushplusResult.kt b/app/src/main/java/com/idormy/sms/forwarder/entity/result/PushplusResult.kt new file mode 100644 index 00000000..a5209cd9 --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/entity/result/PushplusResult.kt @@ -0,0 +1,8 @@ +package com.idormy.sms.forwarder.entity.result + +data class PushplusResult( + var code: Long, + var msg: String, + var data: String?, + var count: Long?, +) \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/entity/result/SendResponse.kt b/app/src/main/java/com/idormy/sms/forwarder/entity/result/SendResponse.kt new file mode 100644 index 00000000..cfcbf2f2 --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/entity/result/SendResponse.kt @@ -0,0 +1,7 @@ +package com.idormy.sms.forwarder.entity.result + +data class SendResponse( + var logId: Long, + var status: Int = 0, + var response: String = "", +) \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/entity/result/ServerchanResult.kt b/app/src/main/java/com/idormy/sms/forwarder/entity/result/ServerchanResult.kt new file mode 100644 index 00000000..2dfed528 --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/entity/result/ServerchanResult.kt @@ -0,0 +1,7 @@ +package com.idormy.sms.forwarder.entity.result + +data class ServerchanResult( + var code: Long, + var message: String, + var data: Any?, +) \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/entity/result/TelegramResult.kt b/app/src/main/java/com/idormy/sms/forwarder/entity/result/TelegramResult.kt new file mode 100644 index 00000000..5d618ee0 --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/entity/result/TelegramResult.kt @@ -0,0 +1,7 @@ +package com.idormy.sms.forwarder.entity.result + +data class TelegramResult( + var ok: Boolean?, + var message: String, + var timestamp: Long?, +) \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/entity/result/WeworkAgentResult.kt b/app/src/main/java/com/idormy/sms/forwarder/entity/result/WeworkAgentResult.kt new file mode 100644 index 00000000..78a86b32 --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/entity/result/WeworkAgentResult.kt @@ -0,0 +1,11 @@ +package com.idormy.sms.forwarder.entity.result + +data class WeworkAgentResult( + var errcode: Long, + var errmsg: String, + //获取access_token返回 + var access_token: String?, + var expires_in: Long?, + //发送接口返回 + var msgid: String?, +) \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/entity/result/WeworkRobotResult.kt b/app/src/main/java/com/idormy/sms/forwarder/entity/result/WeworkRobotResult.kt new file mode 100644 index 00000000..b2ee36d5 --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/entity/result/WeworkRobotResult.kt @@ -0,0 +1,6 @@ +package com.idormy.sms.forwarder.entity.result + +data class WeworkRobotResult( + var errcode: Long, + var errmsg: String, +) \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/entity/setting/BarkSetting.kt b/app/src/main/java/com/idormy/sms/forwarder/entity/setting/BarkSetting.kt new file mode 100644 index 00000000..d9c2af15 --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/entity/setting/BarkSetting.kt @@ -0,0 +1,22 @@ +package com.idormy.sms.forwarder.entity.setting + +import java.io.Serializable + +data class BarkSetting( + //推送地址 + var server: String, + //分组名称 + val group: String? = "", + //消息图标 + val icon: String? = "", + //消息声音 + val sound: String? = "", + //消息角标 + val badge: String? = "", + //消息链接 + val url: String? = "", + //通知级别 + val level: String? = "active", + //标题模板 + val title: String? = "", +) : Serializable \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/entity/setting/DingtalkSetting.kt b/app/src/main/java/com/idormy/sms/forwarder/entity/setting/DingtalkSetting.kt new file mode 100644 index 00000000..02cce253 --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/entity/setting/DingtalkSetting.kt @@ -0,0 +1,10 @@ +package com.idormy.sms.forwarder.entity.setting + +import java.io.Serializable + +data class DingtalkSetting( + var token: String = "", + var secret: String? = "", + var atAll: Boolean? = false, + var atMobiles: String? = "", +) : Serializable \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/entity/setting/EmailSetting.kt b/app/src/main/java/com/idormy/sms/forwarder/entity/setting/EmailSetting.kt new file mode 100644 index 00000000..b1e40b08 --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/entity/setting/EmailSetting.kt @@ -0,0 +1,16 @@ +package com.idormy.sms.forwarder.entity.setting + +import java.io.Serializable + +data class EmailSetting( + var mailType: String? = "", + var fromEmail: String? = "", + var pwd: String? = "", + var nickname: String? = "", + var host: String? = "", + var port: String? = "", + var ssl: Boolean? = false, + var startTls: Boolean? = false, + var toEmail: String? = "", + var title: String? = "", +) : Serializable \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/entity/setting/FeishuSetting.kt b/app/src/main/java/com/idormy/sms/forwarder/entity/setting/FeishuSetting.kt new file mode 100644 index 00000000..621ebc7a --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/entity/setting/FeishuSetting.kt @@ -0,0 +1,20 @@ +package com.idormy.sms.forwarder.entity.setting + +import com.idormy.sms.forwarder.R +import java.io.Serializable + +data class FeishuSetting( + var webhook: String = "", + val secret: String? = "", + val msgType: String? = "interactive", + val titleTemplate: String? = "", +) : Serializable { + + fun getMsgTypeCheckId(): Int { + return if (msgType == null || msgType == "interactive") { + R.id.rb_msg_type_interactive + } else { + R.id.rb_msg_type_text + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/entity/setting/GotifySetting.kt b/app/src/main/java/com/idormy/sms/forwarder/entity/setting/GotifySetting.kt new file mode 100644 index 00000000..e4303d0a --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/entity/setting/GotifySetting.kt @@ -0,0 +1,9 @@ +package com.idormy.sms.forwarder.entity.setting + +import java.io.Serializable + +data class GotifySetting( + var webServer: String = "", + val title: String? = "", + val priority: String? = "", +) : Serializable \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/entity/setting/PushplusSetting.kt b/app/src/main/java/com/idormy/sms/forwarder/entity/setting/PushplusSetting.kt new file mode 100644 index 00000000..c8b503f4 --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/entity/setting/PushplusSetting.kt @@ -0,0 +1,15 @@ +package com.idormy.sms.forwarder.entity.setting + +import java.io.Serializable + +data class PushplusSetting( + var website: String = "www.pushplus.plus", + var token: String = "", + val topic: String? = "", + val template: String? = "", + val channel: String? = "", + val webhook: String? = "", + val callbackUrl: String? = "", + val validTime: String? = "", + val titleTemplate: String? = "", +) : Serializable \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/entity/setting/ServerchanSetting.kt b/app/src/main/java/com/idormy/sms/forwarder/entity/setting/ServerchanSetting.kt new file mode 100644 index 00000000..3e16cac2 --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/entity/setting/ServerchanSetting.kt @@ -0,0 +1,10 @@ +package com.idormy.sms.forwarder.entity.setting + +import java.io.Serializable + +data class ServerchanSetting( + var sendKey: String = "", + var channel: String? = "", + var openid: String? = "", + var titleTemplate: String? = "", +) : Serializable \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/entity/setting/SmsSetting.kt b/app/src/main/java/com/idormy/sms/forwarder/entity/setting/SmsSetting.kt new file mode 100644 index 00000000..a090e036 --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/entity/setting/SmsSetting.kt @@ -0,0 +1,19 @@ +package com.idormy.sms.forwarder.entity.setting + +import com.idormy.sms.forwarder.R +import java.io.Serializable + +data class SmsSetting( + var simSlot: Int = 0, + var mobiles: String = "", + var onlyNoNetwork: Boolean? = false, +) : Serializable { + + fun getSmsSimSlotCheckId(): Int { + return when (simSlot) { + 1 -> R.id.rb_sim_slot_1 + 2 -> R.id.rb_sim_slot_2 + else -> R.id.rb_sim_slot_org + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/entity/setting/TelegramSetting.kt b/app/src/main/java/com/idormy/sms/forwarder/entity/setting/TelegramSetting.kt new file mode 100644 index 00000000..9c61c2db --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/entity/setting/TelegramSetting.kt @@ -0,0 +1,30 @@ +package com.idormy.sms.forwarder.entity.setting + +import com.idormy.sms.forwarder.R +import java.io.Serializable +import java.net.Proxy + +data class TelegramSetting( + val method: String? = "POST", + var apiToken: String = "", + val chatId: String = "", + val proxyType: Proxy.Type = Proxy.Type.DIRECT, + val proxyHost: String? = "", + val proxyPort: String? = "", + val proxyAuthenticator: Boolean? = false, + val proxyUsername: String? = "", + val proxyPassword: String? = "", +) : Serializable { + + fun getMethodCheckId(): Int { + return if (method == null || method == "POST") R.id.rb_method_post else R.id.rb_method_get + } + + fun getProxyTypeCheckId(): Int { + return when (proxyType) { + Proxy.Type.HTTP -> R.id.rb_proxyHttp + Proxy.Type.SOCKS -> R.id.rb_proxySocks + else -> R.id.rb_proxyNone + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/entity/setting/WebhookSetting.kt b/app/src/main/java/com/idormy/sms/forwarder/entity/setting/WebhookSetting.kt new file mode 100644 index 00000000..168c00c1 --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/entity/setting/WebhookSetting.kt @@ -0,0 +1,20 @@ +package com.idormy.sms.forwarder.entity.setting + +import com.idormy.sms.forwarder.R +import java.io.Serializable + +data class WebhookSetting( + val method: String? = "POST", + var webServer: String = "", + val secret: String? = "", + val webParams: String? = "", + val headers: Map?, +) : Serializable { + fun getMethodCheckId(): Int { + return if (method == null || method == "POST") { + R.id.rb_method_post + } else { + R.id.rb_method_get + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/entity/setting/WeworkAgentSetting.kt b/app/src/main/java/com/idormy/sms/forwarder/entity/setting/WeworkAgentSetting.kt new file mode 100644 index 00000000..623893a8 --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/entity/setting/WeworkAgentSetting.kt @@ -0,0 +1,11 @@ +package com.idormy.sms.forwarder.entity.setting + +import java.io.Serializable + +data class WeworkAgentSetting( + var corpID: String = "", + val agentID: String = "", + val secret: String = "", + val atAll: Boolean? = false, + val toUser: String? = "@all", +) : Serializable \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/entity/setting/WeworkRobotSetting.kt b/app/src/main/java/com/idormy/sms/forwarder/entity/setting/WeworkRobotSetting.kt new file mode 100644 index 00000000..39276114 --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/entity/setting/WeworkRobotSetting.kt @@ -0,0 +1,7 @@ +package com.idormy.sms.forwarder.entity.setting + +import java.io.Serializable + +data class WeworkRobotSetting( + var webHook: String, +) : Serializable \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/fragment/AboutFragment.kt b/app/src/main/java/com/idormy/sms/forwarder/fragment/AboutFragment.kt new file mode 100644 index 00000000..7c85ecdb --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/fragment/AboutFragment.kt @@ -0,0 +1,111 @@ +package com.idormy.sms.forwarder.fragment + +import android.view.LayoutInflater +import android.view.ViewGroup +import com.idormy.sms.forwarder.R +import com.idormy.sms.forwarder.core.BaseFragment +import com.idormy.sms.forwarder.core.webview.AgentWebActivity +import com.idormy.sms.forwarder.databinding.FragmentAboutBinding +import com.idormy.sms.forwarder.utils.CacheUtils +import com.idormy.sms.forwarder.utils.CommonUtils.Companion.gotoProtocol +import com.idormy.sms.forwarder.utils.CommonUtils.Companion.previewMarkdown +import com.idormy.sms.forwarder.utils.CommonUtils.Companion.previewPicture +import com.idormy.sms.forwarder.utils.HistoryUtils +import com.idormy.sms.forwarder.utils.XToastUtils +import com.idormy.sms.forwarder.utils.sdkinit.XUpdateInit +import com.xuexiang.xaop.annotation.SingleClick +import com.xuexiang.xpage.annotation.Page +import com.xuexiang.xui.widget.actionbar.TitleBar +import com.xuexiang.xui.widget.textview.supertextview.SuperTextView +import com.xuexiang.xutil.app.AppUtils +import java.text.SimpleDateFormat +import java.util.* + +@Page(name = "关于软件") +class AboutFragment : BaseFragment(), SuperTextView.OnSuperTextViewClickListener { + + override fun viewBindingInflate( + inflater: LayoutInflater, + container: ViewGroup, + ): FragmentAboutBinding { + return FragmentAboutBinding.inflate(inflater, container, false) + } + + override fun initTitle(): TitleBar? { + val titleBar = super.initTitle()!!.setImmersive(false) + titleBar.setTitle(R.string.menu_about) + return titleBar + } + + /** + * 初始化控件 + */ + override fun initViews() { + binding!!.menuVersion.setLeftString(String.format(resources.getString(R.string.about_app_version), AppUtils.getAppVersionName())) + binding!!.menuCache.setLeftString(String.format(resources.getString(R.string.about_cache_size), CacheUtils.getTotalCacheSize(requireContext()))) + + val dateFormat = SimpleDateFormat("yyyy", Locale.CHINA) + val currentYear = dateFormat.format(Date()) + binding!!.copyright.text = java.lang.String.format(resources.getString(R.string.about_copyright), currentYear) + } + + override fun initListeners() { + binding!!.btnUpdate.setOnClickListener { + XUpdateInit.checkUpdate(requireContext(), true) + } + binding!!.btnCache.setOnClickListener { + HistoryUtils.clear() + CacheUtils.clearAllCache(requireContext()) + XToastUtils.success(R.string.about_cache_purged) + binding!!.menuCache.setLeftString(String.format(resources.getString(R.string.about_cache_size), CacheUtils.getTotalCacheSize(requireContext()))) + } + binding!!.btnGithub.setOnClickListener { + AgentWebActivity.goWeb(context, getString(R.string.url_project_github)) + } + binding!!.btnGitee.setOnClickListener { + AgentWebActivity.goWeb(context, getString(R.string.url_project_gitee)) + } + binding!!.btnAddQqGroup1.setOnClickListener { + AgentWebActivity.goWeb(context, getString(R.string.url_add_qq_group_1)) + } + binding!!.btnAddQqGroup2.setOnClickListener { + AgentWebActivity.goWeb(context, getString(R.string.url_add_qq_group_2)) + } + binding!!.btnAddQqGroup3.setOnClickListener { + AgentWebActivity.goWeb(context, getString(R.string.url_add_qq_group_3)) + } + binding!!.btnAddQqGroup4.setOnClickListener { + AgentWebActivity.goWeb(context, getString(R.string.url_add_qq_group_4)) + } + binding!!.btnAddQqGroup5.setOnClickListener { + AgentWebActivity.goWeb(context, getString(R.string.url_add_qq_group_5)) + } + + binding!!.menuDonation.setOnSuperTextViewClickListener(this) + binding!!.menuWecomGroup.setOnSuperTextViewClickListener(this) + binding!!.menuDingtalkGroup.setOnSuperTextViewClickListener(this) + binding!!.menuUserProtocol.setOnSuperTextViewClickListener(this) + binding!!.menuPrivacyProtocol.setOnSuperTextViewClickListener(this) + } + + @SingleClick + override fun onClick(v: SuperTextView) { + when (v.id) { + R.id.menu_donation -> { + previewMarkdown(this, getString(R.string.about_item_donation_link), getString(R.string.url_donation_link), false) + } + R.id.menu_wecom_group -> { + previewPicture(this, getString(R.string.url_wework_group), null) + } + R.id.menu_dingtalk_group -> { + previewPicture(this, getString(R.string.url_dingtalk_group), null) + } + R.id.menu_user_protocol -> { + gotoProtocol(this, isPrivacy = false, isImmersive = false) + } + R.id.menu_privacy_protocol -> { + gotoProtocol(this, isPrivacy = true, isImmersive = false) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/fragment/AppListFragment.kt b/app/src/main/java/com/idormy/sms/forwarder/fragment/AppListFragment.kt new file mode 100644 index 00000000..13c95d5b --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/fragment/AppListFragment.kt @@ -0,0 +1,95 @@ +package com.idormy.sms.forwarder.fragment + +import android.content.ClipData +import android.content.ClipboardManager +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.appcompat.app.AppCompatActivity +import com.idormy.sms.forwarder.App +import com.idormy.sms.forwarder.R +import com.idormy.sms.forwarder.adapter.AppListAdapter +import com.idormy.sms.forwarder.core.BaseFragment +import com.idormy.sms.forwarder.databinding.FragmentAppListBinding +import com.idormy.sms.forwarder.utils.XToastUtils +import com.scwang.smartrefresh.layout.api.RefreshLayout +import com.scwang.smartrefresh.layout.listener.OnRefreshLoadMoreListener +import com.xuexiang.xaop.annotation.SingleClick +import com.xuexiang.xpage.annotation.Page +import com.xuexiang.xui.utils.DensityUtils +import com.xuexiang.xui.utils.ThemeUtils +import com.xuexiang.xui.utils.WidgetUtils +import com.xuexiang.xui.widget.actionbar.TitleBar +import com.xuexiang.xutil.app.AppUtils + +@Page(name = "应用列表") +class AppListFragment : BaseFragment() { + + var appListAdapter: AppListAdapter? = null + + override fun viewBindingInflate( + inflater: LayoutInflater, + container: ViewGroup, + ): FragmentAppListBinding { + return FragmentAppListBinding.inflate(inflater, container, false) + } + + override fun initTitle(): TitleBar { + val titleBar = super.initTitle()!!.setImmersive(false) + titleBar.setTitle(R.string.menu_apps) + titleBar!!.addAction(object : TitleBar.ImageAction(R.drawable.ic_refresh) { + @SingleClick + override fun performAction(view: View) { + binding!!.refreshLayout.autoRefresh() + } + }) + return titleBar + } + + /** + * 初始化控件 + */ + override fun initViews() { + WidgetUtils.initRecyclerView(binding!!.recyclerView, DensityUtils.dp2px(5f), ThemeUtils.resolveColor(context, R.attr.xui_config_color_background)) + binding!!.recyclerView.adapter = AppListAdapter(true).also { appListAdapter = it } + } + + override fun initListeners() { + binding!!.refreshLayout.setOnRefreshLoadMoreListener(object : OnRefreshLoadMoreListener { + override fun onLoadMore(refreshLayout: RefreshLayout) { + refreshLayout.layout.postDelayed({ + refreshLayout.finishLoadMore() + }, 1000) + } + + override fun onRefresh(refreshLayout: RefreshLayout) { + refreshLayout.layout.postDelayed({ + appListAdapter?.refresh(AppUtils.getAppsInfo()) + refreshLayout.finishRefresh() + }, 3000) + } + }) + appListAdapter?.setOnItemClickListener { _, item, _ -> + val cm = requireContext().getSystemService(AppCompatActivity.CLIPBOARD_SERVICE) as ClipboardManager + val mClipData = ClipData.newPlainText("pkgName", item?.packageName) + cm.setPrimaryClip(mClipData) + XToastUtils.toast("已复制包名:" + item?.packageName, 2000) + } + + //设置刷新加载时禁止所有列表操作 + binding!!.refreshLayout.setDisableContentWhenRefresh(true) + binding!!.refreshLayout.setDisableContentWhenLoading(true) + //binding!!.refreshLayout.autoRefresh() + if (App.AppInfoList.isEmpty()) { + appListAdapter?.refresh(AppUtils.getAppsInfo()) + } else { + appListAdapter?.refresh(App.AppInfoList) + } + binding!!.refreshLayout.finishRefresh() + } + + override fun onDestroyView() { + appListAdapter?.recycle() + super.onDestroyView() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/fragment/ClientFragment.kt b/app/src/main/java/com/idormy/sms/forwarder/fragment/ClientFragment.kt new file mode 100644 index 00000000..8336224c --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/fragment/ClientFragment.kt @@ -0,0 +1,196 @@ +package com.idormy.sms.forwarder.fragment + +import android.text.Editable +import android.text.TextWatcher +import android.util.Log +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import com.idormy.sms.forwarder.App +import com.idormy.sms.forwarder.R +import com.idormy.sms.forwarder.adapter.WidgetItemAdapter +import com.idormy.sms.forwarder.core.BaseFragment +import com.idormy.sms.forwarder.databinding.FragmentClientBinding +import com.idormy.sms.forwarder.server.model.BaseResponse +import com.idormy.sms.forwarder.server.model.ConfigData +import com.idormy.sms.forwarder.utils.CLIENT_FRAGMENT_LIST +import com.idormy.sms.forwarder.utils.HttpServerUtils +import com.idormy.sms.forwarder.utils.SettingUtils +import com.idormy.sms.forwarder.utils.XToastUtils +import com.xuexiang.xaop.annotation.SingleClick +import com.xuexiang.xhttp2.XHttp +import com.xuexiang.xhttp2.cache.model.CacheMode +import com.xuexiang.xhttp2.callback.SimpleCallBack +import com.xuexiang.xhttp2.exception.ApiException +import com.xuexiang.xpage.annotation.Page +import com.xuexiang.xpage.base.XPageFragment +import com.xuexiang.xpage.core.PageOption +import com.xuexiang.xpage.model.PageInfo +import com.xuexiang.xrouter.utils.TextUtils +import com.xuexiang.xui.adapter.recyclerview.RecyclerViewHolder +import com.xuexiang.xui.utils.DensityUtils +import com.xuexiang.xui.utils.ResUtils +import com.xuexiang.xui.utils.WidgetUtils +import com.xuexiang.xui.widget.actionbar.TitleBar +import com.xuexiang.xutil.net.NetworkUtils + +@Suppress("PrivatePropertyName", "PropertyName") +@Page(name = "主动控制·客户端") +class ClientFragment : BaseFragment(), + View.OnClickListener, + RecyclerViewHolder.OnItemClickListener { + + val TAG: String = ClientFragment::class.java.simpleName + private var appContext: App? = null + private var serverConfig: ConfigData? = null + + override fun initViews() { + appContext = requireActivity().application as App + //val context = requireContext() + //Set Server-Logging for Server + //val serverLogging = ServerLogging(context.filesDir.absolutePath) + //appContext!!.httpServer.serverLogging = serverLogging + + WidgetUtils.initGridRecyclerView(binding!!.recyclerView, 3, DensityUtils.dp2px(1f)) + val widgetItemAdapter = WidgetItemAdapter(CLIENT_FRAGMENT_LIST) + widgetItemAdapter.setOnItemClickListener(this) + binding!!.recyclerView.adapter = widgetItemAdapter + + if (NetworkUtils.isUrlValid(HttpServerUtils.serverAddress)) queryConfig(false) + } + + override fun initTitle(): TitleBar? { + val titleBar = super.initTitle()!!.setImmersive(false) + titleBar.setTitle(R.string.menu_client) + return titleBar + } + + override fun viewBindingInflate( + inflater: LayoutInflater, + container: ViewGroup, + ): FragmentClientBinding { + return FragmentClientBinding.inflate(inflater, container, false) + } + + override fun initListeners() { + binding!!.etServerAddress.setText(HttpServerUtils.serverAddress) + binding!!.etServerAddress.addTextChangedListener(object : TextWatcher { + override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {} + override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {} + override fun afterTextChanged(s: Editable) { + HttpServerUtils.serverAddress = binding!!.etServerAddress.text.toString().trim().trimEnd('/') + } + }) + + binding!!.etSignKey.setText(HttpServerUtils.clientSignKey) + binding!!.etSignKey.addTextChangedListener(object : TextWatcher { + override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {} + override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {} + override fun afterTextChanged(s: Editable) { + HttpServerUtils.clientSignKey = binding!!.etSignKey.text.toString().trim() + } + }) + + binding!!.btnServerTest.setOnClickListener(this) + } + + @SingleClick + override fun onClick(v: View) { + when (v.id) { + R.id.btn_server_test -> { + if (!NetworkUtils.isUrlValid(HttpServerUtils.serverAddress)) { + XToastUtils.error("请输入有效的服务地址") + return + } + queryConfig(true) + } + else -> {} + } + } + + override fun onItemClick(itemView: View, item: PageInfo, position: Int) { + try { + if (!NetworkUtils.isUrlValid(HttpServerUtils.serverAddress)) { + XToastUtils.error("请输入有效的服务地址") + serverConfig = null + return + } + if (serverConfig == null && item.name != ResUtils.getString(R.string.api_clone)) { + XToastUtils.error("请先点击【测试接口】按钮") + return + } + if (serverConfig != null && ( + (item.name == ResUtils.getString(R.string.api_sms_send) && !serverConfig!!.enableApiSmsSend) + || (item.name == ResUtils.getString(R.string.api_sms_query) && !serverConfig!!.enableApiSmsQuery) + || (item.name == ResUtils.getString(R.string.api_call_query) && !serverConfig!!.enableApiCallQuery) + || (item.name == ResUtils.getString(R.string.api_contact_query) && !serverConfig!!.enableApiContactQuery) + || (item.name == ResUtils.getString(R.string.api_battery_query) && !serverConfig!!.enableApiBatteryQuery) + ) + ) { + XToastUtils.error("服务端未开启此功能") + return + } + @Suppress("UNCHECKED_CAST") + PageOption.to(Class.forName(item.classPath) as Class) //跳转的fragment + .setNewActivity(true) + .open(this) + } catch (e: Exception) { + e.printStackTrace() + XToastUtils.error(e.message.toString()) + } + } + + private fun queryConfig(needToast: Boolean) { + val requestUrl: String = HttpServerUtils.serverAddress + "/config/query" + Log.i(TAG, "requestUrl:$requestUrl") + + val msgMap: MutableMap = mutableMapOf() + val timestamp = System.currentTimeMillis() + msgMap["timestamp"] = timestamp + val clientSignKey = HttpServerUtils.clientSignKey + if (!TextUtils.isEmpty(clientSignKey)) { + msgMap["sign"] = HttpServerUtils.calcSign(timestamp.toString(), clientSignKey.toString()) + } + val dataMap: MutableMap = mutableMapOf() + msgMap["data"] = dataMap + + val requestMsg: String = Gson().toJson(msgMap) + Log.i(TAG, "requestMsg:$requestMsg") + + XHttp.post(requestUrl) + .upJson(requestMsg) + .keepJson(true) + .timeOut((SettingUtils.requestTimeout * 1000).toLong()) //超时时间10s + .cacheMode(CacheMode.NO_CACHE) + //.retryCount(SettingUtils.requestRetryTimes) //超时重试的次数 + //.retryDelay(SettingUtils.requestDelayTime) //超时重试的延迟时间 + //.retryIncreaseDelay(SettingUtils.requestDelayTime) //超时重试叠加延时 + .timeStamp(true) + .execute(object : SimpleCallBack() { + + override fun onError(e: ApiException) { + XToastUtils.error(e.displayMessage) + } + + override fun onSuccess(response: String) { + Log.i(TAG, response) + try { + val resp: BaseResponse = Gson().fromJson(response, object : TypeToken>() {}.type) + if (resp.code == 200) { + serverConfig = resp.data!! + if (needToast) XToastUtils.success(ResUtils.getString(R.string.request_succeeded)) + } else { + if (needToast) XToastUtils.error(ResUtils.getString(R.string.request_failed) + resp.msg) + } + } catch (e: Exception) { + e.printStackTrace() + if (needToast) XToastUtils.error(ResUtils.getString(R.string.request_failed) + response) + } + } + + }) + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/fragment/FrpcEditFragment.kt b/app/src/main/java/com/idormy/sms/forwarder/fragment/FrpcEditFragment.kt new file mode 100644 index 00000000..fd99300f --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/fragment/FrpcEditFragment.kt @@ -0,0 +1,146 @@ +package com.idormy.sms.forwarder.fragment + +import android.annotation.SuppressLint +import android.graphics.Color +import android.text.TextUtils +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.viewModels +import com.idormy.sms.forwarder.R +import com.idormy.sms.forwarder.core.BaseFragment +import com.idormy.sms.forwarder.database.entity.Frpc +import com.idormy.sms.forwarder.database.viewmodel.BaseViewModelFactory +import com.idormy.sms.forwarder.database.viewmodel.FrpcViewModel +import com.idormy.sms.forwarder.databinding.FragmentFrpcEditBinding +import com.idormy.sms.forwarder.utils.EVENT_FRPC_UPDATE_CONFIG +import com.idormy.sms.forwarder.utils.INTENT_FRPC_EDIT_FILE +import com.idormy.sms.forwarder.utils.XToastUtils +import com.jeremyliao.liveeventbus.LiveEventBus +import com.xuexiang.xaop.annotation.SingleClick +import com.xuexiang.xpage.annotation.Page +import com.xuexiang.xui.utils.ResUtils +import com.xuexiang.xui.utils.ThemeUtils +import com.xuexiang.xui.widget.actionbar.TitleBar +import com.xuexiang.xui.widget.button.switchbutton.SwitchButton +import com.xuexiang.xui.widget.dialog.materialdialog.DialogAction +import com.xuexiang.xui.widget.dialog.materialdialog.MaterialDialog +import com.xuexiang.xui.widget.edittext.materialedittext.MaterialEditText + +@Suppress("PrivatePropertyName") +@Page(name = "Frp内网穿透·编辑配置") +class FrpcEditFragment : BaseFragment() { + + var titleBar: TitleBar? = null + var frpc: Frpc? = null + private val viewModel by viewModels { BaseViewModelFactory(context) } + + override fun initViews() { + val pairCompleteMap: MutableMap = HashMap() + pairCompleteMap['{'] = '}' + pairCompleteMap['['] = ']' + pairCompleteMap['('] = ')' + pairCompleteMap['<'] = '>' + pairCompleteMap['"'] = '"' + + binding!!.editText.enablePairComplete(true) + binding!!.editText.enablePairCompleteCenterCursor(true) + binding!!.editText.setPairCompleteMap(pairCompleteMap) + + binding!!.editText.setEnableLineNumber(true) + binding!!.editText.setLineNumberTextColor(Color.LTGRAY) + binding!!.editText.setLineNumberTextSize(24f) + binding!!.editText.textSize = 14f + } + + override fun viewBindingInflate(inflater: LayoutInflater, container: ViewGroup): FragmentFrpcEditBinding { + return FragmentFrpcEditBinding.inflate(inflater, container, false) + } + + override fun initTitle(): TitleBar? { + titleBar = super.initTitle()!!.setImmersive(false) + titleBar!!.setTitle(R.string.menu_frpc) + titleBar!!.setActionTextColor(ThemeUtils.resolveColor(context, R.attr.colorAccent)) + titleBar!!.addAction(object : TitleBar.ImageAction(R.drawable.ic_save) { + @SuppressLint("ResourceAsColor") + @SingleClick + override fun performAction(view: View) { + if (frpc == null) return + + val dialogFrpc = View.inflate(requireContext(), R.layout.dialog_frpc_save, null) + val tvName = dialogFrpc.findViewById(R.id.tv_name) + val sbAutorun = dialogFrpc.findViewById(R.id.sb_autorun) + + tvName.setText(frpc!!.name) + sbAutorun.setCheckedImmediately(frpc!!.autorun == 1) + + frpc!!.config = binding!!.editText.text.toString() + + if (TextUtils.isEmpty(frpc!!.config)) { + XToastUtils.error(R.string.tips_input_config_content) + return + } + + MaterialDialog.Builder(context!!) + .iconRes(R.drawable.ic_menu_frpc) + .title(R.string.title_save_config) + .customView(dialogFrpc, true) + .cancelable(false) + .autoDismiss(false) + .neutralText(R.string.action_quit) + .neutralColor(ResUtils.getColors(R.color.red)) + .onNeutral { dialog: MaterialDialog?, _: DialogAction? -> + dialog?.dismiss() + activity?.onBackPressed() + } + .negativeText(R.string.action_back) + .negativeColor(ResUtils.getColors(R.color.colorBlueGrey)) + .onNegative { dialog: MaterialDialog?, _: DialogAction? -> + dialog?.dismiss() + } + .positiveText(R.string.action_save) + .onPositive { dialog: MaterialDialog?, _: DialogAction? -> + try { + frpc!!.autorun = if (sbAutorun.isChecked) 1 else 0 + frpc!!.name = tvName.text.toString() + if (TextUtils.isEmpty(frpc!!.name)) { + XToastUtils.error(R.string.tips_input_config_name) + return@onPositive + } + + if (TextUtils.isEmpty(frpc!!.uid)) { + viewModel.insert(frpc!!) + } else { + viewModel.update(frpc!!) + } + + dialog?.dismiss() + LiveEventBus.get(EVENT_FRPC_UPDATE_CONFIG).post(frpc) + XToastUtils.success(R.string.tipSaveSuccess) + + activity?.onBackPressed() + } catch (e: Exception) { + XToastUtils.error(e.message.toString()) + } + }.show() + } + }) + titleBar!!.addAction(object : TitleBar.ImageAction(R.drawable.ic_restore) { + @SingleClick + override fun performAction(view: View) { + binding!!.editText.setText(frpc?.config!!) + XToastUtils.success(R.string.tipRestoreSuccess) + } + }) + return titleBar + } + + override fun initListeners() { + LiveEventBus.get(INTENT_FRPC_EDIT_FILE, Frpc::class.java).observeSticky(this) { value: Frpc -> + frpc = value + binding!!.editText.setText(value.config) + titleBar!!.setTitle(if (TextUtils.isEmpty(value.name)) getString(R.string.noName) else value.name) + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/fragment/FrpcFragment.kt b/app/src/main/java/com/idormy/sms/forwarder/fragment/FrpcFragment.kt new file mode 100644 index 00000000..919e0c98 --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/fragment/FrpcFragment.kt @@ -0,0 +1,202 @@ +package com.idormy.sms.forwarder.fragment + +import android.content.Intent +import android.os.Build +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.viewModels +import androidx.lifecycle.lifecycleScope +import androidx.recyclerview.widget.RecyclerView.RecycledViewPool +import com.alibaba.android.vlayout.VirtualLayoutManager +import com.idormy.sms.forwarder.R +import com.idormy.sms.forwarder.adapter.FrpcPagingAdapter +import com.idormy.sms.forwarder.core.BaseFragment +import com.idormy.sms.forwarder.database.entity.Frpc +import com.idormy.sms.forwarder.database.viewmodel.BaseViewModelFactory +import com.idormy.sms.forwarder.database.viewmodel.FrpcViewModel +import com.idormy.sms.forwarder.databinding.FragmentFrpcsBinding +import com.idormy.sms.forwarder.service.ForegroundService +import com.idormy.sms.forwarder.utils.* +import com.jeremyliao.liveeventbus.LiveEventBus +import com.scwang.smartrefresh.layout.api.RefreshLayout +import com.xuexiang.xaop.annotation.SingleClick +import com.xuexiang.xpage.annotation.Page +import com.xuexiang.xpage.base.XPageActivity +import com.xuexiang.xpage.core.PageOption +import com.xuexiang.xui.utils.ThemeUtils +import com.xuexiang.xui.utils.WidgetUtils +import com.xuexiang.xui.widget.actionbar.TitleBar +import com.xuexiang.xui.widget.dialog.LoadingDialog +import frpclib.Frpclib +import io.reactivex.CompletableObserver +import io.reactivex.Observer +import io.reactivex.android.schedulers.AndroidSchedulers +import io.reactivex.disposables.Disposable +import io.reactivex.schedulers.Schedulers +import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.launch + + +@Suppress("DEPRECATION") +@Page(name = "Frp内网穿透") +class FrpcFragment : BaseFragment(), FrpcPagingAdapter.OnItemClickListener { + + var titleBar: TitleBar? = null + private var adapter = FrpcPagingAdapter(this) + private val viewModel by viewModels { BaseViewModelFactory(context) } + + override fun viewBindingInflate( + inflater: LayoutInflater, + container: ViewGroup, + ): FragmentFrpcsBinding { + return FragmentFrpcsBinding.inflate(inflater, container, false) + } + + override fun initTitle(): TitleBar? { + titleBar = super.initTitle()!!.setImmersive(false) + titleBar!!.setTitle(R.string.menu_frpc) + titleBar!!.setActionTextColor(ThemeUtils.resolveColor(context, R.attr.colorAccent)) + titleBar!!.addAction(object : TitleBar.ImageAction(R.drawable.ic_add) { + @SingleClick + override fun performAction(view: View) { + FrpcUtils.getStringFromRaw(context!!, R.raw.frpc) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(object : Observer { + override fun onSubscribe(d: Disposable) {} + override fun onNext(content: String) { + LiveEventBus.get(INTENT_FRPC_EDIT_FILE).post(Frpc(content)) + PageOption.to(FrpcEditFragment::class.java).setNewActivity(true).open((context as XPageActivity?)!!) + } + + override fun onError(e: Throwable) { + e.message?.let { XToastUtils.error(it) } + } + + override fun onComplete() {} + }) + } + }) + return titleBar + } + + /** + * 初始化控件 + */ + override fun initViews() { + val virtualLayoutManager = VirtualLayoutManager(requireContext()) + binding!!.recyclerView.layoutManager = virtualLayoutManager + val viewPool = RecycledViewPool() + binding!!.recyclerView.setRecycledViewPool(viewPool) + viewPool.setMaxRecycledViews(0, 10) + + binding!!.recyclerView.adapter = adapter + } + + override fun initListeners() { + //下拉刷新 + binding!!.refreshLayout.setOnRefreshListener { refreshLayout: RefreshLayout -> + //adapter.refresh() + lifecycleScope.launch { + viewModel.allFrpc.collectLatest { adapter.submitData(it) } + } + refreshLayout.finishRefresh() + } + binding!!.refreshLayout.autoRefresh() + + //更新时间 + LiveEventBus.get(EVENT_FRPC_UPDATE_CONFIG, Frpc::class.java).observe(this) { + adapter.refresh() + } + + //删除事件 + LiveEventBus.get(EVENT_FRPC_DELETE_CONFIG, Frpc::class.java).observe(this) { + adapter.refresh() + } + + //运行出错时间 + LiveEventBus.get(EVENT_FRPC_RUNNING_ERROR, String::class.java).observe(this) { + XToastUtils.error("Frpc运行失败") + //FrpcUtils.checkAndStopService(requireContext()) + adapter.refresh() + } + + //运行成功 + LiveEventBus.get(EVENT_FRPC_RUNNING_SUCCESS, String::class.java).observe(this) { + adapter.refresh() + } + } + + override fun onItemClicked(view: View?, item: Frpc) { + val id = view?.id + if (id == R.id.iv_play) { + //if (!FrpcUtils.isServiceRunning(ForegroundService::class.java.name, requireContext())) { + if (!ForegroundService.isRunning) { + val intent = Intent(requireContext(), ForegroundService::class.java) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + requireContext().startForegroundService(intent) + } else { + requireContext().startService(intent) + } + } + + if (Frpclib.isRunning(item.uid)) { + Frpclib.close(item.uid) + item.setConnecting(false) + LiveEventBus.get(EVENT_FRPC_UPDATE_CONFIG).post(item) + //FrpcUtils.checkAndStopService(requireContext()) + return + } + + FrpcUtils.waitService(ForegroundService::class.java.name, requireContext()) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(object : CompletableObserver { + var mLoadingDialog: LoadingDialog = WidgetUtils.getLoadingDialog(context!!).setIconScale(0.4f).setLoadingSpeed(8) + + override fun onSubscribe(d: Disposable) { + mLoadingDialog.setLoadingIcon(R.drawable.ic_menu_frpc) + mLoadingDialog.updateMessage(R.string.tipWaitService) + mLoadingDialog.show() + } + + override fun onComplete() { + mLoadingDialog.dismiss() + mLoadingDialog.recycle() + LiveEventBus.get(INTENT_FRPC_APPLY_FILE).postAcrossProcess(item.uid) + item.setConnecting(true) + LiveEventBus.get(EVENT_FRPC_UPDATE_CONFIG).post(item) + } + + override fun onError(e: Throwable) { + mLoadingDialog.dismiss() + mLoadingDialog.recycle() + e.message?.let { XToastUtils.error(it) } + item.setConnecting(false) + LiveEventBus.get(EVENT_FRPC_UPDATE_CONFIG).post(item) + } + }) + } else { + //编辑或删除需要先停止客户端 + if (Frpclib.isRunning(item.uid)) { + XToastUtils.warning(R.string.tipServiceRunning) + return + } + if (id == R.id.iv_edit) { + LiveEventBus.get(INTENT_FRPC_EDIT_FILE).post(item) + openNewPage(FrpcEditFragment::class.java) + } else if (id == R.id.iv_delete) { + try { + viewModel.delete(item) + LiveEventBus.get(EVENT_FRPC_DELETE_CONFIG).post(item) + XToastUtils.success("删除成功") + } catch (e: Exception) { + e.message?.let { XToastUtils.error(it) } + } + } + } + } + + override fun onItemRemove(view: View?, id: Int) {} +} \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/fragment/LogcatFragment.kt b/app/src/main/java/com/idormy/sms/forwarder/fragment/LogcatFragment.kt new file mode 100644 index 00000000..6fc2f37a --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/fragment/LogcatFragment.kt @@ -0,0 +1,109 @@ +package com.idormy.sms.forwarder.fragment + +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import com.idormy.sms.forwarder.R +import com.idormy.sms.forwarder.core.BaseFragment +import com.idormy.sms.forwarder.databinding.FragmentLogcatBinding +import com.idormy.sms.forwarder.utils.XToastUtils +import com.xuexiang.xaop.annotation.SingleClick +import com.xuexiang.xpage.annotation.Page +import com.xuexiang.xui.utils.ThemeUtils +import com.xuexiang.xui.widget.actionbar.TitleBar +import com.xuexiang.xutil.system.ClipboardUtils +import io.reactivex.Observable +import io.reactivex.ObservableEmitter +import io.reactivex.Observer +import io.reactivex.android.schedulers.AndroidSchedulers +import io.reactivex.disposables.Disposable +import io.reactivex.schedulers.Schedulers +import java.io.BufferedReader +import java.io.InputStreamReader + +@Suppress("PrivatePropertyName") +@Page(name = "Logcat") +class LogcatFragment : BaseFragment() { + + override fun viewBindingInflate( + inflater: LayoutInflater, + container: ViewGroup, + ): FragmentLogcatBinding { + return FragmentLogcatBinding.inflate(inflater, container, false) + } + + override fun initTitle(): TitleBar { + val titleBar = super.initTitle()!!.setImmersive(false) + titleBar!!.setTitle(R.string.menu_logcat) + titleBar.setActionTextColor(ThemeUtils.resolveColor(context, R.attr.colorAccent)) + titleBar.addAction(object : TitleBar.ImageAction(R.drawable.ic_copy) { + @SingleClick + override fun performAction(view: View) { + ClipboardUtils.copyText(binding!!.tvLogcat.text.toString()) + XToastUtils.success(R.string.copySuccess) + } + }) + titleBar.addAction(object : TitleBar.ImageAction(R.drawable.ic_delete) { + @SingleClick + override fun performAction(view: View) { + readLog(true) + binding!!.tvLogcat.text = "" + } + }) + return titleBar + } + + override fun initViews() { + } + + override fun initListeners() { + readLog(false) + } + + private fun readLog(flush: Boolean) { + val lst: HashSet = LinkedHashSet() + lst.add("logcat") + lst.add("-d") + lst.add("-v") + lst.add("time") + lst.add("-s") + lst.add("GoLog,com.idormy.sms.forwarder.ForegroundService,com.idormy.sms.forwarder.server.ServerService") + Observable.create { emitter: ObservableEmitter -> + if (flush) { + val lst2: HashSet = LinkedHashSet() + lst2.add("logcat") + lst2.add("-c") + val process = Runtime.getRuntime().exec(lst2.toTypedArray()) + process.waitFor() + } + val process = Runtime.getRuntime().exec(lst.toTypedArray()) + val `in` = InputStreamReader(process.inputStream) + val bufferedReader = BufferedReader(`in`) + var line: String? + while (bufferedReader.readLine().also { line = it } != null) { + emitter.onNext(line!!) + } + `in`.close() + bufferedReader.close() + emitter.onComplete() + }.subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(object : Observer { + override fun onSubscribe(d: Disposable) {} + + override fun onNext(s: String) { + binding!!.tvLogcat.append(s) + binding!!.tvLogcat.append("\r\n") + binding!!.svLogcat.fullScroll(View.FOCUS_DOWN) + } + + override fun onError(e: Throwable) { + e.printStackTrace() + } + + override fun onComplete() {} + + }) + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/fragment/LoginFragment.kt b/app/src/main/java/com/idormy/sms/forwarder/fragment/LoginFragment.kt new file mode 100644 index 00000000..06b43aea --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/fragment/LoginFragment.kt @@ -0,0 +1,172 @@ +package com.idormy.sms.forwarder.fragment + +import android.graphics.Color +import android.util.Log +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.CompoundButton +import com.idormy.sms.forwarder.R +import com.idormy.sms.forwarder.activity.MainActivity +import com.idormy.sms.forwarder.core.BaseFragment +import com.idormy.sms.forwarder.databinding.FragmentLoginBinding +import com.idormy.sms.forwarder.utils.CommonUtils.Companion.gotoProtocol +import com.idormy.sms.forwarder.utils.CommonUtils.Companion.showPrivacyDialog +import com.idormy.sms.forwarder.utils.RandomUtils.Companion.getRandomNumbersAndLetters +import com.idormy.sms.forwarder.utils.SettingUtils +import com.idormy.sms.forwarder.utils.SettingUtils.Companion.isAgreePrivacy +import com.idormy.sms.forwarder.utils.TokenUtils.Companion.handleLoginSuccess +import com.idormy.sms.forwarder.utils.XToastUtils +import com.idormy.sms.forwarder.utils.sdkinit.UMengInit +import com.xuexiang.xaop.annotation.SingleClick +import com.xuexiang.xpage.annotation.Page +import com.xuexiang.xpage.enums.CoreAnim +import com.xuexiang.xui.utils.CountDownButtonHelper +import com.xuexiang.xui.utils.ResUtils +import com.xuexiang.xui.utils.ThemeUtils +import com.xuexiang.xui.utils.ViewUtils +import com.xuexiang.xui.widget.actionbar.TitleBar +import com.xuexiang.xui.widget.dialog.materialdialog.DialogAction +import com.xuexiang.xui.widget.dialog.materialdialog.MaterialDialog +import com.xuexiang.xutil.app.ActivityUtils + +/** + * 登录页面 + * + * @author xuexiang + * @since 2019-11-17 22:15 + */ +@Page(anim = CoreAnim.none) +class LoginFragment : BaseFragment(), View.OnClickListener { + + private var mJumpView: View? = null + private var mCountDownHelper: CountDownButtonHelper? = null + override fun viewBindingInflate( + inflater: LayoutInflater, + container: ViewGroup, + ): FragmentLoginBinding { + return FragmentLoginBinding.inflate(inflater, container, false) + } + + override fun initTitle(): TitleBar? { + val titleBar = super.initTitle() + titleBar?.run { + setImmersive(true) + setBackgroundColor(Color.TRANSPARENT) + setTitle("") + setLeftImageDrawable(ResUtils.getVectorDrawable(context, R.drawable.ic_login_close)) + setActionTextColor(ThemeUtils.resolveColor(context, R.attr.colorAccent)) + mJumpView = addAction(object : TitleBar.TextAction(R.string.title_jump_login) { + override fun performAction(view: View) { + onLoginSuccess() + } + }) + } + return titleBar + } + + override fun initViews() { + mCountDownHelper = CountDownButtonHelper(binding!!.btnGetVerifyCode, 60) + //隐私政策弹窗 + if (!isAgreePrivacy) { + showPrivacyDialog(requireContext()) { dialog: MaterialDialog, _: DialogAction? -> + dialog.dismiss() + handleSubmitPrivacy() + } + } + val isAgreePrivacy = isAgreePrivacy + binding!!.cbProtocol.isChecked = isAgreePrivacy + refreshButton(isAgreePrivacy) + binding!!.cbProtocol.setOnCheckedChangeListener { _: CompoundButton?, isChecked: Boolean -> + SettingUtils.isAgreePrivacy = isChecked + refreshButton(isChecked) + } + } + + override fun initListeners() { + binding!!.btnGetVerifyCode.setOnClickListener(this) + binding!!.btnLogin.setOnClickListener(this) + binding!!.tvOtherLogin.setOnClickListener(this) + binding!!.tvForgetPassword.setOnClickListener(this) + binding!!.tvUserProtocol.setOnClickListener(this) + binding!!.tvPrivacyProtocol.setOnClickListener(this) + } + + private fun refreshButton(isChecked: Boolean) { + ViewUtils.setEnabled(binding!!.btnLogin, isChecked) + ViewUtils.setEnabled(mJumpView, isChecked) + } + + private fun handleSubmitPrivacy() { + isAgreePrivacy = true + UMengInit.init() + // 应用市场不让默认勾选 +// ViewUtils.setChecked(cbProtocol, true); + } + + @SingleClick + override fun onClick(v: View) { + val id = v.id + if (id == R.id.btn_get_verify_code) { + if (binding!!.etPhoneNumber.validate()) { + getVerifyCode(binding!!.etPhoneNumber.editValue) + } + } else if (id == R.id.btn_login) { + if (binding!!.etPhoneNumber.validate()) { + if (binding!!.etVerifyCode.validate()) { + loginByVerifyCode( + binding!!.etPhoneNumber.editValue, + binding!!.etVerifyCode.editValue + ) + } + } + } else if (id == R.id.tv_other_login) { + XToastUtils.info("其他登录方式") + } else if (id == R.id.tv_forget_password) { + XToastUtils.info("忘记密码") + } else if (id == R.id.tv_user_protocol) { + gotoProtocol(this, isPrivacy = false, isImmersive = true) + } else if (id == R.id.tv_privacy_protocol) { + gotoProtocol(this, isPrivacy = true, isImmersive = true) + } + } + + /** + * 获取验证码 + */ + private fun getVerifyCode(phoneNumber: String) { + // TODO: 2020/8/29 这里只是界面演示而已 + XToastUtils.warning("只是演示,验证码请随便输 phoneNumber=$phoneNumber") + mCountDownHelper!!.start() + } + + /** + * 根据验证码登录 + * + * @param phoneNumber 手机号 + * @param verifyCode 验证码 + */ + private fun loginByVerifyCode(phoneNumber: String, verifyCode: String) { + // TODO: 2020/8/29 这里只是界面演示而已 + Log.d("loginByVerifyCode", "phoneNumber=$phoneNumber, verifyCode=$verifyCode") + onLoginSuccess() + } + + /** + * 登录成功的处理 + */ + private fun onLoginSuccess() { + val token = getRandomNumbersAndLetters(16) + if (handleLoginSuccess(token)) { + popToBack() + ActivityUtils.startActivity(MainActivity::class.java) + } + } + + override fun onDestroyView() { + if (mCountDownHelper != null) { + mCountDownHelper!!.recycle() + } + super.onDestroyView() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/fragment/LogsFragment.kt b/app/src/main/java/com/idormy/sms/forwarder/fragment/LogsFragment.kt new file mode 100644 index 00000000..d2f9dd2f --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/fragment/LogsFragment.kt @@ -0,0 +1,139 @@ +package com.idormy.sms.forwarder.fragment + +import android.annotation.SuppressLint +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.viewModels +import androidx.lifecycle.lifecycleScope +import androidx.recyclerview.widget.RecyclerView.RecycledViewPool +import com.alibaba.android.vlayout.VirtualLayoutManager +import com.idormy.sms.forwarder.R +import com.idormy.sms.forwarder.adapter.LogsPagingAdapter +import com.idormy.sms.forwarder.core.BaseFragment +import com.idormy.sms.forwarder.database.entity.LogsAndRuleAndSender +import com.idormy.sms.forwarder.database.entity.Rule +import com.idormy.sms.forwarder.database.viewmodel.BaseViewModelFactory +import com.idormy.sms.forwarder.database.viewmodel.LogsViewModel +import com.idormy.sms.forwarder.databinding.FragmentLogsBinding +import com.idormy.sms.forwarder.utils.EVENT_UPDATE_LOGS_TYPE +import com.idormy.sms.forwarder.utils.FORWARD_STATUS_MAP +import com.idormy.sms.forwarder.utils.SendUtils +import com.idormy.sms.forwarder.utils.XToastUtils +import com.jeremyliao.liveeventbus.LiveEventBus +import com.scwang.smartrefresh.layout.api.RefreshLayout +import com.xuexiang.xpage.annotation.Page +import com.xuexiang.xrouter.utils.TextUtils +import com.xuexiang.xui.utils.ResUtils +import com.xuexiang.xui.widget.actionbar.TitleBar +import com.xuexiang.xui.widget.dialog.materialdialog.DialogAction +import com.xuexiang.xui.widget.dialog.materialdialog.MaterialDialog +import com.xuexiang.xutil.data.DateUtils +import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.launch +import java.text.SimpleDateFormat +import java.util.* + +@Suppress("PropertyName") +@Page(name = "转发日志") +class LogsFragment : BaseFragment(), LogsPagingAdapter.OnItemClickListener { + + val TAG: String = LogsFragment::class.java.simpleName + private var adapter = LogsPagingAdapter(this) + private val viewModel by viewModels { BaseViewModelFactory(context) } + private var currentType: String = "sms" + + override fun viewBindingInflate( + inflater: LayoutInflater, + container: ViewGroup, + ): FragmentLogsBinding { + return FragmentLogsBinding.inflate(inflater, container, false) + } + + /** + * @return 返回为 null意为不需要导航栏 + */ + override fun initTitle(): TitleBar? { + return null + } + + /** + * 初始化控件 + */ + override fun initViews() { + val virtualLayoutManager = VirtualLayoutManager(requireContext()) + binding!!.recyclerView.layoutManager = virtualLayoutManager + val viewPool = RecycledViewPool() + binding!!.recyclerView.setRecycledViewPool(viewPool) + viewPool.setMaxRecycledViews(0, 10) + binding!!.recyclerView.isFocusableInTouchMode = false + + binding!!.tabBar.setTabTitles(ResUtils.getStringArray(R.array.type_param_option)) + binding!!.tabBar.setOnTabClickListener { _, position -> + //XToastUtils.toast("点击了$title--$position") + currentType = when (position) { + 1 -> "call" + 2 -> "app" + else -> "sms" + } + viewModel.setType(currentType) + LiveEventBus.get(EVENT_UPDATE_LOGS_TYPE, String::class.java).post(currentType) + adapter.refresh() + binding!!.recyclerView.scrollToPosition(0) + } + } + + override fun initListeners() { + binding!!.recyclerView.adapter = adapter + + //下拉刷新 + binding!!.refreshLayout.setOnRefreshListener { refreshLayout: RefreshLayout -> + //adapter.refresh() + lifecycleScope.launch { + viewModel.setType(currentType).allLogs.collectLatest { adapter.submitData(it) } + } + refreshLayout.finishRefresh() + } + + binding!!.refreshLayout.autoRefresh() + } + + override fun onItemClicked(view: View?, item: LogsAndRuleAndSender) { + val ruleStr = StringBuilder() + ruleStr.append(Rule.getRuleMatch(item.relation.rule.filed, item.relation.rule.check, item.relation.rule.value, item.relation.rule.simSlot)).append(item.relation.sender.name) + val detailStr = StringBuilder() + detailStr.append(ResUtils.getString(R.string.from)).append(item.logs.from).append("\n\n") + detailStr.append(ResUtils.getString(R.string.msg)).append(item.logs.content).append("\n\n") + if (!TextUtils.isEmpty(item.logs.simInfo)) detailStr.append(ResUtils.getString(R.string.slot)).append(item.logs.simInfo).append("\n\n") + detailStr.append(ResUtils.getString(R.string.rule)).append(ruleStr.toString()).append("\n\n") + @SuppressLint("SimpleDateFormat") val utcFormatter = SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault()) + detailStr.append(ResUtils.getString(R.string.time)).append(DateUtils.date2String(item.logs.time, utcFormatter)).append("\n\n") + detailStr.append(ResUtils.getString(R.string.result)).append(FORWARD_STATUS_MAP[item.logs.forwardStatus]).append("\n--------------------\n").append(item.logs.forwardResponse) + + MaterialDialog.Builder(requireContext()) + .iconRes(item.logs.simImageId) + .title(R.string.details) + .content(detailStr.toString()) + .cancelable(true) + .positiveText(R.string.del) + .onPositive { _: MaterialDialog?, _: DialogAction? -> + viewModel.delete(item.logs.id) + XToastUtils.success(R.string.delete_log_toast) + } + .negativeText(R.string.resend) + .onNegative { _: MaterialDialog?, _: DialogAction? -> + XToastUtils.toast(R.string.resend_toast) + SendUtils.resendMsg(item, false) + } + .neutralText(R.string.rematch) + .neutralColor(ResUtils.getColors(R.color.red)) + .onNeutral { _: MaterialDialog?, _: DialogAction? -> + XToastUtils.toast(R.string.rematch_toast) + SendUtils.resendMsg(item, true) + } + .show() + } + + override fun onItemRemove(view: View?, id: Int) {} + +} \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/fragment/MarkdownFragment.kt b/app/src/main/java/com/idormy/sms/forwarder/fragment/MarkdownFragment.kt new file mode 100644 index 00000000..e13ae093 --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/fragment/MarkdownFragment.kt @@ -0,0 +1,56 @@ +package com.idormy.sms.forwarder.fragment + +import android.view.LayoutInflater +import android.view.ViewGroup +import br.tiagohm.markdownview.css.styles.Github +import com.idormy.sms.forwarder.core.BaseFragment +import com.idormy.sms.forwarder.databinding.FragmentMarkdownBinding +import com.xuexiang.xpage.annotation.Page +import com.xuexiang.xrouter.annotation.AutoWired +import com.xuexiang.xrouter.launcher.XRouter +import com.xuexiang.xui.widget.actionbar.TitleBar + +@Page(name = "Markdown") +class MarkdownFragment : BaseFragment() { + companion object { + const val KEY_MD_TITLE = "key_md_title" + const val KEY_MD_URL = "key_md_url" + const val KEY_IS_IMMERSIVE = "key_is_immersive" + } + + @JvmField + @AutoWired(name = KEY_MD_TITLE) + var title: String? = null + + @JvmField + @AutoWired(name = KEY_MD_URL) + var url: String? = null + + @JvmField + @AutoWired(name = KEY_IS_IMMERSIVE) + var isImmersive = false + + override fun initArgs() { + XRouter.getInstance().inject(this) + } + + override fun viewBindingInflate( + inflater: LayoutInflater, + container: ViewGroup, + ): FragmentMarkdownBinding { + return FragmentMarkdownBinding.inflate(inflater, container, false) + } + + override fun initTitle(): TitleBar? { + return super.initTitle()!!.setTitle(title).setImmersive(isImmersive) + } + + /** + * 初始化控件 + */ + override fun initViews() { + binding!!.markdownView.addStyleSheet(Github()) + binding!!.markdownView.loadMarkdownFromUrl(url) + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/fragment/RulesEditFragment.kt b/app/src/main/java/com/idormy/sms/forwarder/fragment/RulesEditFragment.kt new file mode 100644 index 00000000..093c81a8 --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/fragment/RulesEditFragment.kt @@ -0,0 +1,596 @@ +package com.idormy.sms.forwarder.fragment + +import android.util.Log +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.* +import androidx.fragment.app.viewModels +import com.idormy.sms.forwarder.App +import com.idormy.sms.forwarder.R +import com.idormy.sms.forwarder.adapter.spinner.AppListAdapterItem +import com.idormy.sms.forwarder.adapter.spinner.AppListSpinnerAdapter +import com.idormy.sms.forwarder.adapter.spinner.SenderAdapterItem +import com.idormy.sms.forwarder.adapter.spinner.SenderSpinnerAdapter +import com.idormy.sms.forwarder.core.BaseFragment +import com.idormy.sms.forwarder.database.AppDatabase +import com.idormy.sms.forwarder.database.entity.Rule +import com.idormy.sms.forwarder.database.entity.Sender +import com.idormy.sms.forwarder.database.viewmodel.BaseViewModelFactory +import com.idormy.sms.forwarder.database.viewmodel.RuleViewModel +import com.idormy.sms.forwarder.databinding.FragmentRulesEditBinding +import com.idormy.sms.forwarder.entity.MsgInfo +import com.idormy.sms.forwarder.utils.* +import com.xuexiang.xaop.annotation.SingleClick +import com.xuexiang.xpage.annotation.Page +import com.xuexiang.xrouter.annotation.AutoWired +import com.xuexiang.xrouter.launcher.XRouter +import com.xuexiang.xrouter.utils.TextUtils +import com.xuexiang.xui.utils.ResUtils +import com.xuexiang.xui.widget.actionbar.TitleBar +import com.xuexiang.xui.widget.dialog.materialdialog.DialogAction +import com.xuexiang.xui.widget.dialog.materialdialog.MaterialDialog +import com.xuexiang.xutil.app.AppUtils +import io.reactivex.SingleObserver +import io.reactivex.android.schedulers.AndroidSchedulers +import io.reactivex.disposables.Disposable +import io.reactivex.schedulers.Schedulers +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.async +import kotlinx.coroutines.launch +import java.util.* + +@Page(name = "转发规则·编辑器") +@Suppress("PrivatePropertyName") +class RulesEditFragment : BaseFragment(), View.OnClickListener, CompoundButton.OnCheckedChangeListener { + + private val TAG: String = RulesEditFragment::class.java.simpleName + var titleBar: TitleBar? = null + private val viewModel by viewModels { BaseViewModelFactory(context) } + + //当前发送通道 + var senderId = 0L + + //发送通道列表 + private val senderSpinnerList = ArrayList() + + //已安装App信息列表 + private val appListSpinnerList = ArrayList() + + @JvmField + @AutoWired(name = KEY_RULE_ID) + var ruleId: Long = 0 + + @JvmField + @AutoWired(name = KEY_RULE_TYPE) + var ruleType: String = "sms" + + @JvmField + @AutoWired(name = KEY_RULE_CLONE) + var isClone: Boolean = false + + override fun initArgs() { + XRouter.getInstance().inject(this) + } + + override fun viewBindingInflate( + inflater: LayoutInflater, + container: ViewGroup, + ): FragmentRulesEditBinding { + return FragmentRulesEditBinding.inflate(inflater, container, false) + } + + override fun initTitle(): TitleBar? { + titleBar = super.initTitle()!!.setImmersive(false) + titleBar!!.setTitle(R.string.menu_rules) + return titleBar + } + + /** + * 初始化控件 + */ + override fun initViews() { + when (ruleType) { + "app" -> { + titleBar?.setTitle(R.string.app_rule) + binding!!.layoutSimSlot.visibility = View.GONE + binding!!.rbPhone.visibility = View.GONE + binding!!.rbContent.visibility = View.GONE + binding!!.tvMuRuleTips.setText(R.string.mu_rule_app_tips) + binding!!.btInsertSender.visibility = View.GONE + binding!!.btInsertContent.visibility = View.GONE + } + "call" -> { + titleBar?.setTitle(R.string.call_rule) + binding!!.rbContent.visibility = View.GONE + binding!!.rbPackageName.visibility = View.GONE + binding!!.rbInformContent.visibility = View.GONE + binding!!.rbMultiMatch.visibility = View.GONE + binding!!.btInsertContent.visibility = View.GONE + binding!!.btInsertSenderApp.visibility = View.GONE + binding!!.btInsertContentApp.visibility = View.GONE + } + else -> { + titleBar?.setTitle(R.string.sms_rule) + binding!!.rbPackageName.visibility = View.GONE + binding!!.rbInformContent.visibility = View.GONE + binding!!.btInsertSenderApp.visibility = View.GONE + binding!!.btInsertContentApp.visibility = View.GONE + } + } + + if (ruleId <= 0) { //新增 + titleBar?.setSubTitle(getString(R.string.add_rule)) + binding!!.btnDel.setText(R.string.discard) + initSenderSpinner() + } else { //编辑 & 克隆 + binding!!.btnDel.setText(R.string.del) + initForm() + } + + //初始化APP下拉列表 + initAppSpinner() + } + + override fun initListeners() { + binding!!.btInsertSender.setOnClickListener(this) + binding!!.btInsertContent.setOnClickListener(this) + binding!!.btInsertSenderApp.setOnClickListener(this) + binding!!.btInsertContentApp.setOnClickListener(this) + binding!!.btInsertExtra.setOnClickListener(this) + binding!!.btInsertTime.setOnClickListener(this) + binding!!.btInsertDeviceName.setOnClickListener(this) + binding!!.btnTest.setOnClickListener(this) + binding!!.btnDel.setOnClickListener(this) + binding!!.btnSave.setOnClickListener(this) + + binding!!.sbSmsTemplate.setOnCheckedChangeListener(this) + binding!!.sbRegexReplace.setOnCheckedChangeListener(this) + + binding!!.rgFiled.setOnCheckedChangeListener { _: RadioGroup?, checkedId: Int -> + if (ruleType == "app" && appListSpinnerList.isNotEmpty()) { + binding!!.layoutAppList.visibility = if (checkedId == R.id.rb_inform_content) View.GONE else View.VISIBLE + } + when (checkedId) { + R.id.rb_transpond_all -> { + binding!!.rgCheck.check(R.id.rb_is) + binding!!.tvMuRuleTips.visibility = View.GONE + binding!!.layoutMatchType.visibility = View.GONE + binding!!.layoutMatchValue.visibility = View.GONE + } + R.id.rb_multi_match -> { + binding!!.rgCheck.check(R.id.rb_is) + binding!!.tvMuRuleTips.visibility = View.VISIBLE + binding!!.layoutMatchType.visibility = View.GONE + binding!!.layoutMatchValue.visibility = View.VISIBLE + } + else -> { + binding!!.tvMuRuleTips.visibility = View.GONE + binding!!.layoutMatchType.visibility = View.VISIBLE + binding!!.layoutMatchValue.visibility = View.VISIBLE + } + } + } + + binding!!.rgCheck.setOnCheckedChangeListener { group: RadioGroup?, checkedId: Int -> + if (group != null && checkedId > 0) { + binding!!.rgCheck2.clearCheck() + group.check(checkedId) + } + } + + binding!!.rgCheck2.setOnCheckedChangeListener { group: RadioGroup?, checkedId: Int -> + if (group != null && checkedId > 0) { + binding!!.rgCheck.clearCheck() + group.check(checkedId) + } + } + } + + override fun onCheckedChanged(buttonView: CompoundButton?, isChecked: Boolean) { + val visibility = if (isChecked) View.VISIBLE else View.GONE + when (buttonView?.id) { + R.id.sb_sms_template -> binding!!.layoutSmsTemplate.visibility = visibility + R.id.sb_regex_replace -> binding!!.layoutRegexReplace.visibility = visibility + else -> {} + } + } + + @SingleClick + override fun onClick(v: View) { + try { + val etSmsTemplate: EditText = binding!!.etSmsTemplate + when (v.id) { + R.id.bt_insert_sender -> { + CommonUtils.insertOrReplaceText2Cursor(etSmsTemplate, getString(R.string.tag_from)) + return + } + R.id.bt_insert_content -> { + CommonUtils.insertOrReplaceText2Cursor(etSmsTemplate, getString(R.string.tag_sms)) + return + } + R.id.bt_insert_sender_app -> { + CommonUtils.insertOrReplaceText2Cursor(etSmsTemplate, getString(R.string.tag_package_name)) + return + } + R.id.bt_insert_content_app -> { + CommonUtils.insertOrReplaceText2Cursor(etSmsTemplate, getString(R.string.tag_msg)) + return + } + R.id.bt_insert_extra -> { + CommonUtils.insertOrReplaceText2Cursor(etSmsTemplate, getString(R.string.tag_card_slot)) + return + } + R.id.bt_insert_time -> { + CommonUtils.insertOrReplaceText2Cursor(etSmsTemplate, getString(R.string.tag_receive_time)) + return + } + R.id.bt_insert_device_name -> { + CommonUtils.insertOrReplaceText2Cursor(etSmsTemplate, getString(R.string.tag_device_name)) + return + } + R.id.btn_test -> { + val ruleNew = checkForm() + testRule(ruleNew) + return + } + R.id.btn_del -> { + if (ruleId <= 0 || isClone) { + popToBack() + return + } + + MaterialDialog.Builder(requireContext()) + .title(R.string.delete_rule_title) + .content(R.string.delete_rule_tips) + .positiveText(R.string.lab_yes) + .negativeText(R.string.lab_no) + .onPositive { _: MaterialDialog?, _: DialogAction? -> + viewModel.delete(ruleId) + XToastUtils.success(R.string.delete_rule_toast) + popToBack() + } + .show() + return + } + R.id.btn_save -> { + val ruleNew = checkForm() + if (isClone) ruleNew.id = 0 + Log.d(TAG, ruleNew.toString()) + viewModel.insertOrUpdate(ruleNew) + XToastUtils.success(R.string.tipSaveSuccess) + popToBack() + return + } + } + } catch (e: Exception) { + XToastUtils.error(e.message.toString()) + e.printStackTrace() + } + } + + //初始化发送通道下拉框 + private fun initSenderSpinner() { + AppDatabase.getInstance(requireContext()) + .senderDao() + .getAll() + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(object : SingleObserver> { + override fun onSubscribe(d: Disposable) {} + + override fun onError(e: Throwable) { + e.printStackTrace() + } + + override fun onSuccess(senderList: List) { + if (senderList.isEmpty()) { + XToastUtils.error(R.string.add_sender_first) + return + } + + for (sender in senderList) { + senderSpinnerList.add(SenderAdapterItem(sender.name, sender.imageId, sender.id, sender.status)) + } + binding!!.spSender.setAdapter( + SenderSpinnerAdapter(senderSpinnerList) + //.setTextColor(ResUtils.getColor(R.color.green)) + //.setTextSize(12F) + .setIsFilterKey(true) + .setFilterColor("#FFFF00") + .setBackgroundSelector(R.drawable.selector_custom_spinner_bg) + ) + + if (senderId > 0) { + for (sender in senderSpinnerList) { + if (sender.id == senderId) { + binding!!.ivSenderImage.setImageDrawable(sender.icon) + binding!!.ivSenderStatus.setImageDrawable( + ResUtils.getDrawable( + when (sender.status) { + STATUS_OFF -> R.drawable.icon_off + else -> R.drawable.icon_on + } + ) + ) + binding!!.tvSenderName.text = sender.title + } + } + } + } + }) + binding!!.spSender.setOnItemClickListener { _: AdapterView<*>, _: View, position: Int, _: Long -> + try { + val sender = senderSpinnerList[position] + sender.id.also { + senderId = it ?: 0L + } + XToastUtils.toast(sender.title) + binding!!.ivSenderImage.setImageDrawable(sender.icon) + binding!!.ivSenderStatus.setImageDrawable( + ResUtils.getDrawable( + when (sender.status) { + STATUS_OFF -> R.drawable.icon_off + else -> R.drawable.icon_on + } + ) + ) + binding!!.tvSenderName.text = sender.title + if (STATUS_OFF == sender.status) { + XToastUtils.warning("【注意】该发送通道已经禁用,其关联的规则即便匹配上也不会发送!") + } + } catch (e: Exception) { + XToastUtils.error(e.message.toString()) + } + } + } + + //初始化APP下拉列表 + private fun initAppSpinner() { + if (ruleType != "app") return + + val get = GlobalScope.async(Dispatchers.IO) { + App.AppInfoList = AppUtils.getAppsInfo() + } + GlobalScope.launch(Dispatchers.Main) { + runCatching { + get.await() + if (App.AppInfoList.isEmpty()) return@runCatching + + Log.e(TAG, App.AppInfoList.toString()) + for (appInfo in App.AppInfoList) { + appListSpinnerList.add(AppListAdapterItem(appInfo.name, appInfo.icon, appInfo.packageName)) + } + binding!!.spApp.setAdapter( + AppListSpinnerAdapter(appListSpinnerList) + //.setTextColor(ResUtils.getColor(R.color.green)) + //.setTextSize(12F) + .setIsFilterKey(true) + .setFilterColor("#FFFF00") + .setBackgroundSelector(R.drawable.selector_custom_spinner_bg) + ) + binding!!.spApp.setOnItemClickListener { _: AdapterView<*>, _: View, position: Int, _: Long -> + try { + val appInfo = appListSpinnerList[position] + CommonUtils.insertOrReplaceText2Cursor(binding!!.etValue, appInfo.packageName.toString()) + } catch (e: Exception) { + XToastUtils.error(e.message.toString()) + } + } + binding!!.layoutAppList.visibility = View.VISIBLE + }.onFailure { + Log.e("GlobalScope", it.message.toString()) + } + } + } + + //初始化表单 + private fun initForm() { + AppDatabase.getInstance(requireContext()) + .ruleDao() + .get(ruleId) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(object : SingleObserver { + override fun onSubscribe(d: Disposable) {} + + override fun onError(e: Throwable) { + e.printStackTrace() + } + + override fun onSuccess(rule: Rule) { + senderId = rule.senderId + + if (isClone) { + titleBar?.setSubTitle(getString(R.string.clone_rule)) + binding!!.btnDel.setText(R.string.discard) + } else { + titleBar?.setSubTitle(getString(R.string.edit_rule)) + } + Log.d(TAG, rule.toString()) + + binding!!.rgSimSlot.check(rule.getSimSlotCheckId()) + binding!!.rgFiled.check(rule.getFiledCheckId()) + val checkId = rule.getCheckCheckId() + if (checkId == R.id.rb_is || checkId == R.id.rb_contain || checkId == R.id.rb_not_contain) { + binding!!.rgCheck.check(checkId) + } else { + binding!!.rgCheck2.check(checkId) + } + binding!!.etValue.setText(rule.value) + binding!!.sbSmsTemplate.isChecked = !TextUtils.isEmpty(rule.smsTemplate.trim()) + binding!!.etSmsTemplate.setText(rule.smsTemplate.trim()) + binding!!.sbRegexReplace.isChecked = !TextUtils.isEmpty(rule.regexReplace.trim()) + binding!!.etRegexReplace.setText(rule.regexReplace.trim()) + binding!!.sbStatus.isChecked = rule.statusChecked + + //初始化发送通道下拉框 + initSenderSpinner() + } + }) + } + + //提交前检查表单 + private fun checkForm(): Rule { + if (senderId <= 0L) { + throw Exception(getString(R.string.new_sender_first)) + } + val filed = when (binding!!.rgFiled.checkedRadioButtonId) { + R.id.rb_content -> FILED_MSG_CONTENT + R.id.rb_phone -> FILED_PHONE_NUM + R.id.rb_package_name -> FILED_PACKAGE_NAME + R.id.rb_inform_content -> FILED_INFORM_CONTENT + R.id.rb_multi_match -> FILED_MULTI_MATCH + else -> FILED_TRANSPOND_ALL + } + val check = when (kotlin.math.max(binding!!.rgCheck.checkedRadioButtonId, binding!!.rgCheck2.checkedRadioButtonId)) { + R.id.rb_contain -> CHECK_CONTAIN + R.id.rb_not_contain -> CHECK_NOT_CONTAIN + R.id.rb_start_with -> CHECK_START_WITH + R.id.rb_end_with -> CHECK_END_WITH + R.id.rb_regex -> CHECK_REGEX + else -> CHECK_IS + } + val value = binding!!.etValue.text.toString().trim() + if (FILED_TRANSPOND_ALL != filed && TextUtils.isEmpty(value)) { + throw Exception(getString(R.string.invalid_match_value)) + } + if (FILED_MULTI_MATCH == filed) { + val lineError = checkMultiMatch(value) + if (lineError > 0) { + throw Exception(String.format(getString(R.string.invalid_multi_match), lineError)) + } + } + + val smsTemplate = binding!!.etSmsTemplate.text.toString().trim() + val regexReplace = binding!!.etRegexReplace.text.toString().trim() + val lineNum = checkRegexReplace(regexReplace) + if (lineNum > 0) { + throw Exception(String.format(getString(R.string.invalid_regex_replace), lineNum)) + } + + val simSlot = when (binding!!.rgSimSlot.checkedRadioButtonId) { + R.id.rb_sim_slot_1 -> CHECK_SIM_SLOT_1 + R.id.rb_sim_slot_2 -> CHECK_SIM_SLOT_2 + else -> CHECK_SIM_SLOT_ALL + } + val status = if (binding!!.sbStatus.isChecked) STATUS_ON else STATUS_OFF + if (status == STATUS_OFF) { + throw Exception(getString(R.string.invalid_rule_status)) + } + + return Rule(ruleId, ruleType, filed, check, value, senderId, smsTemplate, regexReplace, simSlot, status) + } + + //检查多重匹配规则是否正确 + private fun checkMultiMatch(ruleStr: String?): Int { + if (TextUtils.isEmpty(ruleStr)) return 0 + + Log.d(TAG, getString(R.string.regex_multi_match)) + val regex = Regex(pattern = getString(R.string.regex_multi_match)) + var lineNum = 1 + val lineArray = ruleStr?.split("\\n".toRegex())?.toTypedArray() + for (line in lineArray!!) { + Log.d(TAG, line) + if (!line.matches(regex)) return lineNum + lineNum++ + } + + return 0 + } + + //检查正则替换填写是否正确 + private fun checkRegexReplace(regexReplace: String?): Int { + if (TextUtils.isEmpty(regexReplace)) return 0 + + var lineNum = 1 + val lineArray = regexReplace?.split("\\n".toRegex())?.toTypedArray() + for (line in lineArray!!) { + val position = line.indexOf("===") + if (position < 1) return lineNum + lineNum++ + } + return 0 + } + + private fun testRule(rule: Rule) { + val dialogTest = View.inflate(requireContext(), R.layout.dialog_rule_test, null) + val tvSimSlot = dialogTest.findViewById(R.id.tv_sim_slot) + val rgSimSlot = dialogTest.findViewById(R.id.rg_sim_slot) + val tvFrom = dialogTest.findViewById(R.id.tv_from) + val etFrom = dialogTest.findViewById(R.id.et_from) + val tvContent = dialogTest.findViewById(R.id.tv_content) + val etContent = dialogTest.findViewById(R.id.et_content) + + if ("app" == ruleType) { + tvSimSlot.visibility = View.GONE + rgSimSlot.visibility = View.GONE + tvFrom.setText(R.string.test_package_name) + tvContent.setText(R.string.test_inform_content) + } else if ("call" == ruleType) { + tvContent.visibility = View.GONE + etContent.visibility = View.GONE + } + + MaterialDialog.Builder(requireContext()) + .iconRes(android.R.drawable.ic_dialog_email) + .title(R.string.rule_tester) + .customView(dialogTest, true) + .cancelable(false) + .autoDismiss(false) + .neutralText(R.string.action_back) + .neutralColor(ResUtils.getColors(R.color.darkGrey)) + .onNeutral { dialog: MaterialDialog?, _: DialogAction? -> + dialog?.dismiss() + } + .positiveText(R.string.action_test) + .onPositive { _: MaterialDialog?, _: DialogAction? -> + try { + val simSlot = when (rgSimSlot.checkedRadioButtonId) { + R.id.rb_sim_slot_1 -> 0 + R.id.rb_sim_slot_2 -> 1 + else -> -1 + } + + val testSim = "SIM" + (simSlot + 1) + val ruleSim: String = rule.simSlot + if (ruleSim != "ALL" && ruleSim != testSim) { + throw java.lang.Exception("卡槽未匹配中规则") + } + + //获取卡槽信息 + val simInfo = when (simSlot) { + 0 -> "SIM1_" + SettingUtils.extraSim1 + 1 -> "SIM2_" + SettingUtils.extraSim2 + else -> "" + } + + val msgInfo = MsgInfo(ruleType, etFrom.text.toString(), etContent.text.toString(), Date(), simInfo, simSlot) + if (!rule.checkMsg(msgInfo)) { + throw java.lang.Exception("未匹配中规则") + } + + AppDatabase.getInstance(requireContext()) + .senderDao() + .get(senderId) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(object : SingleObserver { + override fun onSubscribe(d: Disposable) {} + + override fun onError(e: Throwable) { + e.printStackTrace() + } + + override fun onSuccess(sender: Sender) { + SendUtils.sendMsgSender(msgInfo, rule, sender, 0L) + } + }) + + } catch (e: Exception) { + XToastUtils.error(e.message.toString()) + } + }.show() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/fragment/RulesFragment.kt b/app/src/main/java/com/idormy/sms/forwarder/fragment/RulesFragment.kt new file mode 100644 index 00000000..bf353e0c --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/fragment/RulesFragment.kt @@ -0,0 +1,131 @@ +package com.idormy.sms.forwarder.fragment + +import android.util.Log +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.viewModels +import androidx.lifecycle.lifecycleScope +import androidx.recyclerview.widget.RecyclerView.RecycledViewPool +import com.alibaba.android.vlayout.VirtualLayoutManager +import com.idormy.sms.forwarder.R +import com.idormy.sms.forwarder.adapter.RulePagingAdapter +import com.idormy.sms.forwarder.core.BaseFragment +import com.idormy.sms.forwarder.database.entity.RuleAndSender +import com.idormy.sms.forwarder.database.viewmodel.BaseViewModelFactory +import com.idormy.sms.forwarder.database.viewmodel.RuleViewModel +import com.idormy.sms.forwarder.databinding.FragmentRulesBinding +import com.idormy.sms.forwarder.utils.* +import com.jeremyliao.liveeventbus.LiveEventBus +import com.scwang.smartrefresh.layout.api.RefreshLayout +import com.xuexiang.xpage.annotation.Page +import com.xuexiang.xpage.core.PageOption +import com.xuexiang.xui.utils.ResUtils +import com.xuexiang.xui.widget.actionbar.TitleBar +import com.xuexiang.xui.widget.dialog.materialdialog.DialogAction +import com.xuexiang.xui.widget.dialog.materialdialog.MaterialDialog +import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.launch + +@Suppress("PropertyName") +@Page(name = "转发规则") +class RulesFragment : BaseFragment(), RulePagingAdapter.OnItemClickListener { + + val TAG: String = RulesFragment::class.java.simpleName + private var adapter = RulePagingAdapter(this) + private val viewModel by viewModels { BaseViewModelFactory(context) } + private var currentType: String = "sms" + + override fun viewBindingInflate( + inflater: LayoutInflater, + container: ViewGroup, + ): FragmentRulesBinding { + return FragmentRulesBinding.inflate(inflater, container, false) + } + + /** + * @return 返回为 null意为不需要导航栏 + */ + override fun initTitle(): TitleBar? { + return null + } + + /** + * 初始化控件 + */ + override fun initViews() { + val virtualLayoutManager = VirtualLayoutManager(requireContext()) + binding!!.recyclerView.layoutManager = virtualLayoutManager + val viewPool = RecycledViewPool() + binding!!.recyclerView.setRecycledViewPool(viewPool) + viewPool.setMaxRecycledViews(0, 10) + + binding!!.tabBar.setTabTitles(ResUtils.getStringArray(R.array.type_param_option)) + binding!!.tabBar.setOnTabClickListener { _, position -> + //XToastUtils.toast("点击了$title--$position") + currentType = when (position) { + 1 -> "call" + 2 -> "app" + else -> "sms" + } + viewModel.setType(currentType) + LiveEventBus.get(EVENT_UPDATE_RULE_TYPE, String::class.java).post(currentType) + adapter.refresh() + binding!!.recyclerView.scrollToPosition(0) + } + } + + override fun initListeners() { + binding!!.recyclerView.adapter = adapter + + //下拉刷新 + binding!!.refreshLayout.setOnRefreshListener { refreshLayout: RefreshLayout -> + refreshLayout.layout.postDelayed({ + //adapter!!.refresh() + lifecycleScope.launch { + viewModel.setType(currentType).allRules.collectLatest { adapter.submitData(it) } + } + refreshLayout.finishRefresh() + }, 200) + } + + binding!!.refreshLayout.autoRefresh() + } + + override fun onItemClicked(view: View?, item: RuleAndSender) { + Log.e(TAG, item.toString()) + when (view?.id) { + R.id.iv_copy -> { + PageOption.to(RulesEditFragment::class.java) + .setNewActivity(true) + .putLong(KEY_RULE_ID, item.rule.id) + .putString(KEY_RULE_TYPE, item.rule.type) + .putBoolean(KEY_RULE_CLONE, true) + .open(this) + } + R.id.iv_edit -> { + PageOption.to(RulesEditFragment::class.java) + .setNewActivity(true) + .putLong(KEY_RULE_ID, item.rule.id) + .putString(KEY_RULE_TYPE, item.rule.type) + .open(this) + } + R.id.iv_delete -> { + MaterialDialog.Builder(requireContext()) + .title(R.string.delete_rule_title) + .content(R.string.delete_rule_tips) + .positiveText(R.string.lab_yes) + .negativeText(R.string.lab_no) + .onPositive { _: MaterialDialog?, _: DialogAction? -> + viewModel.delete(item.rule.id) + XToastUtils.success(R.string.delete_rule_toast) + } + .show() + } + else -> {} + } + } + + override fun onItemRemove(view: View?, id: Int) {} + +} \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/fragment/SendersFragment.kt b/app/src/main/java/com/idormy/sms/forwarder/fragment/SendersFragment.kt new file mode 100644 index 00000000..2aa661af --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/fragment/SendersFragment.kt @@ -0,0 +1,158 @@ +package com.idormy.sms.forwarder.fragment + +import android.util.Log +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.viewModels +import androidx.lifecycle.lifecycleScope +import androidx.recyclerview.widget.RecyclerView.RecycledViewPool +import com.alibaba.android.vlayout.VirtualLayoutManager +import com.idormy.sms.forwarder.R +import com.idormy.sms.forwarder.adapter.SenderPagingAdapter +import com.idormy.sms.forwarder.core.BaseFragment +import com.idormy.sms.forwarder.database.entity.Sender +import com.idormy.sms.forwarder.database.viewmodel.BaseViewModelFactory +import com.idormy.sms.forwarder.database.viewmodel.SenderViewModel +import com.idormy.sms.forwarder.databinding.FragmentSendersBinding +import com.idormy.sms.forwarder.fragment.senders.* +import com.idormy.sms.forwarder.utils.* +import com.scwang.smartrefresh.layout.api.RefreshLayout +import com.xuexiang.xpage.annotation.Page +import com.xuexiang.xpage.core.PageOption +import com.xuexiang.xui.utils.ResUtils +import com.xuexiang.xui.widget.actionbar.TitleBar +import com.xuexiang.xui.widget.dialog.materialdialog.DialogAction +import com.xuexiang.xui.widget.dialog.materialdialog.MaterialDialog +import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.launch + +@Suppress("PropertyName") +@Page(name = "发送通道") +class SendersFragment : BaseFragment(), SenderPagingAdapter.OnItemClickListener { + + val TAG: String = SendersFragment::class.java.simpleName + private var adapter = SenderPagingAdapter(this) + private val viewModel by viewModels { BaseViewModelFactory(context) } + private var currentStatus: Int = 1 + //private val statusValueArray = ResUtils.getIntArray(R.array.status_param_value) + + override fun viewBindingInflate( + inflater: LayoutInflater, + container: ViewGroup, + ): FragmentSendersBinding { + return FragmentSendersBinding.inflate(inflater, container, false) + } + + /** + * @return 返回为 null意为不需要导航栏 + */ + override fun initTitle(): TitleBar? { + return null + } + + /** + * 初始化控件 + */ + override fun initViews() { + val virtualLayoutManager = VirtualLayoutManager(requireContext()) + binding!!.recyclerView.layoutManager = virtualLayoutManager + val viewPool = RecycledViewPool() + binding!!.recyclerView.setRecycledViewPool(viewPool) + viewPool.setMaxRecycledViews(0, 10) + + binding!!.tabBar.setTabTitles(ResUtils.getStringArray(R.array.status_param_option)) + binding!!.tabBar.setOnTabClickListener { _, position -> + //XToastUtils.toast("点击了$title--$position") + //currentStatus = statusValueArray[position] + currentStatus = 1 - position //TODO:取巧,这里刚好相反 + viewModel.setStatus(currentStatus) + adapter.refresh() + binding!!.recyclerView.scrollToPosition(0) + } + } + + override fun initListeners() { + binding!!.recyclerView.adapter = adapter + + //下拉刷新 + binding!!.refreshLayout.setOnRefreshListener { refreshLayout: RefreshLayout -> + refreshLayout.layout.postDelayed({ + //adapter!!.refresh() + lifecycleScope.launch { + viewModel.setStatus(currentStatus).allSenders.collectLatest { adapter.submitData(it) } + } + refreshLayout.finishRefresh() + }, 200) + } + + binding!!.refreshLayout.autoRefresh() + } + + override fun onItemClicked(view: View?, item: Sender) { + Log.e(TAG, item.toString()) + when (view?.id) { + R.id.iv_copy -> { + PageOption.to( + when (item.type) { + TYPE_DINGDING -> DingtalkFragment::class.java + TYPE_EMAIL -> EmailFragment::class.java + TYPE_BARK -> BarkFragment::class.java + TYPE_WEBHOOK -> WebhookFragment::class.java + TYPE_WEWORK_ROBOT -> WeworkRobotFragment::class.java + TYPE_WEWORK_AGENT -> WeworkAgentFragment::class.java + TYPE_SERVERCHAN -> ServerchanFragment::class.java + TYPE_TELEGRAM -> TelegramFragment::class.java + TYPE_SMS -> SmsFragment::class.java + TYPE_FEISHU -> FeishuFragment::class.java + TYPE_PUSHPLUS -> PushplusFragment::class.java + TYPE_GOTIFY -> GotifyFragment::class.java + else -> DingtalkFragment::class.java + } + ).setNewActivity(true) + .putLong(KEY_SENDER_ID, item.id) + .putInt(KEY_SENDER_TYPE, item.type) + .putBoolean(KEY_SENDER_CLONE, true) + .open(this) + } + R.id.iv_edit -> { + PageOption.to( + when (item.type) { + TYPE_DINGDING -> DingtalkFragment::class.java + TYPE_EMAIL -> EmailFragment::class.java + TYPE_BARK -> BarkFragment::class.java + TYPE_WEBHOOK -> WebhookFragment::class.java + TYPE_WEWORK_ROBOT -> WeworkRobotFragment::class.java + TYPE_WEWORK_AGENT -> WeworkAgentFragment::class.java + TYPE_SERVERCHAN -> ServerchanFragment::class.java + TYPE_TELEGRAM -> TelegramFragment::class.java + TYPE_SMS -> SmsFragment::class.java + TYPE_FEISHU -> FeishuFragment::class.java + TYPE_PUSHPLUS -> PushplusFragment::class.java + TYPE_GOTIFY -> GotifyFragment::class.java + else -> DingtalkFragment::class.java + } + ).setNewActivity(true) + .putLong(KEY_SENDER_ID, item.id) + .putInt(KEY_SENDER_TYPE, item.type) + .open(this) + } + R.id.iv_delete -> { + MaterialDialog.Builder(requireContext()) + .title(R.string.delete_sender_title) + .content(R.string.delete_sender_tips) + .positiveText(R.string.lab_yes) + .negativeText(R.string.lab_no) + .onPositive { _: MaterialDialog?, _: DialogAction? -> + viewModel.delete(item.id) + XToastUtils.success(R.string.delete_sender_toast) + } + .show() + } + else -> {} + } + } + + override fun onItemRemove(view: View?, id: Int) {} + +} \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/fragment/ServerFragment.kt b/app/src/main/java/com/idormy/sms/forwarder/fragment/ServerFragment.kt new file mode 100644 index 00000000..993f421d --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/fragment/ServerFragment.kt @@ -0,0 +1,264 @@ +package com.idormy.sms.forwarder.fragment + +import android.content.Intent +import android.text.Editable +import android.text.TextWatcher +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.CompoundButton +import com.hjq.permissions.OnPermissionCallback +import com.hjq.permissions.Permission +import com.hjq.permissions.XXPermissions +import com.idormy.sms.forwarder.App +import com.idormy.sms.forwarder.R +import com.idormy.sms.forwarder.core.BaseFragment +import com.idormy.sms.forwarder.databinding.FragmentServerBinding +import com.idormy.sms.forwarder.service.HttpService +import com.idormy.sms.forwarder.utils.HTTP_SERVER_PORT +import com.idormy.sms.forwarder.utils.HttpServerUtils +import com.idormy.sms.forwarder.utils.RandomUtils +import com.idormy.sms.forwarder.utils.XToastUtils +import com.xuexiang.xaop.annotation.SingleClick +import com.xuexiang.xpage.annotation.Page +import com.xuexiang.xui.widget.actionbar.TitleBar +import com.xuexiang.xui.widget.button.SmoothCheckBox +import com.xuexiang.xutil.app.ServiceUtils +import com.xuexiang.xutil.net.NetworkUtils +import com.xuexiang.xutil.system.ClipboardUtils +import java.net.InetAddress + +@Suppress("PrivatePropertyName") +@Page(name = "主动控制·服务端") +class ServerFragment : BaseFragment(), View.OnClickListener { + + private var appContext: App? = null + private var inetAddress: InetAddress? = null + + override fun initViews() { + appContext = requireActivity().application as App + } + + override fun initTitle(): TitleBar? { + val titleBar = super.initTitle()!!.setImmersive(false) + titleBar.setTitle(R.string.menu_server) + return titleBar + } + + override fun viewBindingInflate( + inflater: LayoutInflater, + container: ViewGroup, + ): FragmentServerBinding { + return FragmentServerBinding.inflate(inflater, container, false) + } + + override fun initListeners() { + binding!!.tvServerTips.setOnClickListener(this) + binding!!.toggleServerBtn.setOnClickListener(this) + //Refresh Button every sec + Thread { + try { + while (true) { + Thread.sleep(1000) + requireActivity().runOnUiThread { refreshButtonText() } + } + } catch (ex: Exception) { + ex.printStackTrace() + } + }.start() + + binding!!.scbServerAutorun.isChecked = HttpServerUtils.enableServerAutorun + binding!!.scbServerAutorun.setOnCheckedChangeListener { _: SmoothCheckBox, isChecked: Boolean -> + HttpServerUtils.enableServerAutorun = isChecked + } + + binding!!.btnSignKey.setOnClickListener(this) + binding!!.etSignKey.setText(HttpServerUtils.serverSignKey) + binding!!.etSignKey.addTextChangedListener(object : TextWatcher { + override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {} + override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {} + override fun afterTextChanged(s: Editable) { + HttpServerUtils.serverSignKey = binding!!.etSignKey.text.toString().trim() + } + }) + + binding!!.sbApiClone.isChecked = HttpServerUtils.enableApiClone + binding!!.sbApiClone.setOnCheckedChangeListener { _: CompoundButton?, isChecked: Boolean -> + HttpServerUtils.enableApiClone = isChecked + } + + binding!!.sbApiSendSms.isChecked = HttpServerUtils.enableApiSmsSend + binding!!.sbApiSendSms.setOnCheckedChangeListener { _: CompoundButton?, isChecked: Boolean -> + HttpServerUtils.enableApiSmsSend = isChecked + if (isChecked) checkSendSmsPermission() + } + + binding!!.sbApiQuerySms.isChecked = HttpServerUtils.enableApiSmsQuery + binding!!.sbApiQuerySms.setOnCheckedChangeListener { _: CompoundButton?, isChecked: Boolean -> + HttpServerUtils.enableApiSmsQuery = isChecked + if (isChecked) checkReadSmsPermission() + } + + binding!!.sbApiQueryCall.isChecked = HttpServerUtils.enableApiCallQuery + binding!!.sbApiQueryCall.setOnCheckedChangeListener { _: CompoundButton?, isChecked: Boolean -> + HttpServerUtils.enableApiCallQuery = isChecked + if (isChecked) checkCallPermission() + } + + binding!!.sbApiQueryContacts.isChecked = HttpServerUtils.enableApiContactQuery + binding!!.sbApiQueryContacts.setOnCheckedChangeListener { _: CompoundButton?, isChecked: Boolean -> + HttpServerUtils.enableApiContactQuery = isChecked + if (isChecked) checkContactsPermission() + } + + binding!!.sbApiQueryBattery.isChecked = HttpServerUtils.enableApiBatteryQuery + binding!!.sbApiQueryBattery.setOnCheckedChangeListener { _: CompoundButton?, isChecked: Boolean -> + HttpServerUtils.enableApiBatteryQuery = isChecked + } + + } + + @SingleClick + override fun onClick(v: View) { + when (v.id) { + R.id.toggle_server_btn -> { + //检查权限是否获取 + checkSendSmsPermission() + checkReadSmsPermission() + checkCallPermission() + checkContactsPermission() + if (ServiceUtils.isServiceRunning("com.idormy.sms.forwarder.service.HttpService")) { + appContext?.stopService(Intent(appContext, HttpService::class.java)) + } else { + appContext?.startService(Intent(appContext, HttpService::class.java)) + } + refreshButtonText() + } + R.id.btn_sign_key -> { + val sign = RandomUtils.getRandomNumbersAndLetters(16) + ClipboardUtils.copyText(sign) + binding!!.etSignKey.setText(sign) + XToastUtils.info(getString(R.string.sign_key_tips)) + } + R.id.tv_server_tips -> { + if (inetAddress != null) { + val url = "http://${inetAddress!!.hostAddress}:5000" + ClipboardUtils.copyText(url) + XToastUtils.info("已复制到剪贴板:$url") + } + } + else -> {} + } + } + + //刷新按钮 + private fun refreshButtonText() { + if (ServiceUtils.isServiceRunning("com.idormy.sms.forwarder.service.HttpService")) { + binding!!.toggleServerBtn.text = resources.getText(R.string.stop_server) + inetAddress = NetworkUtils.getLocalInetAddress() + binding!!.tvServerTips.text = getString(R.string.http_server_running, inetAddress!!.hostAddress, HTTP_SERVER_PORT) + } else { + binding!!.toggleServerBtn.text = resources.getText(R.string.start_server) + binding!!.tvServerTips.text = getString(R.string.http_server_stopped) + } + } + + //发送短信权限 + private fun checkSendSmsPermission() { + XXPermissions.with(this) + // 发送短信 + .permission(Permission.SEND_SMS) + .request(object : OnPermissionCallback { + override fun onGranted(permissions: List, all: Boolean) { + } + + override fun onDenied(permissions: List, never: Boolean) { + if (never) { + XToastUtils.error(R.string.toast_denied_never) + // 如果是被永久拒绝就跳转到应用权限系统设置页面 + XXPermissions.startPermissionActivity(requireContext(), permissions) + } else { + XToastUtils.error(R.string.toast_denied) + } + HttpServerUtils.enableApiSmsSend = false + binding!!.sbApiSendSms.isChecked = false + } + }) + } + + //读取短信权限 + private fun checkReadSmsPermission() { + XXPermissions.with(this) + // 接收短信 + .permission(Permission.RECEIVE_SMS) + // 发送短信 + .permission(Permission.SEND_SMS) + // 读取短信 + .permission(Permission.READ_SMS) + .request(object : OnPermissionCallback { + override fun onGranted(permissions: List, all: Boolean) { + } + + override fun onDenied(permissions: List, never: Boolean) { + if (never) { + XToastUtils.error(R.string.toast_denied_never) + // 如果是被永久拒绝就跳转到应用权限系统设置页面 + XXPermissions.startPermissionActivity(requireContext(), permissions) + } else { + XToastUtils.error(R.string.toast_denied) + } + HttpServerUtils.enableApiSmsQuery = false + binding!!.sbApiQuerySms.isChecked = false + } + }) + } + + //电话权限 + private fun checkCallPermission() { + XXPermissions.with(this) + // 读取电话状态 + .permission(Permission.READ_PHONE_STATE) + // 读取手机号码 + .permission(Permission.READ_PHONE_NUMBERS) + // 读取通话记录 + .permission(Permission.READ_CALL_LOG) + .request(object : OnPermissionCallback { + override fun onGranted(permissions: List, all: Boolean) { + } + + override fun onDenied(permissions: List, never: Boolean) { + if (never) { + XToastUtils.error(R.string.toast_denied_never) + // 如果是被永久拒绝就跳转到应用权限系统设置页面 + XXPermissions.startPermissionActivity(requireContext(), permissions) + } else { + XToastUtils.error(R.string.toast_denied) + } + HttpServerUtils.enableApiCallQuery = false + binding!!.sbApiQueryCall.isChecked = false + } + }) + } + + //联系人权限 + private fun checkContactsPermission() { + XXPermissions.with(this) + .permission(*Permission.Group.CONTACTS) + .request(object : OnPermissionCallback { + override fun onGranted(permissions: List, all: Boolean) { + } + + override fun onDenied(permissions: List, never: Boolean) { + if (never) { + XToastUtils.error(R.string.toast_denied_never) + // 如果是被永久拒绝就跳转到应用权限系统设置页面 + XXPermissions.startPermissionActivity(requireContext(), permissions) + } else { + XToastUtils.error(R.string.toast_denied) + } + HttpServerUtils.enableApiContactQuery = false + binding!!.sbApiQueryContacts.isChecked = false + } + }) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/fragment/ServiceProtocolFragment.kt b/app/src/main/java/com/idormy/sms/forwarder/fragment/ServiceProtocolFragment.kt new file mode 100644 index 00000000..f97930eb --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/fragment/ServiceProtocolFragment.kt @@ -0,0 +1,80 @@ +package com.idormy.sms.forwarder.fragment + +import android.view.LayoutInflater +import android.view.ViewGroup +import com.idormy.sms.forwarder.R +import com.idormy.sms.forwarder.core.BaseFragment +import com.idormy.sms.forwarder.databinding.FragmentServiceProtocolBinding +import com.xuexiang.xaop.annotation.MemoryCache +import com.xuexiang.xpage.annotation.Page +import com.xuexiang.xrouter.annotation.AutoWired +import com.xuexiang.xrouter.launcher.XRouter +import com.xuexiang.xui.widget.actionbar.TitleBar +import com.xuexiang.xutil.resource.ResUtils +import com.xuexiang.xutil.resource.ResourceUtils + +/** + * 服务协议【本地加载】 + * + * @author xuexiang + * @since 2021/5/18 1:35 AM + */ +@Suppress("REDUNDANT_MODIFIER_IN_GETTER") +@Page +class ServiceProtocolFragment : BaseFragment() { + @JvmField + @AutoWired(name = KEY_PROTOCOL_TITLE) + var title: String? = null + + @JvmField + @AutoWired(name = KEY_IS_IMMERSIVE) + var isImmersive = false + override fun initArgs() { + XRouter.getInstance().inject(this) + } + + override fun viewBindingInflate( + inflater: LayoutInflater, + container: ViewGroup, + ): FragmentServiceProtocolBinding { + return FragmentServiceProtocolBinding.inflate(inflater, container, false) + } + + override fun initTitle(): TitleBar? { + return super.initTitle()!!.setTitle(title).setImmersive(isImmersive) + } + + /** + * 初始化控件 + */ + override fun initViews() { + if (title == ResUtils.getString(R.string.title_user_protocol)) { + binding!!.tvProtocolText.text = accountProtocol + } else { + binding!!.tvProtocolText.text = privacyProtocol + } + } + + @get:MemoryCache("account_protocol") + private val accountProtocol: String + private get() = ResourceUtils.readStringFromAssert(ACCOUNT_PROTOCOL_ASSET_PATH) + + @get:MemoryCache("privacy_protocol") + private val privacyProtocol: String + private get() = ResourceUtils.readStringFromAssert(PRIVACY_PROTOCOL_ASSET_PATH) + + companion object { + const val KEY_PROTOCOL_TITLE = "key_protocol_title" + const val KEY_IS_IMMERSIVE = "key_is_immersive" + + /** + * 用户协议asset本地保存路径 + */ + private const val ACCOUNT_PROTOCOL_ASSET_PATH = "protocol/account_protocol.txt" + + /** + * 隐私政策asset本地保存路径 + */ + private const val PRIVACY_PROTOCOL_ASSET_PATH = "protocol/privacy_protocol.txt" + } +} \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/fragment/SettingsFragment.kt b/app/src/main/java/com/idormy/sms/forwarder/fragment/SettingsFragment.kt new file mode 100644 index 00000000..3aef2474 --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/fragment/SettingsFragment.kt @@ -0,0 +1,907 @@ +package com.idormy.sms.forwarder.fragment + +import android.annotation.SuppressLint +import android.app.ActivityManager +import android.content.ComponentName +import android.content.Context +import android.content.Intent +import android.content.pm.PackageManager +import android.net.Uri +import android.os.Build +import android.provider.Settings +import android.text.Editable +import android.text.TextWatcher +import android.util.Log +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.CompoundButton +import android.widget.EditText +import android.widget.LinearLayout +import android.widget.TextView +import androidx.annotation.RequiresApi +import com.hjq.permissions.OnPermissionCallback +import com.hjq.permissions.Permission +import com.hjq.permissions.XXPermissions +import com.idormy.sms.forwarder.App +import com.idormy.sms.forwarder.R +import com.idormy.sms.forwarder.core.BaseFragment +import com.idormy.sms.forwarder.databinding.FragmentSettingsBinding +import com.idormy.sms.forwarder.entity.SimInfo +import com.idormy.sms.forwarder.receiver.BootReceiver +import com.idormy.sms.forwarder.utils.* +import com.jeremyliao.liveeventbus.LiveEventBus +import com.xuexiang.xaop.annotation.SingleClick +import com.xuexiang.xpage.annotation.Page +import com.xuexiang.xui.widget.actionbar.TitleBar +import com.xuexiang.xui.widget.button.SmoothCheckBox +import com.xuexiang.xui.widget.button.switchbutton.SwitchButton +import com.xuexiang.xui.widget.dialog.materialdialog.DialogAction +import com.xuexiang.xui.widget.dialog.materialdialog.MaterialDialog +import com.xuexiang.xui.widget.picker.XRangeSlider +import com.xuexiang.xui.widget.picker.XRangeSlider.OnRangeSliderListener +import com.xuexiang.xui.widget.picker.XSeekBar +import com.xuexiang.xui.widget.picker.widget.builder.TimePickerBuilder +import com.xuexiang.xutil.XUtil.getPackageManager +import com.xuexiang.xutil.app.AppUtils.getAppPackageName +import com.xuexiang.xutil.data.DateUtils +import java.util.* + +@Suppress("PropertyName", "SpellCheckingInspection") +@Page(name = "通用设置") +class SettingsFragment : BaseFragment(), View.OnClickListener { + + val TAG: String = SettingsFragment::class.java.simpleName + + override fun viewBindingInflate( + inflater: LayoutInflater, + container: ViewGroup, + ): FragmentSettingsBinding { + return FragmentSettingsBinding.inflate(inflater, container, false) + } + + /** + * @return 返回为 null意为不需要导航栏 + */ + override fun initTitle(): TitleBar? { + return null + } + + @SuppressLint("NewApi") + override fun initViews() { + + //转发短信广播 + switchEnableSms(binding!!.sbEnableSms) + //转发通话记录 + switchEnablePhone(binding!!.sbEnablePhone, binding!!.scbCallType1, binding!!.scbCallType2, binding!!.scbCallType3) + //转发应用通知 + switchEnableAppNotify(binding!!.sbEnableAppNotify, binding!!.scbCancelAppNotify, binding!!.scbNotUserPresent) + //过滤多久内重复消息 + binding!!.xsbDuplicateMessagesLimits.setDefaultValue(SettingUtils.duplicateMessagesLimits) + binding!!.xsbDuplicateMessagesLimits.setOnSeekBarListener { _: XSeekBar?, newValue: Int -> + SettingUtils.duplicateMessagesLimits = newValue + } + + //监听电池状态变化 + switchBatteryReceiver(binding!!.sbBatteryReceiver) + //电量预警 + editBatteryLevelAlarm(binding!!.xrsBatteryLevelAlarm, binding!!.scbBatteryLevelAlarmOnce) + //定时推送电池状态 + switchBatteryCron(binding!!.sbBatteryCron) + //设置推送电池状态时机 + editBatteryCronTiming(binding!!.etBatteryCronStartTime, binding!!.etBatteryCronInterval) + + //开机启动 + checkWithReboot(binding!!.sbWithReboot, binding!!.tvAutoStartup) + //忽略电池优化设置 + batterySetting(binding!!.layoutBatterySetting, binding!!.sbBatterySetting) + //不在最近任务列表中显示 + switchExcludeFromRecents(binding!!.layoutExcludeFromRecents, binding!!.sbExcludeFromRecents) + //后台播放无声音乐 + switchPlaySilenceMusic(binding!!.sbPlaySilenceMusic) + //1像素透明Activity保活(只有在android p以下可以使用) + switchOnePixelActivity(binding!!.sbOnePixelActivity) + + //接口请求失败重试时间间隔 + editRetryDelayTime(binding!!.etRetryTimes, binding!!.etDelayTime, binding!!.etTimeout) + + //设备备注 + editAddExtraDeviceMark(binding!!.etExtraDeviceMark) + //SIM1备注 + editAddExtraSim1(binding!!.etExtraSim1) + //SIM2备注 + editAddExtraSim2(binding!!.etExtraSim2) + //通知内容 + editNotifyContent(binding!!.etNotifyContent) + + //启用自定义模版 + switchSmsTemplate(binding!!.sbSmsTemplate) + //自定义模板 + editSmsTemplate(binding!!.etSmsTemplate) + + //帮助提示 + switchHelpTip(binding!!.sbHelpTip) + } + + override fun initListeners() { + binding!!.btnExtraDeviceMark.setOnClickListener(this) + binding!!.btnExtraSim1.setOnClickListener(this) + binding!!.btnExtraSim2.setOnClickListener(this) + binding!!.btInsertSender.setOnClickListener(this) + binding!!.btInsertContent.setOnClickListener(this) + binding!!.btInsertExtra.setOnClickListener(this) + binding!!.btInsertTime.setOnClickListener(this) + binding!!.btInsertDeviceName.setOnClickListener(this) + } + + @SuppressLint("SetTextI18n") + @SingleClick + override fun onClick(v: View) { + val etSmsTemplate: EditText = binding!!.etSmsTemplate + when (v.id) { + R.id.btn_extra_device_mark -> { + binding!!.etExtraDeviceMark.setText(PhoneUtils.getDeviceName()) + return + } + R.id.btn_extra_sim1 -> { + App.SimInfoList = PhoneUtils.getSimMultiInfo() + if (App.SimInfoList.isEmpty()) { + XToastUtils.error(R.string.tip_can_not_get_sim_infos) + return + } + Log.d(TAG, App.SimInfoList.toString()) + if (!App.SimInfoList.containsKey(0)) { + XToastUtils.error(String.format(getString(R.string.tip_can_not_get_sim_info), 1)) + return + } + val simInfo: SimInfo? = App.SimInfoList[0] + binding!!.etExtraSim1.setText(simInfo?.mCarrierName.toString() + "_" + simInfo?.mNumber.toString()) + return + } + R.id.btn_extra_sim2 -> { + App.SimInfoList = PhoneUtils.getSimMultiInfo() + if (App.SimInfoList.isEmpty()) { + XToastUtils.error(R.string.tip_can_not_get_sim_infos) + return + } + Log.d(TAG, App.SimInfoList.toString()) + if (!App.SimInfoList.containsKey(1)) { + XToastUtils.error(String.format(getString(R.string.tip_can_not_get_sim_info), 2)) + return + } + val simInfo: SimInfo? = App.SimInfoList[1] + binding!!.etExtraSim2.setText(simInfo?.mCarrierName.toString() + "_" + simInfo?.mNumber.toString()) + return + } + R.id.bt_insert_sender -> { + CommonUtils.insertOrReplaceText2Cursor(etSmsTemplate, getString(R.string.tag_from)) + return + } + R.id.bt_insert_content -> { + CommonUtils.insertOrReplaceText2Cursor(etSmsTemplate, getString(R.string.tag_sms)) + return + } + R.id.bt_insert_extra -> { + CommonUtils.insertOrReplaceText2Cursor(etSmsTemplate, getString(R.string.tag_card_slot)) + return + } + R.id.bt_insert_time -> { + CommonUtils.insertOrReplaceText2Cursor(etSmsTemplate, getString(R.string.tag_receive_time)) + return + } + R.id.bt_insert_device_name -> { + CommonUtils.insertOrReplaceText2Cursor(etSmsTemplate, getString(R.string.tag_device_name)) + return + } + else -> {} + } + } + + //转发短信 + @SuppressLint("UseSwitchCompatOrMaterialCode") + fun switchEnableSms(sbEnableSms: SwitchButton) { + sbEnableSms.isChecked = SettingUtils.enableSms + sbEnableSms.setOnCheckedChangeListener { _: CompoundButton?, isChecked: Boolean -> + SettingUtils.enableSms = isChecked + if (isChecked) { + //检查权限是否获取 + XXPermissions.with(this) + // 接收短信 + .permission(Permission.RECEIVE_SMS) + // 发送短信 + //.permission(Permission.SEND_SMS) + // 读取短信 + .permission(Permission.READ_SMS) + .request(object : OnPermissionCallback { + override fun onGranted(permissions: List, all: Boolean) { + if (all) { + XToastUtils.info(R.string.toast_granted_all) + } else { + XToastUtils.info(R.string.toast_granted_part) + } + } + + override fun onDenied(permissions: List, never: Boolean) { + if (never) { + XToastUtils.info(R.string.toast_denied_never) + // 如果是被永久拒绝就跳转到应用权限系统设置页面 + XXPermissions.startPermissionActivity(requireContext(), permissions) + } else { + XToastUtils.info(R.string.toast_denied) + } + SettingUtils.enableSms = false + sbEnableSms.isChecked = false + } + }) + } + } + } + + //转发通话 + @SuppressLint("UseSwitchCompatOrMaterialCode") + fun switchEnablePhone(sbEnablePhone: SwitchButton, scbCallType1: SmoothCheckBox, scbCallType2: SmoothCheckBox, scbCallType3: SmoothCheckBox) { + sbEnablePhone.isChecked = SettingUtils.enablePhone + scbCallType1.isChecked = SettingUtils.enableCallType1 + scbCallType2.isChecked = SettingUtils.enableCallType2 + scbCallType3.isChecked = SettingUtils.enableCallType3 + sbEnablePhone.setOnCheckedChangeListener { _: CompoundButton?, isChecked: Boolean -> + if (isChecked && !SettingUtils.enableCallType1 && !SettingUtils.enableCallType2 && !SettingUtils.enableCallType3) { + XToastUtils.info(R.string.enable_phone_fw_tips) + SettingUtils.enablePhone = false + sbEnablePhone.isChecked = false + return@setOnCheckedChangeListener + } + SettingUtils.enablePhone = isChecked + if (isChecked) { + //检查权限是否获取 + XXPermissions.with(this) + // 读取电话状态 + .permission(Permission.READ_PHONE_STATE) + // 读取手机号码 + .permission(Permission.READ_PHONE_NUMBERS) + // 读取通话记录 + .permission(Permission.READ_CALL_LOG) + // 读取联系人 + .permission(Permission.READ_CONTACTS) + .request(object : OnPermissionCallback { + override fun onGranted(permissions: List, all: Boolean) { + if (all) { + XToastUtils.info(R.string.toast_granted_all) + } else { + XToastUtils.info(R.string.toast_granted_part) + } + } + + override fun onDenied(permissions: List, never: Boolean) { + if (never) { + XToastUtils.info(R.string.toast_denied_never) + // 如果是被永久拒绝就跳转到应用权限系统设置页面 + XXPermissions.startPermissionActivity(requireContext(), permissions) + } else { + XToastUtils.info(R.string.toast_denied) + } + SettingUtils.enablePhone = false + sbEnablePhone.isChecked = false + } + }) + } + } + scbCallType1.setOnCheckedChangeListener { _: SmoothCheckBox, isChecked: Boolean -> + SettingUtils.enableCallType1 = isChecked + if (!isChecked && !SettingUtils.enableCallType1 && !SettingUtils.enableCallType2 && !SettingUtils.enableCallType3) { + XToastUtils.info(R.string.enable_phone_fw_tips) + SettingUtils.enablePhone = false + sbEnablePhone.isChecked = false + } + } + scbCallType2.setOnCheckedChangeListener { _: SmoothCheckBox, isChecked: Boolean -> + SettingUtils.enableCallType2 = isChecked + if (!isChecked && !SettingUtils.enableCallType1 && !SettingUtils.enableCallType2 && !SettingUtils.enableCallType3) { + XToastUtils.info(R.string.enable_phone_fw_tips) + SettingUtils.enablePhone = false + sbEnablePhone.isChecked = false + } + } + scbCallType3.setOnCheckedChangeListener { _: SmoothCheckBox, isChecked: Boolean -> + SettingUtils.enableCallType3 = isChecked + if (!isChecked && !SettingUtils.enableCallType1 && !SettingUtils.enableCallType2 && !SettingUtils.enableCallType3) { + XToastUtils.info(R.string.enable_phone_fw_tips) + SettingUtils.enablePhone = false + sbEnablePhone.isChecked = false + } + } + } + + //转发应用通知 + @SuppressLint("UseSwitchCompatOrMaterialCode") + fun switchEnableAppNotify(sbEnableAppNotify: SwitchButton, scbCancelAppNotify: SmoothCheckBox, scbNotUserPresent: SmoothCheckBox) { + val layoutOptionalAction: LinearLayout = binding!!.layoutOptionalAction + val isEnable: Boolean = SettingUtils.enableAppNotify + sbEnableAppNotify.isChecked = isEnable + layoutOptionalAction.visibility = if (isEnable) View.VISIBLE else View.GONE + + sbEnableAppNotify.setOnCheckedChangeListener { _: CompoundButton?, isChecked: Boolean -> + layoutOptionalAction.visibility = if (isChecked) View.VISIBLE else View.GONE + if (isChecked) { + //检查权限是否获取 + XXPermissions.with(this) + // 通知栏监听权限 + .permission(Permission.BIND_NOTIFICATION_LISTENER_SERVICE) + // 通知栏权限 + .permission(Permission.NOTIFICATION_SERVICE) + .request(object : OnPermissionCallback { + override fun onGranted(permissions: List, all: Boolean) { + SettingUtils.enableAppNotify = true + sbEnableAppNotify.isChecked = true + CommonUtils.toggleNotificationListenerService(requireContext()) + } + + override fun onDenied(permissions: List, never: Boolean) { + SettingUtils.enableAppNotify = false + sbEnableAppNotify.isChecked = false + XToastUtils.error(R.string.tips_notification_listener) + // 如果是被永久拒绝就跳转到应用权限系统设置页面 + MaterialDialog.Builder(context!!) + .content(R.string.toast_denied_never) + .positiveText(R.string.lab_yes) + .negativeText(R.string.lab_no) + .onPositive { _: MaterialDialog?, _: DialogAction? -> + XXPermissions.startPermissionActivity(requireContext(), permissions) + } + .show() + } + }) + } + } + scbCancelAppNotify.isChecked = SettingUtils.enableCancelAppNotify + scbCancelAppNotify.setOnCheckedChangeListener { _: SmoothCheckBox, isChecked: Boolean -> + SettingUtils.enableCancelAppNotify = isChecked + } + scbNotUserPresent.isChecked = SettingUtils.enableNotUserPresent + scbNotUserPresent.setOnCheckedChangeListener { _: SmoothCheckBox, isChecked: Boolean -> + SettingUtils.enableNotUserPresent = isChecked + } + } + + //监听电池状态变化 + @SuppressLint("UseSwitchCompatOrMaterialCode") + fun switchBatteryReceiver(sbBatteryReceiver: SwitchButton) { + sbBatteryReceiver.isChecked = SettingUtils.enableBatteryReceiver + sbBatteryReceiver.setOnCheckedChangeListener { _: CompoundButton?, isChecked: Boolean -> + SettingUtils.enableBatteryReceiver = isChecked + } + } + + //设置低电量报警 + private fun editBatteryLevelAlarm(xrsBatteryLevelAlarm: XRangeSlider, scbBatteryLevelAlarmOnce: SmoothCheckBox) { + xrsBatteryLevelAlarm.setStartingMinMax(SettingUtils.batteryLevelMin, SettingUtils.batteryLevelMax) + xrsBatteryLevelAlarm.setOnRangeSliderListener(object : OnRangeSliderListener { + override fun onMaxChanged(slider: XRangeSlider, maxValue: Int) { + //SettingUtils.batteryLevelMin = slider.selectedMin + SettingUtils.batteryLevelMax = slider.selectedMax + } + + override fun onMinChanged(slider: XRangeSlider, minValue: Int) { + SettingUtils.batteryLevelMin = slider.selectedMin + //SettingUtils.batteryLevelMax = slider.selectedMax + } + }) + + scbBatteryLevelAlarmOnce.isChecked = SettingUtils.batteryLevelOnce + scbBatteryLevelAlarmOnce.setOnCheckedChangeListener { _: SmoothCheckBox, isChecked: Boolean -> + SettingUtils.batteryLevelOnce = isChecked + if (isChecked && 0 == SettingUtils.batteryLevelMin && 0 == SettingUtils.batteryLevelMax) { + XToastUtils.warning(R.string.tips_battery_level_alarm_once) + } + } + } + + //定时推送电池状态 + @SuppressLint("UseSwitchCompatOrMaterialCode") + fun switchBatteryCron(sbBatteryCron: SwitchButton) { + sbBatteryCron.isChecked = SettingUtils.enableBatteryCron + binding!!.layoutBatteryCron.visibility = if (SettingUtils.enableBatteryCron) View.VISIBLE else View.GONE + sbBatteryCron.setOnCheckedChangeListener { _: CompoundButton?, isChecked: Boolean -> + binding!!.layoutBatteryCron.visibility = if (isChecked) View.VISIBLE else View.GONE + SettingUtils.enableBatteryCron = isChecked + //TODO:BatteryReportCronTask + //BatteryReportCronTask.getSingleton().updateTimer() + } + } + + //设置推送电池状态时机 + private fun editBatteryCronTiming(etBatteryCronStartTime: EditText, etBatteryCronInterval: EditText) { + etBatteryCronStartTime.setText(SettingUtils.batteryCronStartTime) + etBatteryCronStartTime.setOnClickListener { + val calendar = Calendar.getInstance() + calendar.time = DateUtils.getNowDate() + val mTimePicker = TimePickerBuilder(context) { date: Date?, _: View? -> + etBatteryCronStartTime.setText(DateUtils.date2String(date, DateUtils.HHmm.get())) + //TODO:BatteryReportCronTask + //BatteryReportCronTask.getSingleton().updateTimer() + } + //.setTimeSelectChangeListener { date: Date? -> etBatteryCronStartTime.setText(DateUtils.date2String(date, DateUtils.HHmm.get())) } + .setType(false, false, false, true, true, false) + .setTitleText(getString(R.string.time_picker)) + .setSubmitText(getString(R.string.ok)) + .setCancelText(getString(R.string.cancel)) + .setDate(calendar) + .build() + mTimePicker.show() + } + + etBatteryCronInterval.setText(SettingUtils.batteryCronInterval.toString()) + etBatteryCronInterval.addTextChangedListener(object : TextWatcher { + override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {} + override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {} + override fun afterTextChanged(s: Editable) { + val interval = etBatteryCronInterval.text.toString().trim() + if (interval.isNotEmpty() && interval.toInt() > 0) { + SettingUtils.batteryCronInterval = interval.toInt() + //TODO:BatteryReportCronTask + //BatteryReportCronTask.getSingleton().updateTimer() + } else { + SettingUtils.batteryCronInterval = 60 + } + } + }) + } + + //开机启动 + private fun checkWithReboot(@SuppressLint("UseSwitchCompatOrMaterialCode") sbWithReboot: SwitchButton, tvAutoStartup: TextView) { + tvAutoStartup.text = getAutoStartTips() + + //获取组件 + val cm = ComponentName(getAppPackageName(), BootReceiver::class.java.name) + val pm: PackageManager = getPackageManager() + val state = pm.getComponentEnabledSetting(cm) + sbWithReboot.isChecked = (state != PackageManager.COMPONENT_ENABLED_STATE_DISABLED && state != PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER) + sbWithReboot.setOnCheckedChangeListener { _: CompoundButton?, isChecked: Boolean -> + try { + val newState = if (isChecked) PackageManager.COMPONENT_ENABLED_STATE_ENABLED else PackageManager.COMPONENT_ENABLED_STATE_DISABLED + pm.setComponentEnabledSetting(cm, newState, PackageManager.DONT_KILL_APP) + if (isChecked) startToAutoStartSetting(requireContext()) + } catch (e: Exception) { + XToastUtils.error(e.message.toString()) + } + } + } + + //电池优化设置 + @RequiresApi(api = Build.VERSION_CODES.M) + @SuppressLint("UseSwitchCompatOrMaterialCode") + fun batterySetting(layoutBatterySetting: LinearLayout, sbBatterySetting: SwitchButton) { + //安卓6.0以下没有忽略电池优化 + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { + layoutBatterySetting.visibility = View.GONE + return + } + + try { + val isIgnoreBatteryOptimization: Boolean = KeepAliveUtils.isIgnoreBatteryOptimization(requireActivity()) + sbBatterySetting.isChecked = isIgnoreBatteryOptimization + sbBatterySetting.setOnCheckedChangeListener { _: CompoundButton?, isChecked: Boolean -> + if (isChecked && !isIgnoreBatteryOptimization) { + KeepAliveUtils.ignoreBatteryOptimization(requireActivity()) + } else if (isChecked) { + XToastUtils.info(R.string.isIgnored) + sbBatterySetting.isChecked = isIgnoreBatteryOptimization + } else { + XToastUtils.info(R.string.isIgnored2) + sbBatterySetting.isChecked = isIgnoreBatteryOptimization + } + } + } catch (ex: Exception) { + ex.printStackTrace() + } + } + + //不在最近任务列表中显示 + @SuppressLint("ObsoleteSdkInt,UseSwitchCompatOrMaterialCode") + fun switchExcludeFromRecents(layoutExcludeFromRecents: LinearLayout, sbExcludeFromRecents: SwitchButton) { + //安卓6.0以下没有不在最近任务列表中显示 + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { + layoutExcludeFromRecents.visibility = View.GONE + return + } + sbExcludeFromRecents.isChecked = SettingUtils.enableExcludeFromRecents + sbExcludeFromRecents.setOnCheckedChangeListener { _: CompoundButton?, isChecked: Boolean -> + SettingUtils.enableExcludeFromRecents = isChecked + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + val am = App.context.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager + am.let { + val tasks = it.appTasks + if (!tasks.isNullOrEmpty()) { + tasks[0].setExcludeFromRecents(true) + } + } + } + } + } + + //后台播放无声音乐 + @SuppressLint("ObsoleteSdkInt,UseSwitchCompatOrMaterialCode") + fun switchPlaySilenceMusic(sbPlaySilenceMusic: SwitchButton) { + sbPlaySilenceMusic.isChecked = SettingUtils.enablePlaySilenceMusic + sbPlaySilenceMusic.setOnCheckedChangeListener { _: CompoundButton?, isChecked: Boolean -> + SettingUtils.enablePlaySilenceMusic = isChecked + XToastUtils.warning(getString(R.string.need_to_restart)) + } + } + + //1像素透明Activity保活 + @SuppressLint("ObsoleteSdkInt,UseSwitchCompatOrMaterialCode") + fun switchOnePixelActivity(sbOnePixelActivity: SwitchButton) { + if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.P) { + binding!!.layoutOnePixelActivity.visibility = View.VISIBLE + } + + sbOnePixelActivity.isChecked = SettingUtils.enableOnePixelActivity + sbOnePixelActivity.setOnCheckedChangeListener { _: CompoundButton?, isChecked: Boolean -> + SettingUtils.enableOnePixelActivity = isChecked + XToastUtils.warning(getString(R.string.need_to_restart)) + } + } + + //接口请求失败重试时间间隔 + private fun editRetryDelayTime(etRetryTimes: EditText, etDelayTime: EditText, etTimeout: EditText) { + etRetryTimes.setText(java.lang.String.valueOf(SettingUtils.requestRetryTimes)) + etRetryTimes.addTextChangedListener(object : TextWatcher { + override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {} + override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {} + override fun afterTextChanged(s: Editable) { + val retryTimes = etRetryTimes.text.toString().trim() + if (retryTimes.isNotEmpty()) { + SettingUtils.requestRetryTimes = retryTimes.toInt() + } else { + etRetryTimes.setText("0") + SettingUtils.requestRetryTimes = 0 + } + } + }) + etDelayTime.setText(java.lang.String.valueOf(SettingUtils.requestDelayTime)) + etDelayTime.addTextChangedListener(object : TextWatcher { + override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {} + override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {} + override fun afterTextChanged(s: Editable) { + val delayTime = etDelayTime.text.toString().trim() + if (delayTime.isNotEmpty()) { + SettingUtils.requestDelayTime = delayTime.toInt() + if (SettingUtils.requestDelayTime < 1) { + etDelayTime.setText("1") + XToastUtils.error(R.string.invalid_delay_time) + } + } else { + XToastUtils.warning(R.string.invalid_delay_time) + etDelayTime.setText("1") + SettingUtils.requestDelayTime = 1 + } + } + }) + etTimeout.setText(java.lang.String.valueOf(SettingUtils.requestTimeout)) + etTimeout.addTextChangedListener(object : TextWatcher { + override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {} + override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {} + override fun afterTextChanged(s: Editable) { + val timeout = etTimeout.text.toString().trim() + if (timeout.isNotEmpty()) { + SettingUtils.requestTimeout = timeout.toInt() + if (SettingUtils.requestTimeout < 1) { + etTimeout.setText("1") + XToastUtils.error(R.string.invalid_timeout) + } + } else { + XToastUtils.warning(R.string.invalid_timeout) + etTimeout.setText("1") + SettingUtils.requestTimeout = 1 + } + } + }) + } + + //设置设备名称 + private fun editAddExtraDeviceMark(etExtraDeviceMark: EditText) { + etExtraDeviceMark.setText(SettingUtils.extraDeviceMark) + etExtraDeviceMark.addTextChangedListener(object : TextWatcher { + override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {} + override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {} + override fun afterTextChanged(s: Editable) { + SettingUtils.extraDeviceMark = etExtraDeviceMark.text.toString().trim() + } + }) + } + + //设置SIM1备注 + private fun editAddExtraSim1(etExtraSim1: EditText) { + etExtraSim1.setText(SettingUtils.extraSim1) + etExtraSim1.addTextChangedListener(object : TextWatcher { + override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {} + override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {} + override fun afterTextChanged(s: Editable) { + SettingUtils.extraSim1 = etExtraSim1.text.toString().trim() + } + }) + } + + //设置SIM2备注 + private fun editAddExtraSim2(etExtraSim2: EditText) { + etExtraSim2.setText(SettingUtils.extraSim2) + etExtraSim2.addTextChangedListener(object : TextWatcher { + override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {} + override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {} + override fun afterTextChanged(s: Editable) { + SettingUtils.extraSim2 = etExtraSim2.text.toString().trim() + } + }) + } + + //设置通知内容 + private fun editNotifyContent(etNotifyContent: EditText) { + etNotifyContent.setText(SettingUtils.notifyContent) + etNotifyContent.addTextChangedListener(object : TextWatcher { + override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {} + override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {} + override fun afterTextChanged(s: Editable) { + SettingUtils.notifyContent = etNotifyContent.text.toString().trim() + LiveEventBus.get(EVENT_UPDATE_NOTIFY, String::class.java).post(SettingUtils.notifyContent.toString()) + } + }) + } + + //设置转发时启用自定义模版 + @SuppressLint("UseSwitchCompatOrMaterialCode", "SetTextI18n") + fun switchSmsTemplate(sb_sms_template: SwitchButton) { + val isOn: Boolean = SettingUtils.enableSmsTemplate + sb_sms_template.isChecked = isOn + val layoutSmsTemplate: LinearLayout = binding!!.layoutSmsTemplate + layoutSmsTemplate.visibility = if (isOn) View.VISIBLE else View.GONE + val etSmsTemplate: EditText = binding!!.etSmsTemplate + sb_sms_template.setOnCheckedChangeListener { _: CompoundButton?, isChecked: Boolean -> + layoutSmsTemplate.visibility = if (isChecked) View.VISIBLE else View.GONE + SettingUtils.enableSmsTemplate = isChecked + if (!isChecked) { + etSmsTemplate.setText( + """ + ${getString(R.string.tag_from)} + ${getString(R.string.tag_sms)} + ${getString(R.string.tag_card_slot)} + ${getString(R.string.tag_receive_time)} + ${getString(R.string.tag_device_name)} + """.trimIndent() + ) + } + } + } + + //设置转发信息模版 + private fun editSmsTemplate(textSmsTemplate: EditText) { + textSmsTemplate.setText(SettingUtils.smsTemplate) + textSmsTemplate.addTextChangedListener(object : TextWatcher { + override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {} + override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {} + override fun afterTextChanged(s: Editable) { + SettingUtils.smsTemplate = textSmsTemplate.text.toString().trim() + } + }) + } + + //页面帮助提示 + private fun switchHelpTip(@SuppressLint("UseSwitchCompatOrMaterialCode") switchHelpTip: SwitchButton) { + switchHelpTip.isChecked = SettingUtils.enableHelpTip + switchHelpTip.setOnCheckedChangeListener { _: CompoundButton?, isChecked: Boolean -> + SettingUtils.enableHelpTip = isChecked + } + } + + //获取当前手机品牌 + private fun getAutoStartTips(): String { + return when (Build.BRAND.lowercase(Locale.ROOT)) { + "huawei" -> getString(R.string.auto_start_huawei) + "honor" -> getString(R.string.auto_start_honor) + "xiaomi" -> getString(R.string.auto_start_xiaomi) + "oppo" -> getString(R.string.auto_start_oppo) + "vivo" -> getString(R.string.auto_start_vivo) + "meizu" -> getString(R.string.auto_start_meizu) + "samsung" -> getString(R.string.auto_start_samsung) + "letv" -> getString(R.string.auto_start_letv) + "smartisan" -> getString(R.string.auto_start_smartisan) + else -> getString(R.string.auto_start_unknown) + } + } + + //Intent跳转到[自启动]页面全网最全适配机型解决方案 + val hashMap = object : HashMap?>() { + init { + put( + "Xiaomi", listOf( + "com.miui.securitycenter/com.miui.permcenter.autostart.AutoStartManagementActivity", //MIUI10_9.8.1(9.0) + "com.miui.securitycenter" + ) + ) + put( + "samsung", listOf( + "com.samsung.android.sm_cn/com.samsung.android.sm.ui.ram.AutoRunActivity", + "com.samsung.android.sm_cn/com.samsung.android.sm.ui.appmanagement.AppManagementActivity", + "com.samsung.android.sm_cn/com.samsung.android.sm.ui.cstyleboard.SmartManagerDashBoardActivity", + "com.samsung.android.sm_cn/.ui.ram.RamActivity", + "com.samsung.android.sm_cn/.app.dashboard.SmartManagerDashBoardActivity", + "com.samsung.android.sm/com.samsung.android.sm.ui.ram.AutoRunActivity", + "com.samsung.android.sm/com.samsung.android.sm.ui.appmanagement.AppManagementActivity", + "com.samsung.android.sm/com.samsung.android.sm.ui.cstyleboard.SmartManagerDashBoardActivity", + "com.samsung.android.sm/.ui.ram.RamActivity", + "com.samsung.android.sm/.app.dashboard.SmartManagerDashBoardActivity", + "com.samsung.android.lool/com.samsung.android.sm.ui.battery.BatteryActivity", + "com.samsung.android.sm_cn", + "com.samsung.android.sm" + ) + ) + put( + "HUAWEI", listOf( + "com.huawei.systemmanager/.startupmgr.ui.StartupNormalAppListActivity", //EMUI9.1.0(方舟,9.0) + "com.huawei.systemmanager/.appcontrol.activity.StartupAppControlActivity", + "com.huawei.systemmanager/.optimize.process.ProtectActivity", + "com.huawei.systemmanager/.optimize.bootstart.BootStartActivity", + "com.huawei.systemmanager" //最后一行可以写包名, 这样如果签名的类路径在某些新版本的ROM中没找到 就直接跳转到对应的安全中心/手机管家 首页. + ) + ) + put( + "vivo", listOf( + "com.iqoo.secure/.ui.phoneoptimize.BgStartUpManager", + "com.iqoo.secure/.safeguard.PurviewTabActivity", + "com.vivo.permissionmanager/.activity.BgStartUpManagerActivity", //"com.iqoo.secure/.ui.phoneoptimize.AddWhiteListActivity", //这是白名单, 不是自启动 + "com.iqoo.secure", + "com.vivo.permissionmanager" + ) + ) + put( + "Meizu", listOf( + "com.meizu.safe/.permission.SmartBGActivity", //Flyme7.3.0(7.1.2) + "com.meizu.safe/.permission.PermissionMainActivity", //网上的 + "com.meizu.safe" + ) + ) + put( + "OPPO", listOf( + "com.coloros.safecenter/.startupapp.StartupAppListActivity", + "com.coloros.safecenter/.permission.startup.StartupAppListActivity", + "com.oppo.safe/.permission.startup.StartupAppListActivity", + "com.coloros.oppoguardelf/com.coloros.powermanager.fuelgaue.PowerUsageModelActivity", + "com.coloros.safecenter/com.coloros.privacypermissionsentry.PermissionTopActivity", + "com.coloros.safecenter", + "com.oppo.safe", + "com.coloros.oppoguardelf" + ) + ) + put( + "oneplus", listOf( + "com.oneplus.security/.chainlaunch.view.ChainLaunchAppListActivity", + "com.oneplus.security" + ) + ) + put( + "letv", listOf( + "com.letv.android.letvsafe/.AutobootManageActivity", + "com.letv.android.letvsafe/.BackgroundAppManageActivity", //应用保护 + "com.letv.android.letvsafe" + ) + ) + put( + "zte", listOf( + "com.zte.heartyservice/.autorun.AppAutoRunManager", + "com.zte.heartyservice" + ) + ) + + //金立 + put( + "F", listOf( + "com.gionee.softmanager/.MainActivity", + "com.gionee.softmanager" + ) + ) + + //以下为未确定(厂商名也不确定) + put( + "smartisanos", listOf( + "com.smartisanos.security/.invokeHistory.InvokeHistoryActivity", + "com.smartisanos.security" + ) + ) + + //360 + put( + "360", listOf( + "com.yulong.android.coolsafe/.ui.activity.autorun.AutoRunListActivity", + "com.yulong.android.coolsafe" + ) + ) + + //360 + put( + "ulong", listOf( + "com.yulong.android.coolsafe/.ui.activity.autorun.AutoRunListActivity", + "com.yulong.android.coolsafe" + ) + ) + + //酷派 + put( + "coolpad" /*厂商名称不确定是否正确*/, listOf( + "com.yulong.android.security/com.yulong.android.seccenter.tabbarmain", + "com.yulong.android.security" + ) + ) + + //联想 + put( + "lenovo" /*厂商名称不确定是否正确*/, listOf( + "com.lenovo.security/.purebackground.PureBackgroundActivity", + "com.lenovo.security" + ) + ) + put( + "htc" /*厂商名称不确定是否正确*/, listOf( + "com.htc.pitroad/.landingpage.activity.LandingPageActivity", + "com.htc.pitroad" + ) + ) + + //华硕 + put( + "asus" /*厂商名称不确定是否正确*/, listOf( + "com.asus.mobilemanager/.MainActivity", + "com.asus.mobilemanager" + ) + ) + } + } + + //跳转自启动页面 + private fun startToAutoStartSetting(context: Context) { + Log.e("Util", "******************The current phone model is:" + Build.MANUFACTURER) + val entries: MutableSet?>> = hashMap.entries + var has = false + for ((manufacturer, actCompatList) in entries) { + if (Build.MANUFACTURER.equals(manufacturer, ignoreCase = true)) { + if (actCompatList != null) { + for (act in actCompatList) { + try { + var intent: Intent? + if (act?.contains("/") == true) { + intent = Intent() + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + val componentName = ComponentName.unflattenFromString(act) + intent.component = componentName + } else { + //找不到? 网上的做法都是跳转到设置... 这基本上是没意义的 基本上自启动这个功能是第三方厂商自己写的安全管家类app + //所以我是直接跳转到对应的安全管家/安全中心 + intent = act?.let { context.packageManager.getLaunchIntentForPackage(it) } + } + context.startActivity(intent) + has = true + break + } catch (e: Exception) { + e.printStackTrace() + } + } + } + } + } + if (!has) { + XToastUtils.info(R.string.tips_compatible_solution) + try { + val intent = Intent() + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + intent.action = "android.settings.APPLICATION_DETAILS_SETTINGS" + intent.data = Uri.fromParts("package", context.packageName, null) + context.startActivity(intent) + } catch (e: Exception) { + e.printStackTrace() + val intent = Intent(Settings.ACTION_SETTINGS) + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + context.startActivity(intent) + } + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/fragment/client/BatteryQueryFragment.kt b/app/src/main/java/com/idormy/sms/forwarder/fragment/client/BatteryQueryFragment.kt new file mode 100644 index 00000000..47aca929 --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/fragment/client/BatteryQueryFragment.kt @@ -0,0 +1,112 @@ +package com.idormy.sms.forwarder.fragment.client + +import android.util.Log +import android.view.LayoutInflater +import android.view.ViewGroup +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import com.idormy.sms.forwarder.R +import com.idormy.sms.forwarder.core.BaseFragment +import com.idormy.sms.forwarder.databinding.FragmentClientBatteryQueryBinding +import com.idormy.sms.forwarder.entity.BatteryInfo +import com.idormy.sms.forwarder.server.model.BaseResponse +import com.idormy.sms.forwarder.utils.HttpServerUtils +import com.idormy.sms.forwarder.utils.SettingUtils +import com.idormy.sms.forwarder.utils.XToastUtils +import com.xuexiang.xhttp2.XHttp +import com.xuexiang.xhttp2.cache.model.CacheMode +import com.xuexiang.xhttp2.callback.SimpleCallBack +import com.xuexiang.xhttp2.exception.ApiException +import com.xuexiang.xpage.annotation.Page +import com.xuexiang.xrouter.utils.TextUtils +import com.xuexiang.xui.utils.ResUtils +import com.xuexiang.xui.widget.actionbar.TitleBar +import com.xuexiang.xui.widget.grouplist.XUIGroupListView + +@Suppress("PropertyName") +@Page(name = "远程查电量") +class BatteryQueryFragment : BaseFragment() { + + val TAG: String = BatteryQueryFragment::class.java.simpleName + + override fun viewBindingInflate( + inflater: LayoutInflater, + container: ViewGroup, + ): FragmentClientBatteryQueryBinding { + return FragmentClientBatteryQueryBinding.inflate(inflater, container, false) + } + + override fun initTitle(): TitleBar? { + val titleBar = super.initTitle()!!.setImmersive(false) + titleBar.setTitle(R.string.api_battery_query) + return titleBar + } + + /** + * 初始化控件 + */ + override fun initViews() { + + val requestUrl: String = HttpServerUtils.serverAddress + "/battery/query" + Log.i(TAG, "requestUrl:$requestUrl") + + val msgMap: MutableMap = mutableMapOf() + val timestamp = System.currentTimeMillis() + msgMap["timestamp"] = timestamp + val clientSignKey = HttpServerUtils.clientSignKey + if (!TextUtils.isEmpty(clientSignKey)) { + msgMap["sign"] = HttpServerUtils.calcSign(timestamp.toString(), clientSignKey.toString()) + } + val dataMap: MutableMap = mutableMapOf() + msgMap["data"] = dataMap + + val requestMsg: String = Gson().toJson(msgMap) + Log.i(TAG, "requestMsg:$requestMsg") + + XHttp.post(requestUrl) + .upJson(requestMsg) + .keepJson(true) + .timeOut((SettingUtils.requestTimeout * 1000).toLong()) //超时时间10s + .cacheMode(CacheMode.NO_CACHE) + //.retryCount(SettingUtils.requestRetryTimes) //超时重试的次数 + //.retryDelay(SettingUtils.requestDelayTime) //超时重试的延迟时间 + //.retryIncreaseDelay(SettingUtils.requestDelayTime) //超时重试叠加延时 + .timeStamp(true) + .execute(object : SimpleCallBack() { + + override fun onError(e: ApiException) { + XToastUtils.error(e.displayMessage) + } + + override fun onSuccess(response: String) { + Log.i(TAG, response) + try { + val resp: BaseResponse = Gson().fromJson(response, object : TypeToken>() {}.type) + if (resp.code == 200) { + XToastUtils.success(ResUtils.getString(R.string.request_succeeded)) + val batteryInfo = resp.data ?: return + + val groupListView = binding!!.infoList + val section = XUIGroupListView.newSection(context) + section.addItemView(groupListView.createItemView(String.format(ResUtils.getString(R.string.battery_level), batteryInfo.level))) {} + if (batteryInfo.scale != "") section.addItemView(groupListView.createItemView(String.format(ResUtils.getString(R.string.battery_scale), batteryInfo.scale))) {} + if (batteryInfo.voltage != "") section.addItemView(groupListView.createItemView(String.format(ResUtils.getString(R.string.battery_voltage), batteryInfo.voltage))) {} + if (batteryInfo.temperature != "") section.addItemView(groupListView.createItemView(String.format(ResUtils.getString(R.string.battery_temperature), batteryInfo.temperature))) {} + section.addItemView(groupListView.createItemView(String.format(ResUtils.getString(R.string.battery_status), batteryInfo.status))) {} + section.addItemView(groupListView.createItemView(String.format(ResUtils.getString(R.string.battery_health), batteryInfo.health))) {} + section.addItemView(groupListView.createItemView(String.format(ResUtils.getString(R.string.battery_plugged), batteryInfo.plugged))) {} + section.addTo(groupListView) + + } else { + XToastUtils.error(ResUtils.getString(R.string.request_failed) + resp.msg) + } + } catch (e: Exception) { + e.printStackTrace() + XToastUtils.error(ResUtils.getString(R.string.request_failed) + response) + } + } + + }) + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/fragment/client/CallQueryFragment.kt b/app/src/main/java/com/idormy/sms/forwarder/fragment/client/CallQueryFragment.kt new file mode 100644 index 00000000..b2135074 --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/fragment/client/CallQueryFragment.kt @@ -0,0 +1,261 @@ +package com.idormy.sms.forwarder.fragment.client + +import android.util.Log +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.recyclerview.widget.RecyclerView +import com.alibaba.android.vlayout.DelegateAdapter +import com.alibaba.android.vlayout.VirtualLayoutManager +import com.alibaba.android.vlayout.layout.LinearLayoutHelper +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import com.idormy.sms.forwarder.R +import com.idormy.sms.forwarder.adapter.base.broccoli.BroccoliSimpleDelegateAdapter +import com.idormy.sms.forwarder.adapter.base.delegate.SimpleDelegateAdapter +import com.idormy.sms.forwarder.core.BaseFragment +import com.idormy.sms.forwarder.databinding.FragmentClientCallQueryBinding +import com.idormy.sms.forwarder.entity.CallInfo +import com.idormy.sms.forwarder.server.model.BaseResponse +import com.idormy.sms.forwarder.server.model.CallQueryData +import com.idormy.sms.forwarder.utils.* +import com.jeremyliao.liveeventbus.LiveEventBus +import com.scwang.smartrefresh.layout.api.RefreshLayout +import com.xuexiang.xaop.annotation.SingleClick +import com.xuexiang.xhttp2.XHttp +import com.xuexiang.xhttp2.cache.model.CacheMode +import com.xuexiang.xhttp2.callback.SimpleCallBack +import com.xuexiang.xhttp2.exception.ApiException +import com.xuexiang.xpage.annotation.Page +import com.xuexiang.xpage.base.XPageActivity +import com.xuexiang.xpage.core.PageOption +import com.xuexiang.xrouter.utils.TextUtils +import com.xuexiang.xui.adapter.recyclerview.RecyclerViewHolder +import com.xuexiang.xui.utils.ResUtils +import com.xuexiang.xui.utils.SnackbarUtils +import com.xuexiang.xui.widget.actionbar.TitleBar +import com.xuexiang.xui.widget.searchview.MaterialSearchView +import com.xuexiang.xutil.data.DateUtils +import com.xuexiang.xutil.system.ClipboardUtils +import me.samlss.broccoli.Broccoli + +@Suppress("PropertyName") +@Page(name = "远程查通话") +class CallQueryFragment : BaseFragment() { + + val TAG: String = CallQueryFragment::class.java.simpleName + private var mAdapter: SimpleDelegateAdapter? = null + private var callType: Int = 3 + private var pageNum: Int = 1 + private val pageSize: Int = 20 + private var keyword: String = "" + + override fun viewBindingInflate( + inflater: LayoutInflater, + container: ViewGroup, + ): FragmentClientCallQueryBinding { + return FragmentClientCallQueryBinding.inflate(inflater, container, false) + } + + override fun initTitle(): TitleBar { + val titleBar = super.initTitle()!!.setImmersive(false) + titleBar.setTitle(R.string.api_call_query) + titleBar!!.addAction(object : TitleBar.ImageAction(R.drawable.ic_query) { + @SingleClick + override fun performAction(view: View) { + binding!!.searchView.showSearch() + } + }) + return titleBar + } + + /** + * 初始化控件 + */ + override fun initViews() { + val virtualLayoutManager = VirtualLayoutManager(requireContext()) + binding!!.recyclerView.layoutManager = virtualLayoutManager + val viewPool = RecyclerView.RecycledViewPool() + binding!!.recyclerView.setRecycledViewPool(viewPool) + viewPool.setMaxRecycledViews(0, 10) + + mAdapter = object : BroccoliSimpleDelegateAdapter( + R.layout.adapter_call_card_view_list_item, + LinearLayoutHelper(), + DataProvider.emptyCallInfo + ) { + override fun onBindData( + holder: RecyclerViewHolder, + model: CallInfo, + position: Int, + ) { + val from = if (TextUtils.isEmpty(model.name)) model.number else model.number + " | " + model.name + holder.text(R.id.tv_from, from) + holder.text(R.id.tv_time, DateUtils.getFriendlyTimeSpanByNow(model.dateLong)) + holder.image(R.id.iv_image, model.typeImageId) + holder.image(R.id.iv_sim_image, model.simImageId) + holder.text(R.id.tv_duration, ResUtils.getString(R.string.call_duration) + model.duration + ResUtils.getString(R.string.seconds)) + holder.image(R.id.iv_copy, R.drawable.ic_copy) + holder.image(R.id.iv_call, R.drawable.ic_phone_out) + holder.image(R.id.iv_reply, R.drawable.ic_reply) + holder.click(R.id.iv_copy) { + XToastUtils.info("已经复制到剪贴板!\n$from") + ClipboardUtils.copyText(from) + } + holder.click(R.id.iv_call) { + XToastUtils.info("本地呼叫:" + model.number) + PhoneUtils.dial(model.number) + } + holder.click(R.id.iv_reply) { + XToastUtils.info("远程发短信:" + model.number) + LiveEventBus.get(EVENT_KEY_PHONE_NUMBERS).post(model.number) + PageOption.to(SmsSendFragment::class.java).setNewActivity(true).open((context as XPageActivity?)!!) + } + } + + override fun onBindBroccoli(holder: RecyclerViewHolder, broccoli: Broccoli) { + broccoli.addPlaceholder(PlaceholderHelper.getParameter(holder.findView(R.id.tv_from))) + .addPlaceholder(PlaceholderHelper.getParameter(holder.findView(R.id.tv_time))) + .addPlaceholder(PlaceholderHelper.getParameter(holder.findView(R.id.iv_sim_image))) + .addPlaceholder(PlaceholderHelper.getParameter(holder.findView(R.id.tv_duration))) + .addPlaceholder(PlaceholderHelper.getParameter(holder.findView(R.id.iv_image))) + .addPlaceholder(PlaceholderHelper.getParameter(holder.findView(R.id.iv_copy))) + .addPlaceholder(PlaceholderHelper.getParameter(holder.findView(R.id.iv_call))) + .addPlaceholder(PlaceholderHelper.getParameter(holder.findView(R.id.iv_reply))) + } + } + + val delegateAdapter = DelegateAdapter(virtualLayoutManager) + delegateAdapter.addAdapter(mAdapter) + binding!!.recyclerView.adapter = delegateAdapter + + binding!!.tabBar.setTabTitles(ResUtils.getStringArray(R.array.call_type_option)) + binding!!.tabBar.setOnTabClickListener { _, position -> + //XToastUtils.toast("点击了$title--$position") + callType = 3 - position + pageNum = 1 + loadRemoteData(true) + binding!!.recyclerView.scrollToPosition(0) + } + + //搜索框 + binding!!.searchView.findViewById(com.xuexiang.xui.R.id.search_layout).visibility = View.GONE + binding!!.searchView.setVoiceSearch(true) + binding!!.searchView.setEllipsize(true) + binding!!.searchView.setSuggestions(resources.getStringArray(R.array.query_suggestions)) + binding!!.searchView.setOnQueryTextListener(object : MaterialSearchView.OnQueryTextListener { + override fun onQueryTextSubmit(query: String): Boolean { + SnackbarUtils.Indefinite(view, "搜索关键字: $query").info() + .actionColor(ResUtils.getColor(R.color.xui_config_color_white)) + .setAction("清除") { + keyword = "" + loadRemoteData(true) + }.show() + if (keyword != query) { + keyword = query + loadRemoteData(true) + } + return false + } + + override fun onQueryTextChange(newText: String): Boolean { + //Do some magic + return false + } + }) + binding!!.searchView.setOnSearchViewListener(object : MaterialSearchView.SearchViewListener { + override fun onSearchViewShown() { + //Do some magic + } + + override fun onSearchViewClosed() { + //Do some magic + } + }) + binding!!.searchView.setSubmitOnClick(true) + } + + override fun initListeners() { + //下拉刷新 + binding!!.refreshLayout.setOnRefreshListener { refreshLayout: RefreshLayout -> + refreshLayout.layout.postDelayed({ + pageNum = 1 + loadRemoteData(true) + }, 1000) + } + //上拉加载 + binding!!.refreshLayout.setOnLoadMoreListener { refreshLayout: RefreshLayout -> + refreshLayout.layout.postDelayed({ + loadRemoteData(false) + }, 1000) + } + binding!!.refreshLayout.autoRefresh() //第一次进入触发自动刷新,演示效果 + } + + private fun loadRemoteData(refresh: Boolean) { + + val requestUrl: String = HttpServerUtils.serverAddress + "/call/query" + Log.i(TAG, "requestUrl:$requestUrl") + + val msgMap: MutableMap = mutableMapOf() + val timestamp = System.currentTimeMillis() + msgMap["timestamp"] = timestamp + val clientSignKey = HttpServerUtils.clientSignKey + if (!TextUtils.isEmpty(clientSignKey)) { + msgMap["sign"] = HttpServerUtils.calcSign(timestamp.toString(), clientSignKey.toString()) + } + + /*val dataMap: MutableMap = mutableMapOf() + dataMap["type"] = callType + dataMap["page_num"] = pageNum + dataMap["page_size"] = pageSize + msgMap["data"] = dataMap*/ + msgMap["data"] = CallQueryData(callType, pageNum, pageSize, keyword) + + val requestMsg: String = Gson().toJson(msgMap) + Log.i(TAG, "requestMsg:$requestMsg") + + XHttp.post(requestUrl) + .upJson(requestMsg) + .keepJson(true) + .timeOut((SettingUtils.requestTimeout * 1000).toLong()) //超时时间10s + .cacheMode(CacheMode.NO_CACHE) + //.retryCount(SettingUtils.requestRetryTimes) //超时重试的次数 + //.retryDelay(SettingUtils.requestDelayTime) //超时重试的延迟时间 + //.retryIncreaseDelay(SettingUtils.requestDelayTime) //超时重试叠加延时 + .timeStamp(true) + .execute(object : SimpleCallBack() { + + override fun onError(e: ApiException) { + XToastUtils.error(e.displayMessage) + } + + override fun onSuccess(response: String) { + Log.i(TAG, response) + try { + val resp: BaseResponse?> = Gson().fromJson(response, object : TypeToken?>>() {}.type) + if (resp.code == 200) { + //XToastUtils.success(ResUtils.getString(R.string.request_succeeded)) + pageNum++ + if (refresh) { + mAdapter!!.refresh(resp.data) + binding!!.refreshLayout.finishRefresh() + binding!!.recyclerView.scrollToPosition(0) + } else { + mAdapter!!.loadMore(resp.data) + binding!!.refreshLayout.finishLoadMore() + } + } else { + XToastUtils.error(ResUtils.getString(R.string.request_failed) + resp.msg) + } + } catch (e: Exception) { + e.printStackTrace() + XToastUtils.error(ResUtils.getString(R.string.request_failed) + response) + } + } + + }) + + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/fragment/client/CloneFragment.kt b/app/src/main/java/com/idormy/sms/forwarder/fragment/client/CloneFragment.kt new file mode 100644 index 00000000..fded53ad --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/fragment/client/CloneFragment.kt @@ -0,0 +1,295 @@ +package com.idormy.sms.forwarder.fragment.client + +import android.annotation.SuppressLint +import android.os.Environment +import android.util.Log +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import com.google.gson.Gson +import com.google.gson.GsonBuilder +import com.google.gson.JsonDeserializer +import com.google.gson.reflect.TypeToken +import com.hjq.permissions.OnPermissionCallback +import com.hjq.permissions.Permission +import com.hjq.permissions.XXPermissions +import com.idormy.sms.forwarder.R +import com.idormy.sms.forwarder.core.BaseFragment +import com.idormy.sms.forwarder.databinding.FragmentClientCloneBinding +import com.idormy.sms.forwarder.entity.CloneInfo +import com.idormy.sms.forwarder.server.model.BaseResponse +import com.idormy.sms.forwarder.utils.HttpServerUtils +import com.idormy.sms.forwarder.utils.SettingUtils +import com.idormy.sms.forwarder.utils.XToastUtils +import com.xuexiang.xaop.annotation.SingleClick +import com.xuexiang.xhttp2.XHttp +import com.xuexiang.xhttp2.cache.model.CacheMode +import com.xuexiang.xhttp2.callback.SimpleCallBack +import com.xuexiang.xhttp2.exception.ApiException +import com.xuexiang.xpage.annotation.Page +import com.xuexiang.xrouter.utils.TextUtils +import com.xuexiang.xui.utils.ResUtils +import com.xuexiang.xui.widget.actionbar.TitleBar +import com.xuexiang.xutil.app.AppUtils +import com.xuexiang.xutil.file.FileIOUtils +import com.xuexiang.xutil.file.FileUtils +import java.io.File +import java.util.* + + +@Suppress("PropertyName") +@Page(name = "一键换新机") +class CloneFragment : BaseFragment(), View.OnClickListener { + + val TAG: String = SmsQueryFragment::class.java.simpleName + private var backupPath: String? = null + private val backupFile = "SmsForwarder.json" + + override fun viewBindingInflate( + inflater: LayoutInflater, + container: ViewGroup, + ): FragmentClientCloneBinding { + return FragmentClientCloneBinding.inflate(inflater, container, false) + } + + override fun initTitle(): TitleBar? { + val titleBar = super.initTitle()!!.setImmersive(false) + titleBar.setTitle(R.string.api_clone) + return titleBar + } + + /** + * 初始化控件 + */ + override fun initViews() { + // 申请储存权限 + XXPermissions.with(this) + //.permission(*Permission.Group.STORAGE) + .permission(Permission.MANAGE_EXTERNAL_STORAGE) + .request(object : OnPermissionCallback { + @SuppressLint("SetTextI18n") + override fun onGranted(permissions: List, all: Boolean) { + backupPath = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).path + binding!!.tvBackupPath.text = backupPath + File.separator + backupFile + } + + override fun onDenied(permissions: List, never: Boolean) { + if (never) { + XToastUtils.error(R.string.toast_denied_never) + // 如果是被永久拒绝就跳转到应用权限系统设置页面 + XXPermissions.startPermissionActivity(requireContext(), permissions) + } else { + XToastUtils.error(R.string.toast_denied) + } + binding!!.tvBackupPath.text = "未授权储存权限,该功能无法使用!" + } + }) + + binding!!.tabBar.setTabTitles(ResUtils.getStringArray(R.array.clone_type_option)) + binding!!.tabBar.setOnTabClickListener { _, position -> + //XToastUtils.toast("点击了$title--$position") + if (position == 1) { + binding!!.layoutNetwork.visibility = View.GONE + binding!!.layoutOffline.visibility = View.VISIBLE + } else { + binding!!.layoutNetwork.visibility = View.VISIBLE + binding!!.layoutOffline.visibility = View.GONE + } + } + } + + override fun initListeners() { + binding!!.btnPush.setOnClickListener(this) + binding!!.btnPull.setOnClickListener(this) + binding!!.btnExport.setOnClickListener(this) + binding!!.btnImport.setOnClickListener(this) + } + + @SingleClick + override fun onClick(v: View) { + when (v.id) { + //推送配置 + R.id.btn_push -> pushData() + //拉取配置 + R.id.btn_pull -> pullData() + //导出配置 + R.id.btn_export -> { + try { + val file = File(backupPath + File.separator + backupFile) + //判断文件是否存在,存在则在创建之前删除 + FileUtils.createFileByDeleteOldFile(file) + val cloneInfo = HttpServerUtils.exportSettings() + val jsonStr = Gson().toJson(cloneInfo) + if (FileIOUtils.writeFileFromString(file, jsonStr)) { + XToastUtils.success("导出配置成功!") + } else { + binding!!.tvExport.text = "导出失败,请检查写入权限!" + XToastUtils.error("导出失败,请检查写入权限!") + } + } catch (e: Exception) { + XToastUtils.error("导出失败:" + e.message) + } + } + //导入配置 + R.id.btn_import -> { + try { + val file = File(backupPath + File.separator + backupFile) + //判断文件是否存在 + if (!FileUtils.isFileExists(file)) { + XToastUtils.error("导入失败:本地备份文件不存在!") + return + } + + val jsonStr = FileIOUtils.readFile2String(file) + Log.d(TAG, "jsonStr = $jsonStr") + if (TextUtils.isEmpty(jsonStr)) { + XToastUtils.error("导入失败:请检查是否有外部存储访问权限!") + return + } + + //替换Date字段为当前时间 + val builder = GsonBuilder() + builder.registerTypeAdapter(Date::class.java, JsonDeserializer { _, _, _ -> Date() }) + val gson = builder.create() + val cloneInfo = gson.fromJson(jsonStr, CloneInfo::class.java) + Log.d(TAG, "cloneInfo = $cloneInfo") + + //判断版本是否一致 + HttpServerUtils.compareVersion(cloneInfo) + + if (HttpServerUtils.restoreSettings(cloneInfo)) { + XToastUtils.success("导入配置成功!") + } else { + XToastUtils.error("导入失败") + } + } catch (e: Exception) { + XToastUtils.error("导入失败:" + e.message) + } + } + } + } + + //推送配置 + private fun pushData() { + + val requestUrl: String = HttpServerUtils.serverAddress + "/clone/push" + Log.i(TAG, "requestUrl:$requestUrl") + + val msgMap: MutableMap = mutableMapOf() + val timestamp = System.currentTimeMillis() + msgMap["timestamp"] = timestamp + val clientSignKey = HttpServerUtils.clientSignKey + if (!TextUtils.isEmpty(clientSignKey)) { + msgMap["sign"] = HttpServerUtils.calcSign(timestamp.toString(), clientSignKey.toString()) + } + msgMap["data"] = HttpServerUtils.exportSettings() + + val requestMsg: String = Gson().toJson(msgMap) + Log.i(TAG, "requestMsg:$requestMsg") + + XHttp.post(requestUrl) + .upJson(requestMsg) + .keepJson(true) + .timeOut((SettingUtils.requestTimeout * 1000).toLong()) //超时时间10s + .cacheMode(CacheMode.NO_CACHE) + //.retryCount(SettingUtils.requestRetryTimes) //超时重试的次数 + //.retryDelay(SettingUtils.requestDelayTime) //超时重试的延迟时间 + //.retryIncreaseDelay(SettingUtils.requestDelayTime) //超时重试叠加延时 + .timeStamp(true) + .execute(object : SimpleCallBack() { + + override fun onError(e: ApiException) { + XToastUtils.error(e.displayMessage) + } + + override fun onSuccess(response: String) { + Log.i(TAG, response) + try { + val resp: BaseResponse = Gson().fromJson(response, object : TypeToken>() {}.type) + if (resp.code == 200) { + XToastUtils.success(ResUtils.getString(R.string.request_succeeded)) + } else { + XToastUtils.error(ResUtils.getString(R.string.request_failed) + resp.msg) + } + } catch (e: Exception) { + e.printStackTrace() + XToastUtils.error(ResUtils.getString(R.string.request_failed) + response) + } + } + + }) + + } + + //拉取配置 + private fun pullData() { + + val requestUrl: String = HttpServerUtils.serverAddress + "/clone/pull" + Log.i(TAG, "requestUrl:$requestUrl") + + val msgMap: MutableMap = mutableMapOf() + val timestamp = System.currentTimeMillis() + msgMap["timestamp"] = timestamp + val clientSignKey = HttpServerUtils.clientSignKey + if (!TextUtils.isEmpty(clientSignKey)) { + msgMap["sign"] = HttpServerUtils.calcSign(timestamp.toString(), clientSignKey.toString()) + } + + val dataMap: MutableMap = mutableMapOf() + dataMap["version_code"] = AppUtils.getAppVersionCode() + msgMap["data"] = dataMap + + val requestMsg: String = Gson().toJson(msgMap) + Log.i(TAG, "requestMsg:$requestMsg") + + XHttp.post(requestUrl) + .upJson(requestMsg) + .keepJson(true) + .timeOut((SettingUtils.requestTimeout * 1000).toLong()) //超时时间10s + .cacheMode(CacheMode.NO_CACHE) + //.retryCount(SettingUtils.requestRetryTimes) //超时重试的次数 + //.retryDelay(SettingUtils.requestDelayTime) //超时重试的延迟时间 + //.retryIncreaseDelay(SettingUtils.requestDelayTime) //超时重试叠加延时 + .timeStamp(true) + .execute(object : SimpleCallBack() { + + override fun onError(e: ApiException) { + XToastUtils.error(e.displayMessage) + } + + override fun onSuccess(response: String) { + Log.i(TAG, response) + try { + //替换Date字段为当前时间 + val builder = GsonBuilder() + builder.registerTypeAdapter(Date::class.java, JsonDeserializer { _, _, _ -> Date() }) + val gson = builder.create() + val resp: BaseResponse = gson.fromJson(response, object : TypeToken>() {}.type) + if (resp.code == 200) { + val cloneInfo = resp.data + Log.d(TAG, "cloneInfo = $cloneInfo") + + if (cloneInfo == null) { + XToastUtils.error(ResUtils.getString(R.string.request_failed) + cloneInfo) + return + } + + //判断版本是否一致 + HttpServerUtils.compareVersion(cloneInfo) + + if (HttpServerUtils.restoreSettings(cloneInfo)) { + XToastUtils.success("导入配置成功!") + } + } else { + XToastUtils.error(ResUtils.getString(R.string.request_failed) + resp.msg) + } + } catch (e: Exception) { + e.printStackTrace() + XToastUtils.error(ResUtils.getString(R.string.request_failed) + response) + } + } + + }) + + } +} \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/fragment/client/ContactQueryFragment.kt b/app/src/main/java/com/idormy/sms/forwarder/fragment/client/ContactQueryFragment.kt new file mode 100644 index 00000000..0236befa --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/fragment/client/ContactQueryFragment.kt @@ -0,0 +1,233 @@ +package com.idormy.sms.forwarder.fragment.client + +import android.util.Log +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.core.text.isDigitsOnly +import androidx.recyclerview.widget.RecyclerView +import com.alibaba.android.vlayout.DelegateAdapter +import com.alibaba.android.vlayout.VirtualLayoutManager +import com.alibaba.android.vlayout.layout.LinearLayoutHelper +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import com.idormy.sms.forwarder.R +import com.idormy.sms.forwarder.adapter.base.broccoli.BroccoliSimpleDelegateAdapter +import com.idormy.sms.forwarder.adapter.base.delegate.SimpleDelegateAdapter +import com.idormy.sms.forwarder.core.BaseFragment +import com.idormy.sms.forwarder.databinding.FragmentClientContactQueryBinding +import com.idormy.sms.forwarder.entity.ContactInfo +import com.idormy.sms.forwarder.server.model.BaseResponse +import com.idormy.sms.forwarder.server.model.ContactQueryData +import com.idormy.sms.forwarder.utils.* +import com.jeremyliao.liveeventbus.LiveEventBus +import com.scwang.smartrefresh.layout.api.RefreshLayout +import com.xuexiang.xaop.annotation.SingleClick +import com.xuexiang.xhttp2.XHttp +import com.xuexiang.xhttp2.cache.model.CacheMode +import com.xuexiang.xhttp2.callback.SimpleCallBack +import com.xuexiang.xhttp2.exception.ApiException +import com.xuexiang.xpage.annotation.Page +import com.xuexiang.xpage.base.XPageActivity +import com.xuexiang.xpage.core.PageOption +import com.xuexiang.xrouter.utils.TextUtils +import com.xuexiang.xui.adapter.recyclerview.RecyclerViewHolder +import com.xuexiang.xui.utils.ResUtils +import com.xuexiang.xui.utils.SnackbarUtils +import com.xuexiang.xui.widget.actionbar.TitleBar +import com.xuexiang.xui.widget.searchview.MaterialSearchView +import com.xuexiang.xutil.system.ClipboardUtils +import me.samlss.broccoli.Broccoli + + +@Suppress("PropertyName") +@Page(name = "远程查话簿") +class ContactQueryFragment : BaseFragment() { + + val TAG: String = ContactQueryFragment::class.java.simpleName + private var mAdapter: SimpleDelegateAdapter? = null + private var keyword: String = "" + + override fun viewBindingInflate( + inflater: LayoutInflater, + container: ViewGroup, + ): FragmentClientContactQueryBinding { + return FragmentClientContactQueryBinding.inflate(inflater, container, false) + } + + override fun initTitle(): TitleBar { + val titleBar = super.initTitle()!!.setImmersive(false) + titleBar.setTitle(R.string.api_contact_query) + titleBar!!.addAction(object : TitleBar.ImageAction(R.drawable.ic_query) { + @SingleClick + override fun performAction(view: View) { + binding!!.searchView.showSearch() + } + }) + return titleBar + } + + /** + * 初始化控件 + */ + override fun initViews() { + val virtualLayoutManager = VirtualLayoutManager(requireContext()) + binding!!.recyclerView.layoutManager = virtualLayoutManager + val viewPool = RecyclerView.RecycledViewPool() + binding!!.recyclerView.setRecycledViewPool(viewPool) + viewPool.setMaxRecycledViews(0, 10) + + mAdapter = object : BroccoliSimpleDelegateAdapter( + R.layout.adapter_contact_card_view_list_item, + LinearLayoutHelper(), + DataProvider.emptyContactInfo + ) { + override fun onBindData( + holder: RecyclerViewHolder, + model: ContactInfo, + position: Int, + ) { + holder.text(R.id.sb_letter, model.firstLetter) + holder.text(R.id.tv_name, model.name) + holder.text(R.id.tv_phone_number, model.phoneNumber) + holder.image(R.id.iv_copy, R.drawable.ic_copy) + holder.image(R.id.iv_call, R.drawable.ic_phone_out) + holder.image(R.id.iv_reply, R.drawable.ic_reply) + holder.click(R.id.iv_copy) { + val str = model.toString() + XToastUtils.info("已经复制到剪贴板!\n$str") + ClipboardUtils.copyText(str) + } + holder.click(R.id.iv_call) { + XToastUtils.info("本地呼叫:" + model.phoneNumber) + PhoneUtils.dial(model.phoneNumber) + } + holder.click(R.id.iv_reply) { + XToastUtils.info("远程发短信:" + model.phoneNumber) + /*val params = Bundle() + params.putString(KEY_PHONE_NUMBERS, model.phoneNumber) + openPage(SmsSendFragment::class.java, params)*/ + LiveEventBus.get(EVENT_KEY_PHONE_NUMBERS).post(model.phoneNumber) + PageOption.to(SmsSendFragment::class.java).setNewActivity(true).open((context as XPageActivity?)!!) + } + } + + override fun onBindBroccoli(holder: RecyclerViewHolder, broccoli: Broccoli) { + broccoli.addPlaceholder(PlaceholderHelper.getParameter(holder.findView(R.id.tv_name))) + .addPlaceholder(PlaceholderHelper.getParameter(holder.findView(R.id.tv_phone_number))) + .addPlaceholder(PlaceholderHelper.getParameter(holder.findView(R.id.sb_letter))) + .addPlaceholder(PlaceholderHelper.getParameter(holder.findView(R.id.iv_copy))) + .addPlaceholder(PlaceholderHelper.getParameter(holder.findView(R.id.iv_call))) + .addPlaceholder(PlaceholderHelper.getParameter(holder.findView(R.id.iv_reply))) + } + } + + val delegateAdapter = DelegateAdapter(virtualLayoutManager) + delegateAdapter.addAdapter(mAdapter) + binding!!.recyclerView.adapter = delegateAdapter + + //搜索框 + binding!!.searchView.findViewById(com.xuexiang.xui.R.id.search_layout).visibility = View.GONE + binding!!.searchView.setVoiceSearch(true) + binding!!.searchView.setEllipsize(true) + binding!!.searchView.setSuggestions(resources.getStringArray(R.array.query_suggestions)) + binding!!.searchView.setOnQueryTextListener(object : MaterialSearchView.OnQueryTextListener { + override fun onQueryTextSubmit(query: String): Boolean { + SnackbarUtils.Indefinite(view, "搜索关键字: $query").info() + .actionColor(ResUtils.getColor(R.color.xui_config_color_white)) + .setAction("清除") { + keyword = "" + loadRemoteData() + }.show() + if (keyword != query) { + keyword = query + loadRemoteData() + } + return false + } + + override fun onQueryTextChange(newText: String): Boolean { + //Do some magic + return false + } + }) + binding!!.searchView.setOnSearchViewListener(object : MaterialSearchView.SearchViewListener { + override fun onSearchViewShown() { + //Do some magic + } + + override fun onSearchViewClosed() { + //Do some magic + } + }) + binding!!.searchView.setSubmitOnClick(true) + } + + override fun initListeners() { + //下拉刷新 + binding!!.refreshLayout.setOnRefreshListener { refreshLayout: RefreshLayout -> + refreshLayout.layout.postDelayed({ + loadRemoteData() + }, 1000) + } + binding!!.refreshLayout.autoRefresh() //第一次进入触发自动刷新,演示效果 + } + + private fun loadRemoteData() { + + val requestUrl: String = HttpServerUtils.serverAddress + "/contact/query" + Log.i(TAG, "requestUrl:$requestUrl") + + val msgMap: MutableMap = mutableMapOf() + val timestamp = System.currentTimeMillis() + msgMap["timestamp"] = timestamp + val clientSignKey = HttpServerUtils.clientSignKey + if (!TextUtils.isEmpty(clientSignKey)) { + msgMap["sign"] = HttpServerUtils.calcSign(timestamp.toString(), clientSignKey.toString()) + } + msgMap["data"] = if (keyword.isDigitsOnly()) + ContactQueryData(1, 20, keyword, null) + else + ContactQueryData(1, 20, null, keyword) + + val requestMsg: String = Gson().toJson(msgMap) + Log.i(TAG, "requestMsg:$requestMsg") + + XHttp.post(requestUrl) + .upJson(requestMsg) + .keepJson(true) + .timeOut((SettingUtils.requestTimeout * 1000).toLong()) //超时时间10s + .cacheMode(CacheMode.NO_CACHE) + //.retryCount(SettingUtils.requestRetryTimes) //超时重试的次数 + //.retryDelay(SettingUtils.requestDelayTime) //超时重试的延迟时间 + //.retryIncreaseDelay(SettingUtils.requestDelayTime) //超时重试叠加延时 + .timeStamp(true) + .execute(object : SimpleCallBack() { + + override fun onError(e: ApiException) { + XToastUtils.error(e.displayMessage) + } + + override fun onSuccess(response: String) { + Log.i(TAG, response) + try { + val resp: BaseResponse?> = Gson().fromJson(response, object : TypeToken?>>() {}.type) + if (resp.code == 200) { + //XToastUtils.success(ResUtils.getString(R.string.request_succeeded)) + mAdapter!!.refresh(resp.data) + binding!!.refreshLayout.finishRefresh() + binding!!.recyclerView.scrollToPosition(0) + } else { + XToastUtils.error(ResUtils.getString(R.string.request_failed) + resp.msg) + } + } catch (e: Exception) { + e.printStackTrace() + XToastUtils.error(ResUtils.getString(R.string.request_failed) + response) + } + } + + }) + + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/fragment/client/SmsQueryFragment.kt b/app/src/main/java/com/idormy/sms/forwarder/fragment/client/SmsQueryFragment.kt new file mode 100644 index 00000000..3e99979e --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/fragment/client/SmsQueryFragment.kt @@ -0,0 +1,252 @@ +package com.idormy.sms.forwarder.fragment.client + +import android.util.Log +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.recyclerview.widget.RecyclerView +import com.alibaba.android.vlayout.DelegateAdapter +import com.alibaba.android.vlayout.VirtualLayoutManager +import com.alibaba.android.vlayout.layout.LinearLayoutHelper +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import com.idormy.sms.forwarder.R +import com.idormy.sms.forwarder.adapter.base.broccoli.BroccoliSimpleDelegateAdapter +import com.idormy.sms.forwarder.adapter.base.delegate.SimpleDelegateAdapter +import com.idormy.sms.forwarder.core.BaseFragment +import com.idormy.sms.forwarder.databinding.FragmentClientSmsQueryBinding +import com.idormy.sms.forwarder.entity.SmsInfo +import com.idormy.sms.forwarder.server.model.BaseResponse +import com.idormy.sms.forwarder.server.model.SmsQueryData +import com.idormy.sms.forwarder.utils.* +import com.idormy.sms.forwarder.utils.DataProvider.emptySmsInfo +import com.jeremyliao.liveeventbus.LiveEventBus +import com.scwang.smartrefresh.layout.api.RefreshLayout +import com.xuexiang.xaop.annotation.SingleClick +import com.xuexiang.xhttp2.XHttp +import com.xuexiang.xhttp2.cache.model.CacheMode +import com.xuexiang.xhttp2.callback.SimpleCallBack +import com.xuexiang.xhttp2.exception.ApiException +import com.xuexiang.xpage.annotation.Page +import com.xuexiang.xpage.base.XPageActivity +import com.xuexiang.xpage.core.PageOption +import com.xuexiang.xrouter.utils.TextUtils +import com.xuexiang.xui.adapter.recyclerview.RecyclerViewHolder +import com.xuexiang.xui.utils.ResUtils +import com.xuexiang.xui.utils.SnackbarUtils +import com.xuexiang.xui.widget.actionbar.TitleBar +import com.xuexiang.xui.widget.searchview.MaterialSearchView +import com.xuexiang.xui.widget.searchview.MaterialSearchView.SearchViewListener +import com.xuexiang.xutil.data.DateUtils +import me.samlss.broccoli.Broccoli + + +@Suppress("PropertyName") +@Page(name = "远程查短信") +class SmsQueryFragment : BaseFragment() { + + val TAG: String = SmsQueryFragment::class.java.simpleName + private var mAdapter: SimpleDelegateAdapter? = null + private var smsType: Int = 1 + private var pageNum: Int = 1 + private val pageSize: Int = 20 + private var keyword: String = "" + + override fun viewBindingInflate( + inflater: LayoutInflater, + container: ViewGroup, + ): FragmentClientSmsQueryBinding { + return FragmentClientSmsQueryBinding.inflate(inflater, container, false) + } + + override fun initTitle(): TitleBar { + val titleBar = super.initTitle()!!.setImmersive(false) + titleBar.setTitle(R.string.api_sms_query) + titleBar!!.addAction(object : TitleBar.ImageAction(R.drawable.ic_query) { + @SingleClick + override fun performAction(view: View) { + binding!!.searchView.showSearch() + } + }) + return titleBar + } + + /** + * 初始化控件 + */ + override fun initViews() { + val virtualLayoutManager = VirtualLayoutManager(requireContext()) + binding!!.recyclerView.layoutManager = virtualLayoutManager + val viewPool = RecyclerView.RecycledViewPool() + binding!!.recyclerView.setRecycledViewPool(viewPool) + viewPool.setMaxRecycledViews(0, 10) + + mAdapter = object : BroccoliSimpleDelegateAdapter( + R.layout.adapter_sms_card_view_list_item, + LinearLayoutHelper(), + emptySmsInfo + ) { + override fun onBindData( + holder: RecyclerViewHolder, + model: SmsInfo, + position: Int, + ) { + holder.text(R.id.tv_from, model.number) + holder.text(R.id.tv_time, DateUtils.getFriendlyTimeSpanByNow(model.date)) + holder.image(R.id.iv_image, model.typeImageId) + holder.image(R.id.iv_sim_image, model.simImageId) + holder.text(R.id.tv_content, model.content) + holder.image(R.id.iv_reply, R.drawable.ic_reply) + holder.click(R.id.iv_reply) { + XToastUtils.info("远程回短信:" + model.number) + LiveEventBus.get(EVENT_KEY_PHONE_NUMBERS).post(model.number) + PageOption.to(SmsSendFragment::class.java).setNewActivity(true).open((context as XPageActivity?)!!) + } + } + + override fun onBindBroccoli(holder: RecyclerViewHolder, broccoli: Broccoli) { + broccoli.addPlaceholder(PlaceholderHelper.getParameter(holder.findView(R.id.tv_from))) + .addPlaceholder(PlaceholderHelper.getParameter(holder.findView(R.id.tv_time))) + .addPlaceholder(PlaceholderHelper.getParameter(holder.findView(R.id.iv_sim_image))) + .addPlaceholder(PlaceholderHelper.getParameter(holder.findView(R.id.tv_content))) + .addPlaceholder(PlaceholderHelper.getParameter(holder.findView(R.id.iv_image))) + .addPlaceholder(PlaceholderHelper.getParameter(holder.findView(R.id.iv_reply))) + } + + } + + val delegateAdapter = DelegateAdapter(virtualLayoutManager) + delegateAdapter.addAdapter(mAdapter) + binding!!.recyclerView.adapter = delegateAdapter + + binding!!.tabBar.setTabTitles(ResUtils.getStringArray(R.array.sms_type_option)) + binding!!.tabBar.setOnTabClickListener { _, position -> + //XToastUtils.toast("点击了$title--$position") + smsType = position + 1 + pageNum = 1 + loadRemoteData(true) + binding!!.recyclerView.scrollToPosition(0) + } + + //搜索框 + binding!!.searchView.findViewById(com.xuexiang.xui.R.id.search_layout).visibility = View.GONE + binding!!.searchView.setVoiceSearch(true) + binding!!.searchView.setEllipsize(true) + binding!!.searchView.setSuggestions(resources.getStringArray(R.array.query_suggestions)) + binding!!.searchView.setOnQueryTextListener(object : MaterialSearchView.OnQueryTextListener { + override fun onQueryTextSubmit(query: String): Boolean { + SnackbarUtils.Indefinite(view, "搜索关键字: $query").info() + .actionColor(ResUtils.getColor(R.color.xui_config_color_white)) + .setAction("清除") { + keyword = "" + loadRemoteData(true) + }.show() + if (keyword != query) { + keyword = query + loadRemoteData(true) + } + return false + } + + override fun onQueryTextChange(newText: String): Boolean { + //Do some magic + return false + } + }) + binding!!.searchView.setOnSearchViewListener(object : SearchViewListener { + override fun onSearchViewShown() { + //Do some magic + } + + override fun onSearchViewClosed() { + //Do some magic + } + }) + binding!!.searchView.setSubmitOnClick(true) + } + + override fun initListeners() { + //下拉刷新 + binding!!.refreshLayout.setOnRefreshListener { refreshLayout: RefreshLayout -> + refreshLayout.layout.postDelayed({ + pageNum = 1 + loadRemoteData(true) + }, 1000) + } + //上拉加载 + binding!!.refreshLayout.setOnLoadMoreListener { refreshLayout: RefreshLayout -> + refreshLayout.layout.postDelayed({ + loadRemoteData(false) + }, 1000) + } + binding!!.refreshLayout.autoRefresh() //第一次进入触发自动刷新,演示效果 + } + + private fun loadRemoteData(refresh: Boolean) { + + val requestUrl: String = HttpServerUtils.serverAddress + "/sms/query" + Log.i(TAG, "requestUrl:$requestUrl") + + val msgMap: MutableMap = mutableMapOf() + val timestamp = System.currentTimeMillis() + msgMap["timestamp"] = timestamp + val clientSignKey = HttpServerUtils.clientSignKey + if (!TextUtils.isEmpty(clientSignKey)) { + msgMap["sign"] = HttpServerUtils.calcSign(timestamp.toString(), clientSignKey.toString()) + } + + /*val dataMap: MutableMap = mutableMapOf() + dataMap["type"] = smsType + dataMap["page_num"] = pageNum + dataMap["page_size"] = pageSize + dataMap["keyword"] = keyword + msgMap["data"] = dataMap*/ + msgMap["data"] = SmsQueryData(smsType, pageNum, pageSize, keyword) + + val requestMsg: String = Gson().toJson(msgMap) + Log.i(TAG, "requestMsg:$requestMsg") + + XHttp.post(requestUrl) + .upJson(requestMsg) + .keepJson(true) + .timeOut((SettingUtils.requestTimeout * 1000).toLong()) //超时时间10s + .cacheMode(CacheMode.NO_CACHE) + //.retryCount(SettingUtils.requestRetryTimes) //超时重试的次数 + //.retryDelay(SettingUtils.requestDelayTime) //超时重试的延迟时间 + //.retryIncreaseDelay(SettingUtils.requestDelayTime) //超时重试叠加延时 + .timeStamp(true) + .execute(object : SimpleCallBack() { + + override fun onError(e: ApiException) { + XToastUtils.error(e.displayMessage) + } + + override fun onSuccess(response: String) { + Log.i(TAG, response) + try { + val resp: BaseResponse?> = Gson().fromJson(response, object : TypeToken?>>() {}.type) + if (resp.code == 200) { + //XToastUtils.success(ResUtils.getString(R.string.request_succeeded)) + pageNum++ + if (refresh) { + mAdapter!!.refresh(resp.data) + binding!!.refreshLayout.finishRefresh() + binding!!.recyclerView.scrollToPosition(0) + } else { + mAdapter!!.loadMore(resp.data) + binding!!.refreshLayout.finishLoadMore() + } + } else { + XToastUtils.error(ResUtils.getString(R.string.request_failed) + resp.msg) + } + } catch (e: Exception) { + e.printStackTrace() + XToastUtils.error(ResUtils.getString(R.string.request_failed) + response) + } + } + + }) + + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/fragment/client/SmsSendFragment.kt b/app/src/main/java/com/idormy/sms/forwarder/fragment/client/SmsSendFragment.kt new file mode 100644 index 00000000..be2a0b60 --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/fragment/client/SmsSendFragment.kt @@ -0,0 +1,132 @@ +package com.idormy.sms.forwarder.fragment.client + +import android.util.Log +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import com.idormy.sms.forwarder.R +import com.idormy.sms.forwarder.core.BaseFragment +import com.idormy.sms.forwarder.databinding.FragmentClientSmsSendBinding +import com.idormy.sms.forwarder.server.model.BaseResponse +import com.idormy.sms.forwarder.utils.* +import com.jeremyliao.liveeventbus.LiveEventBus +import com.xuexiang.xaop.annotation.SingleClick +import com.xuexiang.xhttp2.XHttp +import com.xuexiang.xhttp2.cache.model.CacheMode +import com.xuexiang.xhttp2.callback.SimpleCallBack +import com.xuexiang.xhttp2.exception.ApiException +import com.xuexiang.xpage.annotation.Page +import com.xuexiang.xrouter.utils.TextUtils +import com.xuexiang.xui.utils.ResUtils +import com.xuexiang.xui.widget.actionbar.TitleBar + +@Suppress("PropertyName") +@Page(name = "远程发短信") +class SmsSendFragment : BaseFragment(), View.OnClickListener { + + val TAG: String = SmsSendFragment::class.java.simpleName + + override fun viewBindingInflate( + inflater: LayoutInflater, + container: ViewGroup, + ): FragmentClientSmsSendBinding { + return FragmentClientSmsSendBinding.inflate(inflater, container, false) + } + + override fun initTitle(): TitleBar? { + return super.initTitle()!!.setImmersive(false).setTitle(R.string.api_sms_send) + } + + /** + * 初始化控件 + */ + override fun initViews() { + } + + override fun initListeners() { + binding!!.btnSubmit.setOnClickListener(this) + LiveEventBus.get(EVENT_KEY_SIM_SLOT, Int::class.java).observeSticky(this) { value: Int -> + binding!!.rgSimSlot.check(if (value == 2) R.id.rb_sim_slot_2 else R.id.rb_sim_slot_1) + } + LiveEventBus.get(EVENT_KEY_PHONE_NUMBERS, String::class.java).observeSticky(this) { value: String -> + binding!!.etPhoneNumbers.setText(value) + } + } + + @SingleClick + override fun onClick(v: View) { + when (v.id) { + R.id.btn_submit -> { + val requestUrl: String = HttpServerUtils.serverAddress + "/sms/send" + Log.i(TAG, "requestUrl:$requestUrl") + + val msgMap: MutableMap = mutableMapOf() + val timestamp = System.currentTimeMillis() + msgMap["timestamp"] = timestamp + val clientSignKey = HttpServerUtils.clientSignKey + if (!TextUtils.isEmpty(clientSignKey)) { + msgMap["sign"] = HttpServerUtils.calcSign(timestamp.toString(), clientSignKey.toString()) + } + + val phoneNumbers = binding!!.etPhoneNumbers.text.toString() + val phoneRegex = getString(R.string.phone_numbers_regex).toRegex() + if (!phoneRegex.matches(phoneNumbers)) { + XToastUtils.error(ResUtils.getString(R.string.phone_numbers_error)) + return + } + + val msgContent = binding!!.etMsgContent.text.toString() + val msgRegex = getString(R.string.msg_content_regex).toRegex() + if (!msgRegex.matches(msgContent)) { + XToastUtils.error(ResUtils.getString(R.string.msg_content_error)) + return + } + + val dataMap: MutableMap = mutableMapOf() + dataMap["sim_slot"] = if (binding!!.rgSimSlot.checkedRadioButtonId == R.id.rb_sim_slot_2) 2 else 1 + dataMap["phone_numbers"] = phoneNumbers + dataMap["msg_content"] = msgContent + msgMap["data"] = dataMap + + val requestMsg: String = Gson().toJson(msgMap) + Log.i(TAG, "requestMsg:$requestMsg") + + XHttp.post(requestUrl) + .upJson(requestMsg) + .keepJson(true) + .timeOut((SettingUtils.requestTimeout * 1000).toLong()) //超时时间10s + .cacheMode(CacheMode.NO_CACHE) + //.retryCount(SettingUtils.requestRetryTimes) //超时重试的次数 + //.retryDelay(SettingUtils.requestDelayTime) //超时重试的延迟时间 + //.retryIncreaseDelay(SettingUtils.requestDelayTime) //超时重试叠加延时 + .timeStamp(true) + .execute(object : SimpleCallBack() { + + override fun onError(e: ApiException) { + XToastUtils.error(e.displayMessage) + } + + override fun onSuccess(response: String) { + Log.i(TAG, response) + try { + val resp: BaseResponse = Gson().fromJson(response, object : TypeToken>() {}.type) + if (resp.code == 200) { + XToastUtils.success(ResUtils.getString(R.string.request_succeeded)) + } else { + XToastUtils.error(ResUtils.getString(R.string.request_failed) + resp.msg) + } + } catch (e: Exception) { + e.printStackTrace() + XToastUtils.error(ResUtils.getString(R.string.request_failed) + response) + } + } + + }) + } + else -> {} + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/fragment/senders/BarkFragment.kt b/app/src/main/java/com/idormy/sms/forwarder/fragment/senders/BarkFragment.kt new file mode 100644 index 00000000..13ca65ed --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/fragment/senders/BarkFragment.kt @@ -0,0 +1,240 @@ +package com.idormy.sms.forwarder.fragment.senders + +import android.text.TextUtils +import android.util.Log +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.EditText +import androidx.fragment.app.viewModels +import com.google.gson.Gson +import com.idormy.sms.forwarder.R +import com.idormy.sms.forwarder.core.BaseFragment +import com.idormy.sms.forwarder.database.AppDatabase +import com.idormy.sms.forwarder.database.entity.Sender +import com.idormy.sms.forwarder.database.viewmodel.BaseViewModelFactory +import com.idormy.sms.forwarder.database.viewmodel.SenderViewModel +import com.idormy.sms.forwarder.databinding.FragmentSendersBarkBinding +import com.idormy.sms.forwarder.entity.MsgInfo +import com.idormy.sms.forwarder.entity.setting.BarkSetting +import com.idormy.sms.forwarder.utils.* +import com.idormy.sms.forwarder.utils.sender.BarkUtils +import com.xuexiang.xaop.annotation.SingleClick +import com.xuexiang.xpage.annotation.Page +import com.xuexiang.xrouter.annotation.AutoWired +import com.xuexiang.xrouter.launcher.XRouter +import com.xuexiang.xui.widget.actionbar.TitleBar +import com.xuexiang.xui.widget.dialog.materialdialog.DialogAction +import com.xuexiang.xui.widget.dialog.materialdialog.MaterialDialog +import com.xuexiang.xui.widget.spinner.materialspinner.MaterialSpinner +import io.reactivex.SingleObserver +import io.reactivex.android.schedulers.AndroidSchedulers +import io.reactivex.disposables.Disposable +import io.reactivex.schedulers.Schedulers +import java.util.* + +@Page(name = "Bark") +@Suppress("PrivatePropertyName") +class BarkFragment : BaseFragment(), View.OnClickListener { + + private val TAG: String = BarkFragment::class.java.simpleName + var titleBar: TitleBar? = null + private val viewModel by viewModels { BaseViewModelFactory(context) } + private var barkLevel: String = "active" //通知级别 + + @JvmField + @AutoWired(name = KEY_SENDER_ID) + var senderId: Long = 0 + + @JvmField + @AutoWired(name = KEY_SENDER_TYPE) + var senderType: Int = 0 + + @JvmField + @AutoWired(name = KEY_SENDER_CLONE) + var isClone: Boolean = false + + override fun initArgs() { + XRouter.getInstance().inject(this) + } + + override fun viewBindingInflate( + inflater: LayoutInflater, + container: ViewGroup, + ): FragmentSendersBarkBinding { + return FragmentSendersBarkBinding.inflate(inflater, container, false) + } + + override fun initTitle(): TitleBar? { + titleBar = super.initTitle()!!.setImmersive(false).setTitle(R.string.bark) + return titleBar + } + + /** + * 初始化控件 + */ + override fun initViews() { + binding!!.spLevel.setItems(BARK_LEVEL_MAP.values.toList()) + binding!!.spLevel.setOnItemSelectedListener { _: MaterialSpinner?, _: Int, _: Long, item: Any -> + BARK_LEVEL_MAP.forEach { + if (it.value == item) barkLevel = it.key + } + } + binding!!.spLevel.setOnNothingSelectedListener { + binding!!.spLevel.selectedIndex = 0 + barkLevel = "active" + } + binding!!.spLevel.selectedIndex = 0 + + //新增 + if (senderId <= 0) { + titleBar?.setSubTitle(getString(R.string.add_sender)) + binding!!.btnDel.setText(R.string.discard) + return + } + + //编辑 + binding!!.btnDel.setText(R.string.del) + AppDatabase.getInstance(requireContext()) + .senderDao() + .get(senderId) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(object : SingleObserver { + override fun onSubscribe(d: Disposable) {} + + override fun onError(e: Throwable) { + e.printStackTrace() + } + + override fun onSuccess(sender: Sender) { + if (isClone) { + titleBar?.setSubTitle(getString(R.string.clone_sender) + ": " + sender.name) + binding!!.btnDel.setText(R.string.discard) + } else { + titleBar?.setSubTitle(getString(R.string.edit_sender) + ": " + sender.name) + } + binding!!.etName.setText(sender.name) + binding!!.sbEnable.isChecked = sender.status == 1 + val settingVo = Gson().fromJson(sender.jsonSetting, BarkSetting::class.java) + Log.d(TAG, settingVo.toString()) + if (settingVo != null) { + binding!!.etServer.setText(settingVo.server) + binding!!.etGroup.setText(settingVo.group) + binding!!.etIcon.setText(settingVo.icon) + binding!!.etSound.setText(settingVo.sound) + binding!!.etBadge.setText(settingVo.badge) + binding!!.etUrl.setText(settingVo.url) + BARK_LEVEL_MAP.forEach { + if (it.key == settingVo.level) binding!!.spLevel.setSelectedItem(it.value) + } + binding!!.etTitleTemplate.setText(settingVo.title) + } + } + }) + + } + + override fun initListeners() { + binding!!.btInsertSender.setOnClickListener(this) + binding!!.btInsertExtra.setOnClickListener(this) + binding!!.btInsertTime.setOnClickListener(this) + binding!!.btInsertDeviceName.setOnClickListener(this) + binding!!.btnTest.setOnClickListener(this) + binding!!.btnDel.setOnClickListener(this) + binding!!.btnSave.setOnClickListener(this) + } + + @SingleClick + override fun onClick(v: View) { + try { + val etTitleTemplate: EditText = binding!!.etTitleTemplate + when (v.id) { + R.id.bt_insert_sender -> { + CommonUtils.insertOrReplaceText2Cursor(etTitleTemplate, getString(R.string.tag_from)) + return + } + R.id.bt_insert_extra -> { + CommonUtils.insertOrReplaceText2Cursor(etTitleTemplate, getString(R.string.tag_card_slot)) + return + } + R.id.bt_insert_time -> { + CommonUtils.insertOrReplaceText2Cursor(etTitleTemplate, getString(R.string.tag_receive_time)) + return + } + R.id.bt_insert_device_name -> { + CommonUtils.insertOrReplaceText2Cursor(etTitleTemplate, getString(R.string.tag_device_name)) + return + } + R.id.btn_test -> { + val settingVo = checkSetting() + Log.d(TAG, settingVo.toString()) + val msgInfo = MsgInfo("sms", getString(R.string.test_phone_num), getString(R.string.test_sender_sms), Date(), getString(R.string.test_sim_info)) + BarkUtils.sendMsg(settingVo, msgInfo) + return + } + R.id.btn_del -> { + if (senderId <= 0 || isClone) { + popToBack() + return + } + + MaterialDialog.Builder(requireContext()) + .title(R.string.delete_sender_title) + .content(R.string.delete_sender_tips) + .positiveText(R.string.lab_yes) + .negativeText(R.string.lab_no) + .onPositive { _: MaterialDialog?, _: DialogAction? -> + viewModel.delete(senderId) + XToastUtils.success(R.string.delete_sender_toast) + popToBack() + } + .show() + return + } + R.id.btn_save -> { + val name = binding!!.etName.text.toString().trim() + if (TextUtils.isEmpty(name)) { + throw Exception(getString(R.string.invalid_name)) + } + + val status = if (binding!!.sbEnable.isChecked) 1 else 0 + val settingVo = checkSetting() + if (isClone) senderId = 0 + val senderNew = Sender(senderId, senderType, name, Gson().toJson(settingVo), status) + Log.d(TAG, senderNew.toString()) + + viewModel.insertOrUpdate(senderNew) + XToastUtils.success(R.string.tipSaveSuccess) + popToBack() + return + } + } + } catch (e: Exception) { + XToastUtils.error(e.message.toString()) + e.printStackTrace() + } + } + + private fun checkSetting(): BarkSetting { + val server = binding!!.etServer.text.toString().trim() + if (!CommonUtils.checkUrl(server, false)) { + throw Exception(getString(R.string.invalid_bark_server)) + } + val group = binding!!.etGroup.text.toString().trim() + val icon = binding!!.etIcon.text.toString().trim() + if (!CommonUtils.checkUrl(icon, true)) { + throw Exception(getString(R.string.invalid_bark_icon)) + } + val sound = binding!!.etSound.text.toString().trim() + val badge = binding!!.etBadge.text.toString().trim() + val url = binding!!.etUrl.text.toString().trim() + if (!CommonUtils.checkUrl(url, true)) { + throw Exception(getString(R.string.invalid_bark_url)) + } + val title = binding!!.etTitleTemplate.text.toString().trim() + + return BarkSetting(server, group, icon, sound, badge, url, barkLevel, title) + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/fragment/senders/DingtalkFragment.kt b/app/src/main/java/com/idormy/sms/forwarder/fragment/senders/DingtalkFragment.kt new file mode 100644 index 00000000..cf3790ce --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/fragment/senders/DingtalkFragment.kt @@ -0,0 +1,211 @@ +package com.idormy.sms.forwarder.fragment.senders + +import android.text.TextUtils +import android.util.Log +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.CompoundButton +import androidx.fragment.app.viewModels +import com.google.gson.Gson +import com.idormy.sms.forwarder.R +import com.idormy.sms.forwarder.core.BaseFragment +import com.idormy.sms.forwarder.database.AppDatabase +import com.idormy.sms.forwarder.database.entity.Sender +import com.idormy.sms.forwarder.database.viewmodel.BaseViewModelFactory +import com.idormy.sms.forwarder.database.viewmodel.SenderViewModel +import com.idormy.sms.forwarder.databinding.FragmentSendersDingtalkBinding +import com.idormy.sms.forwarder.entity.MsgInfo +import com.idormy.sms.forwarder.entity.setting.DingtalkSetting +import com.idormy.sms.forwarder.utils.* +import com.idormy.sms.forwarder.utils.sender.DingtalkUtils +import com.xuexiang.xaop.annotation.SingleClick +import com.xuexiang.xpage.annotation.Page +import com.xuexiang.xrouter.annotation.AutoWired +import com.xuexiang.xrouter.launcher.XRouter +import com.xuexiang.xui.widget.actionbar.TitleBar +import com.xuexiang.xui.widget.dialog.materialdialog.DialogAction +import com.xuexiang.xui.widget.dialog.materialdialog.MaterialDialog +import io.reactivex.SingleObserver +import io.reactivex.android.schedulers.AndroidSchedulers +import io.reactivex.disposables.Disposable +import io.reactivex.schedulers.Schedulers +import java.util.* + +@Page(name = "钉钉机器人") +@Suppress("PrivatePropertyName") +class DingtalkFragment : BaseFragment(), View.OnClickListener, CompoundButton.OnCheckedChangeListener { + + private val TAG: String = DingtalkFragment::class.java.simpleName + var titleBar: TitleBar? = null + private val viewModel by viewModels { BaseViewModelFactory(context) } + + @JvmField + @AutoWired(name = KEY_SENDER_ID) + var senderId: Long = 0 + + @JvmField + @AutoWired(name = KEY_SENDER_TYPE) + var senderType: Int = 0 + + @JvmField + @AutoWired(name = KEY_SENDER_CLONE) + var isClone: Boolean = false + + override fun initArgs() { + XRouter.getInstance().inject(this) + } + + override fun viewBindingInflate( + inflater: LayoutInflater, + container: ViewGroup, + ): FragmentSendersDingtalkBinding { + return FragmentSendersDingtalkBinding.inflate(inflater, container, false) + } + + override fun initTitle(): TitleBar? { + titleBar = super.initTitle()!!.setImmersive(false).setTitle(R.string.dingtalk_robot) + return titleBar + } + + /** + * 初始化控件 + */ + override fun initViews() { + //新增 + if (senderId <= 0) { + titleBar?.setSubTitle(getString(R.string.add_sender)) + binding!!.btnDel.setText(R.string.discard) + return + } + + //编辑 + binding!!.btnDel.setText(R.string.del) + AppDatabase.getInstance(requireContext()) + .senderDao() + .get(senderId) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(object : SingleObserver { + override fun onSubscribe(d: Disposable) {} + + override fun onError(e: Throwable) { + e.printStackTrace() + } + + override fun onSuccess(sender: Sender) { + if (isClone) { + titleBar?.setSubTitle(getString(R.string.clone_sender) + ": " + sender.name) + binding!!.btnDel.setText(R.string.discard) + } else { + titleBar?.setSubTitle(getString(R.string.edit_sender) + ": " + sender.name) + } + binding!!.etName.setText(sender.name) + binding!!.sbEnable.isChecked = sender.status == 1 + val settingVo = Gson().fromJson(sender.jsonSetting, DingtalkSetting::class.java) + Log.d(TAG, settingVo.toString()) + if (settingVo != null) { + binding!!.etToken.setText(settingVo.token) + binding!!.etSecret.setText(settingVo.secret) + /*if (TextUtils.isEmpty(settingVo.atMobiles)) { + binding!!.sbAtAll.isChecked = true + binding!!.etAtMobiles.setText("") + } else { + binding!!.sbAtAll.isChecked = settingVo.atAll == true + binding!!.etAtMobiles.setText(settingVo.atMobiles) + }*/ + binding!!.sbAtAll.isChecked = settingVo.atAll == true + binding!!.etAtMobiles.setText(settingVo.atMobiles) + } + } + }) + } + + override fun initListeners() { + binding!!.btnTest.setOnClickListener(this) + binding!!.btnDel.setOnClickListener(this) + binding!!.btnSave.setOnClickListener(this) + binding!!.sbAtAll.setOnCheckedChangeListener(this) + } + + override fun onCheckedChanged(buttonView: CompoundButton?, isChecked: Boolean) { + //这里只有一个监听不需要判断id + if (isChecked) { + binding!!.layoutAtMobiles.visibility = View.GONE + binding!!.etAtMobiles.setText("") + } else { + binding!!.layoutAtMobiles.visibility = View.VISIBLE + } + } + + @SingleClick + override fun onClick(v: View) { + try { + when (v.id) { + R.id.btn_test -> { + val settingVo = checkSetting() + Log.d(TAG, settingVo.toString()) + val msgInfo = MsgInfo("sms", getString(R.string.test_phone_num), getString(R.string.test_sender_sms), Date(), getString(R.string.test_sim_info)) + DingtalkUtils.sendMsg(settingVo, msgInfo) + return + } + R.id.btn_del -> { + if (senderId <= 0 || isClone) { + popToBack() + return + } + + MaterialDialog.Builder(requireContext()) + .title(R.string.delete_sender_title) + .content(R.string.delete_sender_tips) + .positiveText(R.string.lab_yes) + .negativeText(R.string.lab_no) + .onPositive { _: MaterialDialog?, _: DialogAction? -> + viewModel.delete(senderId) + XToastUtils.success(R.string.delete_sender_toast) + popToBack() + } + .show() + return + } + R.id.btn_save -> { + val name = binding!!.etName.text.toString().trim() + if (TextUtils.isEmpty(name)) { + throw Exception(getString(R.string.invalid_name)) + } + + val status = if (binding!!.sbEnable.isChecked) 1 else 0 + val settingVo = checkSetting() + if (isClone) senderId = 0 + val senderNew = Sender(senderId, senderType, name, Gson().toJson(settingVo), status) + Log.d(TAG, senderNew.toString()) + + viewModel.insertOrUpdate(senderNew) + XToastUtils.success(R.string.tipSaveSuccess) + popToBack() + return + } + } + } catch (e: Exception) { + XToastUtils.error(e.message.toString()) + e.printStackTrace() + } + } + + private fun checkSetting(): DingtalkSetting { + val token = binding!!.etToken.text.toString().trim() + if (CommonUtils.checkUrl(token, true)) { + throw Exception(getString(R.string.invalid_token)) + } + + val secret = binding!!.etSecret.text.toString().trim() + val atAll = binding!!.sbAtAll.isChecked + val atMobiles = binding!!.etAtMobiles.text.toString().trim() + /*if (!atAll && TextUtils.isEmpty(atMobiles)) { + throw Exception(getString(R.string.invalid_at_mobiles)) + }*/ + + return DingtalkSetting(token, secret, atAll, atMobiles) + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/fragment/senders/EmailFragment.kt b/app/src/main/java/com/idormy/sms/forwarder/fragment/senders/EmailFragment.kt new file mode 100644 index 00000000..0c94056c --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/fragment/senders/EmailFragment.kt @@ -0,0 +1,242 @@ +package com.idormy.sms.forwarder.fragment.senders + +import android.text.TextUtils +import android.util.Log +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.EditText +import androidx.fragment.app.viewModels +import com.google.gson.Gson +import com.idormy.sms.forwarder.R +import com.idormy.sms.forwarder.core.BaseFragment +import com.idormy.sms.forwarder.database.AppDatabase +import com.idormy.sms.forwarder.database.entity.Sender +import com.idormy.sms.forwarder.database.viewmodel.BaseViewModelFactory +import com.idormy.sms.forwarder.database.viewmodel.SenderViewModel +import com.idormy.sms.forwarder.databinding.FragmentSendersEmailBinding +import com.idormy.sms.forwarder.entity.MsgInfo +import com.idormy.sms.forwarder.entity.setting.EmailSetting +import com.idormy.sms.forwarder.utils.* +import com.idormy.sms.forwarder.utils.sender.EmailUtils +import com.xuexiang.xaop.annotation.SingleClick +import com.xuexiang.xpage.annotation.Page +import com.xuexiang.xrouter.annotation.AutoWired +import com.xuexiang.xrouter.launcher.XRouter +import com.xuexiang.xui.utils.ResUtils +import com.xuexiang.xui.widget.actionbar.TitleBar +import com.xuexiang.xui.widget.dialog.materialdialog.DialogAction +import com.xuexiang.xui.widget.dialog.materialdialog.MaterialDialog +import com.xuexiang.xui.widget.spinner.materialspinner.MaterialSpinner +import io.reactivex.SingleObserver +import io.reactivex.android.schedulers.AndroidSchedulers +import io.reactivex.disposables.Disposable +import io.reactivex.schedulers.Schedulers +import java.util.* + +@Page(name = "Email") +@Suppress("PrivatePropertyName") +class EmailFragment : BaseFragment(), View.OnClickListener { + + private val TAG: String = EmailFragment::class.java.simpleName + var titleBar: TitleBar? = null + private val viewModel by viewModels { BaseViewModelFactory(context) } + private var mailType: String = getString(R.string.other_mail_type) //邮箱类型 + + @JvmField + @AutoWired(name = KEY_SENDER_ID) + var senderId: Long = 0 + + @JvmField + @AutoWired(name = KEY_SENDER_TYPE) + var senderType: Int = 0 + + @JvmField + @AutoWired(name = KEY_SENDER_CLONE) + var isClone: Boolean = false + + override fun initArgs() { + XRouter.getInstance().inject(this) + } + + override fun viewBindingInflate( + inflater: LayoutInflater, + container: ViewGroup, + ): FragmentSendersEmailBinding { + return FragmentSendersEmailBinding.inflate(inflater, container, false) + } + + override fun initTitle(): TitleBar? { + titleBar = super.initTitle()!!.setImmersive(false).setTitle(R.string.email) + return titleBar + } + + /** + * 初始化控件 + */ + override fun initViews() { + val mailTypeArray = ResUtils.getStringArray(R.array.MailType) + Log.d(TAG, mailTypeArray.toString()) + binding!!.spMailType.setOnItemSelectedListener { _: MaterialSpinner?, position: Int, _: Long, item: Any -> + mailType = item.toString() + //XToastUtils.warning(mailType) + binding!!.layoutServiceSetting.visibility = if (position == mailTypeArray.size - 1) View.VISIBLE else View.GONE + } + binding!!.spMailType.setOnNothingSelectedListener { + mailType = mailTypeArray[mailTypeArray.size - 1] + binding!!.spMailType.selectedIndex = mailTypeArray.size - 1 + binding!!.layoutServiceSetting.visibility = View.VISIBLE + } + binding!!.spMailType.selectedIndex = mailTypeArray.size - 1 + binding!!.layoutServiceSetting.visibility = View.VISIBLE + + //新增 + if (senderId <= 0) { + titleBar?.setSubTitle(getString(R.string.add_sender)) + binding!!.btnDel.setText(R.string.discard) + return + } + + //编辑 + binding!!.btnDel.setText(R.string.del) + AppDatabase.getInstance(requireContext()) + .senderDao() + .get(senderId) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(object : SingleObserver { + override fun onSubscribe(d: Disposable) {} + + override fun onError(e: Throwable) { + e.printStackTrace() + } + + override fun onSuccess(sender: Sender) { + if (isClone) { + titleBar?.setSubTitle(getString(R.string.clone_sender) + ": " + sender.name) + binding!!.btnDel.setText(R.string.discard) + } else { + titleBar?.setSubTitle(getString(R.string.edit_sender) + ": " + sender.name) + } + binding!!.etName.setText(sender.name) + binding!!.sbEnable.isChecked = sender.status == 1 + val settingVo = Gson().fromJson(sender.jsonSetting, EmailSetting::class.java) + Log.d(TAG, settingVo.toString()) + if (settingVo != null) { + if (!TextUtils.isEmpty(settingVo.mailType)) { + mailType = settingVo.mailType.toString() + binding!!.spMailType.setSelectedItem(mailType) + if (mailType != getString(R.string.other_mail_type)) { + binding!!.layoutServiceSetting.visibility = View.GONE + } + } + binding!!.etFromEmail.setText(settingVo.fromEmail) + binding!!.etPwd.setText(settingVo.pwd) + binding!!.etNickname.setText(settingVo.nickname) + binding!!.etHost.setText(settingVo.host) + binding!!.etPort.setText(settingVo.port) + binding!!.sbSsl.isChecked = settingVo.ssl == true + binding!!.sbStartTls.isChecked = settingVo.startTls == true + binding!!.etToEmail.setText(settingVo.toEmail) + binding!!.etTitleTemplate.setText(settingVo.title) + } + } + }) + } + + override fun initListeners() { + binding!!.btInsertSender.setOnClickListener(this) + binding!!.btInsertExtra.setOnClickListener(this) + binding!!.btInsertTime.setOnClickListener(this) + binding!!.btInsertDeviceName.setOnClickListener(this) + binding!!.btnTest.setOnClickListener(this) + binding!!.btnDel.setOnClickListener(this) + binding!!.btnSave.setOnClickListener(this) + } + + @SingleClick + override fun onClick(v: View) { + try { + val etTitleTemplate: EditText = binding!!.etTitleTemplate + when (v.id) { + R.id.bt_insert_sender -> { + CommonUtils.insertOrReplaceText2Cursor(etTitleTemplate, getString(R.string.tag_from)) + return + } + R.id.bt_insert_extra -> { + CommonUtils.insertOrReplaceText2Cursor(etTitleTemplate, getString(R.string.tag_card_slot)) + return + } + R.id.bt_insert_time -> { + CommonUtils.insertOrReplaceText2Cursor(etTitleTemplate, getString(R.string.tag_receive_time)) + return + } + R.id.bt_insert_device_name -> { + CommonUtils.insertOrReplaceText2Cursor(etTitleTemplate, getString(R.string.tag_device_name)) + return + } + R.id.btn_test -> { + val settingVo = checkSetting() + Log.d(TAG, settingVo.toString()) + val msgInfo = MsgInfo("sms", getString(R.string.test_phone_num), getString(R.string.test_sender_sms), Date(), getString(R.string.test_sim_info)) + EmailUtils.sendMsg(settingVo, msgInfo) + return + } + R.id.btn_del -> { + if (senderId <= 0 || isClone) { + popToBack() + return + } + + MaterialDialog.Builder(requireContext()) + .title(R.string.delete_sender_title) + .content(R.string.delete_sender_tips) + .positiveText(R.string.lab_yes) + .negativeText(R.string.lab_no) + .onPositive { _: MaterialDialog?, _: DialogAction? -> + viewModel.delete(senderId) + XToastUtils.success(R.string.delete_sender_toast) + popToBack() + } + .show() + return + } + R.id.btn_save -> { + val name = binding!!.etName.text.toString().trim() + if (TextUtils.isEmpty(name)) { + throw Exception(getString(R.string.invalid_name)) + } + + val status = if (binding!!.sbEnable.isChecked) 1 else 0 + val settingVo = checkSetting() + if (isClone) senderId = 0 + val senderNew = Sender(senderId, senderType, name, Gson().toJson(settingVo), status) + Log.d(TAG, senderNew.toString()) + + viewModel.insertOrUpdate(senderNew) + XToastUtils.success(R.string.tipSaveSuccess) + popToBack() + return + } + } + } catch (e: Exception) { + XToastUtils.error(e.message.toString()) + e.printStackTrace() + } + } + + private fun checkSetting(): EmailSetting { + val fromEmail = binding!!.etFromEmail.text.toString().trim() + val pwd = binding!!.etPwd.text.toString().trim() + val nickname = binding!!.etNickname.text.toString().trim() + val host = binding!!.etHost.text.toString().trim() + val port = binding!!.etPort.text.toString().trim() + val ssl = binding!!.sbSsl.isChecked + val startTls = binding!!.sbStartTls.isChecked + val toEmail = binding!!.etToEmail.text.toString().trim() + val title = binding!!.etTitleTemplate.text.toString().trim() + + return EmailSetting(mailType, fromEmail, pwd, nickname, host, port, ssl, startTls, toEmail, title) + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/fragment/senders/FeishuFragment.kt b/app/src/main/java/com/idormy/sms/forwarder/fragment/senders/FeishuFragment.kt new file mode 100644 index 00000000..a7286948 --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/fragment/senders/FeishuFragment.kt @@ -0,0 +1,207 @@ +package com.idormy.sms.forwarder.fragment.senders + +import android.text.TextUtils +import android.util.Log +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.EditText +import androidx.fragment.app.viewModels +import com.google.gson.Gson +import com.idormy.sms.forwarder.R +import com.idormy.sms.forwarder.core.BaseFragment +import com.idormy.sms.forwarder.database.AppDatabase +import com.idormy.sms.forwarder.database.entity.Sender +import com.idormy.sms.forwarder.database.viewmodel.BaseViewModelFactory +import com.idormy.sms.forwarder.database.viewmodel.SenderViewModel +import com.idormy.sms.forwarder.databinding.FragmentSendersFeishuBinding +import com.idormy.sms.forwarder.entity.MsgInfo +import com.idormy.sms.forwarder.entity.setting.FeishuSetting +import com.idormy.sms.forwarder.utils.* +import com.idormy.sms.forwarder.utils.sender.FeishuUtils +import com.xuexiang.xaop.annotation.SingleClick +import com.xuexiang.xpage.annotation.Page +import com.xuexiang.xrouter.annotation.AutoWired +import com.xuexiang.xrouter.launcher.XRouter +import com.xuexiang.xui.widget.actionbar.TitleBar +import com.xuexiang.xui.widget.dialog.materialdialog.DialogAction +import com.xuexiang.xui.widget.dialog.materialdialog.MaterialDialog +import io.reactivex.SingleObserver +import io.reactivex.android.schedulers.AndroidSchedulers +import io.reactivex.disposables.Disposable +import io.reactivex.schedulers.Schedulers +import java.util.* + +@Page(name = "飞书机器人") +@Suppress("PrivatePropertyName") +class FeishuFragment : BaseFragment(), View.OnClickListener { + + private val TAG: String = FeishuFragment::class.java.simpleName + var titleBar: TitleBar? = null + private val viewModel by viewModels { BaseViewModelFactory(context) } + + @JvmField + @AutoWired(name = KEY_SENDER_ID) + var senderId: Long = 0 + + @JvmField + @AutoWired(name = KEY_SENDER_TYPE) + var senderType: Int = 0 + + @JvmField + @AutoWired(name = KEY_SENDER_CLONE) + var isClone: Boolean = false + + override fun initArgs() { + XRouter.getInstance().inject(this) + } + + override fun viewBindingInflate( + inflater: LayoutInflater, + container: ViewGroup, + ): FragmentSendersFeishuBinding { + return FragmentSendersFeishuBinding.inflate(inflater, container, false) + } + + override fun initTitle(): TitleBar? { + titleBar = super.initTitle()!!.setImmersive(false).setTitle(R.string.feishu) + return titleBar + } + + /** + * 初始化控件 + */ + override fun initViews() { + //新增 + if (senderId <= 0) { + titleBar?.setSubTitle(getString(R.string.add_sender)) + binding!!.btnDel.setText(R.string.discard) + return + } + + //编辑 + binding!!.btnDel.setText(R.string.del) + AppDatabase.getInstance(requireContext()) + .senderDao() + .get(senderId) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(object : SingleObserver { + override fun onSubscribe(d: Disposable) {} + + override fun onError(e: Throwable) { + e.printStackTrace() + } + + override fun onSuccess(sender: Sender) { + if (isClone) { + titleBar?.setSubTitle(getString(R.string.clone_sender) + ": " + sender.name) + binding!!.btnDel.setText(R.string.discard) + } else { + titleBar?.setSubTitle(getString(R.string.edit_sender) + ": " + sender.name) + } + binding!!.etName.setText(sender.name) + binding!!.sbEnable.isChecked = sender.status == 1 + val settingVo = Gson().fromJson(sender.jsonSetting, FeishuSetting::class.java) + Log.d(TAG, settingVo.toString()) + if (settingVo != null) { + binding!!.etWebhook.setText(settingVo.webhook) + binding!!.etSecret.setText(settingVo.secret) + binding!!.rgMsgType.check(settingVo.getMsgTypeCheckId()) + binding!!.etTitleTemplate.setText(settingVo.titleTemplate) + } + } + }) + } + + override fun initListeners() { + binding!!.btnTest.setOnClickListener(this) + binding!!.btnDel.setOnClickListener(this) + binding!!.btnSave.setOnClickListener(this) + } + + @SingleClick + override fun onClick(v: View) { + try { + val etTitleTemplate: EditText = binding!!.etTitleTemplate + when (v.id) { + R.id.bt_insert_sender -> { + CommonUtils.insertOrReplaceText2Cursor(etTitleTemplate, getString(R.string.tag_from)) + return + } + R.id.bt_insert_extra -> { + CommonUtils.insertOrReplaceText2Cursor(etTitleTemplate, getString(R.string.tag_card_slot)) + return + } + R.id.bt_insert_time -> { + CommonUtils.insertOrReplaceText2Cursor(etTitleTemplate, getString(R.string.tag_receive_time)) + return + } + R.id.bt_insert_device_name -> { + CommonUtils.insertOrReplaceText2Cursor(etTitleTemplate, getString(R.string.tag_device_name)) + return + } + R.id.btn_test -> { + val settingVo = checkSetting() + Log.d(TAG, settingVo.toString()) + val msgInfo = MsgInfo("sms", getString(R.string.test_phone_num), getString(R.string.test_sender_sms), Date(), getString(R.string.test_sim_info)) + FeishuUtils.sendMsg(settingVo, msgInfo) + return + } + R.id.btn_del -> { + if (senderId <= 0 || isClone) { + popToBack() + return + } + + MaterialDialog.Builder(requireContext()) + .title(R.string.delete_sender_title) + .content(R.string.delete_sender_tips) + .positiveText(R.string.lab_yes) + .negativeText(R.string.lab_no) + .onPositive { _: MaterialDialog?, _: DialogAction? -> + viewModel.delete(senderId) + XToastUtils.success(R.string.delete_sender_toast) + popToBack() + } + .show() + return + } + R.id.btn_save -> { + val name = binding!!.etName.text.toString().trim() + if (TextUtils.isEmpty(name)) { + throw Exception(getString(R.string.invalid_name)) + } + + val status = if (binding!!.sbEnable.isChecked) 1 else 0 + val settingVo = checkSetting() + if (isClone) senderId = 0 + val senderNew = Sender(senderId, senderType, name, Gson().toJson(settingVo), status) + Log.d(TAG, senderNew.toString()) + + viewModel.insertOrUpdate(senderNew) + XToastUtils.success(R.string.tipSaveSuccess) + popToBack() + return + } + } + } catch (e: Exception) { + XToastUtils.error(e.message.toString()) + e.printStackTrace() + } + } + + private fun checkSetting(): FeishuSetting { + val webhook = binding!!.etWebhook.text.toString().trim() + if (!CommonUtils.checkUrl(webhook, false)) { + throw Exception(getString(R.string.invalid_webhook)) + } + + val secret = binding!!.etSecret.text.toString().trim() + val msgType = if (binding!!.rgMsgType.checkedRadioButtonId == R.id.rb_msg_type_interactive) "interactive" else "text" + val title = binding!!.etTitleTemplate.text.toString().trim() + + return FeishuSetting(webhook, secret, msgType, title) + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/fragment/senders/GotifyFragment.kt b/app/src/main/java/com/idormy/sms/forwarder/fragment/senders/GotifyFragment.kt new file mode 100644 index 00000000..989db581 --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/fragment/senders/GotifyFragment.kt @@ -0,0 +1,205 @@ +package com.idormy.sms.forwarder.fragment.senders + +import android.text.TextUtils +import android.util.Log +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.EditText +import androidx.fragment.app.viewModels +import com.google.gson.Gson +import com.idormy.sms.forwarder.R +import com.idormy.sms.forwarder.core.BaseFragment +import com.idormy.sms.forwarder.database.AppDatabase +import com.idormy.sms.forwarder.database.entity.Sender +import com.idormy.sms.forwarder.database.viewmodel.BaseViewModelFactory +import com.idormy.sms.forwarder.database.viewmodel.SenderViewModel +import com.idormy.sms.forwarder.databinding.FragmentSendersGotifyBinding +import com.idormy.sms.forwarder.entity.MsgInfo +import com.idormy.sms.forwarder.entity.setting.GotifySetting +import com.idormy.sms.forwarder.utils.* +import com.idormy.sms.forwarder.utils.sender.GotifyUtils +import com.xuexiang.xaop.annotation.SingleClick +import com.xuexiang.xpage.annotation.Page +import com.xuexiang.xrouter.annotation.AutoWired +import com.xuexiang.xrouter.launcher.XRouter +import com.xuexiang.xui.widget.actionbar.TitleBar +import com.xuexiang.xui.widget.dialog.materialdialog.DialogAction +import com.xuexiang.xui.widget.dialog.materialdialog.MaterialDialog +import io.reactivex.SingleObserver +import io.reactivex.android.schedulers.AndroidSchedulers +import io.reactivex.disposables.Disposable +import io.reactivex.schedulers.Schedulers +import java.util.* + +@Page(name = "Gotify") +@Suppress("PrivatePropertyName") +class GotifyFragment : BaseFragment(), View.OnClickListener { + + private val TAG: String = GotifyFragment::class.java.simpleName + var titleBar: TitleBar? = null + private val viewModel by viewModels { BaseViewModelFactory(context) } + + @JvmField + @AutoWired(name = KEY_SENDER_ID) + var senderId: Long = 0 + + @JvmField + @AutoWired(name = KEY_SENDER_TYPE) + var senderType: Int = 0 + + @JvmField + @AutoWired(name = KEY_SENDER_CLONE) + var isClone: Boolean = false + + override fun initArgs() { + XRouter.getInstance().inject(this) + } + + override fun viewBindingInflate( + inflater: LayoutInflater, + container: ViewGroup, + ): FragmentSendersGotifyBinding { + return FragmentSendersGotifyBinding.inflate(inflater, container, false) + } + + override fun initTitle(): TitleBar? { + titleBar = super.initTitle()!!.setImmersive(false).setTitle(R.string.gotify) + return titleBar + } + + /** + * 初始化控件 + */ + override fun initViews() { + //新增 + if (senderId <= 0) { + titleBar?.setSubTitle(getString(R.string.add_sender)) + binding!!.btnDel.setText(R.string.discard) + return + } + + //编辑 + binding!!.btnDel.setText(R.string.del) + AppDatabase.getInstance(requireContext()) + .senderDao() + .get(senderId) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(object : SingleObserver { + override fun onSubscribe(d: Disposable) {} + + override fun onError(e: Throwable) { + e.printStackTrace() + } + + override fun onSuccess(sender: Sender) { + if (isClone) { + titleBar?.setSubTitle(getString(R.string.clone_sender) + ": " + sender.name) + binding!!.btnDel.setText(R.string.discard) + } else { + titleBar?.setSubTitle(getString(R.string.edit_sender) + ": " + sender.name) + } + binding!!.etName.setText(sender.name) + binding!!.sbEnable.isChecked = sender.status == 1 + val settingVo = Gson().fromJson(sender.jsonSetting, GotifySetting::class.java) + Log.d(TAG, settingVo.toString()) + if (settingVo != null) { + binding!!.etWebServer.setText(settingVo.webServer) + binding!!.etTitleTemplate.setText(settingVo.title) + binding!!.etPriority.setText(settingVo.priority) + } + } + }) + } + + override fun initListeners() { + binding!!.btnTest.setOnClickListener(this) + binding!!.btnDel.setOnClickListener(this) + binding!!.btnSave.setOnClickListener(this) + } + + @SingleClick + override fun onClick(v: View) { + try { + val etTitleTemplate: EditText = binding!!.etTitleTemplate + when (v.id) { + R.id.bt_insert_sender -> { + CommonUtils.insertOrReplaceText2Cursor(etTitleTemplate, getString(R.string.tag_from)) + return + } + R.id.bt_insert_extra -> { + CommonUtils.insertOrReplaceText2Cursor(etTitleTemplate, getString(R.string.tag_card_slot)) + return + } + R.id.bt_insert_time -> { + CommonUtils.insertOrReplaceText2Cursor(etTitleTemplate, getString(R.string.tag_receive_time)) + return + } + R.id.bt_insert_device_name -> { + CommonUtils.insertOrReplaceText2Cursor(etTitleTemplate, getString(R.string.tag_device_name)) + return + } + R.id.btn_test -> { + val settingVo = checkSetting() + Log.d(TAG, settingVo.toString()) + val msgInfo = MsgInfo("sms", getString(R.string.test_phone_num), getString(R.string.test_sender_sms), Date(), getString(R.string.test_sim_info)) + GotifyUtils.sendMsg(settingVo, msgInfo) + return + } + R.id.btn_del -> { + if (senderId <= 0 || isClone) { + popToBack() + return + } + + MaterialDialog.Builder(requireContext()) + .title(R.string.delete_sender_title) + .content(R.string.delete_sender_tips) + .positiveText(R.string.lab_yes) + .negativeText(R.string.lab_no) + .onPositive { _: MaterialDialog?, _: DialogAction? -> + viewModel.delete(senderId) + XToastUtils.success(R.string.delete_sender_toast) + popToBack() + } + .show() + return + } + R.id.btn_save -> { + val name = binding!!.etName.text.toString().trim() + if (TextUtils.isEmpty(name)) { + throw Exception(getString(R.string.invalid_name)) + } + + val status = if (binding!!.sbEnable.isChecked) 1 else 0 + val settingVo = checkSetting() + if (isClone) senderId = 0 + val senderNew = Sender(senderId, senderType, name, Gson().toJson(settingVo), status) + Log.d(TAG, senderNew.toString()) + + viewModel.insertOrUpdate(senderNew) + XToastUtils.success(R.string.tipSaveSuccess) + popToBack() + return + } + } + } catch (e: Exception) { + XToastUtils.error(e.message.toString()) + e.printStackTrace() + } + } + + private fun checkSetting(): GotifySetting { + val webServer = binding!!.etWebServer.text.toString().trim() + if (!CommonUtils.checkUrl(webServer, false)) { + throw Exception(getString(R.string.invalid_webserver)) + } + + val title = binding!!.etTitleTemplate.text.toString().trim() + val priority = binding!!.etPriority.text.toString().trim() + + return GotifySetting(webServer, title, priority) + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/fragment/senders/PushplusFragment.kt b/app/src/main/java/com/idormy/sms/forwarder/fragment/senders/PushplusFragment.kt new file mode 100644 index 00000000..5fcebc9a --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/fragment/senders/PushplusFragment.kt @@ -0,0 +1,235 @@ +package com.idormy.sms.forwarder.fragment.senders + +import android.text.TextUtils +import android.util.Log +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.EditText +import android.widget.RadioGroup +import androidx.fragment.app.viewModels +import com.google.gson.Gson +import com.idormy.sms.forwarder.R +import com.idormy.sms.forwarder.core.BaseFragment +import com.idormy.sms.forwarder.database.AppDatabase +import com.idormy.sms.forwarder.database.entity.Sender +import com.idormy.sms.forwarder.database.viewmodel.BaseViewModelFactory +import com.idormy.sms.forwarder.database.viewmodel.SenderViewModel +import com.idormy.sms.forwarder.databinding.FragmentSendersPushplusBinding +import com.idormy.sms.forwarder.entity.MsgInfo +import com.idormy.sms.forwarder.entity.setting.PushplusSetting +import com.idormy.sms.forwarder.utils.* +import com.idormy.sms.forwarder.utils.sender.PushplusUtils +import com.xuexiang.xaop.annotation.SingleClick +import com.xuexiang.xpage.annotation.Page +import com.xuexiang.xrouter.annotation.AutoWired +import com.xuexiang.xrouter.launcher.XRouter +import com.xuexiang.xui.widget.actionbar.TitleBar +import com.xuexiang.xui.widget.dialog.materialdialog.DialogAction +import com.xuexiang.xui.widget.dialog.materialdialog.MaterialDialog +import io.reactivex.SingleObserver +import io.reactivex.android.schedulers.AndroidSchedulers +import io.reactivex.disposables.Disposable +import io.reactivex.schedulers.Schedulers +import java.util.* + +@Page(name = "PushPlus") +@Suppress("PrivatePropertyName") +class PushplusFragment : BaseFragment(), View.OnClickListener { + + private val TAG: String = PushplusFragment::class.java.simpleName + var titleBar: TitleBar? = null + private val viewModel by viewModels { BaseViewModelFactory(context) } + + @JvmField + @AutoWired(name = KEY_SENDER_ID) + var senderId: Long = 0 + + @JvmField + @AutoWired(name = KEY_SENDER_TYPE) + var senderType: Int = 0 + + @JvmField + @AutoWired(name = KEY_SENDER_CLONE) + var isClone: Boolean = false + + override fun initArgs() { + XRouter.getInstance().inject(this) + } + + override fun viewBindingInflate( + inflater: LayoutInflater, + container: ViewGroup, + ): FragmentSendersPushplusBinding { + return FragmentSendersPushplusBinding.inflate(inflater, container, false) + } + + override fun initTitle(): TitleBar? { + titleBar = super.initTitle()!!.setImmersive(false).setTitle(R.string.pushplus) + return titleBar + } + + /** + * 初始化控件 + */ + override fun initViews() { + //新增 + if (senderId <= 0) { + titleBar?.setSubTitle(getString(R.string.add_sender)) + binding!!.btnDel.setText(R.string.discard) + return + } + + //编辑 + binding!!.btnDel.setText(R.string.del) + AppDatabase.getInstance(requireContext()) + .senderDao() + .get(senderId) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(object : SingleObserver { + override fun onSubscribe(d: Disposable) {} + + override fun onError(e: Throwable) { + e.printStackTrace() + } + + override fun onSuccess(sender: Sender) { + if (isClone) { + titleBar?.setSubTitle(getString(R.string.clone_sender) + ": " + sender.name) + binding!!.btnDel.setText(R.string.discard) + } else { + titleBar?.setSubTitle(getString(R.string.edit_sender) + ": " + sender.name) + } + binding!!.etName.setText(sender.name) + binding!!.sbEnable.isChecked = sender.status == 1 + val settingVo = Gson().fromJson(sender.jsonSetting, PushplusSetting::class.java) + Log.d(TAG, settingVo.toString()) + if (settingVo != null) { + if (TextUtils.isEmpty(settingVo.website) || settingVo.website == getString(R.string.pushplus_plus)) { + binding!!.rgWebsite.check(R.id.rb_website_plus) + } else { + binding!!.rgWebsite.check(R.id.rb_website_hxtrip) + } + binding!!.etToken.setText(settingVo.token) + binding!!.etTopic.setText(settingVo.topic) + binding!!.etTemplate.setText(settingVo.template) + binding!!.etChannel.setText(settingVo.channel) + binding!!.etWebhook.setText(settingVo.webhook) + binding!!.etCallbackUrl.setText(settingVo.callbackUrl) + binding!!.etValidTime.setText(settingVo.validTime) + binding!!.etTitleTemplate.setText(settingVo.titleTemplate) + } + } + }) + } + + override fun initListeners() { + binding!!.btnTest.setOnClickListener(this) + binding!!.btnDel.setOnClickListener(this) + binding!!.btnSave.setOnClickListener(this) + binding!!.rgWebsite.setOnCheckedChangeListener { _: RadioGroup?, checkedId: Int -> + if (checkedId == R.id.rb_website_hxtrip) { + binding!!.layoutPlusOne.visibility = View.GONE + binding!!.layoutPlusTwo.visibility = View.GONE + } else { + binding!!.layoutPlusOne.visibility = View.VISIBLE + binding!!.layoutPlusTwo.visibility = View.VISIBLE + } + } + } + + @SingleClick + override fun onClick(v: View) { + try { + val etTitleTemplate: EditText = binding!!.etTitleTemplate + when (v.id) { + R.id.bt_insert_sender -> { + CommonUtils.insertOrReplaceText2Cursor(etTitleTemplate, getString(R.string.tag_from)) + return + } + R.id.bt_insert_extra -> { + CommonUtils.insertOrReplaceText2Cursor(etTitleTemplate, getString(R.string.tag_card_slot)) + return + } + R.id.bt_insert_time -> { + CommonUtils.insertOrReplaceText2Cursor(etTitleTemplate, getString(R.string.tag_receive_time)) + return + } + R.id.bt_insert_device_name -> { + CommonUtils.insertOrReplaceText2Cursor(etTitleTemplate, getString(R.string.tag_device_name)) + return + } + R.id.btn_test -> { + val settingVo = checkSetting() + Log.d(TAG, settingVo.toString()) + val msgInfo = MsgInfo("sms", getString(R.string.test_phone_num), getString(R.string.test_sender_sms), Date(), getString(R.string.test_sim_info)) + PushplusUtils.sendMsg(settingVo, msgInfo) + return + } + R.id.btn_del -> { + if (senderId <= 0 || isClone) { + popToBack() + return + } + + MaterialDialog.Builder(requireContext()) + .title(R.string.delete_sender_title) + .content(R.string.delete_sender_tips) + .positiveText(R.string.lab_yes) + .negativeText(R.string.lab_no) + .onPositive { _: MaterialDialog?, _: DialogAction? -> + viewModel.delete(senderId) + XToastUtils.success(R.string.delete_sender_toast) + popToBack() + } + .show() + return + } + R.id.btn_save -> { + val name = binding!!.etName.text.toString().trim() + if (TextUtils.isEmpty(name)) { + throw Exception(getString(R.string.invalid_name)) + } + + val status = if (binding!!.sbEnable.isChecked) 1 else 0 + val settingVo = checkSetting() + if (isClone) senderId = 0 + val senderNew = Sender(senderId, senderType, name, Gson().toJson(settingVo), status) + Log.d(TAG, senderNew.toString()) + + viewModel.insertOrUpdate(senderNew) + XToastUtils.success(R.string.tipSaveSuccess) + popToBack() + return + } + } + } catch (e: Exception) { + XToastUtils.error(e.message.toString()) + e.printStackTrace() + } + } + + private fun checkSetting(): PushplusSetting { + val website = when (binding!!.rgWebsite.checkedRadioButtonId) { + R.id.rb_website_hxtrip -> getString(R.string.pushplus_hxtrip) + else -> getString(R.string.pushplus_plus) + } + + val token = binding!!.etToken.text.toString().trim() + if (TextUtils.isEmpty(token)) { + throw Exception(getString(R.string.invalid_token)) + } + + val topic = binding!!.etTopic.text.toString().trim() + val template = binding!!.etTemplate.text.toString().trim() + val channel = binding!!.etChannel.text.toString().trim() + val webhook = binding!!.etWebhook.text.toString().trim() + val callbackUrl = binding!!.etCallbackUrl.text.toString().trim() + val validTime = binding!!.etValidTime.text.toString().trim() + val title = binding!!.etTitleTemplate.text.toString().trim() + + return PushplusSetting(website, token, topic, template, channel, webhook, callbackUrl, validTime, title) + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/fragment/senders/ServerchanFragment.kt b/app/src/main/java/com/idormy/sms/forwarder/fragment/senders/ServerchanFragment.kt new file mode 100644 index 00000000..96035937 --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/fragment/senders/ServerchanFragment.kt @@ -0,0 +1,201 @@ +package com.idormy.sms.forwarder.fragment.senders + +import android.text.TextUtils +import android.util.Log +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.viewModels +import com.google.gson.Gson +import com.idormy.sms.forwarder.R +import com.idormy.sms.forwarder.core.BaseFragment +import com.idormy.sms.forwarder.database.AppDatabase +import com.idormy.sms.forwarder.database.entity.Sender +import com.idormy.sms.forwarder.database.viewmodel.BaseViewModelFactory +import com.idormy.sms.forwarder.database.viewmodel.SenderViewModel +import com.idormy.sms.forwarder.databinding.FragmentSendersServerchanBinding +import com.idormy.sms.forwarder.entity.MsgInfo +import com.idormy.sms.forwarder.entity.setting.ServerchanSetting +import com.idormy.sms.forwarder.utils.KEY_SENDER_CLONE +import com.idormy.sms.forwarder.utils.KEY_SENDER_ID +import com.idormy.sms.forwarder.utils.KEY_SENDER_TYPE +import com.idormy.sms.forwarder.utils.XToastUtils +import com.idormy.sms.forwarder.utils.sender.ServerchanUtils +import com.xuexiang.xaop.annotation.SingleClick +import com.xuexiang.xpage.annotation.Page +import com.xuexiang.xrouter.annotation.AutoWired +import com.xuexiang.xrouter.launcher.XRouter +import com.xuexiang.xui.widget.actionbar.TitleBar +import com.xuexiang.xui.widget.dialog.materialdialog.DialogAction +import com.xuexiang.xui.widget.dialog.materialdialog.MaterialDialog +import io.reactivex.SingleObserver +import io.reactivex.android.schedulers.AndroidSchedulers +import io.reactivex.disposables.Disposable +import io.reactivex.schedulers.Schedulers +import java.util.* + +@Page(name = "Server酱·Turbo") +@Suppress("PrivatePropertyName") +class ServerchanFragment : BaseFragment(), View.OnClickListener { + + private val TAG: String = ServerchanFragment::class.java.simpleName + var titleBar: TitleBar? = null + private val viewModel by viewModels { BaseViewModelFactory(context) } + + @JvmField + @AutoWired(name = KEY_SENDER_ID) + var senderId: Long = 0 + + @JvmField + @AutoWired(name = KEY_SENDER_TYPE) + var senderType: Int = 0 + + @JvmField + @AutoWired(name = KEY_SENDER_CLONE) + var isClone: Boolean = false + + override fun initArgs() { + XRouter.getInstance().inject(this) + } + + override fun viewBindingInflate( + inflater: LayoutInflater, + container: ViewGroup, + ): FragmentSendersServerchanBinding { + return FragmentSendersServerchanBinding.inflate(inflater, container, false) + } + + override fun initTitle(): TitleBar? { + titleBar = super.initTitle()!!.setImmersive(false).setTitle(R.string.server_chan) + return titleBar + } + + /** + * 初始化控件 + */ + override fun initViews() { + //新增 + if (senderId <= 0) { + titleBar?.setSubTitle(getString(R.string.add_sender)) + binding!!.btnDel.setText(R.string.discard) + return + } + + //编辑 + binding!!.btnDel.setText(R.string.del) + AppDatabase.getInstance(requireContext()) + .senderDao() + .get(senderId) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(object : SingleObserver { + override fun onSubscribe(d: Disposable) {} + + override fun onError(e: Throwable) { + e.printStackTrace() + } + + override fun onSuccess(sender: Sender) { + if (isClone) { + titleBar?.setSubTitle(getString(R.string.clone_sender) + ": " + sender.name) + binding!!.btnDel.setText(R.string.discard) + } else { + titleBar?.setSubTitle(getString(R.string.edit_sender) + ": " + sender.name) + } + binding!!.etName.setText(sender.name) + binding!!.sbEnable.isChecked = sender.status == 1 + val settingVo = Gson().fromJson(sender.jsonSetting, ServerchanSetting::class.java) + Log.d(TAG, settingVo.toString()) + if (settingVo != null) { + binding!!.etSendKey.setText(settingVo.sendKey) + binding!!.etChannel.setText(settingVo.channel) + binding!!.etOpenid.setText(settingVo.openid) + } + } + }) + } + + override fun initListeners() { + binding!!.btnTest.setOnClickListener(this) + binding!!.btnDel.setOnClickListener(this) + binding!!.btnSave.setOnClickListener(this) + } + + @SingleClick + override fun onClick(v: View) { + try { + when (v.id) { + R.id.btn_test -> { + val settingVo = checkSetting() + Log.d(TAG, settingVo.toString()) + val msgInfo = MsgInfo("sms", getString(R.string.test_phone_num), getString(R.string.test_sender_sms), Date(), getString(R.string.test_sim_info)) + ServerchanUtils.sendMsg(settingVo, msgInfo) + return + } + R.id.btn_del -> { + if (senderId <= 0 || isClone) { + popToBack() + return + } + + MaterialDialog.Builder(requireContext()) + .title(R.string.delete_sender_title) + .content(R.string.delete_sender_tips) + .positiveText(R.string.lab_yes) + .negativeText(R.string.lab_no) + .onPositive { _: MaterialDialog?, _: DialogAction? -> + viewModel.delete(senderId) + XToastUtils.success(R.string.delete_sender_toast) + popToBack() + } + .show() + return + } + R.id.btn_save -> { + val name = binding!!.etName.text.toString().trim() + if (TextUtils.isEmpty(name)) { + throw Exception(getString(R.string.invalid_name)) + } + + val status = if (binding!!.sbEnable.isChecked) 1 else 0 + val settingVo = checkSetting() + if (isClone) senderId = 0 + val senderNew = Sender(senderId, senderType, name, Gson().toJson(settingVo), status) + Log.d(TAG, senderNew.toString()) + + viewModel.insertOrUpdate(senderNew) + XToastUtils.success(R.string.tipSaveSuccess) + popToBack() + return + } + } + } catch (e: Exception) { + XToastUtils.error(e.message.toString()) + e.printStackTrace() + } + } + + private fun checkSetting(): ServerchanSetting { + val sendKey = binding!!.etSendKey.text.toString().trim() + if (TextUtils.isEmpty(sendKey)) { + throw Exception(getString(R.string.invalid_sendkey)) + } + val channel = binding!!.etChannel.text.toString().trim() + if (!TextUtils.isEmpty(channel)) { + val regex = """^\d+(\|\d+)?$""".toRegex() + if (!regex.matches(channel)) { + throw Exception(getString(R.string.invalid_channel)) + } + } + val openid = binding!!.etOpenid.text.toString().trim() + if (!TextUtils.isEmpty(openid)) { + val regex = """^[A-Za-z\d\-_=]+(,[A-Za-z\d\-_=]+)?$""".toRegex() + if (!regex.matches(openid)) { + throw Exception(getString(R.string.invalid_openid)) + } + } + + return ServerchanSetting(sendKey, channel, openid) + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/fragment/senders/SmsFragment.kt b/app/src/main/java/com/idormy/sms/forwarder/fragment/senders/SmsFragment.kt new file mode 100644 index 00000000..989e7366 --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/fragment/senders/SmsFragment.kt @@ -0,0 +1,217 @@ +package com.idormy.sms.forwarder.fragment.senders + +import android.text.TextUtils +import android.util.Log +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.viewModels +import com.google.gson.Gson +import com.hjq.permissions.OnPermissionCallback +import com.hjq.permissions.Permission +import com.hjq.permissions.XXPermissions +import com.idormy.sms.forwarder.R +import com.idormy.sms.forwarder.core.BaseFragment +import com.idormy.sms.forwarder.database.AppDatabase +import com.idormy.sms.forwarder.database.entity.Sender +import com.idormy.sms.forwarder.database.viewmodel.BaseViewModelFactory +import com.idormy.sms.forwarder.database.viewmodel.SenderViewModel +import com.idormy.sms.forwarder.databinding.FragmentSendersSmsBinding +import com.idormy.sms.forwarder.entity.MsgInfo +import com.idormy.sms.forwarder.entity.setting.SmsSetting +import com.idormy.sms.forwarder.utils.* +import com.idormy.sms.forwarder.utils.sender.SmsUtils +import com.xuexiang.xaop.annotation.SingleClick +import com.xuexiang.xpage.annotation.Page +import com.xuexiang.xrouter.annotation.AutoWired +import com.xuexiang.xrouter.launcher.XRouter +import com.xuexiang.xui.widget.actionbar.TitleBar +import com.xuexiang.xui.widget.dialog.materialdialog.DialogAction +import com.xuexiang.xui.widget.dialog.materialdialog.MaterialDialog +import io.reactivex.SingleObserver +import io.reactivex.android.schedulers.AndroidSchedulers +import io.reactivex.disposables.Disposable +import io.reactivex.schedulers.Schedulers +import java.util.* + +@Page(name = "短信") +@Suppress("PrivatePropertyName") +class SmsFragment : BaseFragment(), View.OnClickListener { + + private val TAG: String = SmsFragment::class.java.simpleName + var titleBar: TitleBar? = null + private val viewModel by viewModels { BaseViewModelFactory(context) } + + @JvmField + @AutoWired(name = KEY_SENDER_ID) + var senderId: Long = 0 + + @JvmField + @AutoWired(name = KEY_SENDER_TYPE) + var senderType: Int = 0 + + @JvmField + @AutoWired(name = KEY_SENDER_CLONE) + var isClone: Boolean = false + + override fun initArgs() { + XRouter.getInstance().inject(this) + } + + override fun viewBindingInflate( + inflater: LayoutInflater, + container: ViewGroup, + ): FragmentSendersSmsBinding { + return FragmentSendersSmsBinding.inflate(inflater, container, false) + } + + override fun initTitle(): TitleBar? { + titleBar = super.initTitle()!!.setImmersive(false).setTitle(R.string.sms_menu) + return titleBar + } + + /** + * 初始化控件 + */ + override fun initViews() { + //检查发短信权限 + XXPermissions.with(this) + .permission(Permission.SEND_SMS) + .request(object : OnPermissionCallback { + override fun onGranted(permissions: List, all: Boolean) { + if (!all) { + XToastUtils.error(R.string.toast_granted_part) + HttpServerUtils.enableApiSmsSend = false + } + } + + override fun onDenied(permissions: List, never: Boolean) { + HttpServerUtils.enableApiSmsSend = false + if (never) { + XToastUtils.error(R.string.toast_denied_never) + // 如果是被永久拒绝就跳转到应用权限系统设置页面 + XXPermissions.startPermissionActivity(requireContext(), permissions) + } else { + XToastUtils.error(R.string.toast_denied) + } + } + }) + + //新增 + if (senderId <= 0) { + titleBar?.setSubTitle(getString(R.string.add_sender)) + binding!!.btnDel.setText(R.string.discard) + return + } + + //编辑 + binding!!.btnDel.setText(R.string.del) + AppDatabase.getInstance(requireContext()) + .senderDao() + .get(senderId) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(object : SingleObserver { + override fun onSubscribe(d: Disposable) {} + + override fun onError(e: Throwable) { + e.printStackTrace() + } + + override fun onSuccess(sender: Sender) { + if (isClone) { + titleBar?.setSubTitle(getString(R.string.clone_sender) + ": " + sender.name) + binding!!.btnDel.setText(R.string.discard) + } else { + titleBar?.setSubTitle(getString(R.string.edit_sender) + ": " + sender.name) + } + binding!!.etName.setText(sender.name) + binding!!.sbEnable.isChecked = sender.status == 1 + val settingVo = Gson().fromJson(sender.jsonSetting, SmsSetting::class.java) + Log.d(TAG, settingVo.toString()) + if (settingVo != null) { + binding!!.rgSimSlot.check(settingVo.getSmsSimSlotCheckId()) + binding!!.etMobiles.setText(settingVo.mobiles) + binding!!.sbOnlyNoNetwork.isChecked = settingVo.onlyNoNetwork == true + } + } + }) + } + + override fun initListeners() { + binding!!.btnTest.setOnClickListener(this) + binding!!.btnDel.setOnClickListener(this) + binding!!.btnSave.setOnClickListener(this) + } + + @SingleClick + override fun onClick(v: View) { + try { + when (v.id) { + R.id.btn_test -> { + val settingVo = checkSetting() + Log.d(TAG, settingVo.toString()) + val msgInfo = MsgInfo("sms", getString(R.string.test_phone_num), getString(R.string.test_sender_sms), Date(), getString(R.string.test_sim_info)) + SmsUtils.sendMsg(settingVo, msgInfo) + return + } + R.id.btn_del -> { + if (senderId <= 0 || isClone) { + popToBack() + return + } + + MaterialDialog.Builder(requireContext()) + .title(R.string.delete_sender_title) + .content(R.string.delete_sender_tips) + .positiveText(R.string.lab_yes) + .negativeText(R.string.lab_no) + .onPositive { _: MaterialDialog?, _: DialogAction? -> + viewModel.delete(senderId) + XToastUtils.success(R.string.delete_sender_toast) + popToBack() + } + .show() + return + } + R.id.btn_save -> { + val name = binding!!.etName.text.toString().trim() + if (TextUtils.isEmpty(name)) { + throw Exception(getString(R.string.invalid_name)) + } + + val status = if (binding!!.sbEnable.isChecked) 1 else 0 + val settingVo = checkSetting() + if (isClone) senderId = 0 + val senderNew = Sender(senderId, senderType, name, Gson().toJson(settingVo), status) + Log.d(TAG, senderNew.toString()) + + viewModel.insertOrUpdate(senderNew) + XToastUtils.success(R.string.tipSaveSuccess) + popToBack() + return + } + } + } catch (e: Exception) { + XToastUtils.error(e.message.toString()) + e.printStackTrace() + } + } + + private fun checkSetting(): SmsSetting { + val mobiles = binding!!.etMobiles.text.toString().trim() + if (TextUtils.isEmpty(mobiles)) { + throw Exception(getString(R.string.invalid_phone_num)) + } + + val simSlot = when (binding!!.rgSimSlot.checkedRadioButtonId) { + R.id.rb_sim_slot_1 -> 1 + R.id.rb_sim_slot_2 -> 2 + else -> 0 + } + val onlyNoNetwork = binding!!.sbOnlyNoNetwork.isChecked + + return SmsSetting(simSlot, mobiles, onlyNoNetwork) + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/fragment/senders/TelegramFragment.kt b/app/src/main/java/com/idormy/sms/forwarder/fragment/senders/TelegramFragment.kt new file mode 100644 index 00000000..75b08860 --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/fragment/senders/TelegramFragment.kt @@ -0,0 +1,235 @@ +package com.idormy.sms.forwarder.fragment.senders + +import android.text.TextUtils +import android.util.Log +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.CompoundButton +import android.widget.RadioGroup +import androidx.fragment.app.viewModels +import com.google.gson.Gson +import com.idormy.sms.forwarder.R +import com.idormy.sms.forwarder.core.BaseFragment +import com.idormy.sms.forwarder.database.AppDatabase +import com.idormy.sms.forwarder.database.entity.Sender +import com.idormy.sms.forwarder.database.viewmodel.BaseViewModelFactory +import com.idormy.sms.forwarder.database.viewmodel.SenderViewModel +import com.idormy.sms.forwarder.databinding.FragmentSendersTelegramBinding +import com.idormy.sms.forwarder.entity.MsgInfo +import com.idormy.sms.forwarder.entity.setting.TelegramSetting +import com.idormy.sms.forwarder.utils.KEY_SENDER_CLONE +import com.idormy.sms.forwarder.utils.KEY_SENDER_ID +import com.idormy.sms.forwarder.utils.KEY_SENDER_TYPE +import com.idormy.sms.forwarder.utils.XToastUtils +import com.idormy.sms.forwarder.utils.sender.TelegramUtils +import com.xuexiang.xaop.annotation.SingleClick +import com.xuexiang.xpage.annotation.Page +import com.xuexiang.xrouter.annotation.AutoWired +import com.xuexiang.xrouter.launcher.XRouter +import com.xuexiang.xui.widget.actionbar.TitleBar +import com.xuexiang.xui.widget.dialog.materialdialog.DialogAction +import com.xuexiang.xui.widget.dialog.materialdialog.MaterialDialog +import io.reactivex.SingleObserver +import io.reactivex.android.schedulers.AndroidSchedulers +import io.reactivex.disposables.Disposable +import io.reactivex.schedulers.Schedulers +import java.net.Proxy +import java.util.* + +@Page(name = "Telegram机器人") +@Suppress("PrivatePropertyName") +class TelegramFragment : BaseFragment(), View.OnClickListener, CompoundButton.OnCheckedChangeListener { + + private val TAG: String = TelegramFragment::class.java.simpleName + var titleBar: TitleBar? = null + private val viewModel by viewModels { BaseViewModelFactory(context) } + + @JvmField + @AutoWired(name = KEY_SENDER_ID) + var senderId: Long = 0 + + @JvmField + @AutoWired(name = KEY_SENDER_TYPE) + var senderType: Int = 0 + + @JvmField + @AutoWired(name = KEY_SENDER_CLONE) + var isClone: Boolean = false + + override fun initArgs() { + XRouter.getInstance().inject(this) + } + + override fun viewBindingInflate( + inflater: LayoutInflater, + container: ViewGroup, + ): FragmentSendersTelegramBinding { + return FragmentSendersTelegramBinding.inflate(inflater, container, false) + } + + override fun initTitle(): TitleBar? { + titleBar = super.initTitle()!!.setImmersive(false).setTitle(R.string.telegram) + return titleBar + } + + /** + * 初始化控件 + */ + override fun initViews() { + //新增 + if (senderId <= 0) { + titleBar?.setSubTitle(getString(R.string.add_sender)) + binding!!.btnDel.setText(R.string.discard) + return + } + + //编辑 + binding!!.btnDel.setText(R.string.del) + AppDatabase.getInstance(requireContext()) + .senderDao() + .get(senderId) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(object : SingleObserver { + override fun onSubscribe(d: Disposable) {} + + override fun onError(e: Throwable) { + e.printStackTrace() + } + + override fun onSuccess(sender: Sender) { + if (isClone) { + titleBar?.setSubTitle(getString(R.string.clone_sender) + ": " + sender.name) + binding!!.btnDel.setText(R.string.discard) + } else { + titleBar?.setSubTitle(getString(R.string.edit_sender) + ": " + sender.name) + } + binding!!.etName.setText(sender.name) + binding!!.sbEnable.isChecked = sender.status == 1 + val settingVo = Gson().fromJson(sender.jsonSetting, TelegramSetting::class.java) + Log.d(TAG, settingVo.toString()) + if (settingVo != null) { + binding!!.rgMethod.check(settingVo.getMethodCheckId()) + binding!!.etApiToken.setText(settingVo.apiToken) + binding!!.etChatId.setText(settingVo.chatId) + binding!!.rgProxyType.check(settingVo.getProxyTypeCheckId()) + binding!!.etProxyHost.setText(settingVo.proxyHost) + binding!!.etProxyPort.setText(settingVo.proxyPort) + binding!!.sbProxyAuthenticator.isChecked = settingVo.proxyAuthenticator == true + binding!!.etProxyUsername.setText(settingVo.proxyUsername) + binding!!.etProxyPassword.setText(settingVo.proxyPassword) + } + } + }) + } + + override fun initListeners() { + binding!!.btnTest.setOnClickListener(this) + binding!!.btnDel.setOnClickListener(this) + binding!!.btnSave.setOnClickListener(this) + binding!!.sbProxyAuthenticator.setOnCheckedChangeListener(this) + binding!!.rgProxyType.setOnCheckedChangeListener { _: RadioGroup?, checkedId: Int -> + if (checkedId == R.id.rb_proxyHttp || checkedId == R.id.rb_proxySocks) { + binding!!.layoutProxyHost.visibility = View.VISIBLE + binding!!.layoutProxyPort.visibility = View.VISIBLE + binding!!.layoutProxyAuthenticator.visibility = if (binding!!.sbProxyAuthenticator.isChecked) View.VISIBLE else View.GONE + } else { + binding!!.layoutProxyHost.visibility = View.GONE + binding!!.layoutProxyPort.visibility = View.GONE + binding!!.layoutProxyAuthenticator.visibility = View.GONE + } + } + } + + override fun onCheckedChanged(buttonView: CompoundButton?, isChecked: Boolean) { + //TODO: 这里只有一个监听不需要判断id + binding!!.layoutProxyAuthenticator.visibility = if (isChecked) View.VISIBLE else View.GONE + } + + @SingleClick + override fun onClick(v: View) { + try { + when (v.id) { + R.id.btn_test -> { + val settingVo = checkSetting() + Log.d(TAG, settingVo.toString()) + val msgInfo = MsgInfo("sms", getString(R.string.test_phone_num), getString(R.string.test_sender_sms), Date(), getString(R.string.test_sim_info)) + TelegramUtils.sendMsg(settingVo, msgInfo) + return + } + R.id.btn_del -> { + if (senderId <= 0 || isClone) { + popToBack() + return + } + + MaterialDialog.Builder(requireContext()) + .title(R.string.delete_sender_title) + .content(R.string.delete_sender_tips) + .positiveText(R.string.lab_yes) + .negativeText(R.string.lab_no) + .onPositive { _: MaterialDialog?, _: DialogAction? -> + viewModel.delete(senderId) + XToastUtils.success(R.string.delete_sender_toast) + popToBack() + } + .show() + return + } + R.id.btn_save -> { + val name = binding!!.etName.text.toString().trim() + if (TextUtils.isEmpty(name)) { + throw Exception(getString(R.string.invalid_name)) + } + + val status = if (binding!!.sbEnable.isChecked) 1 else 0 + val settingVo = checkSetting() + if (isClone) senderId = 0 + val senderNew = Sender(senderId, senderType, name, Gson().toJson(settingVo), status) + Log.d(TAG, senderNew.toString()) + + viewModel.insertOrUpdate(senderNew) + XToastUtils.success(R.string.tipSaveSuccess) + popToBack() + return + } + } + } catch (e: Exception) { + XToastUtils.error(e.message.toString()) + e.printStackTrace() + } + } + + private fun checkSetting(): TelegramSetting { + val apiToken = binding!!.etApiToken.text.toString().trim() + val chatId = binding!!.etChatId.text.toString().trim() + if (TextUtils.isEmpty(apiToken) || TextUtils.isEmpty(chatId)) { + throw Exception(getString(R.string.invalid_apiToken_or_chatId)) + } + + val proxyType: Proxy.Type = when (binding!!.rgProxyType.checkedRadioButtonId) { + R.id.rb_proxyHttp -> Proxy.Type.HTTP + R.id.rb_proxySocks -> Proxy.Type.SOCKS + else -> Proxy.Type.DIRECT + } + val proxyHost = binding!!.etProxyHost.text.toString().trim() + val proxyPort = binding!!.etProxyPort.text.toString().trim() + + if (proxyType != Proxy.Type.DIRECT && (TextUtils.isEmpty(proxyHost) || TextUtils.isEmpty(proxyPort))) { + throw Exception(getString(R.string.invalid_host_or_port)) + } + + val proxyAuthenticator = binding!!.sbProxyAuthenticator.isChecked + val proxyUsername = binding!!.etProxyUsername.text.toString().trim() + val proxyPassword = binding!!.etProxyPassword.text.toString().trim() + if (proxyAuthenticator && TextUtils.isEmpty(proxyUsername) && TextUtils.isEmpty(proxyPassword)) { + throw Exception(getString(R.string.invalid_username_or_password)) + } + + val method = if (binding!!.rgMethod.checkedRadioButtonId == R.id.rb_method_get) "GET" else "POST" + + return TelegramSetting(method, apiToken, chatId, proxyType, proxyHost, proxyPort, proxyAuthenticator, proxyUsername, proxyPassword) + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/fragment/senders/WebhookFragment.kt b/app/src/main/java/com/idormy/sms/forwarder/fragment/senders/WebhookFragment.kt new file mode 100644 index 00000000..bacd7898 --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/fragment/senders/WebhookFragment.kt @@ -0,0 +1,251 @@ +package com.idormy.sms.forwarder.fragment.senders + +import android.text.TextUtils +import android.util.Log +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.EditText +import android.widget.ImageView +import android.widget.LinearLayout +import androidx.fragment.app.viewModels +import com.google.gson.Gson +import com.idormy.sms.forwarder.R +import com.idormy.sms.forwarder.core.BaseFragment +import com.idormy.sms.forwarder.database.AppDatabase +import com.idormy.sms.forwarder.database.entity.Sender +import com.idormy.sms.forwarder.database.viewmodel.BaseViewModelFactory +import com.idormy.sms.forwarder.database.viewmodel.SenderViewModel +import com.idormy.sms.forwarder.databinding.FragmentSendersWebhookBinding +import com.idormy.sms.forwarder.entity.MsgInfo +import com.idormy.sms.forwarder.entity.setting.WebhookSetting +import com.idormy.sms.forwarder.utils.* +import com.idormy.sms.forwarder.utils.sender.WebhookUtils +import com.xuexiang.xaop.annotation.SingleClick +import com.xuexiang.xpage.annotation.Page +import com.xuexiang.xrouter.annotation.AutoWired +import com.xuexiang.xrouter.launcher.XRouter +import com.xuexiang.xui.widget.actionbar.TitleBar +import com.xuexiang.xui.widget.dialog.materialdialog.DialogAction +import com.xuexiang.xui.widget.dialog.materialdialog.MaterialDialog +import io.reactivex.SingleObserver +import io.reactivex.android.schedulers.AndroidSchedulers +import io.reactivex.disposables.Disposable +import io.reactivex.schedulers.Schedulers +import java.util.* + +@Page(name = "Webhook") +@Suppress("PrivatePropertyName") +class WebhookFragment : BaseFragment(), View.OnClickListener { + + private val TAG: String = WebhookFragment::class.java.simpleName + var titleBar: TitleBar? = null + private val viewModel by viewModels { BaseViewModelFactory(context) } + private var headerItemMap = HashMap(2) + + @JvmField + @AutoWired(name = KEY_SENDER_ID) + var senderId: Long = 0 + + @JvmField + @AutoWired(name = KEY_SENDER_TYPE) + var senderType: Int = 0 + + @JvmField + @AutoWired(name = KEY_SENDER_CLONE) + var isClone: Boolean = false + + override fun initArgs() { + XRouter.getInstance().inject(this) + } + + override fun viewBindingInflate( + inflater: LayoutInflater, + container: ViewGroup, + ): FragmentSendersWebhookBinding { + return FragmentSendersWebhookBinding.inflate(inflater, container, false) + } + + override fun initTitle(): TitleBar? { + titleBar = super.initTitle()!!.setImmersive(false).setTitle(R.string.webhook) + return titleBar + } + + /** + * 初始化控件 + */ + override fun initViews() { + //新增 + if (senderId <= 0) { + titleBar?.setSubTitle(getString(R.string.add_sender)) + binding!!.btnDel.setText(R.string.discard) + return + } + + //编辑 + binding!!.btnDel.setText(R.string.del) + AppDatabase.getInstance(requireContext()) + .senderDao() + .get(senderId) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(object : SingleObserver { + override fun onSubscribe(d: Disposable) {} + + override fun onError(e: Throwable) { + e.printStackTrace() + } + + override fun onSuccess(sender: Sender) { + if (isClone) { + titleBar?.setSubTitle(getString(R.string.clone_sender) + ": " + sender.name) + binding!!.btnDel.setText(R.string.discard) + } else { + titleBar?.setSubTitle(getString(R.string.edit_sender) + ": " + sender.name) + } + binding!!.etName.setText(sender.name) + binding!!.sbEnable.isChecked = sender.status == 1 + val settingVo = Gson().fromJson(sender.jsonSetting, WebhookSetting::class.java) + Log.d(TAG, settingVo.toString()) + if (settingVo != null) { + binding!!.rgMethod.check(settingVo.getMethodCheckId()) + binding!!.etWebServer.setText(settingVo.webServer) + binding!!.etSecret.setText(settingVo.secret) + binding!!.etWebParams.setText(settingVo.webParams) + //set header + if (settingVo.headers != null) { + for ((key, value) in settingVo.headers) { + addHeaderItemLinearLayout(headerItemMap, binding!!.layoutHeaders, key, value) + } + } + } + } + }) + } + + override fun initListeners() { + binding!!.btnTest.setOnClickListener(this) + binding!!.btnDel.setOnClickListener(this) + binding!!.btnSave.setOnClickListener(this) + binding!!.btnAddHeader.setOnClickListener { addHeaderItemLinearLayout(headerItemMap, binding!!.layoutHeaders, null, null) } + } + + @SingleClick + override fun onClick(v: View) { + try { + when (v.id) { + R.id.btn_test -> { + val settingVo = checkSetting() + Log.d(TAG, settingVo.toString()) + val msgInfo = MsgInfo("sms", getString(R.string.test_phone_num), getString(R.string.test_sender_sms), Date(), getString(R.string.test_sim_info)) + WebhookUtils.sendMsg(settingVo, msgInfo) + return + } + R.id.btn_del -> { + if (senderId <= 0 || isClone) { + popToBack() + return + } + + MaterialDialog.Builder(requireContext()) + .title(R.string.delete_sender_title) + .content(R.string.delete_sender_tips) + .positiveText(R.string.lab_yes) + .negativeText(R.string.lab_no) + .onPositive { _: MaterialDialog?, _: DialogAction? -> + viewModel.delete(senderId) + XToastUtils.success(R.string.delete_sender_toast) + popToBack() + } + .show() + return + } + R.id.btn_save -> { + val name = binding!!.etName.text.toString().trim() + if (TextUtils.isEmpty(name)) { + throw Exception(getString(R.string.invalid_name)) + } + + val status = if (binding!!.sbEnable.isChecked) 1 else 0 + val settingVo = checkSetting() + if (isClone) senderId = 0 + val senderNew = Sender(senderId, senderType, name, Gson().toJson(settingVo), status) + Log.d(TAG, senderNew.toString()) + + viewModel.insertOrUpdate(senderNew) + XToastUtils.success(R.string.tipSaveSuccess) + popToBack() + return + } + } + } catch (e: Exception) { + XToastUtils.error(e.message.toString()) + e.printStackTrace() + } + } + + private fun checkSetting(): WebhookSetting { + val webServer = binding!!.etWebServer.text.toString().trim() + if (!CommonUtils.checkUrl(webServer, false)) { + throw Exception(getString(R.string.invalid_webserver)) + } + + val method = if (binding!!.rgMethod.checkedRadioButtonId == R.id.rb_method_get) "GET" else "POST" + val secret = binding!!.etSecret.text.toString().trim() + val webParams = binding!!.etWebParams.text.toString().trim() + val headers = getHeadersFromHeaderItemMap(headerItemMap) + + return WebhookSetting(method, webServer, secret, webParams, headers) + } + + + //header序号 + private var headerItemId = 0 + + /** + * 动态增删header + * + * @param headerItemMap 管理item的map,用于删除指定header + * @param linearLayoutWebNotifyHeaders 需要挂载item的LinearLayout + * @param key header的key,为空则不设置 + * @param value header的value,为空则不设置 + */ + private fun addHeaderItemLinearLayout(headerItemMap: MutableMap, linearLayoutWebNotifyHeaders: LinearLayout, key: String?, value: String?) { + val linearLayoutItemAddHeader = View.inflate(requireContext(), R.layout.item_add_header, null) as LinearLayout + val imageViewRemoveHeader = linearLayoutItemAddHeader.findViewById(R.id.imageViewRemoveHeader) + if (key != null && value != null) { + val editTextHeaderKey = linearLayoutItemAddHeader.findViewById(R.id.editTextHeaderKey) + val editTextHeaderValue = linearLayoutItemAddHeader.findViewById(R.id.editTextHeaderValue) + editTextHeaderKey.setText(key) + editTextHeaderValue.setText(value) + } + imageViewRemoveHeader.tag = headerItemId + imageViewRemoveHeader.setOnClickListener { view2: View -> + val itemId = view2.tag as Int + linearLayoutWebNotifyHeaders.removeView(headerItemMap[itemId]) + headerItemMap.remove(itemId) + } + linearLayoutWebNotifyHeaders.addView(linearLayoutItemAddHeader) + headerItemMap[headerItemId] = linearLayoutItemAddHeader + headerItemId++ + } + + /** + * 从EditText控件中获取全部headers + * + * @param headerItemMap 管理item的map + * @return 全部headers + */ + private fun getHeadersFromHeaderItemMap(headerItemMap: Map): Map { + val headers: MutableMap = HashMap() + for (headerItem in headerItemMap.values) { + val editTextHeaderKey = headerItem.findViewById(R.id.editTextHeaderKey) + val editTextHeaderValue = headerItem.findViewById(R.id.editTextHeaderValue) + val key = editTextHeaderKey.text.toString().trim() + val value = editTextHeaderValue.text.toString().trim() + headers[key] = value + } + return headers + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/fragment/senders/WeworkAgentFragment.kt b/app/src/main/java/com/idormy/sms/forwarder/fragment/senders/WeworkAgentFragment.kt new file mode 100644 index 00000000..4bbb590f --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/fragment/senders/WeworkAgentFragment.kt @@ -0,0 +1,207 @@ +package com.idormy.sms.forwarder.fragment.senders + +import android.text.TextUtils +import android.util.Log +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.CompoundButton +import androidx.fragment.app.viewModels +import com.google.gson.Gson +import com.idormy.sms.forwarder.R +import com.idormy.sms.forwarder.core.BaseFragment +import com.idormy.sms.forwarder.database.AppDatabase +import com.idormy.sms.forwarder.database.entity.Sender +import com.idormy.sms.forwarder.database.viewmodel.BaseViewModelFactory +import com.idormy.sms.forwarder.database.viewmodel.SenderViewModel +import com.idormy.sms.forwarder.databinding.FragmentSendersWeworkAgentBinding +import com.idormy.sms.forwarder.entity.MsgInfo +import com.idormy.sms.forwarder.entity.setting.WeworkAgentSetting +import com.idormy.sms.forwarder.utils.KEY_SENDER_CLONE +import com.idormy.sms.forwarder.utils.KEY_SENDER_ID +import com.idormy.sms.forwarder.utils.KEY_SENDER_TYPE +import com.idormy.sms.forwarder.utils.XToastUtils +import com.idormy.sms.forwarder.utils.sender.WeworkAgentUtils +import com.xuexiang.xaop.annotation.SingleClick +import com.xuexiang.xpage.annotation.Page +import com.xuexiang.xrouter.annotation.AutoWired +import com.xuexiang.xrouter.launcher.XRouter +import com.xuexiang.xui.widget.actionbar.TitleBar +import com.xuexiang.xui.widget.dialog.materialdialog.DialogAction +import com.xuexiang.xui.widget.dialog.materialdialog.MaterialDialog +import io.reactivex.SingleObserver +import io.reactivex.android.schedulers.AndroidSchedulers +import io.reactivex.disposables.Disposable +import io.reactivex.schedulers.Schedulers +import java.util.* + +@Page(name = "企业微信应用") +@Suppress("PrivatePropertyName") +class WeworkAgentFragment : BaseFragment(), View.OnClickListener, CompoundButton.OnCheckedChangeListener { + + private val TAG: String = WeworkAgentFragment::class.java.simpleName + var titleBar: TitleBar? = null + private val viewModel by viewModels { BaseViewModelFactory(context) } + + @JvmField + @AutoWired(name = KEY_SENDER_ID) + var senderId: Long = 0 + + @JvmField + @AutoWired(name = KEY_SENDER_TYPE) + var senderType: Int = 0 + + @JvmField + @AutoWired(name = KEY_SENDER_CLONE) + var isClone: Boolean = false + + override fun initArgs() { + XRouter.getInstance().inject(this) + } + + override fun viewBindingInflate( + inflater: LayoutInflater, + container: ViewGroup, + ): FragmentSendersWeworkAgentBinding { + return FragmentSendersWeworkAgentBinding.inflate(inflater, container, false) + } + + override fun initTitle(): TitleBar? { + titleBar = super.initTitle()!!.setImmersive(false).setTitle(R.string.wework_agent) + return titleBar + } + + /** + * 初始化控件 + */ + override fun initViews() { + //新增 + if (senderId <= 0) { + titleBar?.setSubTitle(getString(R.string.add_sender)) + binding!!.btnDel.setText(R.string.discard) + return + } + + //编辑 + binding!!.btnDel.setText(R.string.del) + AppDatabase.getInstance(requireContext()) + .senderDao() + .get(senderId) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(object : SingleObserver { + override fun onSubscribe(d: Disposable) {} + + override fun onError(e: Throwable) { + e.printStackTrace() + } + + override fun onSuccess(sender: Sender) { + if (isClone) { + titleBar?.setSubTitle(getString(R.string.clone_sender) + ": " + sender.name) + binding!!.btnDel.setText(R.string.discard) + } else { + titleBar?.setSubTitle(getString(R.string.edit_sender) + ": " + sender.name) + } + binding!!.etName.setText(sender.name) + binding!!.sbEnable.isChecked = sender.status == 1 + val settingVo = Gson().fromJson(sender.jsonSetting, WeworkAgentSetting::class.java) + Log.d(TAG, settingVo.toString()) + if (settingVo != null) { + binding!!.etCorpID.setText(settingVo.corpID) + binding!!.etAgentID.setText(settingVo.agentID) + binding!!.etSecret.setText(settingVo.secret) + binding!!.sbAtAll.isChecked = settingVo.atAll == true + binding!!.etToUser.setText(settingVo.toUser) + } + } + }) + } + + override fun initListeners() { + binding!!.btnTest.setOnClickListener(this) + binding!!.btnDel.setOnClickListener(this) + binding!!.btnSave.setOnClickListener(this) + binding!!.sbAtAll.setOnCheckedChangeListener(this) + } + + override fun onCheckedChanged(buttonView: CompoundButton?, isChecked: Boolean) { + //TODO: 这里只有一个监听不需要判断id + if (isChecked) { + binding!!.etToUser.setText("@all") + binding!!.layoutToUser.visibility = View.GONE + } else { + binding!!.etToUser.setText("") + binding!!.layoutToUser.visibility = View.VISIBLE + } + } + + @SingleClick + override fun onClick(v: View) { + try { + when (v.id) { + R.id.btn_test -> { + val settingVo = checkSetting() + Log.d(TAG, settingVo.toString()) + val msgInfo = MsgInfo("sms", getString(R.string.test_phone_num), getString(R.string.test_sender_sms), Date(), getString(R.string.test_sim_info)) + WeworkAgentUtils.sendMsg(settingVo, msgInfo) + return + } + R.id.btn_del -> { + if (senderId <= 0 || isClone) { + popToBack() + return + } + + MaterialDialog.Builder(requireContext()) + .title(R.string.delete_sender_title) + .content(R.string.delete_sender_tips) + .positiveText(R.string.lab_yes) + .negativeText(R.string.lab_no) + .onPositive { _: MaterialDialog?, _: DialogAction? -> + viewModel.delete(senderId) + XToastUtils.success(R.string.delete_sender_toast) + popToBack() + } + .show() + return + } + R.id.btn_save -> { + val name = binding!!.etName.text.toString().trim() + if (TextUtils.isEmpty(name)) { + throw Exception(getString(R.string.invalid_name)) + } + + val status = if (binding!!.sbEnable.isChecked) 1 else 0 + val settingVo = checkSetting() + if (isClone) senderId = 0 + val senderNew = Sender(senderId, senderType, name, Gson().toJson(settingVo), status) + Log.d(TAG, senderNew.toString()) + + viewModel.insertOrUpdate(senderNew) + XToastUtils.success(R.string.tipSaveSuccess) + popToBack() + return + } + } + } catch (e: Exception) { + XToastUtils.error(e.message.toString()) + e.printStackTrace() + } + } + + private fun checkSetting(): WeworkAgentSetting { + val corpID = binding!!.etCorpID.text.toString().trim() + val agentID = binding!!.etAgentID.text.toString().trim() + val secret = binding!!.etSecret.text.toString().trim() + if (TextUtils.isEmpty(corpID) || TextUtils.isEmpty(agentID) || TextUtils.isEmpty(secret)) { + throw Exception(getString(R.string.invalid_wework_agent)) + } + + val atAll = binding!!.sbAtAll.isChecked + val toUser = binding!!.etToUser.text.toString().trim() + + return WeworkAgentSetting(corpID, agentID, secret, atAll, toUser) + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/fragment/senders/WeworkRobotFragment.kt b/app/src/main/java/com/idormy/sms/forwarder/fragment/senders/WeworkRobotFragment.kt new file mode 100644 index 00000000..5e44c18b --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/fragment/senders/WeworkRobotFragment.kt @@ -0,0 +1,182 @@ +package com.idormy.sms.forwarder.fragment.senders + +import android.text.TextUtils +import android.util.Log +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.viewModels +import com.google.gson.Gson +import com.idormy.sms.forwarder.R +import com.idormy.sms.forwarder.core.BaseFragment +import com.idormy.sms.forwarder.database.AppDatabase +import com.idormy.sms.forwarder.database.entity.Sender +import com.idormy.sms.forwarder.database.viewmodel.BaseViewModelFactory +import com.idormy.sms.forwarder.database.viewmodel.SenderViewModel +import com.idormy.sms.forwarder.databinding.FragmentSendersWeworkRobotBinding +import com.idormy.sms.forwarder.entity.MsgInfo +import com.idormy.sms.forwarder.entity.setting.WeworkRobotSetting +import com.idormy.sms.forwarder.utils.* +import com.idormy.sms.forwarder.utils.sender.WeworkRobotUtils +import com.xuexiang.xaop.annotation.SingleClick +import com.xuexiang.xpage.annotation.Page +import com.xuexiang.xrouter.annotation.AutoWired +import com.xuexiang.xrouter.launcher.XRouter +import com.xuexiang.xui.widget.actionbar.TitleBar +import com.xuexiang.xui.widget.dialog.materialdialog.DialogAction +import com.xuexiang.xui.widget.dialog.materialdialog.MaterialDialog +import io.reactivex.SingleObserver +import io.reactivex.android.schedulers.AndroidSchedulers +import io.reactivex.disposables.Disposable +import io.reactivex.schedulers.Schedulers +import java.util.* + +@Page(name = "企业微信群机器人") +@Suppress("PrivatePropertyName") +class WeworkRobotFragment : BaseFragment(), View.OnClickListener { + + private val TAG: String = WeworkRobotFragment::class.java.simpleName + var titleBar: TitleBar? = null + private val viewModel by viewModels { BaseViewModelFactory(context) } + + @JvmField + @AutoWired(name = KEY_SENDER_ID) + var senderId: Long = 0 + + @JvmField + @AutoWired(name = KEY_SENDER_TYPE) + var senderType: Int = 0 + + @JvmField + @AutoWired(name = KEY_SENDER_CLONE) + var isClone: Boolean = false + + override fun initArgs() { + XRouter.getInstance().inject(this) + } + + override fun viewBindingInflate( + inflater: LayoutInflater, + container: ViewGroup, + ): FragmentSendersWeworkRobotBinding { + return FragmentSendersWeworkRobotBinding.inflate(inflater, container, false) + } + + override fun initTitle(): TitleBar? { + titleBar = super.initTitle()!!.setImmersive(false).setTitle(R.string.wework_robot) + return titleBar + } + + /** + * 初始化控件 + */ + override fun initViews() { + //新增 + if (senderId <= 0) { + titleBar?.setSubTitle(getString(R.string.add_sender)) + binding!!.btnDel.setText(R.string.discard) + return + } + + //编辑 + binding!!.btnDel.setText(R.string.del) + AppDatabase.getInstance(requireContext()) + .senderDao() + .get(senderId) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(object : SingleObserver { + override fun onSubscribe(d: Disposable) {} + + override fun onError(e: Throwable) { + e.printStackTrace() + } + + override fun onSuccess(sender: Sender) { + if (isClone) { + titleBar?.setSubTitle(getString(R.string.clone_sender) + ": " + sender.name) + binding!!.btnDel.setText(R.string.discard) + } else { + titleBar?.setSubTitle(getString(R.string.edit_sender) + ": " + sender.name) + } + binding!!.etName.setText(sender.name) + binding!!.sbEnable.isChecked = sender.status == 1 + val settingVo = Gson().fromJson(sender.jsonSetting, WeworkRobotSetting::class.java) + Log.d(TAG, settingVo.toString()) + if (settingVo != null) { + binding!!.etWebHook.setText(settingVo.webHook) + } + } + }) + } + + override fun initListeners() { + binding!!.btnTest.setOnClickListener(this) + binding!!.btnDel.setOnClickListener(this) + binding!!.btnSave.setOnClickListener(this) + } + + @SingleClick + override fun onClick(v: View) { + try { + when (v.id) { + R.id.btn_test -> { + val settingVo = checkSetting() + Log.d(TAG, settingVo.toString()) + val msgInfo = MsgInfo("sms", getString(R.string.test_phone_num), getString(R.string.test_sender_sms), Date(), getString(R.string.test_sim_info)) + WeworkRobotUtils.sendMsg(settingVo, msgInfo) + return + } + R.id.btn_del -> { + if (senderId <= 0 || isClone) { + popToBack() + return + } + + MaterialDialog.Builder(requireContext()) + .title(R.string.delete_sender_title) + .content(R.string.delete_sender_tips) + .positiveText(R.string.lab_yes) + .negativeText(R.string.lab_no) + .onPositive { _: MaterialDialog?, _: DialogAction? -> + viewModel.delete(senderId) + XToastUtils.success(R.string.delete_sender_toast) + popToBack() + } + .show() + return + } + R.id.btn_save -> { + val name = binding!!.etName.text.toString().trim() + if (TextUtils.isEmpty(name)) { + throw Exception(getString(R.string.invalid_name)) + } + + val status = if (binding!!.sbEnable.isChecked) 1 else 0 + val settingVo = checkSetting() + if (isClone) senderId = 0 + val senderNew = Sender(senderId, senderType, name, Gson().toJson(settingVo), status) + Log.d(TAG, senderNew.toString()) + + viewModel.insertOrUpdate(senderNew) + XToastUtils.success(R.string.tipSaveSuccess) + popToBack() + return + } + } + } catch (e: Exception) { + XToastUtils.error(e.message.toString()) + e.printStackTrace() + } + } + + private fun checkSetting(): WeworkRobotSetting { + val webHook = binding!!.etWebHook.text.toString().trim() + if (!CommonUtils.checkUrl(webHook, false)) { + throw Exception(getString(R.string.invalid_webhook)) + } + + return WeworkRobotSetting(webHook) + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/model/AppInfo.java b/app/src/main/java/com/idormy/sms/forwarder/model/AppInfo.java deleted file mode 100644 index 0c4ed732..00000000 --- a/app/src/main/java/com/idormy/sms/forwarder/model/AppInfo.java +++ /dev/null @@ -1,50 +0,0 @@ -package com.idormy.sms.forwarder.model; - -import android.content.Intent; -import android.graphics.drawable.Drawable; - -import androidx.annotation.NonNull; - -import lombok.Data; - -@Data -public class AppInfo { - public String pkgName; - public String appName; - public Drawable appIcon; - public Intent appIntent; - public String verName; - public int verCode; - - public AppInfo() { - } - - public AppInfo(String appName) { - this.appName = appName; - } - - public AppInfo(String appName, String pkgName) { - this.appName = appName; - this.pkgName = pkgName; - } - - public AppInfo(String appName, String pkgName, Drawable appIcon, String verName, int verCode) { - this.appName = appName; - this.pkgName = pkgName; - this.appIcon = appIcon; - this.verName = verName; - this.verCode = verCode; - } - - @NonNull - @Override - public String toString() { - return "AppInfo{" + - "appName='" + appName + '\'' + - ", pkgName='" + pkgName + '\'' + - ", appIcon=" + appIcon + - ", verName=" + verName + - ", verCode=" + verCode + - '}'; - } -} diff --git a/app/src/main/java/com/idormy/sms/forwarder/model/CallInfo.java b/app/src/main/java/com/idormy/sms/forwarder/model/CallInfo.java deleted file mode 100644 index e088c56d..00000000 --- a/app/src/main/java/com/idormy/sms/forwarder/model/CallInfo.java +++ /dev/null @@ -1,43 +0,0 @@ -package com.idormy.sms.forwarder.model; - -import androidx.annotation.NonNull; - -import lombok.Data; - -@Data -public class CallInfo { - public String name; //姓名 - public String number; //号码 - public Long dateLong; //获取通话日期 - public int duration;//获取通话时长,值为多少秒 - public int type; //获取通话类型:1.呼入 2.呼出 3.未接 - public String viaNumber; //来源号码 - public int subscriptionId; //卡槽id - - public CallInfo() { - } - - public CallInfo(String name, String number, Long dateLong, int duration, int type, String viaNumber, int subscriptionId) { - this.name = name; - this.number = number; - this.dateLong = dateLong; - this.duration = duration; - this.type = type; - this.viaNumber = viaNumber; - this.subscriptionId = subscriptionId; - } - - @NonNull - @Override - public String toString() { - return "CallInfo{" + - "name='" + name + '\'' + - ", number='" + number + '\'' + - ", dateLong=" + dateLong + - ", duration=" + duration + - ", type=" + type + - ", viaNumber=" + viaNumber + - ", subscriptionId=" + subscriptionId + - '}'; - } -} diff --git a/app/src/main/java/com/idormy/sms/forwarder/model/LogModel.java b/app/src/main/java/com/idormy/sms/forwarder/model/LogModel.java deleted file mode 100644 index e76a2cb3..00000000 --- a/app/src/main/java/com/idormy/sms/forwarder/model/LogModel.java +++ /dev/null @@ -1,36 +0,0 @@ -package com.idormy.sms.forwarder.model; - -import androidx.annotation.NonNull; - -import lombok.Data; - -@Data -public class LogModel { - private String from; - private String content; - private Long ruleId; - private Long time; - private String simInfo; - private String type; - - public LogModel(String type, String from, String content, String simInfo, Long ruleId) { - this.from = from; - this.content = content; - this.simInfo = simInfo; - this.ruleId = ruleId; - this.type = type; - } - - @NonNull - @Override - public String toString() { - return "LogModel{" + - "from='" + from + '\'' + - ", content='" + content + '\'' + - ", simInfo=" + simInfo + - ", ruleId=" + ruleId + - ", type=" + type + - ", time=" + time + - '}'; - } -} diff --git a/app/src/main/java/com/idormy/sms/forwarder/model/LogTable.java b/app/src/main/java/com/idormy/sms/forwarder/model/LogTable.java deleted file mode 100644 index b518523d..00000000 --- a/app/src/main/java/com/idormy/sms/forwarder/model/LogTable.java +++ /dev/null @@ -1,23 +0,0 @@ -package com.idormy.sms.forwarder.model; - -import android.provider.BaseColumns; - -public final class LogTable { - // To prevent someone from accidentally instantiating the contract class, - // make the constructor private. - private LogTable() { - } - - /* Inner class that defines the table contents */ - public static class LogEntry implements BaseColumns { - public static final String TABLE_NAME = "log"; - public static final String COLUMN_NAME_TYPE = "type"; - public static final String COLUMN_NAME_FROM = "l_from"; - public static final String COLUMN_NAME_CONTENT = "content"; - public static final String COLUMN_NAME_RULE_ID = "rule_id"; - public static final String COLUMN_NAME_TIME = "time"; - public static final String COLUMN_NAME_SIM_INFO = "sim_info"; - public static final String COLUMN_NAME_FORWARD_STATUS = "forward_status"; - public static final String COLUMN_NAME_FORWARD_RESPONSE = "forward_response"; - } -} diff --git a/app/src/main/java/com/idormy/sms/forwarder/model/PhoneBookEntity.java b/app/src/main/java/com/idormy/sms/forwarder/model/PhoneBookEntity.java deleted file mode 100644 index a63163d4..00000000 --- a/app/src/main/java/com/idormy/sms/forwarder/model/PhoneBookEntity.java +++ /dev/null @@ -1,14 +0,0 @@ -package com.idormy.sms.forwarder.model; - -import lombok.Data; - -@Data -public class PhoneBookEntity { - private String name; - private String phoneNumber; - - public PhoneBookEntity(String name, String phoneNumber) { - this.name = name; - this.phoneNumber = phoneNumber; - } -} diff --git a/app/src/main/java/com/idormy/sms/forwarder/model/RuleModel.java b/app/src/main/java/com/idormy/sms/forwarder/model/RuleModel.java deleted file mode 100644 index c9ea0461..00000000 --- a/app/src/main/java/com/idormy/sms/forwarder/model/RuleModel.java +++ /dev/null @@ -1,355 +0,0 @@ -package com.idormy.sms.forwarder.model; - -import android.annotation.SuppressLint; -import android.util.Log; - -import androidx.annotation.NonNull; - -import com.idormy.sms.forwarder.MyApplication; -import com.idormy.sms.forwarder.R; -import com.idormy.sms.forwarder.model.vo.SmsVo; -import com.idormy.sms.forwarder.utils.RuleLineUtils; - -import java.util.HashMap; -import java.util.Map; -import java.util.regex.Matcher; -import java.util.regex.Pattern; -import java.util.regex.PatternSyntaxException; - -import lombok.Data; - -@SuppressWarnings({"unused"}) -@Data -public class RuleModel { - public static final int STATUS_ON = 1; - public static final int STATUS_OFF = 0; - public static final String FILED_TRANSPOND_ALL = "transpond_all"; - public static final String FILED_PHONE_NUM = "phone_num"; - public static final String FILED_PACKAGE_NAME = "package_name"; - public static final String FILED_MSG_CONTENT = "msg_content"; - public static final String FILED_INFORM_CONTENT = "inform_content"; - public static final String FILED_MULTI_MATCH = "multi_match"; - public static final Map FILED_MAP = new HashMap<>(); - public static final String CHECK_IS = "is"; - public static final String CHECK_CONTAIN = "contain"; - public static final String CHECK_NOT_CONTAIN = "notcontain"; - public static final String CHECK_START_WITH = "startwith"; - public static final String CHECK_END_WITH = "endwith"; - public static final String CHECK_NOT_IS = "notis"; - public static final String CHECK_REGEX = "regex"; - public static final Map CHECK_MAP = new HashMap<>(); - public static final String CHECK_SIM_SLOT_ALL = "ALL"; - public static final String CHECK_SIM_SLOT_1 = "SIM1"; - public static final String CHECK_SIM_SLOT_2 = "SIM2"; - public static final Map SIM_SLOT_MAP = new HashMap<>(); - public static final Map TYPE_MAP = new HashMap<>(); - - static { - TYPE_MAP.put("sms", getString(R.string.rule_sms)); - TYPE_MAP.put("call", getString(R.string.rule_call)); - TYPE_MAP.put("app", getString(R.string.rule_app)); - } - - static { - FILED_MAP.put("transpond_all", getString(R.string.rule_transpond_all)); - FILED_MAP.put("phone_num", getString(R.string.rule_phone_num)); - FILED_MAP.put("msg_content", getString(R.string.rule_msg_content)); - FILED_MAP.put("multi_match", getString(R.string.rule_multi_match)); - FILED_MAP.put("package_name", getString(R.string.rule_package_name)); - FILED_MAP.put("inform_content", getString(R.string.rule_inform_content)); - } - - static { - CHECK_MAP.put("is", getString(R.string.rule_is)); - CHECK_MAP.put("notis", getString(R.string.rule_notis)); - CHECK_MAP.put("contain", getString(R.string.rule_contain)); - CHECK_MAP.put("startwith", getString(R.string.rule_startwith)); - CHECK_MAP.put("endwith", getString(R.string.rule_endwith)); - CHECK_MAP.put("notcontain", getString(R.string.rule_notcontain)); - CHECK_MAP.put("regex", getString(R.string.rule_regex)); - } - - static { - SIM_SLOT_MAP.put("ALL", getString(R.string.rule_all)); - SIM_SLOT_MAP.put("SIM1", "SIM1"); - SIM_SLOT_MAP.put("SIM2", "SIM2"); - } - - private String TAG = "RuleModel"; - private Long id; - private String type; - private String filed; - private String check; - private String value; - private Long senderId; - private Long time; - private String simSlot; - private boolean switchSmsTemplate; - private String smsTemplate; - private boolean switchRegexReplace; - private String regexReplace; - private int status; - - public static String getRuleMatch(String filed, String check, String value, String simSlot) { - String SimStr = SIM_SLOT_MAP.get(simSlot) + getString(R.string.rule_card); - if (filed == null || filed.equals(FILED_TRANSPOND_ALL)) { - return SimStr + getString(R.string.rule_all_fw_to); - } else { - return SimStr + getString(R.string.rule_when) + FILED_MAP.get(filed) + " " + CHECK_MAP.get(check) + " " + value + getString(R.string.rule_fw_to); - } - } - - @SuppressLint("NonConstantResourceId") - public static String getRuleFiledFromCheckId(int id) { - switch (id) { - case R.id.btnContent: - return FILED_MSG_CONTENT; - case R.id.btnPhone: - return FILED_PHONE_NUM; - case R.id.btnPackageName: - return FILED_PACKAGE_NAME; - case R.id.btnInformContent: - return FILED_INFORM_CONTENT; - case R.id.btnMultiMatch: - return FILED_MULTI_MATCH; - default: - return FILED_TRANSPOND_ALL; - } - } - - @SuppressLint("NonConstantResourceId") - public static String getRuleCheckFromCheckId(int id) { - switch (id) { - case R.id.btnContain: - return CHECK_CONTAIN; - case R.id.btnStartWith: - return CHECK_START_WITH; - case R.id.btnEndWith: - return CHECK_END_WITH; - case R.id.btnRegex: - return CHECK_REGEX; - case R.id.btnNotContain: - return CHECK_NOT_CONTAIN; - default: - return CHECK_IS; - } - } - - @SuppressLint("NonConstantResourceId") - public static String getRuleSimSlotFromCheckId(int id) { - switch (id) { - case R.id.btnSimSlot1: - return CHECK_SIM_SLOT_1; - case R.id.btnSimSlot2: - return CHECK_SIM_SLOT_2; - default: - return CHECK_SIM_SLOT_ALL; - } - } - - //字段分支 - public boolean checkMsg(SmsVo msg) throws Exception { - - //检查这一行和上一行合并的结果是否命中 - boolean mixChecked = false; - if (msg != null) { - //先检查规则是否命中 - switch (this.filed) { - case FILED_TRANSPOND_ALL: - mixChecked = true; - break; - case FILED_PHONE_NUM: - case FILED_PACKAGE_NAME: - mixChecked = checkValue(msg.getMobile()); - break; - case FILED_MSG_CONTENT: - case FILED_INFORM_CONTENT: - mixChecked = checkValue(msg.getContent()); - break; - case FILED_MULTI_MATCH: - mixChecked = RuleLineUtils.checkRuleLines(msg, this.value); - break; - default: - break; - } - } - - Log.i(TAG, "rule:" + this + " checkMsg:" + msg + " checked:" + mixChecked); - return mixChecked; - - } - - //内容分支 - public boolean checkValue(String msgValue) { - boolean checked = false; - - if (this.value != null) { - switch (this.check) { - case CHECK_IS: - checked = this.value.equals(msgValue); - break; - case CHECK_NOT_IS: - checked = !this.value.equals(msgValue); - break; - case CHECK_CONTAIN: - if (msgValue != null) { - checked = msgValue.contains(this.value); - } - break; - case CHECK_NOT_CONTAIN: - if (msgValue != null) { - checked = !msgValue.contains(this.value); - } - break; - case CHECK_START_WITH: - if (msgValue != null) { - checked = msgValue.startsWith(this.value); - } - break; - case CHECK_END_WITH: - if (msgValue != null) { - checked = msgValue.endsWith(this.value); - } - break; - case CHECK_REGEX: - if (msgValue != null) { - try { - //checked = Pattern.matches(this.value, msgValue); - Pattern pattern = Pattern.compile(this.value, Pattern.CASE_INSENSITIVE); - Matcher matcher = pattern.matcher(msgValue); - //noinspection LoopStatementThatDoesntLoop - while (matcher.find()) { - checked = true; - break; - } - } catch (PatternSyntaxException e) { - Log.d(TAG, "PatternSyntaxException: "); - Log.d(TAG, "Description: " + e.getDescription()); - Log.d(TAG, "Index: " + e.getIndex()); - Log.d(TAG, "Message: " + e.getMessage()); - Log.d(TAG, "Pattern: " + e.getPattern()); - } - } - break; - default: - break; - } - } - - Log.i(TAG, "checkValue " + msgValue + " " + this.check + " " + this.value + " checked:" + checked); - - return checked; - - } - - public String getRuleMatch() { - String SimStr = "app".equals(type) ? "" : SIM_SLOT_MAP.get(simSlot) + getString(R.string.rule_card); - if (filed == null || filed.equals(FILED_TRANSPOND_ALL)) { - return SimStr + getString(R.string.rule_all_fw_to); - } else { - return SimStr + getString(R.string.rule_when) + FILED_MAP.get(filed) + " " + CHECK_MAP.get(check) + " " + value + getString(R.string.rule_fw_to); - } - } - - public Long getRuleSenderId() { - return senderId; - } - - public int getRuleFiledCheckId() { - switch (filed) { - case FILED_MSG_CONTENT: - return R.id.btnContent; - case FILED_PHONE_NUM: - return R.id.btnPhone; - case FILED_PACKAGE_NAME: - return R.id.btnPackageName; - case FILED_INFORM_CONTENT: - return R.id.btnInformContent; - case FILED_MULTI_MATCH: - return R.id.btnMultiMatch; - default: - return R.id.btnTranspondAll; - } - } - - public int getRuleCheckCheckId() { - switch (check) { - case CHECK_CONTAIN: - return R.id.btnContain; - case CHECK_START_WITH: - return R.id.btnStartWith; - case CHECK_END_WITH: - return R.id.btnEndWith; - case CHECK_REGEX: - return R.id.btnRegex; - case CHECK_NOT_CONTAIN: - return R.id.btnNotContain; - default: - return R.id.btnIs; - } - } - - public int getRuleSimSlotCheckId() { - switch (simSlot) { - case CHECK_SIM_SLOT_1: - return R.id.btnSimSlot1; - case CHECK_SIM_SLOT_2: - return R.id.btnSimSlot2; - default: - return R.id.btnSimSlotAll; - } - } - - public boolean getSwitchSmsTemplate() { - return switchSmsTemplate; - } - - public boolean getSwitchRegexReplace() { - return switchRegexReplace; - } - - public boolean getStatusChecked() { - return !(status == STATUS_OFF); - } - - - public int getImageId() { - switch (simSlot) { - case (CHECK_SIM_SLOT_1): - return R.drawable.sim1; - case (CHECK_SIM_SLOT_2): - return R.drawable.sim2; - case (CHECK_SIM_SLOT_ALL): - default: - return type.equals("app") ? R.drawable.ic_app : R.drawable.ic_sim; - } - } - - public int getStatusImageId() { - switch (status) { - case (STATUS_OFF): - return R.drawable.ic_round_pause; - case (STATUS_ON): - default: - return R.drawable.ic_round_play; - } - } - - private static String getString(int resId) { - return MyApplication.getContext().getString(resId); - } - - @NonNull - @Override - public String toString() { - return "RuleModel{" + - "id=" + id + - ", filed='" + filed + '\'' + - ", check='" + check + '\'' + - ", value='" + value + '\'' + - ", senderId=" + senderId + - ", time=" + time + - ", status=" + status + - '}'; - } -} diff --git a/app/src/main/java/com/idormy/sms/forwarder/model/RuleTable.java b/app/src/main/java/com/idormy/sms/forwarder/model/RuleTable.java deleted file mode 100644 index 356a7f3b..00000000 --- a/app/src/main/java/com/idormy/sms/forwarder/model/RuleTable.java +++ /dev/null @@ -1,25 +0,0 @@ -package com.idormy.sms.forwarder.model; - -import android.provider.BaseColumns; - -public final class RuleTable { - // To prevent someone from accidentally instantiating the contract class, - // make the constructor private. - private RuleTable() { - } - - /* Inner class that defines the table contents */ - public static class RuleEntry implements BaseColumns { - public static final String TABLE_NAME = "rule"; - public static final String COLUMN_NAME_TYPE = "type"; - public static final String COLUMN_NAME_FILED = "filed"; - public static final String COLUMN_NAME_CHECK = "tcheck"; - public static final String COLUMN_NAME_VALUE = "value"; - public static final String COLUMN_NAME_SENDER_ID = "sender_id"; - public static final String COLUMN_NAME_TIME = "time"; - public static final String COLUMN_NAME_SIM_SLOT = "sim_slot"; - public static final String COLUMN_SMS_TEMPLATE = "sms_template"; - public static final String COLUMN_REGEX_REPLACE = "regex_replace"; - public static final String COLUMN_NAME_STATUS = "status"; - } -} diff --git a/app/src/main/java/com/idormy/sms/forwarder/model/SenderModel.java b/app/src/main/java/com/idormy/sms/forwarder/model/SenderModel.java deleted file mode 100644 index afc4b55a..00000000 --- a/app/src/main/java/com/idormy/sms/forwarder/model/SenderModel.java +++ /dev/null @@ -1,139 +0,0 @@ -package com.idormy.sms.forwarder.model; - -import androidx.annotation.NonNull; - -import com.idormy.sms.forwarder.R; - -import lombok.Data; - -@SuppressWarnings("unused") -@Data -public class SenderModel { - public static final int STATUS_ON = 1; - public static final int STATUS_OFF = 0; - public static final int TYPE_DINGDING = 0; - public static final int TYPE_EMAIL = 1; - public static final int TYPE_BARK = 2; - public static final int TYPE_WEB_NOTIFY = 3; - public static final int TYPE_QYWX_GROUP_ROBOT = 4; - public static final int TYPE_QYWX_APP = 5; - public static final int TYPE_SERVER_CHAN = 6; - public static final int TYPE_TELEGRAM = 7; - public static final int TYPE_SMS = 8; - public static final int TYPE_FEISHU = 9; - public static final int TYPE_PUSHPLUS = 10; - public static final int TYPE_GOTIFY = 11; - private Long id; - private String name; - private int status; - private int type; - private String jsonSetting; - private long time; - - public SenderModel() { - } - - public SenderModel(String name, int status, int type, String jsonSetting) { - this.name = name; - this.status = status == STATUS_ON ? STATUS_ON : STATUS_OFF; - this.type = type; - this.jsonSetting = jsonSetting; - } - - public static int getImageId(int type) { - switch (type) { - case (TYPE_DINGDING): - return R.mipmap.dingding; - case (TYPE_EMAIL): - return R.mipmap.email; - case (TYPE_BARK): - return R.mipmap.bark; - case (TYPE_WEB_NOTIFY): - return R.mipmap.webhook; - case (TYPE_QYWX_GROUP_ROBOT): - return R.mipmap.qywx; - case (TYPE_QYWX_APP): - return R.mipmap.qywxapp; - case (TYPE_SERVER_CHAN): - return R.mipmap.serverchan; - case (TYPE_TELEGRAM): - return R.mipmap.telegram; - case (TYPE_FEISHU): - return R.mipmap.feishu; - case (TYPE_PUSHPLUS): - return R.mipmap.pushplus; - case (TYPE_GOTIFY): - return R.mipmap.gotify; - case (TYPE_SMS): - default: - return R.mipmap.sms; - } - } - - public int getImageId() { - switch (type) { - case (TYPE_DINGDING): - return R.mipmap.dingding; - case (TYPE_EMAIL): - return R.mipmap.email; - case (TYPE_BARK): - return R.mipmap.bark; - case (TYPE_WEB_NOTIFY): - return R.mipmap.webhook; - case (TYPE_QYWX_GROUP_ROBOT): - return R.mipmap.qywx; - case (TYPE_QYWX_APP): - return R.mipmap.qywxapp; - case (TYPE_SERVER_CHAN): - return R.mipmap.serverchan; - case (TYPE_TELEGRAM): - return R.mipmap.telegram; - case (TYPE_FEISHU): - return R.mipmap.feishu; - case (TYPE_PUSHPLUS): - return R.mipmap.pushplus; - case (TYPE_GOTIFY): - return R.mipmap.gotify; - case (TYPE_SMS): - default: - return R.mipmap.sms; - } - } - - public int getSmsSimSlotId(int id) { - if (id == R.id.btnSmsSimSlot1) { - return 1; - } else if (id == R.id.btnSmsSimSlot2) { - return 2; - } else { - return 0; - } - } - - public int getStatusImageId() { - switch (status) { - case (STATUS_OFF): - return R.drawable.ic_round_pause; - case (STATUS_ON): - default: - return R.drawable.ic_round_play; - } - } - - public boolean getStatusChecked() { - return !(status == STATUS_OFF); - } - - @NonNull - @Override - public String toString() { - return "SenderModel{" + - "id=" + id + - ", name='" + name + '\'' + - ", status=" + status + - ", type=" + type + - ", jsonSetting='" + jsonSetting + '\'' + - ", time=" + time + - '}'; - } -} diff --git a/app/src/main/java/com/idormy/sms/forwarder/model/SenderTable.java b/app/src/main/java/com/idormy/sms/forwarder/model/SenderTable.java deleted file mode 100644 index 8c0a49af..00000000 --- a/app/src/main/java/com/idormy/sms/forwarder/model/SenderTable.java +++ /dev/null @@ -1,20 +0,0 @@ -package com.idormy.sms.forwarder.model; - -import android.provider.BaseColumns; - -public final class SenderTable { - // To prevent someone from accidentally instantiating the contract class, - // make the constructor private. - private SenderTable() { - } - - /* Inner class that defines the table contents */ - public static class SenderEntry implements BaseColumns { - public static final String TABLE_NAME = "sender"; - public static final String COLUMN_NAME_NAME = "name"; - public static final String COLUMN_NAME_STATUS = "status"; - public static final String COLUMN_NAME_TYPE = "type"; - public static final String COLUMN_NAME_JSON_SETTING = "json_setting"; - public static final String COLUMN_NAME_TIME = "time"; - } -} diff --git a/app/src/main/java/com/idormy/sms/forwarder/model/vo/BarkSettingVo.java b/app/src/main/java/com/idormy/sms/forwarder/model/vo/BarkSettingVo.java deleted file mode 100644 index a3e7895d..00000000 --- a/app/src/main/java/com/idormy/sms/forwarder/model/vo/BarkSettingVo.java +++ /dev/null @@ -1,51 +0,0 @@ -package com.idormy.sms.forwarder.model.vo; - -import com.idormy.sms.forwarder.R; - -import java.io.Serializable; - -import lombok.Data; - -@Data -public class BarkSettingVo implements Serializable { - private String server; //推送地址 - private String icon; //消息图标 - private String title; //标题模板 - private String level; //时效性 - private String sound; //声音 - private String badge; //角标 - private String url; //链接 - - public BarkSettingVo() { - } - - public BarkSettingVo(String server, String icon, String title, int levelId, String sound, String badge, String url) { - this.server = server; - this.icon = icon; - this.title = title; - this.level = getLevelStr(levelId); - this.sound = sound; - this.badge = badge; - this.url = url; - } - - public int getLevelId() { - if (level != null && level.equals("timeSensitive")) { - return R.id.btnBarkLevelTimeSensitive; - } else if (level != null && level.equals("passive")) { - return R.id.btnBarkLevelPassive; - } else { - return R.id.btnBarkLevelActive; - } - } - - public String getLevelStr(int id) { - if (id == R.id.btnBarkLevelTimeSensitive) { - return "timeSensitive"; - } else if (id == R.id.btnBarkLevelPassive) { - return "passive"; - } else { - return "active"; - } - } -} diff --git a/app/src/main/java/com/idormy/sms/forwarder/model/vo/CloneInfoVo.java b/app/src/main/java/com/idormy/sms/forwarder/model/vo/CloneInfoVo.java deleted file mode 100644 index 960bbc0b..00000000 --- a/app/src/main/java/com/idormy/sms/forwarder/model/vo/CloneInfoVo.java +++ /dev/null @@ -1,61 +0,0 @@ -package com.idormy.sms.forwarder.model.vo; - -import androidx.annotation.NonNull; - -import com.idormy.sms.forwarder.model.RuleModel; -import com.idormy.sms.forwarder.model.SenderModel; - -import java.io.Serializable; -import java.util.List; - -import lombok.Getter; -import lombok.Setter; - -@Setter -@Getter -public class CloneInfoVo implements Serializable { - - private int versionCode; - private String versionName; - private boolean enableSms; - private boolean enablePhone; - private boolean callType1; - private boolean callType2; - private boolean callType3; - private boolean enableAppNotify; - private boolean cancelAppNotify; - private int batteryLevelAlarmMin; - private int batteryLevelAlarmMax; - private boolean batteryLevelAlarmOnce; - private int retryTimes; - private int delayTime; - private boolean enableSmsTemplate; - private String smsTemplate; - private List senderList; - private List ruleList; - - @NonNull - @Override - public String toString() { - return "CloneInfoVo{" + - "versionCode='" + versionCode + '\'' + - ", versionName='" + versionName + '\'' + - ", enableSms=" + enableSms + - ", enablePhone=" + enablePhone + - ", callType1=" + callType1 + - ", callType2=" + callType2 + - ", callType3=" + callType3 + - ", enableAppNotify=" + enableAppNotify + - ", cancelAppNotify=" + cancelAppNotify + - ", batteryLevelAlarmMin=" + batteryLevelAlarmMin + - ", batteryLevelAlarmMax=" + batteryLevelAlarmMax + - ", batteryLevelAlarmOnce=" + batteryLevelAlarmOnce + - ", retryTimes=" + retryTimes + - ", delayTime=" + delayTime + - ", enableSmsTemplate=" + enableSmsTemplate + - ", smsTemplate=" + smsTemplate + - ", senderList=" + senderList.toString() + - ", ruleList=" + ruleList.toString() + - '}'; - } -} diff --git a/app/src/main/java/com/idormy/sms/forwarder/model/vo/DingDingSettingVo.java b/app/src/main/java/com/idormy/sms/forwarder/model/vo/DingDingSettingVo.java deleted file mode 100644 index 69042760..00000000 --- a/app/src/main/java/com/idormy/sms/forwarder/model/vo/DingDingSettingVo.java +++ /dev/null @@ -1,23 +0,0 @@ -package com.idormy.sms.forwarder.model.vo; - -import java.io.Serializable; - -import lombok.Data; - -@Data -public class DingDingSettingVo implements Serializable { - private String token; - private String secret; - private String atMobiles; - private Boolean atAll; - - public DingDingSettingVo() { - } - - public DingDingSettingVo(String token, String secret, String atMobiles, Boolean atAll) { - this.token = token; - this.secret = secret; - this.atMobiles = atMobiles; - this.atAll = atAll; - } -} diff --git a/app/src/main/java/com/idormy/sms/forwarder/model/vo/EmailSettingVo.java b/app/src/main/java/com/idormy/sms/forwarder/model/vo/EmailSettingVo.java deleted file mode 100644 index 66a9ad47..00000000 --- a/app/src/main/java/com/idormy/sms/forwarder/model/vo/EmailSettingVo.java +++ /dev/null @@ -1,34 +0,0 @@ -package com.idormy.sms.forwarder.model.vo; - -import java.io.Serializable; - -import lombok.Data; - -@Data -public class EmailSettingVo implements Serializable { - private String mailType; - private String host; - private String port; - private Boolean ssl; - private String fromEmail; - private String nickname; - private String pwd; - private String toEmail; - private String title; - - public EmailSettingVo() { - } - - public EmailSettingVo(String mailType, String host, String port, Boolean ssl, String fromEmail, String nickname, String pwd, String toEmail, String title) { - this.mailType = mailType; - this.host = host; - this.port = port; - this.ssl = ssl; - this.fromEmail = fromEmail; - this.nickname = nickname; - this.pwd = pwd; - this.toEmail = toEmail; - this.title = title; - } - -} diff --git a/app/src/main/java/com/idormy/sms/forwarder/model/vo/FeiShuSettingVo.java b/app/src/main/java/com/idormy/sms/forwarder/model/vo/FeiShuSettingVo.java deleted file mode 100644 index bf532d5b..00000000 --- a/app/src/main/java/com/idormy/sms/forwarder/model/vo/FeiShuSettingVo.java +++ /dev/null @@ -1,33 +0,0 @@ -package com.idormy.sms.forwarder.model.vo; - -import com.idormy.sms.forwarder.R; - -import java.io.Serializable; - -import lombok.Data; - -@Data -public class FeiShuSettingVo implements Serializable { - private String webhook; - private String secret; - private String msgType; - private String titleTemplate; - - public FeiShuSettingVo() { - } - - public FeiShuSettingVo(String webhook, String secret, String msgType, String titleTemplate) { - this.webhook = webhook; - this.secret = secret; - this.msgType = msgType; - this.titleTemplate = titleTemplate; - } - - public int getMsgTypeCheckId() { - if (msgType == null || msgType.equals("interactive")) { - return R.id.radioFeishuMsgTypeInteractive; - } else { - return R.id.radioFeishuMsgTypeText; - } - } -} diff --git a/app/src/main/java/com/idormy/sms/forwarder/model/vo/GotifySettingVo.java b/app/src/main/java/com/idormy/sms/forwarder/model/vo/GotifySettingVo.java deleted file mode 100644 index 6a980bdd..00000000 --- a/app/src/main/java/com/idormy/sms/forwarder/model/vo/GotifySettingVo.java +++ /dev/null @@ -1,22 +0,0 @@ -package com.idormy.sms.forwarder.model.vo; - -import java.io.Serializable; - -import lombok.Data; - -@Data -public class GotifySettingVo implements Serializable { - private String webServer; - private String title; - private String priority; - - public GotifySettingVo() { - } - - public GotifySettingVo(String webServer, String title, String priority) { - this.webServer = webServer; - this.title = title; - this.priority = priority; - } - -} diff --git a/app/src/main/java/com/idormy/sms/forwarder/model/vo/LogVo.java b/app/src/main/java/com/idormy/sms/forwarder/model/vo/LogVo.java deleted file mode 100644 index 25698f30..00000000 --- a/app/src/main/java/com/idormy/sms/forwarder/model/vo/LogVo.java +++ /dev/null @@ -1,68 +0,0 @@ -package com.idormy.sms.forwarder.model.vo; - -import com.idormy.sms.forwarder.MyApplication; -import com.idormy.sms.forwarder.R; - -import lombok.Data; - -@Data -public class LogVo { - private Long id; - private String type; - private String from; - private String content; - private String simInfo; - private String rule; - private int senderImageId; - private String time; - private int forwardStatus; - private String forwardResponse; - - public LogVo() { - } - - public LogVo(Long id, String type, String from, String content, String simInfo, String time, String rule, int senderImageId, int forwardStatus, String forwardResponse) { - this.id = id; - this.type = type; - this.from = from; - this.content = content; - this.simInfo = simInfo; - this.time = time; - this.rule = rule; - this.senderImageId = senderImageId; - this.forwardStatus = forwardStatus; - this.forwardResponse = forwardResponse; - } - - public int getSimImageId() { - if (this.simInfo != null && !this.simInfo.isEmpty()) { - if (this.simInfo.replace("-", "").startsWith("SIM2")) { - return R.drawable.sim2; //mipmap - } else if (this.simInfo.replace("-", "").startsWith("SIM1")) { - return R.drawable.sim1; - } - } - - return R.drawable.ic_app; - } - - public int getStatusImageId() { - if (this.forwardStatus == 1) { - return R.drawable.ic_round_warning; - } else if (this.forwardStatus == 2) { - return R.drawable.ic_round_check; - } - - return R.drawable.ic_round_cancel; - } - - public String getForwardResponse() { - if (this.forwardStatus == 1) return getString(R.string.processing); - - return forwardResponse; - } - - private static String getString(int resId) { - return MyApplication.getContext().getString(resId); - } -} diff --git a/app/src/main/java/com/idormy/sms/forwarder/model/vo/PushPlusSettingVo.java b/app/src/main/java/com/idormy/sms/forwarder/model/vo/PushPlusSettingVo.java deleted file mode 100644 index b482a578..00000000 --- a/app/src/main/java/com/idormy/sms/forwarder/model/vo/PushPlusSettingVo.java +++ /dev/null @@ -1,31 +0,0 @@ -package com.idormy.sms.forwarder.model.vo; - -import java.io.Serializable; - -import lombok.Data; - -@Data -public class PushPlusSettingVo implements Serializable { - private String token; - private String topic; - private String template; - private String channel; - private String webhook; - private String callbackUrl; - private String validTime; - private String titleTemplate; - - public PushPlusSettingVo() { - } - - public PushPlusSettingVo(String token, String topic, String template, String channel, String webhook, String callbackUrl, String validTime, String titleTemplate) { - this.token = token; - this.topic = topic; - this.template = template; - this.channel = channel; - this.webhook = webhook; - this.callbackUrl = callbackUrl; - this.validTime = validTime; - this.titleTemplate = titleTemplate; - } -} diff --git a/app/src/main/java/com/idormy/sms/forwarder/model/vo/QYWXAppSettingVo.java b/app/src/main/java/com/idormy/sms/forwarder/model/vo/QYWXAppSettingVo.java deleted file mode 100644 index db12f3cc..00000000 --- a/app/src/main/java/com/idormy/sms/forwarder/model/vo/QYWXAppSettingVo.java +++ /dev/null @@ -1,43 +0,0 @@ -package com.idormy.sms.forwarder.model.vo; - -import android.text.TextUtils; - -import java.io.Serializable; - -import lombok.Data; - -@Data -public class QYWXAppSettingVo implements Serializable { - private String corpID; - private String agentID; - private String secret; - private String toUser; - private Boolean atAll; - private String accessToken; - private Long expiresIn; - - public QYWXAppSettingVo() { - } - - public QYWXAppSettingVo(String corpID, String agentID, String secret, String toUser, Boolean atAll) { - this.corpID = corpID; - this.agentID = agentID; - this.secret = secret; - this.toUser = toUser; - this.atAll = atAll; - } - - public String getAccessToken() { - if (accessToken == null || accessToken.isEmpty() || expiresIn == null || System.currentTimeMillis() > expiresIn) { - return null; - } - return accessToken; - } - - public boolean checkParms() { - if (TextUtils.isEmpty(corpID)) return false; - if (TextUtils.isEmpty(agentID)) return false; - return !TextUtils.isEmpty(secret); - } - -} diff --git a/app/src/main/java/com/idormy/sms/forwarder/model/vo/QYWXGroupRobotSettingVo.java b/app/src/main/java/com/idormy/sms/forwarder/model/vo/QYWXGroupRobotSettingVo.java deleted file mode 100644 index 7a00fffe..00000000 --- a/app/src/main/java/com/idormy/sms/forwarder/model/vo/QYWXGroupRobotSettingVo.java +++ /dev/null @@ -1,18 +0,0 @@ -package com.idormy.sms.forwarder.model.vo; - -import java.io.Serializable; - -import lombok.Data; - -@Data -public class QYWXGroupRobotSettingVo implements Serializable { - private String webHook; - - public QYWXGroupRobotSettingVo() { - } - - public QYWXGroupRobotSettingVo(String webHook) { - this.webHook = webHook; - } - -} diff --git a/app/src/main/java/com/idormy/sms/forwarder/model/vo/ResVo.java b/app/src/main/java/com/idormy/sms/forwarder/model/vo/ResVo.java deleted file mode 100644 index 8e3462a9..00000000 --- a/app/src/main/java/com/idormy/sms/forwarder/model/vo/ResVo.java +++ /dev/null @@ -1,55 +0,0 @@ -package com.idormy.sms.forwarder.model.vo; - -import lombok.Data; - -@SuppressWarnings("UnusedReturnValue") -@Data -public class ResVo { - public static final int ERROR_CODE = 500; - public static final int Suess_CODE = 200; - private int code; - private String msg; - private String auth; - private T data; - - public ResVo setSuessces(T data) { - this.setCode(Suess_CODE); - this.setData(data); - this.setMsg(""); - return this; - } - - public ResVo setError(Throwable e) { - this.setCode(ERROR_CODE); - this.setMsg(e.getMessage()); - return this; - } - - public ResVo setError(String msg) { - this.setCode(ERROR_CODE); - this.setMsg(msg); - return this; - } - - public static ResVo suessces(T data) { - ResVo resVo = new ResVo<>(); - resVo.setCode(Suess_CODE); - resVo.setData(data); - resVo.setMsg(""); - return resVo; - } - - public static ResVo error(Throwable e) { - ResVo resVo = new ResVo<>(); - resVo.setCode(ERROR_CODE); - resVo.setMsg(e.getMessage()); - return resVo; - } - - public static ResVo error(String msg) { - ResVo resVo = new ResVo<>(); - resVo.setCode(ERROR_CODE); - resVo.setMsg(msg); - return resVo; - } -} diff --git a/app/src/main/java/com/idormy/sms/forwarder/model/vo/RuleVo.java b/app/src/main/java/com/idormy/sms/forwarder/model/vo/RuleVo.java deleted file mode 100644 index d8115e3d..00000000 --- a/app/src/main/java/com/idormy/sms/forwarder/model/vo/RuleVo.java +++ /dev/null @@ -1,9 +0,0 @@ -package com.idormy.sms.forwarder.model.vo; - -import lombok.Data; - -@Data -public class RuleVo { - private String matchStr; - private String senderStr; -} diff --git a/app/src/main/java/com/idormy/sms/forwarder/model/vo/ServerChanSettingVo.java b/app/src/main/java/com/idormy/sms/forwarder/model/vo/ServerChanSettingVo.java deleted file mode 100644 index b1f2eb72..00000000 --- a/app/src/main/java/com/idormy/sms/forwarder/model/vo/ServerChanSettingVo.java +++ /dev/null @@ -1,18 +0,0 @@ -package com.idormy.sms.forwarder.model.vo; - -import java.io.Serializable; - -import lombok.Data; - -@Data -public class ServerChanSettingVo implements Serializable { - private String sendKey; - - public ServerChanSettingVo() { - } - - public ServerChanSettingVo(String sendKey) { - this.sendKey = sendKey; - } - -} diff --git a/app/src/main/java/com/idormy/sms/forwarder/model/vo/SmsHubVo.java b/app/src/main/java/com/idormy/sms/forwarder/model/vo/SmsHubVo.java deleted file mode 100644 index bf4badff..00000000 --- a/app/src/main/java/com/idormy/sms/forwarder/model/vo/SmsHubVo.java +++ /dev/null @@ -1,122 +0,0 @@ -package com.idormy.sms.forwarder.model.vo; - -import androidx.annotation.NonNull; - -import com.alibaba.fastjson.JSON; -import com.idormy.sms.forwarder.utils.PhoneUtils; -import com.idormy.sms.forwarder.utils.SettingUtils; -import com.idormy.sms.forwarder.utils.SimUtils; - -import java.io.Serializable; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.UUID; -import java.util.concurrent.ConcurrentHashMap; - -import lombok.Data; - -@SuppressWarnings("unchecked") -@Data -public class SmsHubVo implements Serializable { - private static final long serialVersionUID = 1L; - - public SmsHubVo() { - } - - public SmsHubVo(Type type, Integer simId, String content, String target) { - this.msgId = UUID.randomUUID().toString(); - if (simId != null) { - String simInfo = simId == 2 ? SettingUtils.getAddExtraSim2() : SettingUtils.getAddExtraSim1(); //自定义备注优先 - simInfo = "SIM" + simId + ":" + simInfo; - this.channel = simInfo; - } - this.content = content; - this.target = target; - this.action = Action.receive.code(); - this.type = type.code; - this.ts = System.currentTimeMillis() + ""; - } - - //唯一id - private String msgId; - //心跳数据时发送的设备名 - private String deviceInfo; - //卡槽信息 - private String channel; - //消息内容 - private String content; - //错误消息 - private String errMsg; - //手机号(;分隔)或包名 - private String target; - //状态或操作 0:发送短信, 1:接收到的消息, 2:操作处理成功, 3:操作处理失败, -1:心跳包 (包含deviceInfo字段,children里带有两次心跳间收到的消息) - private String action; - //消息类型 app:通知 phone:来电, sms:短信, battery:电池信息 - private String type; - //时间戳 - private String ts; - //两次交互之间接收到的消息 - private List children; - - public static SmsHubVo heartbeatInstance(List data) { - SmsHubVo smsHubVo = new SmsHubVo(); - HashMap deviInfoMap = getDevInfoMap(false); - smsHubVo.setDeviceInfo(JSON.toJSONString(deviInfoMap)); - smsHubVo.setChannel("SIM1:" + SimUtils.getSimInfo(1) + SettingUtils.getAddExtraSim1() + ";SIM2:" + SimUtils.getSimInfo(2) + SettingUtils.getAddExtraSim2()); - smsHubVo.setTs(Long.toString(System.currentTimeMillis())); - smsHubVo.setAction(SmsHubVo.Action.heartbeat.code()); - if (data != null && data.size() > 0) { - smsHubVo.setChildren(data); - } - return smsHubVo; - } - - private static Map cache = new ConcurrentHashMap<>(); - - @NonNull - public static HashMap getDevInfoMap(boolean reflush) { - String key = "deviceInfo"; - if (reflush || !cache.containsKey(key)) { - HashMap deviInfoMap = new HashMap<>(); - deviInfoMap.put("mark", SettingUtils.getAddExtraDeviceMark()); - deviInfoMap.put("simOperatorName", PhoneUtils.getSimOperatorName()); - deviInfoMap.put("phoneNumber", PhoneUtils.getPhoneNumber()); - deviInfoMap.put("imei", PhoneUtils.getIMEI()); - deviInfoMap.put("SDKVersion", PhoneUtils.getSDKVersion() + ""); - deviInfoMap.put("Version", SettingUtils.getVersionName()); - cache.put(key, deviInfoMap); - return deviInfoMap; - } - return (HashMap) Objects.requireNonNull(cache.get(key)); - } - - public enum Action { - send("0"), receive("1"), suessces("2"), failure("3"), heartbeat("-1"); - - Action(String code) { - this.code = code; - } - - private final String code; - - public String code() { - return code; - } - } - - public enum Type { - app("app"), phone("phone"), sms("sms"), battery("battery"); - - Type(String code) { - this.code = code; - } - - private final String code; - - public String code() { - return code; - } - } -} diff --git a/app/src/main/java/com/idormy/sms/forwarder/model/vo/SmsSettingVo.java b/app/src/main/java/com/idormy/sms/forwarder/model/vo/SmsSettingVo.java deleted file mode 100644 index 6824ee3f..00000000 --- a/app/src/main/java/com/idormy/sms/forwarder/model/vo/SmsSettingVo.java +++ /dev/null @@ -1,33 +0,0 @@ -package com.idormy.sms.forwarder.model.vo; - -import com.idormy.sms.forwarder.R; - -import java.io.Serializable; - -import lombok.Data; - -@Data -public class SmsSettingVo implements Serializable { - private int simSlot; - private String mobiles; - private Boolean onlyNoNetwork; - - public SmsSettingVo() { - } - - public SmsSettingVo(int simSlot, String mobiles, Boolean onlyNoNetwork) { - this.simSlot = simSlot; - this.mobiles = mobiles; - this.onlyNoNetwork = onlyNoNetwork; - } - - public int getSmsSimSlotCheckId() { - if (simSlot == 1) { - return R.id.btnSmsSimSlot1; - } else if (simSlot == 2) { - return R.id.btnSmsSimSlot2; - } else { - return R.id.btnSmsSimSlotOrg; - } - } -} diff --git a/app/src/main/java/com/idormy/sms/forwarder/model/vo/SmsVo.java b/app/src/main/java/com/idormy/sms/forwarder/model/vo/SmsVo.java deleted file mode 100644 index 4bd2fcab..00000000 --- a/app/src/main/java/com/idormy/sms/forwarder/model/vo/SmsVo.java +++ /dev/null @@ -1,148 +0,0 @@ -package com.idormy.sms.forwarder.model.vo; - -import android.annotation.SuppressLint; -import android.util.Log; - -import androidx.annotation.NonNull; - -import com.idormy.sms.forwarder.MyApplication; -import com.idormy.sms.forwarder.R; -import com.idormy.sms.forwarder.utils.SettingUtils; - -import java.io.Serializable; -import java.text.SimpleDateFormat; -import java.util.Date; - -import lombok.Data; - -@Data -public class SmsVo implements Serializable { - String mobile; - String content; - Date date; - String simInfo; - - public SmsVo() { - } - - public SmsVo(String mobile, String content, Date date, String simInfo) { - this.mobile = mobile; - this.content = content; - this.date = date; - this.simInfo = simInfo; - } - - @SuppressLint("SimpleDateFormat") - public String getTitleForSend() { - return getTitleForSend("", ""); - } - - - @SuppressLint("SimpleDateFormat") - public String getTitleForSend(String titleTemplate) { - return getTitleForSend(titleTemplate, ""); - } - - @SuppressLint("SimpleDateFormat") - public String getTitleForSend(String titleTemplate, String regexReplace) { - if (titleTemplate == null || titleTemplate.isEmpty()) titleTemplate = getString(R.string.tag_from); - - String deviceMark = SettingUtils.getAddExtraDeviceMark().trim(); - String versionName = SettingUtils.getVersionName(); - String titleForSend = titleTemplate.replace(getString(R.string.tag_from), mobile) - .replace(getString(R.string.tag_package_name), mobile) - .replace(getString(R.string.tag_sms), content) - .replace(getString(R.string.tag_msg), content) - .replace(getString(R.string.tag_card_slot), simInfo) - .replace(getString(R.string.tag_title), simInfo) - .replace(getString(R.string.tag_receive_time), new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(date)) - .replace(getString(R.string.tag_device_name), deviceMark) - .replace(getString(R.string.tag_app_version), versionName) - .trim(); - - return RegexReplace(regexReplace, titleForSend); - } - - @SuppressLint("SimpleDateFormat") - public String getSmsVoForSend() { - return getSmsVoForSend("", ""); - } - - @SuppressLint("SimpleDateFormat") - public String getSmsVoForSend(String ruleSmsTemplate) { - return getSmsVoForSend(ruleSmsTemplate, ""); - } - - @SuppressLint("SimpleDateFormat") - public String getSmsVoForSend(String ruleSmsTemplate, String regexReplace) { - String deviceMark = SettingUtils.getAddExtraDeviceMark().trim(); - String customSmsTemplate = getString(R.string.tag_from) + "\n" + - getString(R.string.tag_sms) + "\n" + - getString(R.string.tag_card_slot) + "\n" + - getString(R.string.tag_receive_time) + "\n" + - getString(R.string.tag_device_name); - - //优先取转发规则的自定义模板,留空则取全局设置 - if (!ruleSmsTemplate.isEmpty()) { - customSmsTemplate = ruleSmsTemplate; - } else { - boolean switchSmsTemplate = SettingUtils.getSwitchSmsTemplate(); - String smsTemplate = SettingUtils.getSmsTemplate().trim(); - if (switchSmsTemplate && !smsTemplate.isEmpty()) { - customSmsTemplate = smsTemplate; - } - } - - String versionName = SettingUtils.getVersionName(); - String smsVoForSend = customSmsTemplate.replace(getString(R.string.tag_from), mobile) - .replace(getString(R.string.tag_package_name), mobile) - .replace(getString(R.string.tag_sms), content) - .replace(getString(R.string.tag_msg), content) - .replace(getString(R.string.tag_card_slot), simInfo) - .replace(getString(R.string.tag_title), simInfo) - .replace(getString(R.string.tag_receive_time), new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(date)) - .replace(getString(R.string.tag_device_name), deviceMark) - .replace(getString(R.string.tag_app_version), versionName) - .trim(); - - return RegexReplace(regexReplace, smsVoForSend); - } - - //正则替换内容 - private String RegexReplace(String regexReplace, String Content) { - if (regexReplace == null || regexReplace.isEmpty()) return Content; - - try { - String newContent = Content; - String[] lineArray = regexReplace.split("\\n"); - for (String line : lineArray) { - String[] lineSplit = line.split("==="); - if (lineSplit.length >= 1) { - String regex = lineSplit[0]; - String replacement = lineSplit.length >= 2 ? lineSplit[1] : ""; - newContent = newContent.replaceAll(regex, replacement); - } - } - return newContent; - } catch (Exception e) { - Log.e("RegexReplace", "Failed to get the receiving phone number:" + e.getMessage()); - return Content; - } - } - - private static String getString(int resId) { - return MyApplication.getContext().getString(resId); - } - - @NonNull - @Override - public String toString() { - return "SmsVo{" + - "mobile='" + mobile + '\'' + - ", content='" + content + '\'' + - ", date=" + date + - ", simInfo=" + simInfo + - '}'; - } - -} diff --git a/app/src/main/java/com/idormy/sms/forwarder/model/vo/TelegramSettingVo.java b/app/src/main/java/com/idormy/sms/forwarder/model/vo/TelegramSettingVo.java deleted file mode 100644 index 4d9a3c0c..00000000 --- a/app/src/main/java/com/idormy/sms/forwarder/model/vo/TelegramSettingVo.java +++ /dev/null @@ -1,69 +0,0 @@ -package com.idormy.sms.forwarder.model.vo; - -import android.text.TextUtils; - -import com.idormy.sms.forwarder.R; - -import java.io.Serializable; -import java.net.Proxy; - -import lombok.Data; - -@Data -public class TelegramSettingVo implements Serializable { - private String apiToken; - private String chatId; - private Proxy.Type proxyType = Proxy.Type.DIRECT; - private String proxyHost; - private String proxyPort; - private Boolean proxyAuthenticator = false; - private String proxyUsername; - private String proxyPassword; - private String method; - - public TelegramSettingVo() { - } - - public TelegramSettingVo(String apiToken, String chatId) { - this.apiToken = apiToken; - this.chatId = chatId; - this.proxyType = Proxy.Type.DIRECT; - this.method = "POST"; - } - - public TelegramSettingVo(String apiToken, String chatId, int proxyTypeId, String proxyHost, String proxyPort, boolean proxyAuthenticator, String proxyUsername, String proxyPassword, String method) { - this.apiToken = apiToken; - this.chatId = chatId; - if (proxyTypeId == R.id.btnProxyHttp) { - this.proxyType = Proxy.Type.HTTP; - } else if (proxyTypeId == R.id.btnProxySocks) { - this.proxyType = Proxy.Type.SOCKS; - } else { - this.proxyType = Proxy.Type.DIRECT; - } - this.proxyHost = proxyHost; - this.proxyPort = proxyPort; - this.proxyAuthenticator = proxyAuthenticator; - this.proxyUsername = proxyUsername; - this.proxyPassword = proxyPassword; - this.method = TextUtils.isEmpty(method) ? "POST" : method; - } - - public int getProxyTypeCheckId() { - if (proxyType == Proxy.Type.HTTP) { - return R.id.btnProxyHttp; - } else if (proxyType == Proxy.Type.SOCKS) { - return R.id.btnProxySocks; - } else { - return R.id.btnProxyNone; - } - } - - public int getMethodCheckId() { - if (method == null || method.equals("POST")) { - return R.id.radioTelegramMethodPost; - } else { - return R.id.radioTelegramMethodGet; - } - } -} diff --git a/app/src/main/java/com/idormy/sms/forwarder/model/vo/WebNotifySettingVo.java b/app/src/main/java/com/idormy/sms/forwarder/model/vo/WebNotifySettingVo.java deleted file mode 100644 index 48bcf67a..00000000 --- a/app/src/main/java/com/idormy/sms/forwarder/model/vo/WebNotifySettingVo.java +++ /dev/null @@ -1,38 +0,0 @@ -package com.idormy.sms.forwarder.model.vo; - -import com.idormy.sms.forwarder.R; - -import java.io.Serializable; -import java.util.Map; - -import lombok.Data; - -@Data -public class WebNotifySettingVo implements Serializable { - private String webServer; - private String secret; - private String method; - private String webParams; - private Map headers; - - public WebNotifySettingVo() { - } - - public WebNotifySettingVo(String webServer, String secret, String method, String webParams, Map headers) { - this.webServer = webServer; - this.secret = secret; - this.method = method; - this.webParams = webParams; - this.headers = headers; - } - - public int getWebNotifyMethodCheckId() { - if (method == null || method.equals("POST")) { - return R.id.radioWebNotifyMethodPost; - } else { - return R.id.radioWebNotifyMethodGet; - } - } - - -} diff --git a/app/src/main/java/com/idormy/sms/forwarder/receiver/BaseServlet.java b/app/src/main/java/com/idormy/sms/forwarder/receiver/BaseServlet.java deleted file mode 100644 index 119d3d10..00000000 --- a/app/src/main/java/com/idormy/sms/forwarder/receiver/BaseServlet.java +++ /dev/null @@ -1,260 +0,0 @@ -package com.idormy.sms.forwarder.receiver; - -import android.annotation.SuppressLint; -import android.content.Context; -import android.util.Log; - -import com.alibaba.fastjson.JSON; -import com.alibaba.fastjson.TypeReference; -import com.alibaba.fastjson.util.IOUtils; -import com.idormy.sms.forwarder.model.LogModel; -import com.idormy.sms.forwarder.model.vo.ResVo; -import com.idormy.sms.forwarder.model.vo.SmsHubVo; -import com.idormy.sms.forwarder.utils.CloneUtils; -import com.idormy.sms.forwarder.utils.HttpUtils; -import com.idormy.sms.forwarder.utils.LogUtils; -import com.idormy.sms.forwarder.utils.SettingUtils; -import com.idormy.sms.forwarder.utils.SimUtils; -import com.idormy.sms.forwarder.utils.SmsUtils; - -import org.eclipse.jetty.server.Server; -import org.eclipse.jetty.servlet.ServletContextHandler; -import org.eclipse.jetty.servlet.ServletHolder; -import org.eclipse.jetty.util.StringUtil; - -import java.io.BufferedReader; -import java.io.File; -import java.io.IOException; -import java.io.PrintWriter; -import java.io.Reader; -import java.nio.charset.StandardCharsets; -import java.util.List; - -import javax.servlet.MultipartConfigElement; -import javax.servlet.ServletOutputStream; -import javax.servlet.annotation.MultipartConfig; -import javax.servlet.annotation.WebServlet; -import javax.servlet.http.HttpServlet; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -@WebServlet -@MultipartConfig -public class BaseServlet extends HttpServlet { - - private static final String TAG = "BaseServlet"; - public static final int BUFFER_SIZE = 1 << 12; - private static final long serialVersionUID = 1L; - public static final long RULE_ID = -999L; - public static final String SMSHUB_PATH = "/send_api"; - public static final String CLONE_PATH = "/clone"; - - private final String path; - @SuppressLint("StaticFieldLeak") - private final Context context; - - public BaseServlet(String path, Context context) { - this.path = path; - this.context = context; - SettingUtils.init(context); - } - - public Context getContext() { - return context; - } - - public String getPath() { - return path; - } - - public static void addServlet(Server jettyServer, Context context) { - ServletContextHandler contextHandler = new ServletContextHandler(); - addHolder(contextHandler, new BaseServlet(BaseServlet.CLONE_PATH, context)); - addHolder(contextHandler, new BaseServlet(BaseServlet.SMSHUB_PATH, context)); - // addholder(contextHandler, new BaseServlet("/", context)); - jettyServer.setHandler(contextHandler); - } - - @Override - protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException { - String msg = "HTTP method POST is not supported by this URL"; - if (CLONE_PATH.equals(path)) { - clone_api(req, resp); - } else if (SMSHUB_PATH.equals(path)) { - send_api(req, resp); - } else if ("1.1".endsWith(req.getProtocol())) { - resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED, msg); - } else { - resp.sendError(HttpServletResponse.SC_BAD_REQUEST, msg); - } - } - - @Override - protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException { - String msg = "HTTP method GET is not supported by this URL"; - if (CLONE_PATH.equals(path)) { - clone(req, resp); - } else if ("1.1".endsWith(req.getProtocol())) { - resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED, msg); - } else { - resp.sendError(HttpServletResponse.SC_BAD_REQUEST, msg); - } - } - - //发送短信api - private void send_api(HttpServletRequest req, HttpServletResponse resp) throws IOException { - resp.setCharacterEncoding("utf-8"); - PrintWriter writer = resp.getWriter(); - BufferedReader reader = req.getReader(); - try { - String read = read(reader); - Log.i(TAG, "Request message:" + read); - ResVo> result = ResVo.suessces(null); - if (StringUtil.isNotBlank(read)) { - ResVo> obj = JSON.parseObject(read, new TypeReference>>() { - }.getType()); - List data = obj.getData(); - for (SmsHubVo vo : data) { - sendSms(TAG, vo); - } - result.setData(data); - } - resp.setContentType("application/json;charset=utf-8"); - StringBuilder sb = new StringBuilder(); - int i = 0; - for (SmsHubVo datum : result.getData()) { - if (SmsHubVo.Action.failure.code().equals(datum.getAction())) { - sb.append(",").append("第").append(i++).append("条处理失败:").append(datum.getErrMsg()); - } - i++; - } - if (sb.length() > 0) { - result.setError(sb.substring(1)); - } - String text = JSON.toJSONString(result); - writer.println(text); - } catch (Exception e) { - e.printStackTrace(); - printErrMsg(resp, writer, e); - } finally { - IOUtils.close(reader); - IOUtils.close(writer); - } - } - - //一键克隆——查询接口 - private void clone_api(HttpServletRequest req, HttpServletResponse resp) throws IOException { - resp.setCharacterEncoding("utf-8"); - PrintWriter writer = resp.getWriter(); - BufferedReader reader = req.getReader(); - - try { - resp.setContentType("application/json;charset=utf-8"); - String json = CloneUtils.exportSettings(); - writer.println(json); - } catch (Exception e) { - e.printStackTrace(); - printErrMsg(resp, writer, e); - } finally { - IOUtils.close(reader); - IOUtils.close(writer); - } - } - - //一键克隆——下载接口 - private void clone(HttpServletRequest req, HttpServletResponse resp) throws IOException { - resp.addHeader("Content-Disposition", "attachment;filename=" + "SmsForwarder.json"); - ServletOutputStream outputStream = resp.getOutputStream(); - try { - String json = CloneUtils.exportSettings(); - outputStream.write(json.getBytes(StandardCharsets.UTF_8)); - } catch (Exception e) { - e.printStackTrace(); - String text = "Internal server error: " + e.getMessage(); - Log.e(TAG, text); - resp.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); - } finally { - IOUtils.close(outputStream); - } - } - - //短信列表api - - //联系人api - - //查询通话记录api - - //查询电量api - - //规则列表api(增删改查) - - //发送通道api(增删改查) - - ///////////////////////////////////////////////////////////////////////////////////////////////////////// - - private static String read(Reader reader) throws IOException { - char[] buffer = new char[BUFFER_SIZE]; - int size; - StringBuilder sb = new StringBuilder(); - while ((size = reader.read(buffer)) != -1) { - char[] chars = new char[size]; - System.arraycopy(buffer, 0, chars, 0, size); - sb.append(chars); - } - return sb.toString(); - } - - private static void addHolder(ServletContextHandler servletContextHandler, BaseServlet baseServlet) { - ServletHolder servletHolder = new ServletHolder(baseServlet); - servletHolder.getRegistration().setMultipartConfig(new MultipartConfigElement(baseServlet.getContext().getCacheDir().getAbsolutePath() + File.pathSeparator + "jettyServer")); - servletContextHandler.addServlet(servletHolder, baseServlet.getPath()); - } - - private void printErrMsg(HttpServletResponse resp, PrintWriter writer, Exception e) { - String text = "Internal server error: " + e.getMessage(); - Log.e(TAG, text); - resp.setStatus(HttpServletResponse.SC_OK); - resp.setContentType("application/json;charset=utf-8"); - writer.println(JSON.toJSONString(ResVo.error(text))); - } - - private static void sendSms(String tag, SmsHubVo vo) { - boolean failure = true; - String msg = ""; - Long logId = null; - try { - if (SmsHubVo.Action.send.code().equals(vo.getAction())) { - vo.setType(SmsHubVo.Type.sms.code()); - if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP_MR1) { - int subscriptionIdBySimId = SimUtils.getSubscriptionIdBySimId(Integer.parseInt(vo.getChannel()) - 1); - msg = SmsUtils.sendSms(subscriptionIdBySimId, vo.getTarget(), vo.getContent()); - String simInfo = "SIM" + (subscriptionIdBySimId + 1); - vo.setChannel(simInfo); - logId = LogUtils.addLog(new LogModel(vo.getType(), vo.getTarget(), vo.getContent(), simInfo, RULE_ID)); - if (msg == null) { - failure = false; - HttpUtils.Toast(tag, "短信发送成功"); - Log.i(tag, "短信发送成功"); - vo.setAction(SmsHubVo.Action.suessces.code()); - LogUtils.updateLog(logId, 2, SmsHubVo.Action.suessces.code()); - } - } else { - msg = "api<22"; - } - } - } catch (Exception e) { - msg += e.getMessage(); - e.printStackTrace(); - } - if (failure) { - msg = "短信发送失败:" + msg; - HttpUtils.Toast(tag, msg); - Log.i(tag, msg); - vo.setAction(SmsHubVo.Action.failure.code()); - vo.setErrMsg(msg); - if (logId != null) { - LogUtils.updateLog(logId, 0, msg); - } - } - } -} diff --git a/app/src/main/java/com/idormy/sms/forwarder/receiver/BootReceiver.kt b/app/src/main/java/com/idormy/sms/forwarder/receiver/BootReceiver.kt new file mode 100644 index 00000000..6bf9a02d --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/receiver/BootReceiver.kt @@ -0,0 +1,47 @@ +package com.idormy.sms.forwarder.receiver + +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.os.Build +import android.util.Log +import com.idormy.sms.forwarder.activity.SplashActivity +import com.idormy.sms.forwarder.service.ForegroundService + +@Suppress("PropertyName") +class BootReceiver : BroadcastReceiver() { + + val TAG: String = BootReceiver::class.java.simpleName + + override fun onReceive(context: Context, intent: Intent?) { + val receiveAction: String? = intent?.action + Log.d(TAG, "onReceive intent $receiveAction") + if (receiveAction == "android.intent.action.BOOT_COMPLETED") { + try { + val i = Intent(context, SplashActivity::class.java) + i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + context.startActivity(i) + + //前台服务 + val frontServiceIntent = Intent(context, ForegroundService::class.java) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + context.startForegroundService(frontServiceIntent) + } else { + context.startService(frontServiceIntent) + } + + /*InitUtils.init(context) + //电池状态监听 + val batteryServiceIntent = Intent(context, BatteryService::class.java) + context.startService(batteryServiceIntent) + + //后台播放无声音乐 + if (SettingUtils.getPlaySilenceMusic()) { + context.startService(Intent(context, MusicService::class.java)) + }*/ + } catch (e: Exception) { + e.printStackTrace() + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/receiver/CactusReceiver.kt b/app/src/main/java/com/idormy/sms/forwarder/receiver/CactusReceiver.kt new file mode 100644 index 00000000..a8e6055d --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/receiver/CactusReceiver.kt @@ -0,0 +1,33 @@ +package com.idormy.sms.forwarder.receiver + +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.util.Log +import com.gyf.cactus.Cactus +import com.idormy.sms.forwarder.App + +//接收Cactus广播 +class CactusReceiver : BroadcastReceiver() { + override fun onReceive(context: Context, intent: Intent) { + intent.action?.apply { + when (this) { + Cactus.CACTUS_WORK -> { + Log.d( + App.TAG, + this + "--" + intent.getIntExtra(Cactus.CACTUS_TIMES, 0) + ) + } + Cactus.CACTUS_STOP -> { + Log.d(App.TAG, this) + } + Cactus.CACTUS_BACKGROUND -> { + Log.d(App.TAG, this) + } + Cactus.CACTUS_FOREGROUND -> { + Log.d(App.TAG, this) + } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/receiver/PhoneStateReceiver.java b/app/src/main/java/com/idormy/sms/forwarder/receiver/PhoneStateReceiver.java deleted file mode 100644 index e936d140..00000000 --- a/app/src/main/java/com/idormy/sms/forwarder/receiver/PhoneStateReceiver.java +++ /dev/null @@ -1,142 +0,0 @@ -package com.idormy.sms.forwarder.receiver; - -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.telephony.TelephonyManager; -import android.text.TextUtils; -import android.util.Log; - -import com.idormy.sms.forwarder.R; -import com.idormy.sms.forwarder.model.CallInfo; -import com.idormy.sms.forwarder.model.PhoneBookEntity; -import com.idormy.sms.forwarder.model.vo.SmsVo; -import com.idormy.sms.forwarder.sender.SendUtil; -import com.idormy.sms.forwarder.utils.CommonUtils; -import com.idormy.sms.forwarder.utils.ContactHelper; -import com.idormy.sms.forwarder.utils.PhoneUtils; -import com.idormy.sms.forwarder.utils.SettingUtils; -import com.idormy.sms.forwarder.utils.SimUtils; - -import java.text.SimpleDateFormat; -import java.util.Date; -import java.util.List; -import java.util.Locale; - -@SuppressWarnings("CommentedOutCode") -public class PhoneStateReceiver extends BroadcastReceiver { - private static final String TAG = "PhoneStateReceiver"; - private TelephonyManager mTelephonyManager; - - @Override - public void onReceive(Context context, Intent intent) { - if (!SettingUtils.getSwitchEnablePhone()) { - return; - } - - String action = intent.getAction(); - //Bundle bundle = intent.getExtras(); - //if (bundle != null) { - // for (String key : bundle.keySet()) { - // Log.e(TAG, key + " : " + (bundle.get(key) != null ? bundle.get(key) : "NULL")); - // } - //} - - if (TelephonyManager.ACTION_PHONE_STATE_CHANGED.equals(action)) { - //获取来电号码 - String phoneNumber = intent.getStringExtra(TelephonyManager.EXTRA_INCOMING_NUMBER); - if (mTelephonyManager == null) { - mTelephonyManager = - (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE); - } - int state = mTelephonyManager.getCallState(); - Log.d(TAG, "Caller information: state=" + state + " phoneNumber = " + phoneNumber); - switch (state) { - //包括响铃、第三方来电等待 - case TelephonyManager.CALL_STATE_RINGING: - break; - //空闲态(没有通话活动) - case TelephonyManager.CALL_STATE_IDLE: - if (!TextUtils.isEmpty(phoneNumber)) { - try { - //必须休眠才能获取来电记录 - Thread.sleep(1000); - - sendReceiveCallMsg(context, phoneNumber); - } catch (InterruptedException e) { - e.printStackTrace(); - } - } - break; - //包括dialing拨号中、active接通、hold挂起等 - case TelephonyManager.CALL_STATE_OFFHOOK: - break; - } - - } - - } - - private void sendReceiveCallMsg(Context context, String phoneNumber) { - - //获取后一条通话记录 - CallInfo callInfo = PhoneUtils.getLastCallInfo(phoneNumber); - if (callInfo == null) return; - - int type = callInfo.getType(); - if ((type == 1 && !SettingUtils.getSwitchCallType1()) - || (type == 2 && !SettingUtils.getSwitchCallType2()) - || (type == 3 && !SettingUtils.getSwitchCallType3())) { - Log.w(TAG, "Call record forwarding of this type is not enabled, no processing will be done!"); - return; - } - - Log.d(TAG, callInfo.toString()); - String name = callInfo.getName(); - String viaNumber = callInfo.getViaNumber(); //来源号码 - - //卡槽判断:获取卡槽失败时,默认为卡槽1 - String simInfo; - int simId = 1; - Log.d(TAG, "getSubscriptionId = " + callInfo.getSubscriptionId()); //TODO:这里的SubscriptionId跟短信的不一样 - if (callInfo.getSubscriptionId() != -1) { - simId = SimUtils.getSimIdBySubscriptionId(callInfo.getSubscriptionId()); - } - simInfo = simId == 2 ? SettingUtils.getAddExtraSim2() : SettingUtils.getAddExtraSim1(); //自定义备注优先 - simInfo = "SIM" + simId + "_" + simInfo; - - if (TextUtils.isEmpty(name)) { - List contacts = ContactHelper.getInstance().getContactByNumber(context, phoneNumber); - if (contacts != null && contacts.size() > 0) { - PhoneBookEntity phoneBookEntity = contacts.get(0); - name = phoneBookEntity.getName(); - } - if (TextUtils.isEmpty(name)) name = context.getString(R.string.unknown_number); - } - - //TODO:同一卡槽同一秒的重复未接来电广播不再重复处理(部分机型会收到两条广播?) - String time = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.CHINESE).format(new Date()); - String prevHash = SettingUtils.getPrevNoticeHash(phoneNumber); - String currHash = CommonUtils.MD5(phoneNumber + simInfo + time); - Log.d(TAG, "prevHash=" + prevHash + " currHash=" + currHash); - if (prevHash != null && prevHash.equals(currHash)) { - Log.w(TAG, "Repeated missed call broadcasts of the same card slot in the same second are no longer processed repeatedly (some models will receive two broadcasts)"); - return; - } - SettingUtils.setPrevNoticeHash(phoneNumber, currHash); - - SmsVo smsVo = new SmsVo(phoneNumber, getTypeText(context, type, name, viaNumber), new Date(), simInfo); - Log.d(TAG, "send_msg" + smsVo); - SendUtil.send_msg(context, smsVo, simId, "call"); - } - - //获取通话类型:1.呼入 2.呼出 3.未接 - private String getTypeText(Context context, int type, String name, String viaNumber) { - String str = context.getString(R.string.linkman) + name + "\n"; - if (!TextUtils.isEmpty(viaNumber)) str += context.getString(R.string.via_number) + viaNumber + "\n"; - str += context.getString(R.string.mandatory_type); - if (type == 1) return str + context.getString(R.string.received_call); - if (type == 2) return str + context.getString(R.string.local_outgoing_call); - return str + context.getString(R.string.missed_call); - } -} diff --git a/app/src/main/java/com/idormy/sms/forwarder/receiver/PhoneStateReceiver.kt b/app/src/main/java/com/idormy/sms/forwarder/receiver/PhoneStateReceiver.kt new file mode 100644 index 00000000..fdba8c13 --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/receiver/PhoneStateReceiver.kt @@ -0,0 +1,191 @@ +package com.idormy.sms.forwarder.receiver + +import android.Manifest +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.content.pm.PackageManager +import android.telephony.TelephonyManager +import android.text.TextUtils +import android.util.Log +import androidx.core.app.ActivityCompat +import androidx.work.OneTimeWorkRequestBuilder +import androidx.work.WorkManager +import androidx.work.workDataOf +import com.google.gson.Gson +import com.idormy.sms.forwarder.App +import com.idormy.sms.forwarder.R +import com.idormy.sms.forwarder.entity.CallInfo +import com.idormy.sms.forwarder.entity.MsgInfo +import com.idormy.sms.forwarder.utils.MMKVUtils +import com.idormy.sms.forwarder.utils.PhoneUtils +import com.idormy.sms.forwarder.utils.SettingUtils +import com.idormy.sms.forwarder.utils.Worker +import com.idormy.sms.forwarder.workers.SendWorker +import com.xuexiang.xutil.resource.ResUtils.getString +import java.util.* + +@Suppress("DEPRECATION") +class PhoneStateReceiver : BroadcastReceiver() { + + override fun onReceive(context: Context, intent: Intent) { + try { + //总开关 + if (!SettingUtils.enablePhone) return + + //过滤广播 + if (TelephonyManager.ACTION_PHONE_STATE_CHANGED != intent.action) return + + //权限判断 + if (ActivityCompat.checkSelfPermission(context, Manifest.permission.READ_PHONE_STATE) != PackageManager.PERMISSION_GRANTED) return + + //获取来电号码 + /*val phoneNumber = intent.getStringExtra(TelephonyManager.EXTRA_INCOMING_NUMBER) + val state = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + Core.telephonyManager.callStateForSubscription + } else { + Core.telephonyManager.callState + } + Log.d(TAG, "来电信息:state=$state phoneNumber = $phoneNumber") + if (TextUtils.isEmpty(phoneNumber)) return + + when (state) { + TelephonyManager.CALL_STATE_RINGING -> {} + TelephonyManager.CALL_STATE_IDLE -> { + *//*(Core.app as App).applicationScope.launch { + delay(500L) //延时0.5秒获取通话记录 + sendReceiveCallMsg(context, phoneNumber) + }*//* + sendReceiveCallMsg(context, phoneNumber) + } + TelephonyManager.CALL_STATE_OFFHOOK -> {} + }*/ + + val number = intent.extras!!.getString(TelephonyManager.EXTRA_INCOMING_NUMBER) + val stateStr = intent.extras!!.getString(TelephonyManager.EXTRA_STATE) + var state = 0 + when (stateStr) { + TelephonyManager.EXTRA_STATE_IDLE -> state = TelephonyManager.CALL_STATE_IDLE + TelephonyManager.EXTRA_STATE_OFFHOOK -> state = TelephonyManager.CALL_STATE_OFFHOOK + TelephonyManager.EXTRA_STATE_RINGING -> state = TelephonyManager.CALL_STATE_RINGING + } + onCallStateChanged(context, state, number) + + } catch (e: Exception) { + Log.e(TAG, e.message.toString()) + } + } + + //Incoming call- goes from IDLE to RINGING when it rings, to OFFHOOK when it's answered, to IDLE when its hung up + //Outgoing call- goes from IDLE to OFFHOOK when it dials out, to IDLE when hung up + private fun onCallStateChanged(context: Context, state: Int, number: String?) { + val lastState = MMKVUtils.getInt("CALL_LAST_STATE", TelephonyManager.CALL_STATE_IDLE) + if (lastState == state) { + //No change, debounce extras + return + } + + when (state) { + TelephonyManager.CALL_STATE_RINGING -> { + Log.d(TAG, "来电响铃") + MMKVUtils.put("CALL_IS_INCOMING", true) + //MMKVUtils.put("CALL_START_TIME", Date()) + MMKVUtils.put("CALL_SAVED_NUMBER", number) + } + TelephonyManager.CALL_STATE_OFFHOOK -> + //Transition of ringing->offhook are pickups of incoming calls. Nothing done on them + when { + lastState != TelephonyManager.CALL_STATE_RINGING -> { + Log.d(TAG, "去电接通") + MMKVUtils.put("CALL_IS_INCOMING", false) + //MMKVUtils.put("CALL_START_TIME", Date()) + } + else -> { + Log.d(TAG, "来电接通") + MMKVUtils.put("CALL_IS_INCOMING", true) + //MMKVUtils.put("CALL_START_TIME", Date()) + } + } + TelephonyManager.CALL_STATE_IDLE -> + //Went to idle- this is the end of a call. What type depends on previous state(s) + when { + lastState == TelephonyManager.CALL_STATE_RINGING -> { + Log.d(TAG, "来电未接") + sendReceiveCallMsg(context, 3, MMKVUtils.getString("CALL_SAVED_NUMBER", null)) + } + MMKVUtils.getBoolean("CALL_IS_INCOMING", false) -> { + Log.d(TAG, "来电挂机") + sendReceiveCallMsg(context, 1, MMKVUtils.getString("CALL_SAVED_NUMBER", null)) + } + else -> { + Log.d(TAG, "去电挂机") + sendReceiveCallMsg(context, 2, MMKVUtils.getString("CALL_SAVED_NUMBER", null)) + } + } + } + MMKVUtils.put("CALL_LAST_STATE", state) + } + + private fun sendReceiveCallMsg(context: Context, callType: Int, phoneNumber: String?) { + //必须休眠才能获取来电记录,否则可能获取到上一次通话的 + Thread.sleep(500) + //获取后一条通话记录 + Log.d(TAG, "callType = $callType, phoneNumber = $phoneNumber") + val callInfo: CallInfo? = PhoneUtils.getLastCallInfo(callType, phoneNumber) + Log.d(TAG, "callInfo = $callInfo") + if (callInfo?.number == null) return + + //判断是否开启该类型转发 + if ((callInfo.type == 1 && !SettingUtils.enableCallType1) + || (callInfo.type == 2 && !SettingUtils.enableCallType2) + || (callInfo.type == 3 && !SettingUtils.enableCallType3) + ) { + Log.w(TAG, "未开启该类型转发,type=" + callInfo.type) + return + } + + //获取卡槽信息 + if (App.SimInfoList.isEmpty()) { + App.SimInfoList = PhoneUtils.getSimMultiInfo() + } + Log.e(TAG, "SimInfoList = " + App.SimInfoList.toString()) + + //卡槽id:-1=获取失败、0=卡槽1、1=卡槽2 + var simSlot = -1 + Log.e(TAG, "getSubscriptionId = " + callInfo.simId) + if (callInfo.simId != -1 && App.SimInfoList.isNotEmpty()) { + for (simInfo in App.SimInfoList.values) { + if (simInfo.mSubscriptionId == callInfo.simId) { + simSlot = simInfo.mSimSlotIndex + } + } + } + //获取卡槽信息 + val simInfo = when (simSlot) { + 0 -> "SIM1_" + SettingUtils.extraSim1 + 1 -> "SIM2_" + SettingUtils.extraSim2 + else -> "" + } + + //获取联系人姓名 + if (TextUtils.isEmpty(callInfo.name)) { + val contacts = PhoneUtils.getContactByNumber(phoneNumber) + callInfo.name = if (contacts.isNotEmpty()) contacts[0].name else getString(R.string.unknown_number) + } + + val msgInfo = MsgInfo("call", callInfo.number, PhoneUtils.getCallMsg(callInfo), Date(), simInfo, simSlot) + val request = OneTimeWorkRequestBuilder() + .setInputData( + workDataOf( + Worker.sendMsgInfo to Gson().toJson(msgInfo) + ) + ) + .build() + WorkManager.getInstance(context).enqueue(request) + + } + + companion object { + private const val TAG = "PhoneStateReceiver" + } +} \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/receiver/RebootBroadcastReceiver.java b/app/src/main/java/com/idormy/sms/forwarder/receiver/RebootBroadcastReceiver.java deleted file mode 100644 index f034b3fb..00000000 --- a/app/src/main/java/com/idormy/sms/forwarder/receiver/RebootBroadcastReceiver.java +++ /dev/null @@ -1,45 +0,0 @@ -package com.idormy.sms.forwarder.receiver; - -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.os.Build; -import android.util.Log; - -import com.idormy.sms.forwarder.service.BatteryService; -import com.idormy.sms.forwarder.service.FrontService; -import com.idormy.sms.forwarder.service.MusicService; -import com.idormy.sms.forwarder.utils.InitUtils; -import com.idormy.sms.forwarder.utils.SettingUtils; - -public class RebootBroadcastReceiver extends BroadcastReceiver { - - @Override - public void onReceive(Context context, Intent intent) { - String receiveAction = intent.getAction(); - String TAG = "RebootBroadcastReceiver"; - Log.d(TAG, "onReceive intent " + receiveAction); - if (receiveAction.equals("android.intent.action.BOOT_COMPLETED")) { - InitUtils.init(context); - - //前台服务 - Intent frontServiceIntent = new Intent(context, FrontService.class); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - context.startForegroundService(frontServiceIntent); - } else { - context.startService(frontServiceIntent); - } - - //电池状态监听 - Intent batteryServiceIntent = new Intent(context, BatteryService.class); - context.startService(batteryServiceIntent); - - //后台播放无声音乐 - if (SettingUtils.getPlaySilenceMusic()) { - context.startService(new Intent(context, MusicService.class)); - } - } - - } - -} diff --git a/app/src/main/java/com/idormy/sms/forwarder/receiver/ScreenBroadcastReceiver.java b/app/src/main/java/com/idormy/sms/forwarder/receiver/ScreenBroadcastReceiver.java deleted file mode 100644 index e4c4a602..00000000 --- a/app/src/main/java/com/idormy/sms/forwarder/receiver/ScreenBroadcastReceiver.java +++ /dev/null @@ -1,35 +0,0 @@ -package com.idormy.sms.forwarder.receiver; - -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.util.Log; - -import com.idormy.sms.forwarder.MyApplication; -import com.idormy.sms.forwarder.utils.OnePixelManager; -import com.idormy.sms.forwarder.utils.SettingUtils; - -//监听屏幕状态变换广播(开屏、锁屏、解锁) -public class ScreenBroadcastReceiver extends BroadcastReceiver { - private static final String TAG = "ScreenBroadcastReceiver"; - - @Override - public void onReceive(Context context, Intent intent) { - String action = intent.getAction(); - - //1像素透明Activity保活 - if (SettingUtils.getOnePixelActivity()) { - OnePixelManager manager = new OnePixelManager(); - if (Intent.ACTION_SCREEN_ON.equals(action)) {//如果亮屏,则关闭1像素Activity - manager.finishOnePixelActivity(); - } else if (Intent.ACTION_SCREEN_OFF.equals(action)) {//如果息屏,则开启1像素Activity - manager.startOnePixelActivity(context); - } - } - - //是否已解锁 - MyApplication.isUserPresent = Intent.ACTION_USER_PRESENT.equals(action); - Log.d(TAG, String.format("isUserPresent=%s", MyApplication.isUserPresent)); - } -} - diff --git a/app/src/main/java/com/idormy/sms/forwarder/receiver/SimStateReceiver.java b/app/src/main/java/com/idormy/sms/forwarder/receiver/SimStateReceiver.java deleted file mode 100644 index 6252c7b1..00000000 --- a/app/src/main/java/com/idormy/sms/forwarder/receiver/SimStateReceiver.java +++ /dev/null @@ -1,38 +0,0 @@ -package com.idormy.sms.forwarder.receiver; - -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.util.Log; - -import com.idormy.sms.forwarder.MyApplication; -import com.idormy.sms.forwarder.utils.PhoneUtils; - -public class SimStateReceiver extends BroadcastReceiver { - - private static final String TAG = "SimStateReceiver"; - public static final String ACTION_SIM_STATE_CHANGED = "android.intent.action.SIM_STATE_CHANGED"; - private static final String EXTRA_SIM_STATE = "ss"; - private static final String SIM_STATE_LOADED = "LOADED"; - - /** - * 更换SIM卡,如果不杀后台并重启,则发送出的「卡槽信息」仍然是刚启动应用时读取的SIM卡 - * 增加这个Receiver,接收SIM卡插拔状态广播,自动更新缓存 - */ - @Override - public void onReceive(Context context, Intent intent) { - String receiveAction = intent.getAction(); - Log.d(TAG, "onReceive intent " + receiveAction); - if (ACTION_SIM_STATE_CHANGED.equals(receiveAction)) { - //SIM状态的额外信息 - String state = intent.getExtras().getString(EXTRA_SIM_STATE); - Log.d(TAG, state); - //只需要最后一个SIM加载完毕的 LOADED 状态 - if (SIM_STATE_LOADED.equals(state)) { - //刷新SimInfoList - MyApplication.SimInfoList = PhoneUtils.getSimMultiInfo(); - Log.d(TAG, MyApplication.SimInfoList.toString()); - } - } - } -} diff --git a/app/src/main/java/com/idormy/sms/forwarder/receiver/SimStateReceiver.kt b/app/src/main/java/com/idormy/sms/forwarder/receiver/SimStateReceiver.kt new file mode 100644 index 00000000..bf0943f6 --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/receiver/SimStateReceiver.kt @@ -0,0 +1,37 @@ +package com.idormy.sms.forwarder.receiver + +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.util.Log +import com.idormy.sms.forwarder.App +import com.idormy.sms.forwarder.utils.PhoneUtils + +class SimStateReceiver : BroadcastReceiver() { + /** + * 更换SIM卡,如果不杀后台并重启,则发送出的「卡槽信息」仍然是刚启动应用时读取的SIM卡 + * 增加这个Receiver,接收SIM卡插拔状态广播,自动更新缓存 + */ + override fun onReceive(context: Context, intent: Intent) { + val receiveAction = intent.action + Log.d(TAG, "onReceive intent $receiveAction") + if (ACTION_SIM_STATE_CHANGED == receiveAction) { + //SIM状态的额外信息 + val state = intent.extras!!.getString(EXTRA_SIM_STATE) + Log.d(TAG, state!!) + //只需要最后一个SIM加载完毕的 LOADED 状态 + if (SIM_STATE_LOADED == state) { + //刷新SimInfoList + App.SimInfoList = PhoneUtils.getSimMultiInfo() + Log.d(TAG, App.SimInfoList.toString()) + } + } + } + + companion object { + private const val TAG = "SimStateReceiver" + const val ACTION_SIM_STATE_CHANGED = "android.intent.action.SIM_STATE_CHANGED" + private const val EXTRA_SIM_STATE = "ss" + private const val SIM_STATE_LOADED = "LOADED" + } +} \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/receiver/SmsBroadcastReceiver.java b/app/src/main/java/com/idormy/sms/forwarder/receiver/SmsBroadcastReceiver.java deleted file mode 100644 index 0e6d9b85..00000000 --- a/app/src/main/java/com/idormy/sms/forwarder/receiver/SmsBroadcastReceiver.java +++ /dev/null @@ -1,104 +0,0 @@ -package com.idormy.sms.forwarder.receiver; - -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.os.Build; -import android.os.Bundle; -import android.telephony.SmsMessage; -import android.util.Log; - -import androidx.annotation.RequiresApi; - -import com.idormy.sms.forwarder.model.vo.SmsVo; -import com.idormy.sms.forwarder.sender.SendUtil; -import com.idormy.sms.forwarder.utils.SettingUtils; -import com.idormy.sms.forwarder.utils.SimUtils; - -import java.util.ArrayList; -import java.util.Date; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; - -public class SmsBroadcastReceiver extends BroadcastReceiver { - - @RequiresApi(api = Build.VERSION_CODES.M) - @Override - public void onReceive(Context context, Intent intent) { - - String receiveAction = intent.getAction(); - String TAG = "SmsBroadcastReceiver"; - Log.d(TAG, "onReceive intent " + receiveAction); - - String SMS_RECEIVED_ACTION = "android.provider.Telephony.SMS_RECEIVED"; - String SMS_DELIVER_ACTION = "android.provider.Telephony.SMS_DELIVER"; - - if (SMS_RECEIVED_ACTION.equals(receiveAction) || SMS_DELIVER_ACTION.equals(receiveAction)) { - try { - if (!SettingUtils.getSwitchEnableSms()) { - return; - } - - Bundle extras = intent.getExtras(); - Object[] object = (Object[]) Objects.requireNonNull(extras).get("pdus"); - if (object != null) { - - //接收手机卡信息 - String simInfo = ""; - //卡槽ID,默认卡槽为1 - int simId = 1; - try { - if (extras.containsKey("simId")) { - simId = extras.getInt("simId"); - } else if (extras.containsKey("subscription")) { - simId = SimUtils.getSimIdBySubscriptionId(extras.getInt("subscription")); - } - - //自定义备注优先 - simInfo = simId == 2 ? SettingUtils.getAddExtraSim2() : SettingUtils.getAddExtraSim1(); - simInfo = "SIM" + simId + "_" + simInfo; - } catch (Exception e) { - Log.e(TAG, "Failed to get the receiving phone number:" + e.getMessage()); - } - - List smsVoList = new ArrayList<>(); - String format = intent.getStringExtra("format"); - Map mobileToContent = new HashMap<>(); - Date date = new Date(); - for (Object pdus : object) { - byte[] pdusMsg = (byte[]) pdus; - SmsMessage sms = SmsMessage.createFromPdu(pdusMsg, format); - String mobile = sms.getOriginatingAddress();//发送短信的手机号 - if (mobile == null) { - continue; - } - //下面是获取短信的发送时间 - date = new Date(sms.getTimestampMillis()); - - String content = mobileToContent.get(mobile); - if (content == null) content = ""; - - content += sms.getMessageBody().trim();//短信内容 - mobileToContent.put(mobile, content); - - } - - for (String mobile : mobileToContent.keySet()) { - smsVoList.add(new SmsVo(mobile, mobileToContent.get(mobile), date, simInfo)); - } - - Log.d(TAG, "SMS: " + smsVoList); - SendUtil.send_msg_list(context, smsVoList, simId, "sms"); - } - - } catch (Throwable throwable) { - Log.e(TAG, "Parsing SMS failed: " + throwable.getMessage()); - } - - } - - } - -} diff --git a/app/src/main/java/com/idormy/sms/forwarder/receiver/SmsReceiver.kt b/app/src/main/java/com/idormy/sms/forwarder/receiver/SmsReceiver.kt new file mode 100644 index 00000000..3a7fc304 --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/receiver/SmsReceiver.kt @@ -0,0 +1,84 @@ +package com.idormy.sms.forwarder.receiver + +import android.Manifest +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.content.pm.PackageManager +import android.provider.Telephony +import android.util.Log +import androidx.core.app.ActivityCompat +import androidx.work.OneTimeWorkRequestBuilder +import androidx.work.WorkManager +import androidx.work.workDataOf +import com.google.gson.Gson +import com.idormy.sms.forwarder.App +import com.idormy.sms.forwarder.entity.MsgInfo +import com.idormy.sms.forwarder.utils.SettingUtils +import com.idormy.sms.forwarder.utils.Worker +import com.idormy.sms.forwarder.workers.SendWorker +import java.util.* + +//短信广播 +@Suppress("PrivatePropertyName", "DEPRECATION") +class SmsReceiver : BroadcastReceiver() { + + private var TAG = "SmsReceiver" + + override fun onReceive(context: Context, intent: Intent) { + try { + //总开关 + if (!SettingUtils.enableSms) return + + //过滤广播 + if (intent.action != Telephony.Sms.Intents.SMS_RECEIVED_ACTION && intent.action != Telephony.Sms.Intents.SMS_DELIVER_ACTION) return + + //权限判断 + if (ActivityCompat.checkSelfPermission(context, Manifest.permission.RECEIVE_SMS) != PackageManager.PERMISSION_GRANTED) return + + var from = "" + var content = "" + for (smsMessage in Telephony.Sms.Intents.getMessagesFromIntent(intent)) { + from = smsMessage.displayOriginatingAddress + content += smsMessage.messageBody + } + Log.d(TAG, "from = $from") + Log.d(TAG, "content = $content") + + //TODO:准确获取卡槽信息,目前测试结果只有 subscription 相对靠谱 + //val slot = intent.extras?.getInt("slot") ?: -1 + //val simId = intent.extras?.getInt("simId") ?: -1 + val subscription = intent.extras?.getInt("subscription") ?: -1 + //Logs.d(TAG, "slot = $slot, simId = $simId, subscription = $subscription") + //卡槽id:-1=获取失败、0=卡槽1、1=卡槽2 + var simSlot = -1 + if (App.SimInfoList.isNotEmpty()) { + for (simInfo in App.SimInfoList.values) { + if (simInfo.mSubscriptionId == subscription) { + simSlot = simInfo.mSimSlotIndex + } + } + } + //获取卡槽信息 + val simInfo = when (simSlot) { + 0 -> "SIM1_" + SettingUtils.extraSim1 + 1 -> "SIM2_" + SettingUtils.extraSim2 + else -> "" + } + + val msgInfo = MsgInfo("sms", from, content, Date(), simInfo, simSlot) + val request = OneTimeWorkRequestBuilder() + .setInputData( + workDataOf( + Worker.sendMsgInfo to Gson().toJson(msgInfo) + ) + ) + .build() + WorkManager.getInstance(context).enqueue(request) + + } catch (e: Exception) { + Log.e(TAG, "Parsing SMS failed: " + e.message.toString()) + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/sender/BatteryReportCronTask.java b/app/src/main/java/com/idormy/sms/forwarder/sender/BatteryReportCronTask.java deleted file mode 100644 index 76516217..00000000 --- a/app/src/main/java/com/idormy/sms/forwarder/sender/BatteryReportCronTask.java +++ /dev/null @@ -1,97 +0,0 @@ -package com.idormy.sms.forwarder.sender; - -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.util.Log; - -import com.idormy.sms.forwarder.MyApplication; -import com.idormy.sms.forwarder.model.vo.SmsVo; -import com.idormy.sms.forwarder.utils.BatteryUtils; -import com.idormy.sms.forwarder.utils.SettingUtils; - -import java.util.Calendar; -import java.util.Date; -import java.util.Timer; -import java.util.TimerTask; - -public class BatteryReportCronTask { - private static final String TAG = "BatteryReportCronTask"; - private volatile static BatteryReportCronTask singleton; - private static Timer timer; - - private BatteryReportCronTask() { - } - - public static BatteryReportCronTask getSingleton() { - if (singleton == null) { - synchronized (BatteryReportCronTask.class) { - if (singleton == null) { - singleton = new BatteryReportCronTask(); - } - } - } - return singleton; - } - - public void updateTimer() { - cancelTimer(); - if (SettingUtils.getSwitchEnableBatteryCron()) { - startTimer(); - } else { - Log.d(TAG, "Cancel Task"); - } - } - - private void cancelTimer() { - if (timer != null) { - timer.cancel(); - timer = null; - } - } - - private void startTimer() { - String startTime = SettingUtils.getBatteryCronStartTime(); - int interval = SettingUtils.getBatteryCronInterval(); - Log.i(TAG, "Task started, startTime: " + startTime + ", interval: " + interval); - - int hour = Integer.parseInt(startTime.split(":")[0]); - int minute = Integer.parseInt(startTime.split(":")[1]); - - Calendar startTimeCalendar = Calendar.getInstance(); - startTimeCalendar.set(Calendar.HOUR_OF_DAY, hour); - startTimeCalendar.set(Calendar.MINUTE, minute); - startTimeCalendar.set(Calendar.SECOND, 0); - - Calendar currentTimeCalendar = Calendar.getInstance(); - if (startTimeCalendar.before(currentTimeCalendar)) { - //首次发送时间在当前时间之前,日期加一天 - startTimeCalendar.add(Calendar.DATE, 1); - } - Log.d(TAG, startTimeCalendar.getTime().toString()); - - timer = new Timer("BatteryReportCronTimer", true); - timer.schedule(new Task(), startTimeCalendar.getTime(), interval * 60 * 1000L); - } - - static class Task extends TimerTask { - @Override - public void run() { - IntentFilter intentFilter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED); - Intent intent = MyApplication.getContext().registerReceiver(null, intentFilter); - String msg = BatteryUtils.getBatteryInfo(intent); - sendMessage(MyApplication.getContext(), msg); - } - - //发送信息 - private void sendMessage(Context context, String msg) { - Log.i(TAG, msg); - try { - SmsVo smsVo = new SmsVo("88888888", msg, new Date(), "电池状态定时推送"); - SendUtil.send_msg(context, smsVo, 1, "app"); - } catch (Exception e) { - Log.e(TAG, "sendMessage e:" + e.getMessage()); - } - } - } -} diff --git a/app/src/main/java/com/idormy/sms/forwarder/sender/HttpServer.java b/app/src/main/java/com/idormy/sms/forwarder/sender/HttpServer.java deleted file mode 100644 index 6876493e..00000000 --- a/app/src/main/java/com/idormy/sms/forwarder/sender/HttpServer.java +++ /dev/null @@ -1,108 +0,0 @@ -package com.idormy.sms.forwarder.sender; - -import android.annotation.SuppressLint; -import android.content.Context; -import android.util.Log; - -import com.hjq.toast.ToastUtils; -import com.idormy.sms.forwarder.R; -import com.idormy.sms.forwarder.model.vo.SmsHubVo; -import com.idormy.sms.forwarder.receiver.BaseServlet; -import com.idormy.sms.forwarder.utils.Define; -import com.idormy.sms.forwarder.utils.NetUtils; -import com.idormy.sms.forwarder.utils.SettingUtils; - -import org.eclipse.jetty.server.Server; - -public class HttpServer { - private static Boolean hasInit = false; - private static Server jettyServer; - @SuppressLint("StaticFieldLeak") - private static Context context; - private static long ts = 0L; - - @SuppressLint("HandlerLeak") - public static void init(Context context) { - //noinspection SynchronizeOnNonFinalField - synchronized (hasInit) { - if (hasInit) return; - - hasInit = true; - HttpServer.context = context; - jettyServer = new Server(Define.HTTP_SERVER_PORT); - BaseServlet.addServlet(jettyServer, context); - } - } - - /** - * Checks if the Jetty is running - * boolean - true when server is running/starting/stopping, false otherwise - */ - public synchronized static Boolean asRunning() { - if (jettyServer != null) { - return jettyServer.isRunning() && !jettyServer.isStopping(); - } - return false; - } - - /** - * Checks if Jetty is stopping - * boolean - True when server is stopping - */ - private synchronized static Boolean asStopp() { - if (jettyServer != null) { - return !(jettyServer.isRunning() || jettyServer.isStopping()); - } - return true; - } - - public synchronized static boolean update() { - //非WiFi网络下不可启用 - if (NetUtils.NETWORK_WIFI != NetUtils.getNetWorkStatus()) { - ToastUtils.show(R.string.no_wifi_network); - if (asRunning()) stop(); - return false; - } - long l = System.currentTimeMillis(); - if (l - ts < 3000 && asRunning()) { - ToastUtils.show(R.string.tips_wait_3_seconds); - return false; - } - if (asRunning().equals(SettingUtils.getSwitchEnableHttpServer())) { - return false; - } - if (SettingUtils.getSwitchEnableHttpServer()) { - SmsHubVo.getDevInfoMap(true); - start(); - ts = System.currentTimeMillis(); - ToastUtils.show(R.string.server_has_started); - } else { - stop(); - ToastUtils.show(R.string.server_has_stopped); - } - return true; - } - - private static void start() { - stop(); - Log.i("HttpServer", "start"); - try { - jettyServer.start(); - } catch (Exception e) { - e.printStackTrace(); - } - } - - private static void stop() { - if (asStopp()) return; - - try { - if (jettyServer != null) { - jettyServer.stop(); - } - } catch (Exception e) { - e.printStackTrace(); - } - } - -} diff --git a/app/src/main/java/com/idormy/sms/forwarder/sender/RetryIntercepter.java b/app/src/main/java/com/idormy/sms/forwarder/sender/RetryIntercepter.java deleted file mode 100644 index 231d445b..00000000 --- a/app/src/main/java/com/idormy/sms/forwarder/sender/RetryIntercepter.java +++ /dev/null @@ -1,105 +0,0 @@ -package com.idormy.sms.forwarder.sender; - -import android.util.Log; - -import androidx.annotation.NonNull; - -import com.idormy.sms.forwarder.utils.LogUtils; - -import java.io.IOException; -import java.io.InterruptedIOException; - -import okhttp3.Interceptor; -import okhttp3.Request; -import okhttp3.Response; - -public class RetryIntercepter implements Interceptor { - static final String TAG = "RetryIntercepter"; - private final long retryInterval;//重试的间隔 - private final long logId;//更新记录ID - public final int executionCount;//最大重试次数 - - RetryIntercepter(Builder builder) { - this.executionCount = builder.executionCount; - this.retryInterval = builder.retryInterval; - this.logId = builder.logId; - } - - @NonNull - @Override - public Response intercept(Chain chain) throws IOException { - int retryTimes = 0; - Request request = chain.request(); - Response response; - do { - if (retryTimes > 0 && getRetryInterval() > 0) { - final long delayTime = retryTimes * getRetryInterval(); - try { - Log.w(TAG, "第 " + retryTimes + " 次重试,休眠 " + delayTime + " 秒"); - //noinspection BusyWait - Thread.sleep(delayTime * 1000); - } catch (final InterruptedException e) { - Thread.currentThread().interrupt(); - throw new InterruptedIOException(e.getMessage()); - } - } - - response = doRequest(chain, request, retryTimes); - retryTimes++; - } while ((response == null || !response.isSuccessful()) && retryTimes <= executionCount); - - if (response == null) throw new InterruptedIOException("服务端无应答,结束重试"); - return response; - } - - private Response doRequest(Chain chain, Request request, int retryTimes) { - Response response = null; - try { - response = chain.proceed(request); - } catch (Exception e) { - String resp = retryTimes > 0 ? "第" + retryTimes + "次重试:" + e.getMessage() : e.getMessage(); - LogUtils.updateLog(logId, 1, resp); - Log.w(TAG, resp); - } - return response; - } - - /** - * retry间隔时间 - */ - public long getRetryInterval() { - return this.retryInterval; - } - - public static final class Builder { - private int executionCount; - private long retryInterval; - private long logId; - - public Builder() { - executionCount = 3; - retryInterval = 1000; - logId = 0; - } - - public RetryIntercepter.Builder executionCount(int executionCount) { - this.executionCount = executionCount; - return this; - } - - public RetryIntercepter.Builder retryInterval(long retryInterval) { - this.retryInterval = retryInterval; - return this; - } - - public RetryIntercepter.Builder logId(long logId) { - this.logId = logId; - return this; - } - - public RetryIntercepter build() { - return new RetryIntercepter(this); - } - } - -} \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/sender/SendHistory.java b/app/src/main/java/com/idormy/sms/forwarder/sender/SendHistory.java deleted file mode 100644 index a7121a76..00000000 --- a/app/src/main/java/com/idormy/sms/forwarder/sender/SendHistory.java +++ /dev/null @@ -1,175 +0,0 @@ -package com.idormy.sms.forwarder.sender; - -import android.annotation.SuppressLint; -import android.content.ContentValues; -import android.content.Context; -import android.content.SharedPreferences; -import android.database.Cursor; -import android.database.sqlite.SQLiteDatabase; -import android.provider.BaseColumns; -import android.util.Log; - -import com.idormy.sms.forwarder.model.LogModel; -import com.idormy.sms.forwarder.model.LogTable; -import com.idormy.sms.forwarder.utils.DbHelper; -import com.idormy.sms.forwarder.utils.Define; -import com.idormy.sms.forwarder.utils.SettingUtils; - -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; -import java.util.Set; - -@SuppressWarnings({"SynchronizeOnNonFinalField", "unused", "MismatchedQueryAndUpdateOfCollection"}) -public class SendHistory { - static final String TAG = "SendHistory"; - static Boolean hasInit = false; - - @SuppressLint("StaticFieldLeak") - static Context context; - static DbHelper dbHelper; - static SQLiteDatabase db; - - public static void init(Context context1) { - synchronized (hasInit) { - if (hasInit) return; - hasInit = true; - context = context1; - dbHelper = new DbHelper(context); - db = dbHelper.getReadableDatabase(); - } - } - - @SuppressLint("MutatingSharedPrefs") - public static void addHistory(String msg) { - //不保存转发消息 - if (SettingUtils.saveMsgHistory()) return; - //保存 - SharedPreferences sp = context.getSharedPreferences(Define.SP_MSG, Context.MODE_PRIVATE); - Set msg_set_default = new HashSet<>(); - Set msg_set; - msg_set = sp.getStringSet(Define.SP_MSG_SET_KEY, msg_set_default); - Log.d(TAG, "msg_set:" + msg_set.toString()); - Log.d(TAG, "msg_set_size:" + msg_set.size()); - msg_set.add(msg); - sp.edit().putStringSet(Define.SP_MSG_SET_KEY, msg_set).apply(); - } - - public static String getHistory() { - SharedPreferences sp = context.getSharedPreferences(Define.SP_MSG, Context.MODE_PRIVATE); - Set msg_set = new HashSet<>(); - msg_set = sp.getStringSet(Define.SP_MSG_SET_KEY, msg_set); - Log.d(TAG, "msg_set.toString()" + msg_set.toString()); - StringBuilder getMsg = new StringBuilder(); - for (String str : msg_set) { - getMsg.append(str).append("\n"); - } - return getMsg.toString(); - } - - public static long addHistoryDb(LogModel logModel) { - //不保存转发消息 - if (SettingUtils.saveMsgHistory()) return 0; - - // Gets the data repository in write mode - SQLiteDatabase db = dbHelper.getWritableDatabase(); - - // Create a new map of values, where column names are the keys - ContentValues values = new ContentValues(); - values.put(LogTable.LogEntry.COLUMN_NAME_FROM, logModel.getFrom()); - values.put(LogTable.LogEntry.COLUMN_NAME_CONTENT, logModel.getContent()); - values.put(LogTable.LogEntry.COLUMN_NAME_TIME, logModel.getTime()); - - // Insert the new row, returning the primary key value of the new row - - return db.insert(LogTable.LogEntry.TABLE_NAME, null, values); - } - - public static int delHistoryDb(Long id, String key) { - // Define 'where' part of query. - String selection = " 1 "; - // Specify arguments in placeholder order. - List selectionArgList = new ArrayList<>(); - if (id != null) { - // Define 'where' part of query. - selection += " and " + LogTable.LogEntry._ID + " = ? "; - // Specify arguments in placeholder order. - selectionArgList.add(String.valueOf(id)); - - } - - if (key != null) { - // Define 'where' part of query. - selection = " and (" + LogTable.LogEntry.COLUMN_NAME_FROM + " LIKE ? or " + LogTable.LogEntry.COLUMN_NAME_CONTENT + " LIKE ? ) "; - // Specify arguments in placeholder order. - selectionArgList.add(key); - selectionArgList.add(key); - } - String[] selectionArgs = selectionArgList.toArray(new String[0]); - // Issue SQL statement. - return db.delete(LogTable.LogEntry.TABLE_NAME, selection, selectionArgs); - - } - - public static String getHistoryDb(Long id, String key) { - // Define a projection that specifies which columns from the database - // you will actually use after this query. - String[] projection = { - BaseColumns._ID, - LogTable.LogEntry.COLUMN_NAME_FROM, - LogTable.LogEntry.COLUMN_NAME_CONTENT, - LogTable.LogEntry.COLUMN_NAME_TIME - }; - // Define 'where' part of query. - String selection = " 1 "; - // Specify arguments in placeholder order. - List selectionArgList = new ArrayList<>(); - if (id != null) { - // Define 'where' part of query. - selection += " and " + LogTable.LogEntry._ID + " = ? "; - // Specify arguments in placeholder order. - selectionArgList.add(String.valueOf(id)); - } - - if (key != null) { - // Define 'where' part of query. - selection = " and (" + LogTable.LogEntry.COLUMN_NAME_FROM + " LIKE ? or " + LogTable.LogEntry.COLUMN_NAME_CONTENT + " LIKE ? ) "; - // Specify arguments in placeholder order. - selectionArgList.add(key); - selectionArgList.add(key); - } - String[] selectionArgs = selectionArgList.toArray(new String[0]); - - // How you want the results sorted in the resulting Cursor - String sortOrder = - LogTable.LogEntry._ID + " DESC"; - - Cursor cursor = db.query( - LogTable.LogEntry.TABLE_NAME, // The table to query - projection, // The array of columns to return (pass null to get all) - selection, // The columns for the WHERE clause - selectionArgs, // The values for the WHERE clause - null, // don't group the rows - null, // don't filter by row groups - sortOrder // The sort order - ); - List tLogs = new ArrayList<>(); - while (cursor.moveToNext()) { - long itemId = cursor.getLong( - cursor.getColumnIndexOrThrow(LogTable.LogEntry._ID)); - tLogs.add(itemId); - } - cursor.close(); - - SharedPreferences sp = context.getSharedPreferences(Define.SP_MSG, Context.MODE_PRIVATE); - Set msg_set = new HashSet<>(); - msg_set = sp.getStringSet(Define.SP_MSG_SET_KEY, msg_set); - Log.d(TAG, "msg_set.toString()" + msg_set.toString()); - StringBuilder getMsg = new StringBuilder(); - for (String str : msg_set) { - getMsg.append(str).append("\n"); - } - return getMsg.toString(); - } - -} diff --git a/app/src/main/java/com/idormy/sms/forwarder/sender/SendUtil.java b/app/src/main/java/com/idormy/sms/forwarder/sender/SendUtil.java deleted file mode 100644 index 4edda071..00000000 --- a/app/src/main/java/com/idormy/sms/forwarder/sender/SendUtil.java +++ /dev/null @@ -1,389 +0,0 @@ -package com.idormy.sms.forwarder.sender; - -import static com.idormy.sms.forwarder.model.SenderModel.STATUS_OFF; -import static com.idormy.sms.forwarder.model.SenderModel.TYPE_BARK; -import static com.idormy.sms.forwarder.model.SenderModel.TYPE_DINGDING; -import static com.idormy.sms.forwarder.model.SenderModel.TYPE_EMAIL; -import static com.idormy.sms.forwarder.model.SenderModel.TYPE_FEISHU; -import static com.idormy.sms.forwarder.model.SenderModel.TYPE_GOTIFY; -import static com.idormy.sms.forwarder.model.SenderModel.TYPE_PUSHPLUS; -import static com.idormy.sms.forwarder.model.SenderModel.TYPE_QYWX_APP; -import static com.idormy.sms.forwarder.model.SenderModel.TYPE_QYWX_GROUP_ROBOT; -import static com.idormy.sms.forwarder.model.SenderModel.TYPE_SERVER_CHAN; -import static com.idormy.sms.forwarder.model.SenderModel.TYPE_SMS; -import static com.idormy.sms.forwarder.model.SenderModel.TYPE_TELEGRAM; -import static com.idormy.sms.forwarder.model.SenderModel.TYPE_WEB_NOTIFY; - -import android.content.Context; -import android.os.Handler; -import android.util.Log; - -import com.alibaba.fastjson.JSON; -import com.idormy.sms.forwarder.model.LogModel; -import com.idormy.sms.forwarder.model.RuleModel; -import com.idormy.sms.forwarder.model.SenderModel; -import com.idormy.sms.forwarder.model.vo.BarkSettingVo; -import com.idormy.sms.forwarder.model.vo.DingDingSettingVo; -import com.idormy.sms.forwarder.model.vo.EmailSettingVo; -import com.idormy.sms.forwarder.model.vo.FeiShuSettingVo; -import com.idormy.sms.forwarder.model.vo.GotifySettingVo; -import com.idormy.sms.forwarder.model.vo.LogVo; -import com.idormy.sms.forwarder.model.vo.PushPlusSettingVo; -import com.idormy.sms.forwarder.model.vo.QYWXAppSettingVo; -import com.idormy.sms.forwarder.model.vo.QYWXGroupRobotSettingVo; -import com.idormy.sms.forwarder.model.vo.ServerChanSettingVo; -import com.idormy.sms.forwarder.model.vo.SmsSettingVo; -import com.idormy.sms.forwarder.model.vo.SmsVo; -import com.idormy.sms.forwarder.model.vo.TelegramSettingVo; -import com.idormy.sms.forwarder.model.vo.WebNotifySettingVo; -import com.idormy.sms.forwarder.utils.LogUtils; -import com.idormy.sms.forwarder.utils.NetUtils; -import com.idormy.sms.forwarder.utils.RuleUtils; -import com.idormy.sms.forwarder.utils.SettingUtils; -import com.idormy.sms.forwarder.utils.TimeUtils; - -import java.text.ParseException; -import java.util.Date; -import java.util.List; - -public class SendUtil { - private static final String TAG = "SendUtil"; - - public static void send_msg_list(Context context, List smsVoList, int simId, String type) { - Log.i(TAG, "send_msg_list size: " + smsVoList.size()); - for (SmsVo smsVo : smsVoList) { - SendUtil.send_msg(context, smsVo, simId, type); - } - } - - public static void send_msg(Context context, SmsVo smsVo, int simId, String type) { - Log.i(TAG, "send_msg smsVo:" + smsVo); - RuleUtils.init(context); - LogUtils.init(context); - - String key = "SIM" + simId; - List ruleList = RuleUtils.getRule(null, key, type, "1"); //只取已启用的规则 - if (!ruleList.isEmpty()) { - Log.d(TAG, ruleList.toString()); - SenderUtil.init(context); - for (RuleModel ruleModel : ruleList) { - //规则匹配发现需要发送 - try { - if (ruleModel.checkMsg(smsVo)) { - List senderModels = SenderUtil.getSender(ruleModel.getSenderId(), null); - for (SenderModel senderModel : senderModels - ) { - long logId = LogUtils.addLog(new LogModel(type, smsVo.getMobile(), smsVo.getContent(), smsVo.getSimInfo(), ruleModel.getId())); - String smsTemplate = ruleModel.getSwitchSmsTemplate() ? ruleModel.getSmsTemplate() : ""; - String regexReplace = ruleModel.getSwitchRegexReplace() ? ruleModel.getRegexReplace() : ""; - SendUtil.senderSendMsgNoHandError(smsVo, senderModel, logId, smsTemplate, regexReplace); - } - } - } catch (Exception e) { - Log.e(TAG, "send_msg: fail checkMsg:", e); - } - } - } - } - - /** - * 从日志获取消息内容并尝试重发 - * 根据当前rule和sender来重发,而不是失败时设置的规则 - * - * @param context 上下文 - * @param handler 回调,用于刷新日志列表 - * @param logVo 日志 - */ - public static void resendMsgByLog(Context context, Handler handler, LogVo logVo) { - Log.d(TAG, logVo.toString()); - Date date = new Date(); - try { - date = TimeUtils.utc2LocalDate(logVo.getTime()); - } catch (ParseException e) { - Log.e(TAG, "SimpleDateFormat parse error", e); - } - SmsVo smsVo = new SmsVo(logVo.getFrom(), logVo.getContent(), date, logVo.getSimInfo()); - Log.d(TAG, "resendMsgByLog smsVo:" + smsVo); - - //从simInfo判断接收的是SIM1还是SIM2,获取不到时默认走ALL - String simInfo = smsVo.getSimInfo(); - String key = null; - if (simInfo.startsWith("SIM1")) { - key = "SIM1"; - } else if (simInfo.startsWith("SIM2")) { - key = "SIM2"; - } - - RuleUtils.init(context); - List ruleList = RuleUtils.getRule(null, key, logVo.getType(), "1"); //只取已启用的规则 - if (!ruleList.isEmpty()) { - SenderUtil.init(context); - for (RuleModel ruleModel : ruleList) { - //规则匹配发现需要发送 - try { - if (ruleModel.checkMsg(smsVo)) { - List senderModels = SenderUtil.getSender(ruleModel.getSenderId(), null); - for (SenderModel senderModel : senderModels) { - String smsTemplate = ruleModel.getSwitchSmsTemplate() ? ruleModel.getSmsTemplate() : ""; - String regexReplace = ruleModel.getSwitchRegexReplace() ? ruleModel.getRegexReplace() : ""; - SendUtil.senderSendMsg(handler, null, smsVo, senderModel, logVo.getId(), smsTemplate, regexReplace); - } - } - } catch (Exception e) { - Log.e(TAG, "resendMsgByLog: fail checkMsg:", e); - } - } - } - } - - public static void sendMsgByRuleModelSenderId(final Handler handError, RuleModel ruleModel, SmsVo smsVo, Long senderId) throws Exception { - if (senderId == null) { - throw new Exception("先新建并选择发送通道"); - } - - String testSim = smsVo.getSimInfo().substring(0, 4); - String ruleSim = ruleModel.getSimSlot(); - - if (!ruleSim.equals("ALL") && !ruleSim.equals(testSim)) { - throw new Exception("接收卡槽未匹配中规则"); - } - - if (!ruleModel.checkMsg(smsVo)) { - throw new Exception("短信未匹配中规则"); - } - - List senderModels = SenderUtil.getSender(senderId, null); - if (senderModels.isEmpty()) { - throw new Exception("未找到发送通道"); - } - - for (SenderModel senderModel : senderModels) { - String smsTemplate = ruleModel.getSwitchSmsTemplate() ? ruleModel.getSmsTemplate() : ""; - String regexReplace = ruleModel.getSwitchRegexReplace() ? ruleModel.getRegexReplace() : ""; - SendUtil.senderSendMsg(handError, null, smsVo, senderModel, 0, smsTemplate, regexReplace); - } - } - - public static void senderSendMsgNoHandError(SmsVo smsVo, SenderModel senderModel, long logId, String smsTemplate, String regexReplace) { - //网络请求+延时重试比较耗时,创建子线程处理 - new Thread(() -> { - try { - int retryTimes = SettingUtils.getRetryTimes(); - int delayTime = SettingUtils.getDelayTime(); - RetryIntercepter retryInterceptor = retryTimes < 1 ? null : new RetryIntercepter.Builder().executionCount(retryTimes).retryInterval(delayTime).logId(logId).build(); - SendUtil.senderSendMsg(null, retryInterceptor, smsVo, senderModel, logId, smsTemplate, regexReplace); - } catch (Exception e) { - Log.e(TAG, e.getMessage()); - } - }).start(); - } - - public static void senderSendMsg(Handler handError, RetryIntercepter retryInterceptor, SmsVo smsVo, SenderModel senderModel, long logId, String smsTemplate, String regexReplace) { - - Log.i(TAG, "senderSendMsg smsVo:" + smsVo.toString() + "senderModel:" + senderModel.toString()); - - if (senderModel.getStatus() == STATUS_OFF) { - LogUtils.updateLog(logId, 0, "发送通道已被禁用!"); - Log.i(TAG, "发送通道已被禁用!"); - return; - } - - switch (senderModel.getType()) { - case TYPE_DINGDING: - //try phrase json setting - if (senderModel.getJsonSetting() != null) { - DingDingSettingVo dingDingSettingVo = JSON.parseObject(senderModel.getJsonSetting(), DingDingSettingVo.class); - if (dingDingSettingVo != null) { - try { - SenderDingdingMsg.sendMsg(logId, handError, retryInterceptor, dingDingSettingVo.getToken(), dingDingSettingVo.getSecret(), dingDingSettingVo.getAtMobiles(), dingDingSettingVo.getAtAll(), smsVo.getSmsVoForSend(smsTemplate, regexReplace)); - } catch (Exception e) { - LogUtils.updateLog(logId, 0, e.getMessage()); - Log.e(TAG, "senderSendMsg: dingding error " + e.getMessage()); - } - } - } - break; - - case TYPE_EMAIL: - //try phrase json setting - if (senderModel.getJsonSetting() != null) { - EmailSettingVo emailSettingVo = JSON.parseObject(senderModel.getJsonSetting(), EmailSettingVo.class); - if (emailSettingVo != null) { - try { - SenderMailMsg.sendEmail(logId, handError, emailSettingVo, smsVo.getTitleForSend(emailSettingVo.getTitle(), regexReplace), smsVo.getSmsVoForSend(smsTemplate, regexReplace)); - } catch (Exception e) { - LogUtils.updateLog(logId, 0, e.getMessage()); - Log.e(TAG, "senderSendMsg: SenderMailMsg error " + e.getMessage()); - } - } - } - break; - - case TYPE_BARK: - //try phrase json setting - if (senderModel.getJsonSetting() != null) { - BarkSettingVo barkSettingVo = JSON.parseObject(senderModel.getJsonSetting(), BarkSettingVo.class); - if (barkSettingVo != null) { - try { - SenderBarkMsg.sendMsg(logId, handError, retryInterceptor, barkSettingVo, smsVo.getTitleForSend(barkSettingVo.getTitle()), smsVo.getSmsVoForSend(smsTemplate, regexReplace), senderModel.getName()); - } catch (Exception e) { - LogUtils.updateLog(logId, 0, e.getMessage()); - Log.e(TAG, "senderSendMsg: SenderBarkMsg error " + e.getMessage()); - } - } - } - break; - - case TYPE_WEB_NOTIFY: - //try phrase json setting - if (senderModel.getJsonSetting() != null) { - WebNotifySettingVo webNotifySettingVo = JSON.parseObject(senderModel.getJsonSetting(), WebNotifySettingVo.class); - if (webNotifySettingVo != null) { - try { - SenderWebNotifyMsg.sendMsg(logId, handError, retryInterceptor, webNotifySettingVo.getWebServer(), webNotifySettingVo.getWebParams(), webNotifySettingVo.getSecret(), webNotifySettingVo.getMethod(), webNotifySettingVo.getHeaders(), smsVo, smsTemplate, regexReplace); - } catch (Exception e) { - LogUtils.updateLog(logId, 0, e.getMessage()); - Log.e(TAG, "senderSendMsg: SenderWebNotifyMsg error " + e.getMessage()); - } - } - } - break; - - case TYPE_QYWX_GROUP_ROBOT: - //try phrase json setting - if (senderModel.getJsonSetting() != null) { - QYWXGroupRobotSettingVo qywxGroupRobotSettingVo = JSON.parseObject(senderModel.getJsonSetting(), QYWXGroupRobotSettingVo.class); - if (qywxGroupRobotSettingVo != null) { - try { - SenderQyWxGroupRobotMsg.sendMsg(logId, handError, retryInterceptor, qywxGroupRobotSettingVo.getWebHook(), smsVo.getMobile(), smsVo.getSmsVoForSend(smsTemplate, regexReplace)); - } catch (Exception e) { - LogUtils.updateLog(logId, 0, e.getMessage()); - Log.e(TAG, "senderSendMsg: SenderQyWxGroupRobotMsg error " + e.getMessage()); - } - } - } - break; - - case TYPE_QYWX_APP: - //try phrase json setting - if (senderModel.getJsonSetting() != null) { - QYWXAppSettingVo qYWXAppSettingVo = JSON.parseObject(senderModel.getJsonSetting(), QYWXAppSettingVo.class); - if (qYWXAppSettingVo != null) { - try { - SenderQyWxAppMsg.sendMsg(logId, handError, retryInterceptor, senderModel, qYWXAppSettingVo, smsVo.getSmsVoForSend(smsTemplate, regexReplace)); - } catch (Exception e) { - LogUtils.updateLog(logId, 0, e.getMessage()); - Log.e(TAG, "senderSendMsg: qywx_app error " + e.getMessage()); - } - } - } - break; - - case TYPE_SERVER_CHAN: - //try phrase json setting - if (senderModel.getJsonSetting() != null) { - ServerChanSettingVo serverChanSettingVo = JSON.parseObject(senderModel.getJsonSetting(), ServerChanSettingVo.class); - if (serverChanSettingVo != null) { - try { - SenderServerChanMsg.sendMsg(logId, handError, retryInterceptor, serverChanSettingVo.getSendKey(), smsVo.getMobile(), smsVo.getSmsVoForSend(smsTemplate, regexReplace)); - } catch (Exception e) { - LogUtils.updateLog(logId, 0, e.getMessage()); - Log.e(TAG, "senderSendMsg: SenderServerChanMsg error " + e.getMessage()); - } - } - } - break; - - case TYPE_TELEGRAM: - //try phrase json setting - if (senderModel.getJsonSetting() != null) { - TelegramSettingVo telegramSettingVo = JSON.parseObject(senderModel.getJsonSetting(), TelegramSettingVo.class); - if (telegramSettingVo != null) { - try { - SenderTelegramMsg.sendMsg(logId, handError, retryInterceptor, telegramSettingVo, smsVo.getMobile(), smsVo.getSmsVoForSend(smsTemplate, regexReplace), telegramSettingVo.getMethod()); - } catch (Exception e) { - LogUtils.updateLog(logId, 0, e.getMessage()); - Log.e(TAG, "senderSendMsg: SenderTelegramMsg error " + e.getMessage()); - } - } - } - break; - - case TYPE_SMS: - //try phrase json setting - if (senderModel.getJsonSetting() != null) { - SmsSettingVo smsSettingVo = JSON.parseObject(senderModel.getJsonSetting(), SmsSettingVo.class); - if (smsSettingVo != null) { - //仅当无网络时启用 - if (smsSettingVo.getOnlyNoNetwork() && 0 != NetUtils.getNetWorkStatus()) { - String msg = "仅当无网络时启用,当前网络状态:" + NetUtils.getNetWorkStatus(); - LogUtils.updateLog(logId, 0, msg); - Log.d(TAG, msg); - return; - } - try { - int simSlot = smsSettingVo.getSimSlot() - 1; - if (simSlot < 0) { //原进原出 - simSlot = Integer.parseInt(smsVo.getSimInfo().substring(3, 4)) - 1; - Log.d(TAG, "simSlot = " + simSlot); - } - SenderSmsMsg.sendMsg(logId, handError, simSlot, smsSettingVo.getMobiles(), smsSettingVo.getOnlyNoNetwork(), smsVo.getMobile(), smsVo.getSmsVoForSend(smsTemplate, regexReplace)); - } catch (Exception e) { - LogUtils.updateLog(logId, 0, e.getMessage()); - Log.e(TAG, "senderSendMsg: SenderSmsMsg error " + e.getMessage()); - } - } - } - break; - - case TYPE_FEISHU: - //try phrase json setting - if (senderModel.getJsonSetting() != null) { - FeiShuSettingVo feiShuSettingVo = JSON.parseObject(senderModel.getJsonSetting(), FeiShuSettingVo.class); - if (feiShuSettingVo != null) { - try { - SenderFeishuMsg.sendMsg(logId, handError, retryInterceptor, feiShuSettingVo.getWebhook(), feiShuSettingVo.getSecret(), feiShuSettingVo.getMsgType(), smsVo.getMobile(), smsVo.getDate(), smsVo.getTitleForSend(feiShuSettingVo.getTitleTemplate()), smsVo.getSmsVoForSend(smsTemplate, regexReplace)); - } catch (Exception e) { - LogUtils.updateLog(logId, 0, e.getMessage()); - Log.e(TAG, "senderSendMsg: feishu error " + e.getMessage()); - } - } - } - break; - - case TYPE_PUSHPLUS: - //try phrase json setting - if (senderModel.getJsonSetting() != null) { - PushPlusSettingVo pushPlusSettingVo = JSON.parseObject(senderModel.getJsonSetting(), PushPlusSettingVo.class); - if (pushPlusSettingVo != null) { - try { - SenderPushPlusMsg.sendMsg(logId, handError, retryInterceptor, pushPlusSettingVo, smsVo.getTitleForSend(pushPlusSettingVo.getTitleTemplate()), smsVo.getSmsVoForSend(smsTemplate, regexReplace)); - } catch (Exception e) { - LogUtils.updateLog(logId, 0, e.getMessage()); - Log.e(TAG, "senderSendMsg: feishu error " + e.getMessage()); - } - } - } - break; - - case TYPE_GOTIFY: - //try phrase json setting - if (senderModel.getJsonSetting() != null) { - GotifySettingVo gotifySettingVo = JSON.parseObject(senderModel.getJsonSetting(), GotifySettingVo.class); - if (gotifySettingVo != null) { - try { - SenderGotifyMsg.sendMsg(logId, handError, retryInterceptor, gotifySettingVo, smsVo.getMobile(), smsVo.getSmsVoForSend(smsTemplate, regexReplace)); - } catch (Exception e) { - LogUtils.updateLog(logId, 0, e.getMessage()); - Log.e(TAG, "senderSendMsg: gotify error " + e.getMessage()); - } - } - } - break; - - default: - break; - } - } - -} diff --git a/app/src/main/java/com/idormy/sms/forwarder/sender/SenderBarkMsg.java b/app/src/main/java/com/idormy/sms/forwarder/sender/SenderBarkMsg.java deleted file mode 100644 index 7c4efdb4..00000000 --- a/app/src/main/java/com/idormy/sms/forwarder/sender/SenderBarkMsg.java +++ /dev/null @@ -1,113 +0,0 @@ -package com.idormy.sms.forwarder.sender; - -import android.os.Handler; -import android.text.TextUtils; -import android.util.Log; - -import androidx.annotation.NonNull; - -import com.idormy.sms.forwarder.model.vo.BarkSettingVo; -import com.idormy.sms.forwarder.utils.Define; -import com.idormy.sms.forwarder.utils.LogUtils; - -import java.io.IOException; -import java.util.Objects; -import java.util.concurrent.TimeUnit; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import okhttp3.Call; -import okhttp3.Callback; -import okhttp3.MultipartBody; -import okhttp3.OkHttpClient; -import okhttp3.Request; -import okhttp3.RequestBody; -import okhttp3.Response; - -public class SenderBarkMsg extends SenderBaseMsg { - - static final String TAG = "SenderBarkMsg"; - - @SuppressWarnings("RedundantThrows") - public static void sendMsg(final long logId, final Handler handError, final RetryIntercepter retryInterceptor, BarkSettingVo barkSettingVo, String title, String content, String groupName) throws Exception { - Log.i(TAG, "sendMsg barkServer:" + barkSettingVo.toString() + " title:" + title + " content:" + content); - - String requestUrl = barkSettingVo.getServer(); //推送地址 - Log.i(TAG, "requestUrl:" + requestUrl); - if (requestUrl == null || requestUrl.isEmpty()) { - return; - } - - String icon = barkSettingVo.getIcon(); //消息图标 - String level = barkSettingVo.getLevel(); //时效性 - String sound = barkSettingVo.getSound(); //声音 - String badge = barkSettingVo.getBadge(); //角标 - String url = barkSettingVo.getUrl(); //链接 - //特殊处理避免标题重复 - content = content.replaceFirst("^" + title + "(.*)", "").trim(); - - String Content_Type = "application/x-www-form-urlencoded"; - MultipartBody.Builder builder = new MultipartBody.Builder().setType(MultipartBody.FORM) - .addFormDataPart("title", title) - .addFormDataPart("body", content) - .addFormDataPart("isArchive", "1") - .addFormDataPart("group", groupName); - - if (!TextUtils.isEmpty(icon)) builder.addFormDataPart("icon", icon); - if (!TextUtils.isEmpty(level)) builder.addFormDataPart("level", level); - if (!TextUtils.isEmpty(sound)) builder.addFormDataPart("sound", sound); - if (!TextUtils.isEmpty(badge)) builder.addFormDataPart("badge", badge); - if (!TextUtils.isEmpty(url)) builder.addFormDataPart("url", url); - - int isCode = content.indexOf("验证码"); - int isPassword = content.indexOf("动态密码"); - int isPassword2 = content.indexOf("短信密码"); - if (isCode != -1 || isPassword != -1 || isPassword2 != -1) { - Pattern p = Pattern.compile("(\\d{4,6})"); - Matcher m = p.matcher(content); - if (m.find()) { - System.out.println(m.group()); - builder.addFormDataPart("automaticallyCopy", "1"); - builder.addFormDataPart("copy", m.group()); - } - } - - RequestBody body = builder.build(); - Log.d(TAG, "method = POST, Body = " + body); - - OkHttpClient.Builder clientBuilder = new OkHttpClient.Builder(); - //设置重试拦截器 - if (retryInterceptor != null) clientBuilder.addInterceptor(retryInterceptor); - //设置读取超时时间 - OkHttpClient client = clientBuilder - .readTimeout(Define.REQUEST_TIMEOUT_SECONDS, TimeUnit.SECONDS) - .writeTimeout(Define.REQUEST_TIMEOUT_SECONDS, TimeUnit.SECONDS) - .connectTimeout(Define.REQUEST_TIMEOUT_SECONDS, TimeUnit.SECONDS) - .build(); - - final Request request = new Request.Builder().url(requestUrl).method("POST", body).build(); - client.newCall(request).enqueue(new Callback() { - @Override - public void onFailure(@NonNull Call call, @NonNull final IOException e) { - LogUtils.updateLog(logId, 0, e.getMessage()); - Toast(handError, TAG, "发送失败:" + e.getMessage()); - } - - @Override - public void onResponse(@NonNull Call call, @NonNull Response response) throws IOException { - final String responseStr = Objects.requireNonNull(response.body()).string(); - Log.d(TAG, "Response:" + response.code() + "," + responseStr); - Toast(handError, TAG, "发送状态:" + responseStr); - - //TODO:粗略解析是否发送成功 - if (responseStr.contains("\"message\":\"success\"")) { - LogUtils.updateLog(logId, 2, responseStr); - } else { - LogUtils.updateLog(logId, 0, responseStr); - } - } - }); - - } - -} diff --git a/app/src/main/java/com/idormy/sms/forwarder/sender/SenderBaseMsg.java b/app/src/main/java/com/idormy/sms/forwarder/sender/SenderBaseMsg.java deleted file mode 100644 index 0026c32b..00000000 --- a/app/src/main/java/com/idormy/sms/forwarder/sender/SenderBaseMsg.java +++ /dev/null @@ -1,24 +0,0 @@ -package com.idormy.sms.forwarder.sender; - -import static com.idormy.sms.forwarder.SenderActivity.NOTIFY; - -import android.os.Bundle; -import android.os.Handler; -import android.os.Message; -import android.util.Log; - -public class SenderBaseMsg { - - public static void Toast(Handler handError, String Tag, String data) { - Log.i(Tag, data); - if (handError != null) { - Message msg = new Message(); - msg.what = NOTIFY; - Bundle bundle = new Bundle(); - bundle.putString("DATA", Tag + "-" + data); - msg.setData(bundle); - handError.sendMessage(msg); - } - } - -} \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/sender/SenderDingdingMsg.java b/app/src/main/java/com/idormy/sms/forwarder/sender/SenderDingdingMsg.java deleted file mode 100644 index 52c726f5..00000000 --- a/app/src/main/java/com/idormy/sms/forwarder/sender/SenderDingdingMsg.java +++ /dev/null @@ -1,134 +0,0 @@ -package com.idormy.sms.forwarder.sender; - -import android.os.Handler; -import android.text.TextUtils; -import android.util.Base64; -import android.util.Log; - -import androidx.annotation.NonNull; - -import com.alibaba.fastjson.JSON; -import com.idormy.sms.forwarder.utils.Define; -import com.idormy.sms.forwarder.utils.LogUtils; - -import java.io.IOException; -import java.net.URLEncoder; -import java.nio.charset.StandardCharsets; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.concurrent.TimeUnit; - -import javax.crypto.Mac; -import javax.crypto.spec.SecretKeySpec; - -import okhttp3.Call; -import okhttp3.Callback; -import okhttp3.MediaType; -import okhttp3.OkHttpClient; -import okhttp3.Request; -import okhttp3.RequestBody; -import okhttp3.Response; - -@SuppressWarnings({"rawtypes", "unchecked", "deprecation"}) -public class SenderDingdingMsg extends SenderBaseMsg { - - static final String TAG = "SenderDingdingMsg"; - - public static void sendMsg(final long logId, final Handler handError, final RetryIntercepter retryInterceptor, String token, String secret, String atMobiles, Boolean atAll, String content) throws Exception { - Log.i(TAG, "sendMsg token:" + token + " secret:" + secret + " atMobiles:" + atMobiles + " atAll:" + atAll + " content:" + content); - - if (token == null || token.isEmpty()) { - return; - } - - if (secret != null && !secret.isEmpty()) { - Long timestamp = System.currentTimeMillis(); - String stringToSign = timestamp + "\n" + secret; - Mac mac = Mac.getInstance("HmacSHA256"); - mac.init(new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), "HmacSHA256")); - byte[] signData = mac.doFinal(stringToSign.getBytes(StandardCharsets.UTF_8)); - String sign = URLEncoder.encode(new String(Base64.encode(signData, Base64.NO_WRAP)), "UTF-8"); - token += "×tamp=" + timestamp + "&sign=" + sign; - Log.i(TAG, "token:" + token); - } - - Map textMsgMap = new HashMap(); - textMsgMap.put("msgtype", "text"); - Map textText = new HashMap(); - textText.put("content", content); - textMsgMap.put("text", textText); - if (atMobiles != null || atAll != null) { - Map AtMap = new HashMap(); - if (atMobiles != null) { - String[] atMobilesArray = atMobiles.split(","); - List atMobilesList = new ArrayList<>(); - for (String atMobile : atMobilesArray - ) { - if (TextUtils.isDigitsOnly(atMobile)) { - atMobilesList.add(atMobile); - } - } - if (!atMobilesList.isEmpty()) { - AtMap.put("atMobiles", atMobilesList); - } - } - - AtMap.put("isAtAll", false); - if (atAll != null) { - AtMap.put("isAtAll", atAll); - } - - textMsgMap.put("at", AtMap); - } - - final String requestUrl = "https://oapi.dingtalk.com/robot/send?access_token=" + token; - Log.i(TAG, "requestUrl:" + requestUrl); - final String requestMsg = JSON.toJSONString(textMsgMap); - Log.i(TAG, "requestMsg:" + requestMsg); - - OkHttpClient.Builder builder = new OkHttpClient.Builder(); - //设置重试拦截器 - if (retryInterceptor != null) builder.addInterceptor(retryInterceptor); - //设置读取超时时间 - OkHttpClient client = builder - .readTimeout(Define.REQUEST_TIMEOUT_SECONDS, TimeUnit.SECONDS) - .writeTimeout(Define.REQUEST_TIMEOUT_SECONDS, TimeUnit.SECONDS) - .connectTimeout(Define.REQUEST_TIMEOUT_SECONDS, TimeUnit.SECONDS) - .build(); - - RequestBody requestBody = RequestBody.create(MediaType.parse("application/json;charset=utf-8"), requestMsg); - - final Request request = new Request.Builder() - .url(requestUrl) - .addHeader("Content-Type", "application/json; charset=utf-8") - .post(requestBody) - .build(); - - client.newCall(request).enqueue(new Callback() { - @Override - public void onFailure(@NonNull Call call, @NonNull final IOException e) { - LogUtils.updateLog(logId, 0, e.getMessage()); - Toast(handError, TAG, "发送失败:" + e.getMessage()); - } - - @Override - public void onResponse(@NonNull Call call, @NonNull Response response) throws IOException { - final String responseStr = Objects.requireNonNull(response.body()).string(); - Log.d(TAG, "Response:" + response.code() + "," + responseStr); - Toast(handError, TAG, "发送状态:" + responseStr); - - //TODO:粗略解析是否发送成功 - if (responseStr.contains("\"errcode\":0")) { - LogUtils.updateLog(logId, 2, responseStr); - } else { - LogUtils.updateLog(logId, 0, responseStr); - } - } - }); - - } - -} \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/sender/SenderFeishuMsg.java b/app/src/main/java/com/idormy/sms/forwarder/sender/SenderFeishuMsg.java deleted file mode 100644 index b9ee0503..00000000 --- a/app/src/main/java/com/idormy/sms/forwarder/sender/SenderFeishuMsg.java +++ /dev/null @@ -1,195 +0,0 @@ -package com.idormy.sms.forwarder.sender; - -import android.os.Handler; -import android.util.Base64; -import android.util.Log; - -import androidx.annotation.NonNull; - -import com.alibaba.fastjson.JSON; -import com.idormy.sms.forwarder.utils.Define; -import com.idormy.sms.forwarder.utils.LogUtils; - -import java.io.IOException; -import java.nio.charset.StandardCharsets; -import java.text.SimpleDateFormat; -import java.util.Date; -import java.util.HashMap; -import java.util.Locale; -import java.util.Map; -import java.util.Objects; -import java.util.concurrent.TimeUnit; - -import javax.crypto.Mac; -import javax.crypto.spec.SecretKeySpec; - -import okhttp3.Call; -import okhttp3.Callback; -import okhttp3.MediaType; -import okhttp3.OkHttpClient; -import okhttp3.Request; -import okhttp3.RequestBody; -import okhttp3.Response; - -@SuppressWarnings({"rawtypes", "unchecked", "deprecation"}) -public class SenderFeishuMsg extends SenderBaseMsg { - - static final String TAG = "SenderFeishuMsg"; - - static final String MSG_TEMPLATE = "{\n" + - " \"config\": {\n" + - " \"wide_screen_mode\": true\n" + - " },\n" + - " \"elements\": [\n" + - " {\n" + - " \"fields\": [\n" + - " {\n" + - " \"is_short\": true,\n" + - " \"text\": {\n" + - " \"content\": \"**时间**\\n${MSG_TIME}\",\n" + - " \"tag\": \"lark_md\"\n" + - " }\n" + - " },\n" + - " {\n" + - " \"is_short\": true,\n" + - " \"text\": {\n" + - " \"content\": \"**来源**\\n${MSG_FROM}\",\n" + - " \"tag\": \"lark_md\"\n" + - " }\n" + - " }\n" + - " ],\n" + - " \"tag\": \"div\"\n" + - " },\n" + - " {\n" + - " \"tag\": \"div\",\n" + - " \"text\": {\n" + - " \"content\": \"${MSG_CONTENT}\",\n" + - " \"tag\": \"lark_md\"\n" + - " }\n" + - " },\n" + - " {\n" + - " \"tag\": \"hr\"\n" + - " },\n" + - " {\n" + - " \"elements\": [\n" + - " {\n" + - " \"content\": \"[SmsForwarder](https://github.com/pppscn/SmsForwarder)\",\n" + - " \"tag\": \"lark_md\"\n" + - " }\n" + - " ],\n" + - " \"tag\": \"note\"\n" + - " }\n" + - " ],\n" + - " \"header\": {\n" + - " \"template\": \"turquoise\",\n" + - " \"title\": {\n" + - " \"content\": \"${MSG_TITLE}\",\n" + - " \"tag\": \"plain_text\"\n" + - " }\n" + - " }\n" + - "}"; - - public static void sendMsg(final long logId, final Handler handError, final RetryIntercepter retryInterceptor, String webhook, String secret, String msgType, String from, Date date, String title, String content) throws Exception { - Log.i(TAG, "sendMsg webhook:" + webhook + " secret:" + secret + " content:" + content); - - if (webhook == null || webhook.isEmpty()) { - return; - } - - Map textMsgMap = new HashMap(); - - //签名校验 - if (secret != null && !secret.isEmpty()) { - Long timestamp = System.currentTimeMillis() / 1000; - //把timestamp+"\n"+密钥当做签名字符串 - String stringToSign = timestamp + "\n" + secret; - Log.i(TAG, "stringToSign = " + stringToSign); - - //使用HmacSHA256算法计算签名 - Mac mac = Mac.getInstance("HmacSHA256"); - mac.init(new SecretKeySpec(stringToSign.getBytes(StandardCharsets.UTF_8), "HmacSHA256")); - byte[] signData = mac.doFinal(new byte[]{}); - String sign = new String(Base64.encode(signData, Base64.NO_WRAP)); - - textMsgMap.put("timestamp", timestamp); - textMsgMap.put("sign", sign); - } - - //组装报文 - if (msgType == null || msgType.equals("interactive")) { - textMsgMap.put("msg_type", "interactive"); - textMsgMap.put("card", "${CARD_BODY}"); - } else { - textMsgMap.put("msg_type", "text"); - Map contentMap = new HashMap(); - contentMap.put("text", content); - textMsgMap.put("content", contentMap); - } - - Log.i(TAG, "requestUrl:" + webhook); - final String requestMsg = JSON.toJSONString(textMsgMap).replace("\"${CARD_BODY}\"", buildMsg(from, date, title, content)); - Log.i(TAG, "requestMsg:" + requestMsg); - - OkHttpClient.Builder builder = new OkHttpClient.Builder(); - //设置重试拦截器 - if (retryInterceptor != null) builder.addInterceptor(retryInterceptor); - //设置读取超时时间 - OkHttpClient client = builder - .readTimeout(Define.REQUEST_TIMEOUT_SECONDS, TimeUnit.SECONDS) - .writeTimeout(Define.REQUEST_TIMEOUT_SECONDS, TimeUnit.SECONDS) - .connectTimeout(Define.REQUEST_TIMEOUT_SECONDS, TimeUnit.SECONDS) - .build(); - - @SuppressWarnings("deprecation") RequestBody requestBody = RequestBody.create(MediaType.parse("application/json;charset=utf-8"), requestMsg); - - final Request request = new Request.Builder() - .url(webhook) - .addHeader("Content-Type", "application/json; charset=utf-8") - .post(requestBody) - .build(); - - client.newCall(request).enqueue(new Callback() { - @Override - public void onFailure(@NonNull Call call, @NonNull final IOException e) { - LogUtils.updateLog(logId, 0, e.getMessage()); - Toast(handError, TAG, "发送失败:" + e.getMessage()); - } - - @Override - public void onResponse(@NonNull Call call, @NonNull Response response) throws IOException { - final String responseStr = Objects.requireNonNull(response.body()).string(); - Log.d(TAG, "Response:" + response.code() + "," + responseStr); - Toast(handError, TAG, "发送状态:" + responseStr); - - //TODO:粗略解析是否发送成功 - if (responseStr.contains("\"StatusCode\":0")) { - LogUtils.updateLog(logId, 2, responseStr); - } else { - LogUtils.updateLog(logId, 0, responseStr); - } - } - }); - - } - - private static String buildMsg(String from, Date date, String title, String content) { - //if (TextUtils.isEmpty(title)) title = "【" + SettingUtils.getAddExtraDeviceMark().trim() + "】来自" + from + "的通知"; - String msgTitle = jsonInnerStr(title); - String msgTime = jsonInnerStr(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault()).format(date)); - String msgFrom = jsonInnerStr(from); - String msgContent = jsonInnerStr(content); - return MSG_TEMPLATE.replace("${MSG_TITLE}", msgTitle) - .replace("${MSG_TIME}", msgTime) - .replace("${MSG_FROM}", msgFrom) - .replace("${MSG_CONTENT}", msgContent); - } - - private static String jsonInnerStr(String string) { - if (string == null) { - return "null"; - } - String jsonStr = JSON.toJSONString(string); - return jsonStr.length() >= 2 ? jsonStr.substring(1, jsonStr.length() - 1) : jsonStr; - } - -} diff --git a/app/src/main/java/com/idormy/sms/forwarder/sender/SenderGotifyMsg.java b/app/src/main/java/com/idormy/sms/forwarder/sender/SenderGotifyMsg.java deleted file mode 100644 index ad028769..00000000 --- a/app/src/main/java/com/idormy/sms/forwarder/sender/SenderGotifyMsg.java +++ /dev/null @@ -1,82 +0,0 @@ -package com.idormy.sms.forwarder.sender; - -import android.os.Handler; -import android.util.Log; - -import androidx.annotation.NonNull; - -import com.idormy.sms.forwarder.model.vo.GotifySettingVo; -import com.idormy.sms.forwarder.utils.CertUtils; -import com.idormy.sms.forwarder.utils.Define; -import com.idormy.sms.forwarder.utils.LogUtils; - -import java.io.IOException; -import java.util.Objects; -import java.util.concurrent.TimeUnit; - -import okhttp3.Call; -import okhttp3.Callback; -import okhttp3.FormBody; -import okhttp3.OkHttpClient; -import okhttp3.Request; -import okhttp3.RequestBody; -import okhttp3.Response; - -@SuppressWarnings("RedundantThrows") -public class SenderGotifyMsg extends SenderBaseMsg { - - static final String TAG = "SenderGotifyMsg"; - - public static void sendMsg(final long logId, final Handler handError, final RetryIntercepter retryInterceptor, GotifySettingVo gotifySettingVo, String title, String message) throws Exception { - - //具体消息内容 - if (message == null || message.isEmpty()) return; - - RequestBody formBody = new FormBody.Builder() - .add("title", title) - .add("message", message) - .add("priority", gotifySettingVo.getPriority()) - .build(); - - String requestUrl = gotifySettingVo.getWebServer(); - Log.i(TAG, "requestUrl:" + requestUrl); - - OkHttpClient.Builder builder = new OkHttpClient.Builder(); - //设置重试拦截器 - if (retryInterceptor != null) builder.addInterceptor(retryInterceptor); - //忽略https证书 - builder.sslSocketFactory(CertUtils.getSSLSocketFactory(), CertUtils.getX509TrustManager()).hostnameVerifier(CertUtils.getHostnameVerifier()); - //设置读取超时时间 - OkHttpClient client = builder - .readTimeout(Define.REQUEST_TIMEOUT_SECONDS, TimeUnit.SECONDS) - .writeTimeout(Define.REQUEST_TIMEOUT_SECONDS, TimeUnit.SECONDS) - .connectTimeout(Define.REQUEST_TIMEOUT_SECONDS, TimeUnit.SECONDS) - .build(); - - Request request = new Request.Builder().url(requestUrl).post(formBody).build(); - - client.newCall(request).enqueue(new Callback() { - @Override - public void onFailure(@NonNull Call call, @NonNull final IOException e) { - LogUtils.updateLog(logId, 0, e.getMessage()); - Toast(handError, TAG, "发送失败:" + e.getMessage()); - } - - @Override - public void onResponse(@NonNull Call call, @NonNull Response response) throws IOException { - final String responseStr = Objects.requireNonNull(response.body()).string(); - Log.d(TAG, "Response:" + response.code() + "," + responseStr); - Toast(handError, TAG, "发送状态:" + responseStr); - - //TODO:粗略解析是否发送成功 - if (response.isSuccessful()) { - LogUtils.updateLog(logId, 2, responseStr); - } else { - LogUtils.updateLog(logId, 0, responseStr); - } - } - }); - - } - -} diff --git a/app/src/main/java/com/idormy/sms/forwarder/sender/SenderMailMsg.java b/app/src/main/java/com/idormy/sms/forwarder/sender/SenderMailMsg.java deleted file mode 100644 index 404b3aa7..00000000 --- a/app/src/main/java/com/idormy/sms/forwarder/sender/SenderMailMsg.java +++ /dev/null @@ -1,79 +0,0 @@ -package com.idormy.sms.forwarder.sender; - -import android.os.Handler; -import android.text.TextUtils; -import android.util.Log; - -import com.idormy.sms.forwarder.MyApplication; -import com.idormy.sms.forwarder.R; -import com.idormy.sms.forwarder.model.vo.EmailSettingVo; -import com.idormy.sms.forwarder.utils.LogUtils; -import com.smailnet.emailkit.Draft; -import com.smailnet.emailkit.EmailKit; - -import java.util.Arrays; -import java.util.HashSet; -import java.util.Set; - - -public class SenderMailMsg extends SenderBaseMsg { - private static final String TAG = "SenderMailMsg"; - - public static void sendEmail(final long logId, final Handler handError, final EmailSettingVo emailSettingVo, final String title, final String content) { - - Log.d(TAG, "emailSettingVo: " + emailSettingVo.toString()); - - try { - //初始化框架 - //EmailKit.initialize(MyApplication.getContext()); - - //配置发件人邮件服务器参数 - String fromEmail = emailSettingVo.getFromEmail(); - EmailKit.Config config = new EmailKit.Config(); - if (TextUtils.isEmpty(emailSettingVo.getMailType()) || emailSettingVo.getMailType().equals(MyApplication.getContext().getString(R.string.other_mail_type))) { - config.setSMTP(emailSettingVo.getHost(), Integer.parseInt(emailSettingVo.getPort()), emailSettingVo.getSsl()); //设置SMTP服务器主机地址、端口和是否开启ssl - } else { - fromEmail += emailSettingVo.getMailType(); - config.setMailType(emailSettingVo.getMailType());//选择邮箱类型 - } - - config.setAccount(fromEmail) //发件人邮箱 - .setPassword(emailSettingVo.getPwd()); //密码或授权码 - - //多个收件人邮箱 - Set toSet = new HashSet<>(Arrays.asList(emailSettingVo.getToEmail().replace(",", ",").split(","))); - - //设置一封草稿邮件 - Draft draft = new Draft() - .setNickname(emailSettingVo.getNickname()) //发件人昵称 - .setTo(toSet) //收件人邮箱 - .setSubject(title) //邮件主题 - .setText(content); //邮件正文 - - //使用SMTP服务发送邮件 - EmailKit.useSMTPService(config) - .send(draft, new EmailKit.GetSendCallback() { - @Override - public void onSuccess() { - LogUtils.updateLog(logId, 2, "发送成功"); - Toast(handError, TAG, "发送成功"); - } - - @Override - public void onFailure(String errMsg) { - LogUtils.updateLog(logId, 0, errMsg); - Toast(handError, TAG, "发送失败,错误:" + errMsg); - } - }); - - //销毁框架 - EmailKit.destroy(); - - } catch (Exception e) { - LogUtils.updateLog(logId, 0, e.getMessage()); - Log.e(TAG, e.getMessage(), e); - Toast(handError, TAG, "发送失败:" + e.getMessage()); - } - - } -} \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/sender/SenderPushPlusMsg.java b/app/src/main/java/com/idormy/sms/forwarder/sender/SenderPushPlusMsg.java deleted file mode 100644 index 72a5af1f..00000000 --- a/app/src/main/java/com/idormy/sms/forwarder/sender/SenderPushPlusMsg.java +++ /dev/null @@ -1,120 +0,0 @@ -package com.idormy.sms.forwarder.sender; - -import android.os.Handler; -import android.util.Log; - -import androidx.annotation.NonNull; - -import com.alibaba.fastjson.JSON; -import com.idormy.sms.forwarder.model.vo.PushPlusSettingVo; -import com.idormy.sms.forwarder.utils.Define; -import com.idormy.sms.forwarder.utils.LogUtils; - -import java.io.IOException; -import java.util.HashMap; -import java.util.Map; -import java.util.Objects; -import java.util.concurrent.TimeUnit; - -import okhttp3.Call; -import okhttp3.Callback; -import okhttp3.MediaType; -import okhttp3.OkHttpClient; -import okhttp3.Request; -import okhttp3.RequestBody; -import okhttp3.Response; - -@SuppressWarnings({"rawtypes", "unchecked", "deprecation", "RedundantThrows"}) -public class SenderPushPlusMsg extends SenderBaseMsg { - - static final String TAG = "SenderPushPlusMsg"; - - public static void sendMsg(final long logId, final Handler handError, final RetryIntercepter retryInterceptor, PushPlusSettingVo pushPlusSettingVo, String title, String content) throws Exception { - - //用户令牌 - String token = pushPlusSettingVo.getToken(); - if (token == null || token.isEmpty()) return; - - Map textMsgMap = new HashMap(); - - //消息标题 - if (title != null && !title.isEmpty()) textMsgMap.put("title", title); - - //具体消息内容 - if (content == null || content.isEmpty()) return; - textMsgMap.put("content", content); - - //群组编码,不填仅发送给自己;channel为webhook时无效 - String topic = pushPlusSettingVo.getTopic(); - if (topic != null && !topic.isEmpty()) textMsgMap.put("topic", topic); - - //发送模板 - String template = pushPlusSettingVo.getTemplate(); - if (template != null && !template.isEmpty()) textMsgMap.put("template", template); - - //发送渠道 - String channel = pushPlusSettingVo.getChannel(); - if (channel != null && !channel.isEmpty()) textMsgMap.put("channel", channel); - - //webhook编码,仅在channel使用webhook渠道和CP渠道时需要填写 - String webhook = pushPlusSettingVo.getChannel(); - if (webhook != null && !webhook.isEmpty()) textMsgMap.put("webhook", webhook); - - //发送结果回调地址 - String callbackUrl = pushPlusSettingVo.getCallbackUrl(); - if (callbackUrl != null && !callbackUrl.isEmpty()) textMsgMap.put("callbackUrl", callbackUrl); - - //毫秒时间戳。格式如:1632993318000。服务器时间戳大于此时间戳,则消息不会发送 - String validTime = pushPlusSettingVo.getValidTime(); - if (validTime != null && !validTime.isEmpty() && Integer.parseInt(validTime) > 0) { - textMsgMap.put("timestamp", System.currentTimeMillis() + Integer.parseInt(validTime) * 1000L); - } - - final String requestUrl = "http://www.pushplus.plus/send/" + token; - Log.i(TAG, "requestUrl:" + requestUrl); - final String requestMsg = JSON.toJSONString(textMsgMap); - Log.i(TAG, "requestMsg:" + requestMsg); - - OkHttpClient.Builder builder = new OkHttpClient.Builder(); - //设置重试拦截器 - if (retryInterceptor != null) builder.addInterceptor(retryInterceptor); - //设置读取超时时间 - OkHttpClient client = builder - .readTimeout(Define.REQUEST_TIMEOUT_SECONDS, TimeUnit.SECONDS) - .writeTimeout(Define.REQUEST_TIMEOUT_SECONDS, TimeUnit.SECONDS) - .connectTimeout(Define.REQUEST_TIMEOUT_SECONDS, TimeUnit.SECONDS) - .build(); - - RequestBody requestBody = RequestBody.create(MediaType.parse("application/json;charset=utf-8"), requestMsg); - - final Request request = new Request.Builder() - .url(requestUrl) - .addHeader("Content-Type", "application/json; charset=utf-8") - .post(requestBody) - .build(); - - client.newCall(request).enqueue(new Callback() { - @Override - public void onFailure(@NonNull Call call, @NonNull final IOException e) { - LogUtils.updateLog(logId, 0, e.getMessage()); - Toast(handError, TAG, "发送失败:" + e.getMessage()); - } - - @Override - public void onResponse(@NonNull Call call, @NonNull Response response) throws IOException { - final String responseStr = Objects.requireNonNull(response.body()).string(); - Log.d(TAG, "Response:" + response.code() + "," + responseStr); - Toast(handError, TAG, "发送状态:" + responseStr); - - //TODO:粗略解析是否发送成功 - if (responseStr.contains("\"code\":200")) { - LogUtils.updateLog(logId, 2, responseStr); - } else { - LogUtils.updateLog(logId, 0, responseStr); - } - } - }); - - } - -} diff --git a/app/src/main/java/com/idormy/sms/forwarder/sender/SenderQyWxAppMsg.java b/app/src/main/java/com/idormy/sms/forwarder/sender/SenderQyWxAppMsg.java deleted file mode 100644 index 3c62e91b..00000000 --- a/app/src/main/java/com/idormy/sms/forwarder/sender/SenderQyWxAppMsg.java +++ /dev/null @@ -1,184 +0,0 @@ -package com.idormy.sms.forwarder.sender; - -import android.os.Handler; -import android.util.Log; - -import androidx.annotation.NonNull; - -import com.alibaba.fastjson.JSON; -import com.alibaba.fastjson.JSONObject; -import com.idormy.sms.forwarder.model.SenderModel; -import com.idormy.sms.forwarder.model.vo.QYWXAppSettingVo; -import com.idormy.sms.forwarder.utils.Define; -import com.idormy.sms.forwarder.utils.LogUtils; - -import java.io.IOException; -import java.util.HashMap; -import java.util.Map; -import java.util.Objects; -import java.util.concurrent.TimeUnit; - -import okhttp3.Call; -import okhttp3.Callback; -import okhttp3.MediaType; -import okhttp3.OkHttpClient; -import okhttp3.Request; -import okhttp3.RequestBody; -import okhttp3.Response; - -@SuppressWarnings({"rawtypes", "unchecked", "deprecation", "RedundantThrows"}) -public class SenderQyWxAppMsg extends SenderBaseMsg { - - static final String TAG = "SenderQyWxAppMsg"; - - public static void sendMsg(final long logId, final Handler handError, final RetryIntercepter retryInterceptor, final SenderModel senderModel, final QYWXAppSettingVo qYWXAppSettingVo, String content) throws Exception { - - if (qYWXAppSettingVo == null) { - Toast(handError, TAG, "参数错误"); - return; - } - - String corpID = qYWXAppSettingVo.getCorpID(); - String agentID = qYWXAppSettingVo.getAgentID(); - String secret = qYWXAppSettingVo.getSecret(); - String toUser = qYWXAppSettingVo.getToUser(); - Boolean atAll = qYWXAppSettingVo.getAtAll(); - - Log.i(TAG, "sendMsg corpID:" + corpID + " agentID:" + agentID + " secret:" + secret + " toUser:" + toUser + " content:" + content); - - if (corpID == null || corpID.isEmpty() || agentID == null || agentID.isEmpty() || secret == null || secret.isEmpty()) { - return; - } - - //TODO:获取有效access_token - String accessToken = qYWXAppSettingVo.getAccessToken(); - if (accessToken == null || accessToken.isEmpty()) { - - String getTokenUrl = "https://qyapi.weixin.qq.com/cgi-bin/gettoken?"; - getTokenUrl += "corpid=" + corpID; - getTokenUrl += "&corpsecret=" + secret; - Log.d(TAG, "getTokenUrl:" + getTokenUrl); - - OkHttpClient.Builder builder = new OkHttpClient.Builder(); - //设置重试拦截器 - if (retryInterceptor != null) builder.addInterceptor(retryInterceptor); - //设置读取超时时间 - OkHttpClient client = builder - .readTimeout(Define.REQUEST_TIMEOUT_SECONDS, TimeUnit.SECONDS) - .writeTimeout(Define.REQUEST_TIMEOUT_SECONDS, TimeUnit.SECONDS) - .connectTimeout(Define.REQUEST_TIMEOUT_SECONDS, TimeUnit.SECONDS) - .build(); - - final Request request = new Request.Builder().url(getTokenUrl).get().build(); - - client.newCall(request).enqueue(new Callback() { - @Override - public void onFailure(@NonNull Call call, @NonNull final IOException e) { - LogUtils.updateLog(logId, 0, e.getMessage()); - qYWXAppSettingVo.setAccessToken(""); - qYWXAppSettingVo.setExpiresIn(0L); - if (senderModel != null) { - senderModel.setJsonSetting(JSON.toJSONString(qYWXAppSettingVo)); - SenderUtil.updateSender(senderModel); - } - Log.d(TAG, "onFailure:" + e.getMessage()); - Toast(handError, TAG, "获取access_token失败:" + e.getMessage()); - } - - @Override - public void onResponse(@NonNull Call call, @NonNull Response response) throws IOException { - final String json = Objects.requireNonNull(response.body()).string(); - Log.d(TAG, "Code:" + response.code() + " Response: " + json); - JSONObject jsonObject = JSON.parseObject(json); - int errcode = jsonObject.getInteger("errcode"); - if (errcode == 0) { - String access_token = jsonObject.getString("access_token"); - long expires_in = System.currentTimeMillis() + (jsonObject.getInteger("expires_in") - 120) * 1000L; //提前2分钟过期 - Log.d(TAG, "access_token:" + access_token); - Log.d(TAG, "expires_in:" + expires_in); - - qYWXAppSettingVo.setAccessToken(access_token); - qYWXAppSettingVo.setExpiresIn(expires_in); - if (senderModel != null) { - senderModel.setJsonSetting(JSON.toJSONString(qYWXAppSettingVo)); - SenderUtil.updateSender(senderModel); - } - - sendTextMsg(retryInterceptor, logId, handError, agentID, toUser, content, access_token); - } else { - String errmsg = jsonObject.getString("errmsg"); - LogUtils.updateLog(logId, 0, errmsg); - Log.d(TAG, "onFailure:" + errmsg); - Toast(handError, TAG, "获取access_token失败:" + errmsg); - } - } - - }); - - } else { - sendTextMsg(retryInterceptor, logId, handError, agentID, toUser, content, accessToken); - } - - } - - //发送文本消息 - public static void sendTextMsg(RetryIntercepter retryInterceptor, final long logId, final Handler handError, String agentID, String toUser, String content, String accessToken) { - - Map textMsgMap = new HashMap(); - textMsgMap.put("touser", toUser); - textMsgMap.put("msgtype", "text"); - textMsgMap.put("agentid", agentID); - - Map textText = new HashMap(); - textText.put("content", content); - textMsgMap.put("text", textText); - - final String requestUrl = "https://qyapi.weixin.qq.com/cgi-bin/message/send?access_token=" + accessToken; - Log.i(TAG, "requestUrl:" + requestUrl); - final String requestMsg = JSON.toJSONString(textMsgMap); - Log.i(TAG, "requestMsg:" + requestMsg); - - OkHttpClient.Builder builder = new OkHttpClient.Builder(); - //设置重试拦截器 - if (retryInterceptor != null) builder.addInterceptor(retryInterceptor); - //设置读取超时时间 - OkHttpClient client = builder - .readTimeout(Define.REQUEST_TIMEOUT_SECONDS, TimeUnit.SECONDS) - .writeTimeout(Define.REQUEST_TIMEOUT_SECONDS, TimeUnit.SECONDS) - .connectTimeout(Define.REQUEST_TIMEOUT_SECONDS, TimeUnit.SECONDS) - .build(); - - RequestBody requestBody = RequestBody.create(MediaType.parse("application/json;charset=utf-8"), requestMsg); - - final Request request = new Request.Builder() - .url(requestUrl) - .addHeader("Content-Type", "application/json; charset=utf-8") - .post(requestBody) - .build(); - - client.newCall(request).enqueue(new Callback() { - @Override - public void onFailure(@NonNull Call call, @NonNull final IOException e) { - LogUtils.updateLog(logId, 0, e.getMessage()); - Toast(handError, TAG, "发送失败:" + e.getMessage()); - } - - @Override - public void onResponse(@NonNull Call call, @NonNull Response response) throws IOException { - final String responseStr = Objects.requireNonNull(response.body()).string(); - Log.d(TAG, "Response:" + response.code() + "," + responseStr); - Toast(handError, TAG, "发送状态:" + responseStr); - - //TODO:粗略解析是否发送成功 - if (responseStr.contains("\"errcode\":0")) { - LogUtils.updateLog(logId, 2, responseStr); - } else { - LogUtils.updateLog(logId, 0, responseStr); - } - } - }); - - - } - -} \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/sender/SenderQyWxGroupRobotMsg.java b/app/src/main/java/com/idormy/sms/forwarder/sender/SenderQyWxGroupRobotMsg.java deleted file mode 100644 index 9cb108b2..00000000 --- a/app/src/main/java/com/idormy/sms/forwarder/sender/SenderQyWxGroupRobotMsg.java +++ /dev/null @@ -1,90 +0,0 @@ -package com.idormy.sms.forwarder.sender; - -import android.os.Handler; -import android.util.Log; - -import androidx.annotation.NonNull; - -import com.alibaba.fastjson.JSON; -import com.idormy.sms.forwarder.utils.Define; -import com.idormy.sms.forwarder.utils.LogUtils; - -import java.io.IOException; -import java.util.HashMap; -import java.util.Map; -import java.util.Objects; -import java.util.concurrent.TimeUnit; - -import okhttp3.Call; -import okhttp3.Callback; -import okhttp3.MediaType; -import okhttp3.OkHttpClient; -import okhttp3.Request; -import okhttp3.RequestBody; -import okhttp3.Response; - -@SuppressWarnings({"rawtypes", "unchecked", "deprecation", "RedundantThrows"}) -public class SenderQyWxGroupRobotMsg extends SenderBaseMsg { - - static final String TAG = "SenderQyWxGroupRobotMsg"; - - public static void sendMsg(final long logId, final Handler handError, final RetryIntercepter retryInterceptor, String webHook, String from, String content) throws Exception { - Log.i(TAG, "sendMsg webHook:" + webHook + " from:" + from + " content:" + content); - - if (webHook == null || webHook.isEmpty()) { - return; - } - - Map textMsgMap = new HashMap(); - textMsgMap.put("msgtype", "text"); - Map textText = new HashMap(); - textText.put("content", content); - textMsgMap.put("text", textText); - - Log.i(TAG, "requestUrl:" + webHook); - final String requestMsg = JSON.toJSONString(textMsgMap); - Log.i(TAG, "requestMsg:" + requestMsg); - - OkHttpClient.Builder builder = new OkHttpClient.Builder(); - //设置重试拦截器 - if (retryInterceptor != null) builder.addInterceptor(retryInterceptor); - //设置读取超时时间 - OkHttpClient client = builder - .readTimeout(Define.REQUEST_TIMEOUT_SECONDS, TimeUnit.SECONDS) - .writeTimeout(Define.REQUEST_TIMEOUT_SECONDS, TimeUnit.SECONDS) - .connectTimeout(Define.REQUEST_TIMEOUT_SECONDS, TimeUnit.SECONDS) - .build(); - - RequestBody requestBody = RequestBody.create(MediaType.parse("application/json;charset=utf-8"), requestMsg); - - final Request request = new Request.Builder() - .url(webHook) - .addHeader("Content-Type", "application/json; charset=utf-8") - .post(requestBody) - .build(); - - client.newCall(request).enqueue(new Callback() { - @Override - public void onFailure(@NonNull Call call, @NonNull final IOException e) { - LogUtils.updateLog(logId, 0, e.getMessage()); - Toast(handError, TAG, "发送失败:" + e.getMessage()); - } - - @Override - public void onResponse(@NonNull Call call, @NonNull Response response) throws IOException { - final String responseStr = Objects.requireNonNull(response.body()).string(); - Log.d(TAG, "Response:" + response.code() + "," + responseStr); - Toast(handError, TAG, "发送状态:" + responseStr); - - //TODO:粗略解析是否发送成功 - if (responseStr.contains("\"errcode\":0")) { - LogUtils.updateLog(logId, 2, responseStr); - } else { - LogUtils.updateLog(logId, 0, responseStr); - } - } - }); - - } - -} diff --git a/app/src/main/java/com/idormy/sms/forwarder/sender/SenderServerChanMsg.java b/app/src/main/java/com/idormy/sms/forwarder/sender/SenderServerChanMsg.java deleted file mode 100644 index a3f4fa6a..00000000 --- a/app/src/main/java/com/idormy/sms/forwarder/sender/SenderServerChanMsg.java +++ /dev/null @@ -1,82 +0,0 @@ -package com.idormy.sms.forwarder.sender; - -import android.os.Handler; -import android.util.Log; - -import androidx.annotation.NonNull; - -import com.idormy.sms.forwarder.utils.Define; -import com.idormy.sms.forwarder.utils.LogUtils; - -import java.io.IOException; -import java.util.Objects; -import java.util.concurrent.TimeUnit; - -import okhttp3.Call; -import okhttp3.Callback; -import okhttp3.MultipartBody; -import okhttp3.OkHttpClient; -import okhttp3.Request; -import okhttp3.RequestBody; -import okhttp3.Response; - -@SuppressWarnings("RedundantThrows") -public class SenderServerChanMsg extends SenderBaseMsg { - - static final String TAG = "SenderServerChanMsg"; - - public static void sendMsg(final long logId, final Handler handError, final RetryIntercepter retryInterceptor, final String sendKey, final String title, final String desp) throws Exception { - Log.i(TAG, "sendMsg sendKey:" + sendKey + " title:" + title + " desp:" + desp); - - if (sendKey == null || sendKey.isEmpty()) { - return; - } - - final String requestUrl = "https://sctapi.ftqq.com/" + sendKey + ".send"; - Log.i(TAG, "requestUrl:" + requestUrl); - //特殊处理避免标题重复 - final String requestMsg = desp.replaceFirst("^" + title + "(.*)", "").trim(); - Log.i(TAG, "requestMsg:" + requestMsg); - - OkHttpClient.Builder clientBuilder = new OkHttpClient.Builder(); - //设置重试拦截器 - if (retryInterceptor != null) clientBuilder.addInterceptor(retryInterceptor); - //设置读取超时时间 - OkHttpClient client = clientBuilder - .readTimeout(Define.REQUEST_TIMEOUT_SECONDS, TimeUnit.SECONDS) - .writeTimeout(Define.REQUEST_TIMEOUT_SECONDS, TimeUnit.SECONDS) - .connectTimeout(Define.REQUEST_TIMEOUT_SECONDS, TimeUnit.SECONDS) - .build(); - - MultipartBody.Builder bodyBuilder = new MultipartBody.Builder().setType(MultipartBody.FORM) - .addFormDataPart("title", title) - .addFormDataPart("desp", requestMsg); - - RequestBody body = bodyBuilder.build(); - Request request = new Request.Builder().url(requestUrl).method("POST", body).build(); - - client.newCall(request).enqueue(new Callback() { - @Override - public void onFailure(@NonNull Call call, @NonNull final IOException e) { - LogUtils.updateLog(logId, 0, e.getMessage()); - Toast(handError, TAG, "发送失败:" + e.getMessage()); - } - - @Override - public void onResponse(@NonNull Call call, @NonNull Response response) throws IOException { - final String responseStr = Objects.requireNonNull(response.body()).string(); - Log.d(TAG, "Response:" + response.code() + "," + responseStr); - Toast(handError, TAG, "发送状态:" + responseStr); - - //TODO:粗略解析是否发送成功 - if (responseStr.contains("\"code\":0")) { - LogUtils.updateLog(logId, 2, responseStr); - } else { - LogUtils.updateLog(logId, 0, responseStr); - } - } - }); - - } - -} diff --git a/app/src/main/java/com/idormy/sms/forwarder/sender/SenderSmsMsg.java b/app/src/main/java/com/idormy/sms/forwarder/sender/SenderSmsMsg.java deleted file mode 100644 index 987696d6..00000000 --- a/app/src/main/java/com/idormy/sms/forwarder/sender/SenderSmsMsg.java +++ /dev/null @@ -1,34 +0,0 @@ -package com.idormy.sms.forwarder.sender; - -import android.annotation.SuppressLint; -import android.os.Handler; -import android.util.Log; - -import com.idormy.sms.forwarder.utils.LogUtils; -import com.idormy.sms.forwarder.utils.SimUtils; -import com.idormy.sms.forwarder.utils.SmsUtils; - -@SuppressWarnings("RedundantThrows") -public class SenderSmsMsg extends SenderBaseMsg { - - static final String TAG = "SenderSmsMsg"; - - @SuppressLint("NewApi") - public static void sendMsg(final long logId, final Handler handError, int simSlot, String mobiles, Boolean onlyNoNetwork, String from, String text) throws Exception { - Log.i(TAG, "sendMsg simSlot:" + simSlot + " mobiles:" + mobiles + " onlyNoNetwork:" + onlyNoNetwork + " from:" + from + " text:" + text); - - //TODO:simSlot转subId - final int subId = SimUtils.getSubscriptionIdBySimId(simSlot); - String res = SmsUtils.sendSms(subId, mobiles, text); - - //TODO:粗略解析是否发送成功 - if (res == null) { - LogUtils.updateLog(logId, 2, "发送成功"); - } else { - LogUtils.updateLog(logId, 0, res); - Toast(handError, TAG, "短信发送失败"); - } - - } - -} diff --git a/app/src/main/java/com/idormy/sms/forwarder/sender/SenderTelegramMsg.java b/app/src/main/java/com/idormy/sms/forwarder/sender/SenderTelegramMsg.java deleted file mode 100644 index bc85977d..00000000 --- a/app/src/main/java/com/idormy/sms/forwarder/sender/SenderTelegramMsg.java +++ /dev/null @@ -1,135 +0,0 @@ -package com.idormy.sms.forwarder.sender; - -import android.os.Handler; -import android.text.TextUtils; -import android.util.Log; - -import androidx.annotation.NonNull; - -import com.alibaba.fastjson.JSON; -import com.idormy.sms.forwarder.model.vo.TelegramSettingVo; -import com.idormy.sms.forwarder.utils.Define; -import com.idormy.sms.forwarder.utils.LogUtils; - -import java.io.IOException; -import java.net.InetSocketAddress; -import java.net.Proxy; -import java.net.URLEncoder; -import java.util.HashMap; -import java.util.Map; -import java.util.Objects; -import java.util.concurrent.TimeUnit; - -import okhttp3.Call; -import okhttp3.Callback; -import okhttp3.Credentials; -import okhttp3.MediaType; -import okhttp3.OkHttpClient; -import okhttp3.Request; -import okhttp3.RequestBody; -import okhttp3.Response; - -@SuppressWarnings({"rawtypes", "unchecked", "deprecation"}) -public class SenderTelegramMsg extends SenderBaseMsg { - - static final String TAG = "SenderTelegramMsg"; - - public static void sendMsg(final long logId, final Handler handError, final RetryIntercepter retryInterceptor, TelegramSettingVo telegramSettingVo, final String from, final String text, final String method) throws Exception { - Log.i(TAG, "sendMsg telegramSettingVo:" + telegramSettingVo.toString() + " text:" + text); - - String apiToken = telegramSettingVo.getApiToken(); - String chatId = telegramSettingVo.getChatId(); - if (apiToken == null || apiToken.isEmpty()) { - return; - } - - final String finalText = text.trim(); //.replaceAll("#", "井") - - if (!apiToken.startsWith("http")) { - apiToken = "https://api.telegram.org/bot" + apiToken + "/sendMessage"; - } - - final String requestUrl = apiToken; - Log.i(TAG, "requestUrl:" + requestUrl); - - //代理相关 - final Proxy.Type proxyType = telegramSettingVo.getProxyType(); - final String proxyHost = telegramSettingVo.getProxyHost(); - final String proxyPort = telegramSettingVo.getProxyPort(); - final Boolean needProxyAuthenticator = telegramSettingVo.getProxyAuthenticator(); - final String proxyUsername = telegramSettingVo.getProxyUsername(); - final String proxyPassword = telegramSettingVo.getProxyPassword(); - - OkHttpClient.Builder clientBuilder = new OkHttpClient.Builder(); - //设置代理 - if ((proxyType == Proxy.Type.HTTP || proxyType == Proxy.Type.SOCKS) && !TextUtils.isEmpty(proxyHost) && !TextUtils.isEmpty(proxyPort)) { - //代理服务器的IP和端口号 - clientBuilder.proxy(new Proxy(proxyType, new InetSocketAddress(proxyHost, Integer.parseInt(proxyPort)))); - - //代理的鉴权账号密码 - if (needProxyAuthenticator && (!TextUtils.isEmpty(proxyUsername) || !TextUtils.isEmpty(proxyPassword))) { - clientBuilder.proxyAuthenticator((route, response) -> { - //设置代理服务器账号密码 - String credential = Credentials.basic(proxyUsername, proxyPassword); - return response.request().newBuilder() - .header("Proxy-Authorization", credential) - .build(); - }); - } - } - //设置重试拦截器 - if (retryInterceptor != null) clientBuilder.addInterceptor(retryInterceptor); - //设置读取超时时间 - OkHttpClient client = clientBuilder - .readTimeout(Define.REQUEST_TIMEOUT_SECONDS, TimeUnit.SECONDS) - .writeTimeout(Define.REQUEST_TIMEOUT_SECONDS, TimeUnit.SECONDS) - .connectTimeout(Define.REQUEST_TIMEOUT_SECONDS, TimeUnit.SECONDS) - .build(); - - final Request request; - if (method != null && method.equals("GET")) { - request = new Request.Builder() - .url(requestUrl + "?chat_id=" + chatId + "&text=" + URLEncoder.encode(finalText, "UTF-8")) - .build(); - } else { - Map bodyMap = new HashMap(); - bodyMap.put("chat_id", chatId); - bodyMap.put("text", finalText); - bodyMap.put("parse_mode", "HTML"); - - String requestMsg = JSON.toJSONString(bodyMap); - Log.i(TAG, "requestMsg:" + requestMsg); - - RequestBody requestBody = RequestBody.create(MediaType.parse("application/json;charset=utf-8"), requestMsg); - request = new Request.Builder() - .url(requestUrl) - .addHeader("Content-Type", "application/json; charset=utf-8") - .post(requestBody) - .build(); - } - - client.newCall(request).enqueue(new Callback() { - @Override - public void onFailure(@NonNull Call call, @NonNull final IOException e) { - LogUtils.updateLog(logId, 0, e.getMessage()); - Toast(handError, TAG, "发送失败:" + e.getMessage()); - } - - @Override - public void onResponse(@NonNull Call call, @NonNull Response response) throws IOException { - final String responseStr = Objects.requireNonNull(response.body()).string(); - Log.d(TAG, "Response:" + response.code() + "," + responseStr); - Toast(handError, TAG, "发送状态:" + responseStr); - - //TODO:粗略解析是否发送成功 - if (responseStr.contains("\"ok\":true")) { - LogUtils.updateLog(logId, 2, responseStr); - } else { - LogUtils.updateLog(logId, 0, responseStr); - } - } - }); - - } - -} diff --git a/app/src/main/java/com/idormy/sms/forwarder/sender/SenderUtil.java b/app/src/main/java/com/idormy/sms/forwarder/sender/SenderUtil.java deleted file mode 100644 index f8ae1dff..00000000 --- a/app/src/main/java/com/idormy/sms/forwarder/sender/SenderUtil.java +++ /dev/null @@ -1,199 +0,0 @@ -package com.idormy.sms.forwarder.sender; - -import android.annotation.SuppressLint; -import android.content.ContentValues; -import android.content.Context; -import android.database.Cursor; -import android.database.sqlite.SQLiteDatabase; -import android.provider.BaseColumns; -import android.util.Log; - -import com.idormy.sms.forwarder.model.SenderModel; -import com.idormy.sms.forwarder.model.SenderTable; -import com.idormy.sms.forwarder.utils.DbHelper; - -import java.util.ArrayList; -import java.util.List; - -@SuppressWarnings({"SynchronizeOnNonFinalField", "UnusedReturnValue", "unused"}) -public class SenderUtil { - static final String TAG = "SenderUtil"; - static Boolean hasInit = false; - - @SuppressLint("StaticFieldLeak") - static Context context; - static DbHelper dbHelper; - static SQLiteDatabase db; - - public static void init(Context context1) { - synchronized (hasInit) { - if (hasInit) return; - hasInit = true; - context = context1; - dbHelper = new DbHelper(context); - // Gets the data repository in write mode - db = dbHelper.getReadableDatabase(); - } - - } - - public static long addSender(SenderModel senderModel) { - - // Create a new map of values, where column names are the keys - ContentValues values = new ContentValues(); - values.put(SenderTable.SenderEntry.COLUMN_NAME_NAME, senderModel.getName()); - values.put(SenderTable.SenderEntry.COLUMN_NAME_TYPE, senderModel.getType()); - values.put(SenderTable.SenderEntry.COLUMN_NAME_STATUS, senderModel.getStatus()); - values.put(SenderTable.SenderEntry.COLUMN_NAME_JSON_SETTING, senderModel.getJsonSetting()); - - if (null != senderModel.getId()) { - values.put(BaseColumns._ID, senderModel.getId()); - return db.replace(SenderTable.SenderEntry.TABLE_NAME, null, values); - } else { - // Insert the new row, returning the primary key value of the new row - return db.insert(SenderTable.SenderEntry.TABLE_NAME, null, values); - } - } - - public static long updateSender(SenderModel senderModel) { - if (senderModel == null) return 0; - - // Create a new map of values, where column names are the keys - ContentValues values = new ContentValues(); - values.put(SenderTable.SenderEntry.COLUMN_NAME_NAME, senderModel.getName()); - values.put(SenderTable.SenderEntry.COLUMN_NAME_TYPE, senderModel.getType()); - values.put(SenderTable.SenderEntry.COLUMN_NAME_STATUS, senderModel.getStatus()); - values.put(SenderTable.SenderEntry.COLUMN_NAME_JSON_SETTING, senderModel.getJsonSetting()); - - String selection = SenderTable.SenderEntry._ID + " = ? "; - String[] whereArgs = {String.valueOf(senderModel.getId())}; - - return db.update(SenderTable.SenderEntry.TABLE_NAME, values, selection, whereArgs); - } - - public static int delSender(Long id) { - // Define 'where' part of query. - String selection = " 1 "; - // Specify arguments in placeholder order. - List selectionArgList = new ArrayList<>(); - if (id != null) { - // Define 'where' part of query. - selection += " and " + SenderTable.SenderEntry._ID + " = ? "; - // Specify arguments in placeholder order. - selectionArgList.add(String.valueOf(id)); - - } - String[] selectionArgs = selectionArgList.toArray(new String[0]); - // Issue SQL statement. - return db.delete(SenderTable.SenderEntry.TABLE_NAME, selection, selectionArgs); - - } - - public static List getSender(Long id, String key) { - // Define a projection that specifies which columns from the database - // you will actually use after this query. - String[] projection = { - BaseColumns._ID, - SenderTable.SenderEntry.COLUMN_NAME_NAME, - SenderTable.SenderEntry.COLUMN_NAME_TYPE, - SenderTable.SenderEntry.COLUMN_NAME_STATUS, - SenderTable.SenderEntry.COLUMN_NAME_JSON_SETTING, - SenderTable.SenderEntry.COLUMN_NAME_TIME - }; - // Define 'where' part of query. - String selection = " 1 "; - // Specify arguments in placeholder order. - List selectionArgList = new ArrayList<>(); - if (id != null) { - // Define 'where' part of query. - selection += " and " + SenderTable.SenderEntry._ID + " = ? "; - // Specify arguments in placeholder order. - selectionArgList.add(String.valueOf(id)); - } - - if (key != null) { - // Define 'where' part of query. - selection = " and (" + SenderTable.SenderEntry.COLUMN_NAME_NAME + " LIKE ? or " + SenderTable.SenderEntry.COLUMN_NAME_JSON_SETTING + " LIKE ? ) "; - // Specify arguments in placeholder order. - selectionArgList.add(key); - selectionArgList.add(key); - } - String[] selectionArgs = selectionArgList.toArray(new String[0]); - - // How you want the results sorted in the resulting Cursor - String sortOrder = - SenderTable.SenderEntry._ID + " DESC"; - - Cursor cursor = db.query( - SenderTable.SenderEntry.TABLE_NAME, // The table to query - projection, // The array of columns to return (pass null to get all) - selection, // The columns for the WHERE clause - selectionArgs, // The values for the WHERE clause - null, // don't group the rows - null, // don't filter by row groups - sortOrder // The sort order - ); - List tSenders = new ArrayList<>(); - while (cursor.moveToNext()) { - - long itemId = cursor.getLong( - cursor.getColumnIndexOrThrow(SenderTable.SenderEntry._ID)); - String itemName = cursor.getString( - cursor.getColumnIndexOrThrow(SenderTable.SenderEntry.COLUMN_NAME_NAME)); - int itemStatus = cursor.getInt( - cursor.getColumnIndexOrThrow(SenderTable.SenderEntry.COLUMN_NAME_STATUS)); - int itemType = cursor.getInt( - cursor.getColumnIndexOrThrow(SenderTable.SenderEntry.COLUMN_NAME_TYPE)); - String itemJsonSetting = cursor.getString( - cursor.getColumnIndexOrThrow(SenderTable.SenderEntry.COLUMN_NAME_JSON_SETTING)); - long itemTime = cursor.getLong( - cursor.getColumnIndexOrThrow(SenderTable.SenderEntry.COLUMN_NAME_TIME)); - Log.d(TAG, "getSender: itemId" + itemId); - - SenderModel senderModel = new SenderModel(); - senderModel.setId(itemId); - senderModel.setName(itemName); - senderModel.setStatus(itemStatus); - senderModel.setType(itemType); - senderModel.setJsonSetting(itemJsonSetting); - senderModel.setTime(itemTime); - - tSenders.add(senderModel); - } - cursor.close(); - return tSenders; - } - - public static int countSender(String status, String key) { - String[] projection = {}; - String selection = " 1 "; - List selectionArgList = new ArrayList<>(); - - if (status != null && !status.isEmpty()) { - selection += " and " + SenderTable.SenderEntry.COLUMN_NAME_STATUS + " = ? "; - selectionArgList.add(status); - } - - if (key != null && !key.isEmpty()) { - selection += " and (" + SenderTable.SenderEntry.COLUMN_NAME_NAME + " LIKE ? or " + SenderTable.SenderEntry.COLUMN_NAME_JSON_SETTING + " LIKE ? ) "; - selectionArgList.add(key); - selectionArgList.add(key); - } - - String[] selectionArgs = selectionArgList.toArray(new String[0]); - Cursor cursor = db.query( - SenderTable.SenderEntry.TABLE_NAME, // The table to query - projection, // The array of columns to return (pass null to get all) - selection, // The columns for the WHERE clause - selectionArgs, // The values for the WHERE clause - null, // don't group the rows - null, // don't filter by row groups - null // The sort order - ); - - int count = cursor.getCount(); - cursor.close(); - return count; - } - -} diff --git a/app/src/main/java/com/idormy/sms/forwarder/sender/SenderWebNotifyMsg.java b/app/src/main/java/com/idormy/sms/forwarder/sender/SenderWebNotifyMsg.java deleted file mode 100644 index 97f23e11..00000000 --- a/app/src/main/java/com/idormy/sms/forwarder/sender/SenderWebNotifyMsg.java +++ /dev/null @@ -1,192 +0,0 @@ -package com.idormy.sms.forwarder.sender; - -import android.annotation.SuppressLint; -import android.os.Handler; -import android.text.TextUtils; -import android.util.Base64; -import android.util.Log; - -import androidx.annotation.NonNull; - -import com.alibaba.fastjson.JSON; -import com.idormy.sms.forwarder.model.vo.SmsVo; -import com.idormy.sms.forwarder.utils.CertUtils; -import com.idormy.sms.forwarder.utils.Define; -import com.idormy.sms.forwarder.utils.LogUtils; -import com.idormy.sms.forwarder.utils.SettingUtils; - -import java.io.IOException; -import java.net.URLEncoder; -import java.nio.charset.StandardCharsets; -import java.text.SimpleDateFormat; -import java.util.Map; -import java.util.Objects; -import java.util.concurrent.TimeUnit; - -import javax.crypto.Mac; -import javax.crypto.spec.SecretKeySpec; - -import okhttp3.Call; -import okhttp3.Callback; -import okhttp3.MediaType; -import okhttp3.MultipartBody; -import okhttp3.OkHttpClient; -import okhttp3.Request; -import okhttp3.RequestBody; -import okhttp3.Response; - -@SuppressWarnings({"deprecation"}) -public class SenderWebNotifyMsg extends SenderBaseMsg { - - static final String TAG = "SenderWebNotifyMsg"; - - public static void sendMsg(final long logId, final Handler handError, final RetryIntercepter retryInterceptor, String webServer, String webParams, String secret, String method, Map headers, SmsVo smsVo, String smsTemplate, String regexReplace) throws Exception { - String from = smsVo.getMobile(); - String content = smsVo.getSmsVoForSend(smsTemplate, regexReplace); - Log.i(TAG, "sendMsg webServer:" + webServer + " webParams:" + webParams + " from:" + from + " content:" + content); - - if (webServer == null || webServer.isEmpty()) { - return; - } - - Long timestamp = System.currentTimeMillis(); - String orgContent = smsVo.getContent(); - String deviceMark = SettingUtils.getAddExtraDeviceMark().trim(); - String appVersion = SettingUtils.getVersionName(); - String simInfo = smsVo.getSimInfo(); - @SuppressLint("SimpleDateFormat") String receiveTime = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(smsVo.getDate()); - - String sign = ""; - if (secret != null && !secret.isEmpty()) { - String stringToSign = timestamp + "\n" + secret; - Mac mac = Mac.getInstance("HmacSHA256"); - mac.init(new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), "HmacSHA256")); - byte[] signData = mac.doFinal(stringToSign.getBytes(StandardCharsets.UTF_8)); - sign = URLEncoder.encode(new String(Base64.encode(signData, Base64.NO_WRAP)), "UTF-8"); - Log.i(TAG, "sign:" + sign); - } - - Request.Builder requestBuilder; - if (method.equals("GET") && TextUtils.isEmpty(webParams)) { - webServer += (webServer.contains("?") ? "&" : "?") + "from=" + URLEncoder.encode(from, "UTF-8"); - webServer += "&content=" + URLEncoder.encode(content, "UTF-8"); - if (secret != null && !secret.isEmpty()) { - webServer += "×tamp=" + timestamp; - webServer += "&sign=" + sign; - } - - Log.d(TAG, "method = GET, Url = " + webServer); - requestBuilder = new Request.Builder().url(webServer).get(); - } else if (method.equals("GET") && !TextUtils.isEmpty(webParams)) { - webParams = webParams.replace("[from]", URLEncoder.encode(from, "UTF-8")) - .replace("[content]", URLEncoder.encode(content, "UTF-8")) - .replace("[msg]", URLEncoder.encode(content, "UTF-8")) - .replace("[org_content]", URLEncoder.encode(orgContent, "UTF-8")) - .replace("[device_mark]", URLEncoder.encode(deviceMark, "UTF-8")) - .replace("[app_version]", URLEncoder.encode(appVersion, "UTF-8")) - .replace("[title]", URLEncoder.encode(simInfo, "UTF-8")) - .replace("[card_slot]", URLEncoder.encode(simInfo, "UTF-8")) - .replace("[receive_time]", URLEncoder.encode(receiveTime, "UTF-8")) - .replace("\n", "%0A"); - if (secret != null && !secret.isEmpty()) { - webParams = webParams.replace("[timestamp]", String.valueOf(timestamp)) - .replace("[sign]", URLEncoder.encode(sign, "UTF-8")); - } - webServer += (webServer.contains("?") ? "&" : "?") + webParams; - - Log.d(TAG, "method = GET, Url = " + webServer); - requestBuilder = new Request.Builder().url(webServer).get(); - } else if (webParams != null && webParams.contains("[msg]")) { - String bodyMsg; - String contentType = "application/x-www-form-urlencoded"; - if (webParams.startsWith("{")) { - contentType = "application/json;charset=utf-8"; - bodyMsg = webParams.replace("[from]", from) - .replace("[content]", escapeJson(content)) - .replace("[msg]", escapeJson(content)) - .replace("[org_content]", escapeJson(orgContent)) - .replace("[device_mark]", escapeJson(deviceMark)) - .replace("[app_version]", appVersion) - .replace("[title]", escapeJson(simInfo)) - .replace("[card_slot]", escapeJson(simInfo)) - .replace("[receive_time]", receiveTime); - } else { - bodyMsg = webParams.replace("[from]", URLEncoder.encode(from, "UTF-8")) - .replace("[content]", URLEncoder.encode(content, "UTF-8")) - .replace("[msg]", URLEncoder.encode(content, "UTF-8")) - .replace("[org_content]", URLEncoder.encode(orgContent, "UTF-8")) - .replace("[device_mark]", URLEncoder.encode(deviceMark, "UTF-8")) - .replace("[app_version]", URLEncoder.encode(appVersion, "UTF-8")) - .replace("[title]", URLEncoder.encode(simInfo, "UTF-8")) - .replace("[card_slot]", URLEncoder.encode(simInfo, "UTF-8")) - .replace("[receive_time]", URLEncoder.encode(receiveTime, "UTF-8")); - } - RequestBody body = RequestBody.create(MediaType.parse(contentType), bodyMsg); - requestBuilder = new Request.Builder() - .url(webServer) - .addHeader("Content-Type", contentType) - .method("POST", body); - Log.d(TAG, "method = POST webParams, Body = " + bodyMsg); - } else { - MultipartBody.Builder builder = new MultipartBody.Builder().setType(MultipartBody.FORM) - .addFormDataPart("from", from) - .addFormDataPart("content", content); - if (secret != null && !secret.isEmpty()) { - builder.addFormDataPart("timestamp", String.valueOf(timestamp)); - builder.addFormDataPart("sign", sign); - } - - RequestBody body = builder.build(); - Log.d(TAG, "method = POST, Body = " + body); - requestBuilder = new Request.Builder().url(webServer).method("POST", body); - } - - for (Map.Entry entry : headers.entrySet()) { - requestBuilder.addHeader(entry.getKey(), entry.getValue()); - } - - OkHttpClient.Builder clientBuilder = new OkHttpClient.Builder(); - //设置重试拦截器 - if (retryInterceptor != null) clientBuilder.addInterceptor(retryInterceptor); - //忽略https证书 - clientBuilder.sslSocketFactory(CertUtils.getSSLSocketFactory(), CertUtils.getX509TrustManager()).hostnameVerifier(CertUtils.getHostnameVerifier()); - //设置读取超时时间 - OkHttpClient client = clientBuilder - .readTimeout(Define.REQUEST_TIMEOUT_SECONDS, TimeUnit.SECONDS) - .writeTimeout(Define.REQUEST_TIMEOUT_SECONDS, TimeUnit.SECONDS) - .connectTimeout(Define.REQUEST_TIMEOUT_SECONDS, TimeUnit.SECONDS) - .build(); - - client.newCall(requestBuilder.build()).enqueue(new Callback() { - @Override - public void onFailure(@NonNull Call call, @NonNull final IOException e) { - LogUtils.updateLog(logId, 0, e.getMessage()); - Toast(handError, TAG, "发送失败:" + e.getMessage()); - } - - @Override - public void onResponse(@NonNull Call call, @NonNull Response response) throws IOException { - final String responseStr = Objects.requireNonNull(response.body()).string(); - Log.d(TAG, "Response:" + response.code() + "," + responseStr); - Toast(handError, TAG, "发送状态:" + responseStr); - - //返回http状态200即为成功 - if (200 == response.code()) { - LogUtils.updateLog(logId, 2, responseStr); - } else { - LogUtils.updateLog(logId, 0, responseStr); - } - } - }); - - } - - //JSON需要转义的字符 - private static String escapeJson(String str) { - if (str == null) return "null"; - - String jsonStr = JSON.toJSONString(str); - return jsonStr.length() >= 2 ? jsonStr.substring(1, jsonStr.length() - 1) : jsonStr; - } - -} \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/server/component/AppConfig.kt b/app/src/main/java/com/idormy/sms/forwarder/server/component/AppConfig.kt new file mode 100644 index 00000000..6ae00db8 --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/server/component/AppConfig.kt @@ -0,0 +1,26 @@ +package com.idormy.sms.forwarder.server.component + +import android.content.Context +import com.yanzhenjie.andserver.annotation.Config +import com.yanzhenjie.andserver.framework.config.Multipart +import com.yanzhenjie.andserver.framework.config.WebConfig +import com.yanzhenjie.andserver.framework.website.AssetsWebsite + +@Config +class AppConfig : WebConfig { + + override fun onConfig(context: Context, delegate: WebConfig.Delegate) { + // 增加一个位于assert的Web目录网站 + delegate.addWebsite(AssetsWebsite(context, "/web/")) + + delegate.setMultipart( + Multipart.newBuilder() + .allFileMaxSize(1024 * 1024 * 20) // 单个请求所有文件总大小 + .fileMaxSize(1024 * 1024 * 5) // 单个请求每个文件大小 + .maxInMemorySize(1024 * 20) // 内存缓存大小 + .uploadTempDir(context.cacheDir) // 上传文件保存目录 + .build() + ) + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/server/component/AppExceptionResolver.kt b/app/src/main/java/com/idormy/sms/forwarder/server/component/AppExceptionResolver.kt new file mode 100644 index 00000000..31bbb126 --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/server/component/AppExceptionResolver.kt @@ -0,0 +1,26 @@ +package com.idormy.sms.forwarder.server.component + +import com.idormy.sms.forwarder.utils.HttpServerUtils +import com.yanzhenjie.andserver.annotation.Resolver +import com.yanzhenjie.andserver.error.HttpException +import com.yanzhenjie.andserver.framework.ExceptionResolver +import com.yanzhenjie.andserver.framework.body.JsonBody +import com.yanzhenjie.andserver.http.HttpRequest +import com.yanzhenjie.andserver.http.HttpResponse +import com.yanzhenjie.andserver.http.StatusCode + +@Resolver +class AppExceptionResolver : ExceptionResolver { + + override fun onResolve(request: HttpRequest, response: HttpResponse, e: Throwable) { + e.printStackTrace() + if (e is HttpException) { + response.status = e.statusCode + } else { + response.status = StatusCode.SC_INTERNAL_SERVER_ERROR + } + //返回统一结构报文 + response.setBody(JsonBody(HttpServerUtils.response(e.message.toString()))) + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/server/component/AppMessageConverter.kt b/app/src/main/java/com/idormy/sms/forwarder/server/component/AppMessageConverter.kt new file mode 100644 index 00000000..15153b1b --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/server/component/AppMessageConverter.kt @@ -0,0 +1,51 @@ +package com.idormy.sms.forwarder.server.component + +import android.text.TextUtils +import android.util.Log +import com.google.gson.Gson +import com.idormy.sms.forwarder.server.model.BaseRequest +import com.idormy.sms.forwarder.utils.HttpServerUtils +import com.yanzhenjie.andserver.annotation.Converter +import com.yanzhenjie.andserver.framework.MessageConverter +import com.yanzhenjie.andserver.framework.body.JsonBody +import com.yanzhenjie.andserver.http.ResponseBody +import com.yanzhenjie.andserver.util.IOUtils +import com.yanzhenjie.andserver.util.MediaType +import java.io.IOException +import java.io.InputStream +import java.lang.reflect.Type +import java.nio.charset.Charset + + +@Suppress("PrivatePropertyName") +@Converter +class AppMessageConverter : MessageConverter { + + private val TAG: String = "AppMessageConverter" + + override fun convert(output: Any?, mediaType: MediaType?): ResponseBody { + //返回统一结构报文 + return JsonBody(HttpServerUtils.response(output)) + } + + @Throws(IOException::class) + override fun convert(stream: InputStream, mediaType: MediaType?, type: Type?): T? { + val charset: Charset? = mediaType?.charset + Log.d(TAG, "Charset: $charset") + + val json = if (charset == null) IOUtils.toString(stream) else IOUtils.toString(stream, charset) + Log.d(TAG, "Json: $json") + + //TODO:待迁移kotlinx.serialization,type转换问题 + val t: T? = Gson().fromJson(json, type) + Log.d(TAG, "Bean: $t") + + //校验时间戳(时间误差不能超过1小时)&& 签名 + if (!TextUtils.isEmpty(HttpServerUtils.serverSignKey)) { + HttpServerUtils.checkSign(t as BaseRequest<*>) + } + + return t + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/server/component/LoggerInterceptor.kt b/app/src/main/java/com/idormy/sms/forwarder/server/component/LoggerInterceptor.kt new file mode 100644 index 00000000..5d562c71 --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/server/component/LoggerInterceptor.kt @@ -0,0 +1,53 @@ +package com.idormy.sms.forwarder.server.component + +import android.util.Log +import com.idormy.sms.forwarder.utils.HttpServerUtils +import com.yanzhenjie.andserver.annotation.Interceptor +import com.yanzhenjie.andserver.error.HttpException +import com.yanzhenjie.andserver.framework.HandlerInterceptor +import com.yanzhenjie.andserver.framework.handler.MethodHandler +import com.yanzhenjie.andserver.framework.handler.RequestHandler +import com.yanzhenjie.andserver.http.HttpMethod +import com.yanzhenjie.andserver.http.HttpRequest +import com.yanzhenjie.andserver.http.HttpResponse + +@Suppress("PrivatePropertyName") +@Interceptor +class LoggerInterceptor : HandlerInterceptor { + + private val TAG: String = "LoggerInterceptor" + + override fun onIntercept( + request: HttpRequest, + respons: HttpResponse, + handler: RequestHandler, + ): Boolean { + if (handler is MethodHandler) { + val httpPath = request.path + val method: HttpMethod = request.method + val valueMap = request.parameter + Log.i(TAG, "Path: $httpPath") + Log.i(TAG, "Method: " + method.value()) + Log.i(TAG, "Param: $valueMap") + + //判断是否开启该功能 + if ( + (httpPath.startsWith("/clone") && !HttpServerUtils.enableApiClone) + || (httpPath.startsWith("/sms/send") && !HttpServerUtils.enableApiSmsSend) + || (httpPath.startsWith("/sms/query") && !HttpServerUtils.enableApiSmsQuery) + || (httpPath.startsWith("/call/query") && !HttpServerUtils.enableApiCallQuery) + || (httpPath.startsWith("/contact/query") && !HttpServerUtils.enableApiContactQuery) + || (httpPath.startsWith("/battery/query") && !HttpServerUtils.enableApiBatteryQuery) + ) { + throw HttpException(500, "服务端未开启此功能") + } + + /* + //TODO:这里读取body会导致 MessageConverter 报错:RequestBody is missing. + val body = request.body?.string() + Log.i(TAG, "Body: $body") + */ + } + return false + } +} \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/server/component/LoginInterceptor.kt b/app/src/main/java/com/idormy/sms/forwarder/server/component/LoginInterceptor.kt new file mode 100644 index 00000000..1a74bfa8 --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/server/component/LoginInterceptor.kt @@ -0,0 +1,57 @@ +package com.idormy.sms.forwarder.server.component + +import com.yanzhenjie.andserver.error.HttpException +import com.yanzhenjie.andserver.framework.HandlerInterceptor +import com.yanzhenjie.andserver.framework.handler.MethodHandler +import com.yanzhenjie.andserver.framework.handler.RequestHandler +import com.yanzhenjie.andserver.framework.mapping.Addition +import com.yanzhenjie.andserver.http.HttpRequest +import com.yanzhenjie.andserver.http.HttpResponse + +//@Interceptor +class LoginInterceptor : HandlerInterceptor { + override fun onIntercept( + request: HttpRequest, + response: HttpResponse, + handler: RequestHandler, + ): Boolean { + if (handler is MethodHandler) { + val methodHandler: MethodHandler = handler + val addition: Addition = methodHandler.addition + if (!isLogin(request, addition)) { + throw HttpException(401, "You are not logged in yet.") + } + } + return false + } + + private fun isNeedLogin(addition: Addition?): Boolean { + if (addition == null) { + return false + } + val stringType = addition.stringType + if (stringType.isEmpty()) { + return false + } + val booleanType = addition.booleanType + return if (booleanType.isEmpty()) { + false + } else stringType[0].equals("login", ignoreCase = true) && booleanType[0] + } + + private fun isLogin(request: HttpRequest, addition: Addition): Boolean { + if (isNeedLogin(addition)) { + val session = request.session + if (session != null) { + val o = session.getAttribute(LOGIN_ATTRIBUTE) + return o is Boolean && o + } + return false + } + return true + } + + companion object { + const val LOGIN_ATTRIBUTE = "USER.LOGIN.SIGN" + } +} \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/server/controller/BatteryController.kt b/app/src/main/java/com/idormy/sms/forwarder/server/controller/BatteryController.kt new file mode 100644 index 00000000..b382a1a8 --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/server/controller/BatteryController.kt @@ -0,0 +1,34 @@ +package com.idormy.sms.forwarder.server.controller + +import android.content.Intent +import android.content.IntentFilter +import android.util.Log +import com.idormy.sms.forwarder.App +import com.idormy.sms.forwarder.entity.BatteryInfo +import com.idormy.sms.forwarder.server.model.BaseRequest +import com.idormy.sms.forwarder.server.model.EmptyData +import com.idormy.sms.forwarder.utils.BatteryUtils +import com.yanzhenjie.andserver.annotation.PostMapping +import com.yanzhenjie.andserver.annotation.RequestBody +import com.yanzhenjie.andserver.annotation.RequestMapping +import com.yanzhenjie.andserver.annotation.RestController + +@Suppress("PrivatePropertyName") +@RestController +@RequestMapping(path = ["/battery"]) +class BatteryController { + + private val TAG: String = BatteryController::class.java.simpleName + + //远程查电量 + @PostMapping("/query") + fun query(@RequestBody bean: BaseRequest): BatteryInfo { + val cloneBean = bean.data + Log.d(TAG, cloneBean.toString()) + + val intentFilter = IntentFilter(Intent.ACTION_BATTERY_CHANGED) + val intent: Intent? = App.context.registerReceiver(null, intentFilter) + return BatteryUtils.getBatteryInfo(intent) + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/server/controller/CallController.kt b/app/src/main/java/com/idormy/sms/forwarder/server/controller/CallController.kt new file mode 100644 index 00000000..6d3de66e --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/server/controller/CallController.kt @@ -0,0 +1,28 @@ +package com.idormy.sms.forwarder.server.controller + +import com.idormy.sms.forwarder.entity.CallInfo +import com.idormy.sms.forwarder.server.model.BaseRequest +import com.idormy.sms.forwarder.server.model.CallQueryData +import com.idormy.sms.forwarder.utils.PhoneUtils +import com.yanzhenjie.andserver.annotation.PostMapping +import com.yanzhenjie.andserver.annotation.RequestBody +import com.yanzhenjie.andserver.annotation.RequestMapping +import com.yanzhenjie.andserver.annotation.RestController + +@Suppress("PrivatePropertyName") +@RestController +@RequestMapping(path = ["/call"]) +class CallController { + + //private val TAG: String = CallController::class.java.simpleName + + //远程查通话 + @PostMapping("/query") + fun query(@RequestBody bean: BaseRequest): List? { + val callQueryData = bean.data + val limit = callQueryData.pageSize + val offset = (callQueryData.pageNum - 1) * limit + return PhoneUtils.getCallInfoList(callQueryData.type, limit, offset, callQueryData.phoneNumber) + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/server/controller/CloneController.kt b/app/src/main/java/com/idormy/sms/forwarder/server/controller/CloneController.kt new file mode 100644 index 00000000..bac1ba57 --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/server/controller/CloneController.kt @@ -0,0 +1,41 @@ +package com.idormy.sms.forwarder.server.controller + +import android.util.Log +import com.idormy.sms.forwarder.entity.CloneInfo +import com.idormy.sms.forwarder.server.model.BaseRequest +import com.idormy.sms.forwarder.utils.HttpServerUtils +import com.yanzhenjie.andserver.annotation.PostMapping +import com.yanzhenjie.andserver.annotation.RequestBody +import com.yanzhenjie.andserver.annotation.RequestMapping +import com.yanzhenjie.andserver.annotation.RestController + +@Suppress("PrivatePropertyName") +@RestController +@RequestMapping(path = ["/clone"]) +class CloneController { + + private val TAG: String = CloneController::class.java.simpleName + + //客户端从服务端拉取克隆信息 + @PostMapping("/pull") + fun pull(@RequestBody bean: BaseRequest): CloneInfo { + val cloneBean = bean.data + Log.d(TAG, cloneBean.toString()) + HttpServerUtils.compareVersion(cloneBean) + + val cloneInfo = HttpServerUtils.exportSettings() + Log.d(TAG, cloneInfo.toString()) + return cloneInfo + } + + //客户端向服务端推送克隆信息 + @PostMapping("/push") + fun push(@RequestBody bean: BaseRequest): String { + val cloneInfo = bean.data + Log.d(TAG, cloneInfo.toString()) + HttpServerUtils.compareVersion(cloneInfo) + + return if (HttpServerUtils.restoreSettings(cloneInfo)) "success" else "还原失败" + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/server/controller/ConfigController.kt b/app/src/main/java/com/idormy/sms/forwarder/server/controller/ConfigController.kt new file mode 100644 index 00000000..ecedbb50 --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/server/controller/ConfigController.kt @@ -0,0 +1,27 @@ +package com.idormy.sms.forwarder.server.controller + +import com.idormy.sms.forwarder.server.model.BaseRequest +import com.idormy.sms.forwarder.server.model.ConfigData +import com.idormy.sms.forwarder.utils.HttpServerUtils +import com.yanzhenjie.andserver.annotation.PostMapping +import com.yanzhenjie.andserver.annotation.RequestBody +import com.yanzhenjie.andserver.annotation.RequestMapping +import com.yanzhenjie.andserver.annotation.RestController + +@RestController +@RequestMapping(path = ["/config"]) +class ConfigController { + + @PostMapping("/query") + fun test(@RequestBody bean: BaseRequest<*>): ConfigData { + return ConfigData( + HttpServerUtils.enableApiClone, + HttpServerUtils.enableApiSmsSend, + HttpServerUtils.enableApiSmsQuery, + HttpServerUtils.enableApiCallQuery, + HttpServerUtils.enableApiContactQuery, + HttpServerUtils.enableApiBatteryQuery, + ) + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/server/controller/ContactController.kt b/app/src/main/java/com/idormy/sms/forwarder/server/controller/ContactController.kt new file mode 100644 index 00000000..acf5d7eb --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/server/controller/ContactController.kt @@ -0,0 +1,26 @@ +package com.idormy.sms.forwarder.server.controller + +import com.idormy.sms.forwarder.entity.ContactInfo +import com.idormy.sms.forwarder.server.model.BaseRequest +import com.idormy.sms.forwarder.server.model.ContactQueryData +import com.idormy.sms.forwarder.utils.PhoneUtils +import com.yanzhenjie.andserver.annotation.PostMapping +import com.yanzhenjie.andserver.annotation.RequestBody +import com.yanzhenjie.andserver.annotation.RequestMapping +import com.yanzhenjie.andserver.annotation.RestController + +@Suppress("PrivatePropertyName") +@RestController +@RequestMapping(path = ["/contact"]) +class ContactController { + + //远程查话簿 + @PostMapping("/query") + fun query(@RequestBody bean: BaseRequest): MutableList? { + val callQueryData = bean.data + val limit = callQueryData.pageSize + val offset = (callQueryData.pageNum - 1) * limit + return PhoneUtils.getContactInfoList(limit, offset, callQueryData.phoneNumber, callQueryData.name) + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/server/controller/SmsController.kt b/app/src/main/java/com/idormy/sms/forwarder/server/controller/SmsController.kt new file mode 100644 index 00000000..28314238 --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/server/controller/SmsController.kt @@ -0,0 +1,60 @@ +package com.idormy.sms.forwarder.server.controller + +import android.Manifest +import android.content.pm.PackageManager +import android.util.Log +import androidx.core.app.ActivityCompat +import com.idormy.sms.forwarder.App +import com.idormy.sms.forwarder.R +import com.idormy.sms.forwarder.entity.SmsInfo +import com.idormy.sms.forwarder.server.model.BaseRequest +import com.idormy.sms.forwarder.server.model.SmsQueryData +import com.idormy.sms.forwarder.server.model.SmsSendData +import com.idormy.sms.forwarder.utils.PhoneUtils +import com.xuexiang.xui.utils.ResUtils +import com.xuexiang.xutil.XUtil +import com.yanzhenjie.andserver.annotation.PostMapping +import com.yanzhenjie.andserver.annotation.RequestBody +import com.yanzhenjie.andserver.annotation.RequestMapping +import com.yanzhenjie.andserver.annotation.RestController + +@Suppress("PrivatePropertyName") +@RestController +@RequestMapping(path = ["/sms"]) +class SmsController { + + private val TAG: String = SmsController::class.java.simpleName + + //发送短信 + @PostMapping("/send") + fun send(@RequestBody bean: BaseRequest): String { + val smsSendData = bean.data + Log.d(TAG, smsSendData.toString()) + + //获取卡槽信息 + if (App.SimInfoList.isEmpty()) { + App.SimInfoList = PhoneUtils.getSimMultiInfo() + } + Log.d(TAG, App.SimInfoList.toString()) + + //发送卡槽: 1=SIM1, 2=SIM2 + val simSlotIndex = smsSendData.simSlot - 1 + //TODO:取不到卡槽信息时,采用默认卡槽发送 + val mSubscriptionId: Int = App.SimInfoList[simSlotIndex]?.mSubscriptionId ?: -1 + + if (ActivityCompat.checkSelfPermission(XUtil.getContext(), Manifest.permission.SEND_SMS) != PackageManager.PERMISSION_GRANTED) { + return ResUtils.getString(R.string.no_sms_sending_permission) + } + + return PhoneUtils.sendSms(mSubscriptionId, smsSendData.phoneNumbers, smsSendData.msgContent) ?: "success" + } + + //查询短信 + @PostMapping("/query") + fun query(@RequestBody bean: BaseRequest): List? { + val smsQueryData = bean.data + val limit = smsQueryData.pageSize + val offset = (smsQueryData.pageNum - 1) * limit + return PhoneUtils.getSmsList(smsQueryData.type, limit, offset, smsQueryData.keyword) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/server/model/BaseRequest.kt b/app/src/main/java/com/idormy/sms/forwarder/server/model/BaseRequest.kt new file mode 100644 index 00000000..6ff4124c --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/server/model/BaseRequest.kt @@ -0,0 +1,7 @@ +package com.idormy.sms.forwarder.server.model + +data class BaseRequest( + var data: T, + var timestamp: Long = 0L, + var sign: String? = "", +) \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/server/model/BaseResponse.kt b/app/src/main/java/com/idormy/sms/forwarder/server/model/BaseResponse.kt new file mode 100644 index 00000000..72aff78e --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/server/model/BaseResponse.kt @@ -0,0 +1,9 @@ +package com.idormy.sms.forwarder.server.model + +data class BaseResponse( + var code: Int = 200, + var msg: String = "", + var data: T?, + var timestamp: Long = 0L, + var sign: String? = "", +) \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/server/model/CallQueryData.kt b/app/src/main/java/com/idormy/sms/forwarder/server/model/CallQueryData.kt new file mode 100644 index 00000000..ea8ab65d --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/server/model/CallQueryData.kt @@ -0,0 +1,16 @@ +package com.idormy.sms.forwarder.server.model + +import com.google.gson.annotations.SerializedName +import java.io.Serializable + +data class CallQueryData( + // 短信类型: 1=接收, 2=发送 + @SerializedName("type") + var type: Int = 1, + @SerializedName("page_num") + var pageNum: Int = 1, + @SerializedName("page_size") + var pageSize: Int = 10, + @SerializedName("phone_number") + var phoneNumber: String? = "", +) : Serializable \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/server/model/ConfigData.kt b/app/src/main/java/com/idormy/sms/forwarder/server/model/ConfigData.kt new file mode 100644 index 00000000..935f9e1a --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/server/model/ConfigData.kt @@ -0,0 +1,19 @@ +package com.idormy.sms.forwarder.server.model + +import com.google.gson.annotations.SerializedName +import java.io.Serializable + +data class ConfigData( + @SerializedName("enable_api_clone") + var enableApiClone: Boolean = false, + @SerializedName("enable_api_sms_send") + var enableApiSmsSend: Boolean = false, + @SerializedName("enable_api_sms_query") + var enableApiSmsQuery: Boolean = false, + @SerializedName("enable_api_call_query") + var enableApiCallQuery: Boolean = false, + @SerializedName("enable_api_contact_query") + var enableApiContactQuery: Boolean = false, + @SerializedName("enable_api_battery_query") + var enableApiBatteryQuery: Boolean = false, +) : Serializable \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/server/model/ContactQueryData.kt b/app/src/main/java/com/idormy/sms/forwarder/server/model/ContactQueryData.kt new file mode 100644 index 00000000..0d21a724 --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/server/model/ContactQueryData.kt @@ -0,0 +1,15 @@ +package com.idormy.sms.forwarder.server.model + +import com.google.gson.annotations.SerializedName +import java.io.Serializable + +data class ContactQueryData( + @SerializedName("page_num") + var pageNum: Int = 1, + @SerializedName("page_size") + var pageSize: Int = 10, + @SerializedName("phone_number") + var phoneNumber: String? = "", + @SerializedName("name") + var name: String? = "", +) : Serializable \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/server/model/EmptyData.kt b/app/src/main/java/com/idormy/sms/forwarder/server/model/EmptyData.kt new file mode 100644 index 00000000..8ab3f5d2 --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/server/model/EmptyData.kt @@ -0,0 +1,9 @@ +package com.idormy.sms.forwarder.server.model + +import com.google.gson.annotations.SerializedName +import java.io.Serializable + +data class EmptyData( + @SerializedName("version_code") + var versionCode: Long = 100038L, +) : Serializable diff --git a/app/src/main/java/com/idormy/sms/forwarder/server/model/SmsQueryData.kt b/app/src/main/java/com/idormy/sms/forwarder/server/model/SmsQueryData.kt new file mode 100644 index 00000000..a33cf305 --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/server/model/SmsQueryData.kt @@ -0,0 +1,14 @@ +package com.idormy.sms.forwarder.server.model + +import com.google.gson.annotations.SerializedName +import java.io.Serializable + +data class SmsQueryData( + // 短信类型: 1=接收, 2=发送 + var type: Int = 1, + @SerializedName("page_num") + var pageNum: Int = 1, + @SerializedName("page_size") + var pageSize: Int = 10, + var keyword: String = "", +) : Serializable \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/server/model/SmsSendData.kt b/app/src/main/java/com/idormy/sms/forwarder/server/model/SmsSendData.kt new file mode 100644 index 00000000..6b74f9f3 --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/server/model/SmsSendData.kt @@ -0,0 +1,13 @@ +package com.idormy.sms.forwarder.server.model + +import com.google.gson.annotations.SerializedName +import java.io.Serializable + +data class SmsSendData( + @SerializedName("sim_slot") + var simSlot: Int, + @SerializedName("phone_numbers") + var phoneNumbers: String, + @SerializedName("msg_content") + var msgContent: String, +) : Serializable \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/service/BatteryService.java b/app/src/main/java/com/idormy/sms/forwarder/service/BatteryService.java deleted file mode 100644 index 5f4a0117..00000000 --- a/app/src/main/java/com/idormy/sms/forwarder/service/BatteryService.java +++ /dev/null @@ -1,125 +0,0 @@ -package com.idormy.sms.forwarder.service; - -import android.annotation.SuppressLint; -import android.app.Service; -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.os.IBinder; -import android.util.Log; - -import com.idormy.sms.forwarder.MyApplication; -import com.idormy.sms.forwarder.model.vo.SmsVo; -import com.idormy.sms.forwarder.sender.SendUtil; -import com.idormy.sms.forwarder.utils.BatteryUtils; -import com.idormy.sms.forwarder.utils.SettingUtils; - -import java.util.Date; - -@SuppressWarnings("deprecation") -public class BatteryService extends Service { - - private static final String TAG = "BatteryReceiver"; - - @Override - public IBinder onBind(Intent intent) { - return null; - } - - @Override - public void onCreate() { - super.onCreate(); - Log.i(TAG, "onCreate--------------"); - - //是否同意隐私协议 - if (!MyApplication.allowPrivacyPolicy) return; - - IntentFilter batteryfilter = new IntentFilter(); - batteryfilter.addAction(Intent.ACTION_BATTERY_CHANGED); - registerReceiver(batteryReceiver, batteryfilter); - } - - @Override - public void onStart(Intent intent, int startId) { - super.onStart(intent, startId); - } - - @Override - public int onStartCommand(Intent intent, int flags, int startId) { - Log.i(TAG, "onStartCommand--------------"); - return Service.START_STICKY; // - } - - @Override - public void onDestroy() { - Log.i(TAG, "onDestroy--------------"); - super.onDestroy(); - - //是否同意隐私协议 - if (!MyApplication.allowPrivacyPolicy) return; - - this.unregisterReceiver(batteryReceiver); - } - - // 接收电池信息更新的广播 - private final BroadcastReceiver batteryReceiver = new BroadcastReceiver() { - @SuppressLint("DefaultLocale") - @Override - public void onReceive(Context context, Intent intent) { - - //电量发生变化 - int levelCur = intent.getIntExtra("level", 0); - int levelPre = SettingUtils.getBatteryLevelCurrent(); - if (levelCur != levelPre) { - String msg = BatteryUtils.getBatteryInfo(intent); - SettingUtils.setBatteryLevelCurrent(levelCur); - - int levelMin = SettingUtils.getBatteryLevelAlarmMin(); - int levelMax = SettingUtils.getBatteryLevelAlarmMax(); - if (SettingUtils.getBatteryLevelAlarmOnce() && levelMin > 0 && levelPre > levelCur && levelCur <= levelMin) { //电量下降到下限 - msg = "【电量预警】已低于电量预警下限,请及时充电!" + msg; - sendMessage(context, msg); - return; - } else if (SettingUtils.getBatteryLevelAlarmOnce() && levelMax > 0 && levelPre < levelCur && levelCur >= levelMax) { //电量上升到上限 - msg = "【电量预警】已高于电量预警上限,请拔掉充电器!" + msg; - sendMessage(context, msg); - return; - } else if (!SettingUtils.getBatteryLevelAlarmOnce() && levelMin > 0 && levelPre > levelCur && levelCur == levelMin) { //电量下降到下限 - msg = "【电量预警】已到达电量预警下限,请及时充电!" + msg; - sendMessage(context, msg); - return; - } else if (!SettingUtils.getBatteryLevelAlarmOnce() && levelMax > 0 && levelPre < levelCur && levelCur == levelMax) { //电量上升到上限 - msg = "【电量预警】已到达电量预警上限,请拔掉充电器!" + msg; - sendMessage(context, msg); - return; - } - } - - //充电状态改变 - int status = intent.getIntExtra("status", 0); - if (SettingUtils.getSwitchEnableBatteryReceiver()) { - int oldStatus = SettingUtils.getBatteryStatus(); - if (status != oldStatus) { - String msg = BatteryUtils.getBatteryInfo(intent); - SettingUtils.setBatteryStatus(status); - msg = "【充电状态】发生变化:" + BatteryUtils.getStatus(oldStatus) + " → " + BatteryUtils.getStatus(status) + msg; - sendMessage(context, msg); - } - } - } - }; - - //发送信息 - private void sendMessage(Context context, String msg) { - Log.i(TAG, msg); - try { - SmsVo smsVo = new SmsVo("88888888", msg, new Date(), "电池状态监听"); - Log.d(TAG, "send_msg" + smsVo); - SendUtil.send_msg(context, smsVo, 1, "app"); - } catch (Exception e) { - Log.e(TAG, "getLog e:" + e.getMessage()); - } - } - -} diff --git a/app/src/main/java/com/idormy/sms/forwarder/service/BatteryService.kt b/app/src/main/java/com/idormy/sms/forwarder/service/BatteryService.kt new file mode 100644 index 00000000..451aa8fd --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/service/BatteryService.kt @@ -0,0 +1,122 @@ +package com.idormy.sms.forwarder.service + +import android.annotation.SuppressLint +import android.app.Service +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.content.IntentFilter +import android.os.IBinder +import android.util.Log +import androidx.work.OneTimeWorkRequestBuilder +import androidx.work.WorkManager +import androidx.work.workDataOf +import com.google.gson.Gson +import com.idormy.sms.forwarder.entity.MsgInfo +import com.idormy.sms.forwarder.utils.BatteryUtils +import com.idormy.sms.forwarder.utils.SettingUtils +import com.idormy.sms.forwarder.utils.Worker +import com.idormy.sms.forwarder.workers.SendWorker +import java.util.* + +@Suppress("DEPRECATION") +class BatteryService : Service() { + + override fun onBind(intent: Intent): IBinder? { + return null + } + + override fun onCreate() { + super.onCreate() + Log.i(TAG, "onCreate--------------") + + //是否同意隐私协议 + //if (!MyApplication.allowPrivacyPolicy) return + + val batteryFilter = IntentFilter() + batteryFilter.addAction(Intent.ACTION_BATTERY_CHANGED) + registerReceiver(batteryReceiver, batteryFilter) + } + + override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int { + Log.i(TAG, "onStartCommand--------------") + return START_STICKY // + } + + override fun onDestroy() { + Log.i(TAG, "onDestroy--------------") + super.onDestroy() + + //是否同意隐私协议 + //if (!MyApplication.allowPrivacyPolicy) return + unregisterReceiver(batteryReceiver) + } + + // 接收电池信息更新的广播 + private val batteryReceiver: BroadcastReceiver = object : BroadcastReceiver() { + @SuppressLint("DefaultLocale") + override fun onReceive(context: Context, intent: Intent) { + + //电量发生变化 + val levelCur: Int = intent.getIntExtra("level", 0) + val levelPre: Int = SettingUtils.batteryLevelCurrent + if (levelCur != levelPre) { + var msg: String = BatteryUtils.getBatteryInfo(intent).toString() + SettingUtils.batteryLevelCurrent = levelCur + val levelMin: Int = SettingUtils.batteryLevelMin + val levelMax: Int = SettingUtils.batteryLevelMax + if (SettingUtils.batteryLevelOnce && levelMin > 0 && levelPre > levelCur && levelCur <= levelMin) { //电量下降到下限 + msg = "【电量预警】已低于电量预警下限,请及时充电!$msg" + sendMessage(context, msg) + return + } else if (SettingUtils.batteryLevelOnce && levelMax > 0 && levelPre < levelCur && levelCur >= levelMax) { //电量上升到上限 + msg = "【电量预警】已高于电量预警上限,请拔掉充电器!$msg" + sendMessage(context, msg) + return + } else if (!SettingUtils.batteryLevelOnce && levelMin > 0 && levelPre > levelCur && levelCur == levelMin) { //电量下降到下限 + msg = "【电量预警】已到达电量预警下限,请及时充电!$msg" + sendMessage(context, msg) + return + } else if (!SettingUtils.batteryLevelOnce && levelMax > 0 && levelPre < levelCur && levelCur == levelMax) { //电量上升到上限 + msg = "【电量预警】已到达电量预警上限,请拔掉充电器!$msg" + sendMessage(context, msg) + return + } + } + + //充电状态改变 + val status: Int = intent.getIntExtra("status", 0) + if (SettingUtils.enableBatteryReceiver) { + val oldStatus: Int = SettingUtils.batteryStatus + if (status != oldStatus) { + var msg: String = BatteryUtils.getBatteryInfo(intent).toString() + SettingUtils.batteryStatus = status + msg = "【充电状态】发生变化:" + BatteryUtils.getStatus(oldStatus) + " → " + BatteryUtils.getStatus(status) + msg + sendMessage(context, msg) + } + } + } + } + + //发送信息 + private fun sendMessage(context: Context, msg: String) { + Log.i(TAG, msg) + try { + val msgInfo = MsgInfo("app", "88888888", msg, Date(), "电池状态监听", -1) + val request = OneTimeWorkRequestBuilder() + .setInputData( + workDataOf( + Worker.sendMsgInfo to Gson().toJson(msgInfo), + ) + ) + .build() + WorkManager.getInstance(context).enqueue(request) + } catch (e: Exception) { + Log.e(TAG, "getLog e:" + e.message) + } + } + + companion object { + private const val TAG = "BatteryReceiver" + } +} \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/service/ForegroundService.kt b/app/src/main/java/com/idormy/sms/forwarder/service/ForegroundService.kt new file mode 100644 index 00000000..df871296 --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/service/ForegroundService.kt @@ -0,0 +1,151 @@ +package com.idormy.sms.forwarder.service + +import android.app.* +import android.content.Intent +import android.graphics.BitmapFactory +import android.graphics.Color +import android.os.Build +import android.os.IBinder +import android.text.TextUtils +import android.util.Log +import androidx.core.app.NotificationCompat +import androidx.lifecycle.Observer +import com.idormy.sms.forwarder.App +import com.idormy.sms.forwarder.R +import com.idormy.sms.forwarder.activity.MainActivity +import com.idormy.sms.forwarder.database.AppDatabase +import com.idormy.sms.forwarder.utils.* +import com.jeremyliao.liveeventbus.LiveEventBus +import frpclib.Frpclib +import io.reactivex.Single +import io.reactivex.SingleObserver +import io.reactivex.android.schedulers.AndroidSchedulers +import io.reactivex.disposables.CompositeDisposable +import io.reactivex.disposables.Disposable +import io.reactivex.schedulers.Schedulers +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.async + +@Suppress("PrivatePropertyName", "DeferredResultUnused", "OPT_IN_USAGE") +class ForegroundService : Service() { + private val TAG: String = "ForegroundService" + private val compositeDisposable = CompositeDisposable() + private val frpcObserver = Observer { uid: String -> + if (Frpclib.isRunning(uid)) { + return@Observer + } + AppDatabase.getInstance(App.context) + .frpcDao() + .get(uid) + .flatMap { (uid1, _, config) -> + val error = Frpclib.runContent(uid1, config) + Single.just(error) + } + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(object : SingleObserver { + override fun onSubscribe(d: Disposable) { + compositeDisposable.add(d) + } + + override fun onError(e: Throwable) { + e.printStackTrace() + LiveEventBus.get(EVENT_FRPC_RUNNING_ERROR, String::class.java).post(uid) + } + + override fun onSuccess(msg: String) { + if (!TextUtils.isEmpty(msg)) { + Log.e(TAG, msg) + LiveEventBus.get(EVENT_FRPC_RUNNING_ERROR, String::class.java).post(uid) + } else { + LiveEventBus.get(EVENT_FRPC_RUNNING_SUCCESS, String::class.java).post(uid) + } + } + }) + } + private var notificationManager: NotificationManager? = null + + companion object { + var isRunning = false + } + + override fun onCreate() { + super.onCreate() + notificationManager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager + startForeground(FRONT_NOTIFY_ID, createForegroundNotification()) + isRunning = true + + //开关通知监听服务 + if (SettingUtils.enableAppNotify && CommonUtils.isNotificationListenerServiceEnabled(this)) { + CommonUtils.toggleNotificationListenerService(this) + } + + //监听Frpc启动指令 + LiveEventBus.get(INTENT_FRPC_APPLY_FILE, String::class.java).observeStickyForever(frpcObserver) + //自启动的Frpc + GlobalScope.async(Dispatchers.IO) { + val frpcList = AppDatabase.getInstance(App.context).frpcDao().getAutorun() + + if (frpcList.isEmpty()) { + Log.d(TAG, "没有自启动的Frpc") + return@async + } + + for (frpc in frpcList) { + val error = Frpclib.runContent(frpc.uid, frpc.config) + if (!TextUtils.isEmpty(error)) { + Log.e(TAG, error) + } + } + } + + } + + override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int { + isRunning = true + return START_STICKY + } + + override fun onDestroy() { + stopForeground(true) + compositeDisposable.dispose() + isRunning = false + super.onDestroy() + } + + override fun onBind(intent: Intent): IBinder? { + return null + } + + private fun createForegroundNotification(): Notification { + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + val importance = NotificationManager.IMPORTANCE_HIGH + val notificationChannel = NotificationChannel(FRONT_CHANNEL_ID, FRONT_CHANNEL_NAME, importance) + notificationChannel.description = "Frpc Foreground Service" + notificationChannel.enableLights(true) + notificationChannel.lightColor = Color.GREEN + notificationChannel.vibrationPattern = longArrayOf(0, 1000, 500, 1000) + notificationChannel.enableVibration(true) + if (notificationManager != null) { + notificationManager!!.createNotificationChannel(notificationChannel) + } + } + val builder = NotificationCompat.Builder(this, FRONT_CHANNEL_ID) + builder.setSmallIcon(R.drawable.ic_forwarder) + builder.setLargeIcon(BitmapFactory.decodeResource(resources, R.drawable.ic_menu_frpc)) + // TODO: 部分机型标题会重复待排除 + // if (DeviceUtils.getDeviceBrand().contains("Xiaomi")) { + builder.setContentTitle(getString(R.string.app_name)) + //} + builder.setContentText(SettingUtils.notifyContent.toString()) + builder.setWhen(System.currentTimeMillis()) + val activityIntent = Intent(this, MainActivity::class.java) + val flags = if (Build.VERSION.SDK_INT >= 30) PendingIntent.FLAG_IMMUTABLE else PendingIntent.FLAG_UPDATE_CURRENT + val pendingIntent = PendingIntent.getActivity(this, 0, activityIntent, flags) + builder.setContentIntent(pendingIntent) + return builder.build() + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/service/FrontService.java b/app/src/main/java/com/idormy/sms/forwarder/service/FrontService.java deleted file mode 100644 index 3f2b3508..00000000 --- a/app/src/main/java/com/idormy/sms/forwarder/service/FrontService.java +++ /dev/null @@ -1,177 +0,0 @@ -package com.idormy.sms.forwarder.service; - -import android.annotation.SuppressLint; -import android.app.Notification; -import android.app.NotificationChannel; -import android.app.NotificationManager; -import android.app.PendingIntent; -import android.app.Service; -import android.content.Intent; -import android.graphics.BitmapFactory; -import android.os.Build; -import android.os.IBinder; -import android.text.TextUtils; -import android.util.Log; -import android.widget.Toast; - -import androidx.annotation.Nullable; -import androidx.lifecycle.Observer; - -import com.idormy.sms.forwarder.MainActivity; -import com.idormy.sms.forwarder.MyApplication; -import com.idormy.sms.forwarder.R; -import com.idormy.sms.forwarder.database.AppDatabase; -import com.idormy.sms.forwarder.database.Config; -import com.idormy.sms.forwarder.utils.CommonUtils; -import com.idormy.sms.forwarder.utils.Define; -import com.idormy.sms.forwarder.utils.OSUtils; -import com.idormy.sms.forwarder.utils.PhoneUtils; -import com.idormy.sms.forwarder.utils.SettingUtils; -import com.jeremyliao.liveeventbus.LiveEventBus; - -import frpclib.Frpclib; -import io.reactivex.Single; -import io.reactivex.SingleObserver; -import io.reactivex.SingleSource; -import io.reactivex.android.schedulers.AndroidSchedulers; -import io.reactivex.disposables.CompositeDisposable; -import io.reactivex.disposables.Disposable; -import io.reactivex.functions.Function; -import io.reactivex.schedulers.Schedulers; - -public class FrontService extends Service { - private static final String TAG = "FrontService"; - public static final int NOTIFY_ID = 0x1010; - private static final String CHANNEL_ONE_ID = "com.idormy.sms.forwarder"; - private static final String CHANNEL_ONE_NAME = "com.idormy.sms.forwarderName"; - private static final String ACTION_NOTIFICATION_LISTENER_SETTINGS = "android.settings.ACTION_NOTIFICATION_LISTENER_SETTINGS"; - - //Frc相关 - public static final String INTENT_KEY_FILE = "INTENT_KEY_FILE"; - private final CompositeDisposable compositeDisposable = new CompositeDisposable(); - private final Observer keyObserver = uid -> { - if (Frpclib.isRunning(uid)) { - return; - } - - AppDatabase.getInstance(FrontService.this) - .configDao() - .getConfigByUid(uid) - .flatMap((Function>) config -> { - String error = Frpclib.runContent(config.getUid(), config.getCfg()); - return Single.just(error); - }) - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(new SingleObserver<>() { - - public void onSubscribe(Disposable d) { - compositeDisposable.add(d); - } - - @Override - public void onSuccess(String error) { - if (!TextUtils.isEmpty(error)) { - Toast.makeText(FrontService.this, error, Toast.LENGTH_SHORT).show(); - LiveEventBus.get(Define.EVENT_RUNNING_ERROR, String.class).post(uid); - } - } - - @Override - public void onError(Throwable e) { - - } - }); - }; - - @SuppressLint("IconColors") - @Override - public void onCreate() { - super.onCreate(); - Log.i(TAG, "onCreate"); - - //是否同意隐私协议 - if (!MyApplication.allowPrivacyPolicy) return; - - //开启通知栏 - startForeground(NOTIFY_ID, createNotification()); - - //Android8.1以下尝试启动主界面,以便动态获取权限 - if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.O_MR1) { - Intent intent = new Intent(this, MainActivity.class); - intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - startActivity(intent); - } - - //手机重启,未打开app时,主动获取SIM卡信息 - if (MyApplication.SimInfoList.isEmpty()) { - PhoneUtils.init(this); - MyApplication.SimInfoList = PhoneUtils.getSimMultiInfo(); - } - - //开关通知监听服务 - if (SettingUtils.getSwitchEnableAppNotify() && CommonUtils.isNotificationListenerServiceEnabled(this)) { - CommonUtils.toggleNotificationListenerService(this); - } - - //Frc内网穿透 - LiveEventBus.get(INTENT_KEY_FILE, String.class).observeStickyForever(keyObserver); - } - - @Override - public void onDestroy() { - super.onDestroy(); - - //是否同意隐私协议 - if (!MyApplication.allowPrivacyPolicy) return; - - //进行自动重启 - Intent intent = new Intent(FrontService.this, FrontService.class); - //重新开启服务 - startService(intent); - stopForeground(true); - - compositeDisposable.dispose(); - } - - @Override - public int onStartCommand(Intent intent, int flags, int startId) { - //return super.onStartCommand(intent, flags, startId); - return START_STICKY; //保证service不被杀死 - } - - @Nullable - @Override - public IBinder onBind(Intent intent) { - return null; - } - - //创建通知栏 - private Notification createNotification() { - Notification.Builder builder = new Notification.Builder(this); - builder.setSmallIcon(R.drawable.ic_forwarder); - builder.setLargeIcon(BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher)); - if (OSUtils.isMIUI()) { - builder.setContentTitle(getString(R.string.app_name)); - } - builder.setContentText(getString(R.string.notification_content)); - Intent intent = new Intent(this, MainActivity.class); - int flags = Build.VERSION.SDK_INT >= 30 ? PendingIntent.FLAG_IMMUTABLE : PendingIntent.FLAG_UPDATE_CURRENT; - PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, intent, flags); - builder.setContentIntent(pendingIntent); - - if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) { - //修改安卓8.1以上系统报错 - NotificationChannel notificationChannel = new NotificationChannel(CHANNEL_ONE_ID, CHANNEL_ONE_NAME, NotificationManager.IMPORTANCE_MIN); - notificationChannel.enableLights(false);//如果使用中的设备支持通知灯,则说明此通知通道是否应显示灯 - notificationChannel.setShowBadge(false);//是否显示角标 - notificationChannel.setLockscreenVisibility(Notification.VISIBILITY_SECRET); - NotificationManager manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE); - manager.createNotificationChannel(notificationChannel); - builder.setChannelId(CHANNEL_ONE_ID); - } - - return builder.build(); - } - -} \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/service/HttpService.kt b/app/src/main/java/com/idormy/sms/forwarder/service/HttpService.kt new file mode 100644 index 00000000..d27a895c --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/service/HttpService.kt @@ -0,0 +1,57 @@ +package com.idormy.sms.forwarder.service + +import android.app.Service +import android.content.Intent +import android.os.IBinder +import android.util.Log +import com.idormy.sms.forwarder.utils.HTTP_SERVER_PORT +import com.idormy.sms.forwarder.utils.HTTP_SERVER_TIME_OUT +import com.yanzhenjie.andserver.AndServer +import com.yanzhenjie.andserver.Server +import java.util.concurrent.TimeUnit + +@Suppress("PrivatePropertyName") +class HttpService : Service(), Server.ServerListener { + + private val TAG: String = "HttpService" + private val server by lazy { + AndServer.webServer(this) + .port(HTTP_SERVER_PORT) + .listener(this) + .timeout(HTTP_SERVER_TIME_OUT, TimeUnit.SECONDS) + .build() + } + + override fun onBind(p0: Intent?): IBinder? { + return null + } + + override fun onCreate() { + super.onCreate() + Log.i(TAG, "onCreate: ") + server.startup() + } + + override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { + Log.i(TAG, "onStartCommand: ") + return super.onStartCommand(intent, flags, startId) + } + + override fun onDestroy() { + super.onDestroy() + Log.i(TAG, "onDestroy: ") + server.shutdown() + } + + override fun onException(e: Exception?) { + Log.i(TAG, "onException: ") + } + + override fun onStarted() { + Log.i(TAG, "onStarted: ") + } + + override fun onStopped() { + Log.i(TAG, "onStopped: ") + } +} \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/service/MusicService.java b/app/src/main/java/com/idormy/sms/forwarder/service/MusicService.java deleted file mode 100644 index 1a88abf8..00000000 --- a/app/src/main/java/com/idormy/sms/forwarder/service/MusicService.java +++ /dev/null @@ -1,95 +0,0 @@ -package com.idormy.sms.forwarder.service; - -import android.app.Service; -import android.content.Intent; -import android.media.AudioManager; -import android.media.MediaPlayer; -import android.os.IBinder; -import android.util.Log; - -import com.idormy.sms.forwarder.R; - -public class MusicService extends Service { - - private final String TAG = "MusicService"; - - private MediaPlayer mMediaPlayer; - - private AudioManager mAudioManager; - - private final AudioManager.OnAudioFocusChangeListener mAudioFocusChange = new - AudioManager.OnAudioFocusChangeListener() { - @Override - public void onAudioFocusChange(int focusChange) { - switch (focusChange) { - case AudioManager.AUDIOFOCUS_GAIN: - Log.e(TAG, "AUDIOFOCUS_GAIN"); - try { - startPlayMusic(); - } catch (Exception e) { - e.printStackTrace(); - } - break; - case AudioManager.AUDIOFOCUS_LOSS: - Log.e(TAG, "AUDIOFOCUS_LOSS"); - mAudioManager.abandonAudioFocus(mAudioFocusChange); - break; - case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT: - Log.e(TAG, "AUDIOFOCUS_LOSS_TRANSIENT"); - break; - case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK: - Log.e(TAG, "AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK"); - break; - } - } - }; - - public MusicService() { - - } - - @Override - public IBinder onBind(Intent intent) { - // TODO: Return the communication channel to the service. - throw new UnsupportedOperationException("Not yet implemented"); - } - - @Override - public int onStartCommand(Intent intent, int flags, int startId) { - //音标处理 - mAudioManager = (AudioManager) getSystemService(AUDIO_SERVICE); - if (mAudioManager != null) - mAudioManager.requestAudioFocus(mAudioFocusChange, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN); - - mMediaPlayer = MediaPlayer.create(getApplicationContext(), R.raw.silence); - mMediaPlayer.setLooping(true); - - startPlayMusic(); - - return START_STICKY; - } - - - private void startPlayMusic() { - if (mMediaPlayer != null && !mMediaPlayer.isPlaying()) { - Log.e(TAG, "启动后台播放音乐"); - mMediaPlayer.start(); - } - } - - private void stopPlayMusic() { - if (mMediaPlayer != null) { - Log.e(TAG, "关闭后台播放音乐"); - mMediaPlayer.stop(); - } - } - - @Override - public void onDestroy() { - super.onDestroy(); - stopPlayMusic(); - Log.e(TAG, TAG + "---->onDestroy,停止服务"); - } - -} - diff --git a/app/src/main/java/com/idormy/sms/forwarder/service/NotifyService.java b/app/src/main/java/com/idormy/sms/forwarder/service/NotifyService.java deleted file mode 100644 index 0d505e80..00000000 --- a/app/src/main/java/com/idormy/sms/forwarder/service/NotifyService.java +++ /dev/null @@ -1,129 +0,0 @@ -package com.idormy.sms.forwarder.service; - -import android.content.ComponentName; -import android.os.Build; -import android.service.notification.NotificationListenerService; -import android.service.notification.StatusBarNotification; -import android.util.Log; - -import androidx.annotation.RequiresApi; - -import com.idormy.sms.forwarder.MyApplication; -import com.idormy.sms.forwarder.model.vo.SmsVo; -import com.idormy.sms.forwarder.sender.SendUtil; -import com.idormy.sms.forwarder.utils.CommonUtils; -import com.idormy.sms.forwarder.utils.SettingUtils; - -import java.text.SimpleDateFormat; -import java.util.Date; -import java.util.Locale; - -@RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR2) -public class NotifyService extends NotificationListenerService { - - public static final String TAG = "NotifyService"; - - /** - * 发布通知 - * - * @param sbn 状态栏通知 - */ - @RequiresApi(api = Build.VERSION_CODES.KITKAT) - @Override - public void onNotificationPosted(StatusBarNotification sbn) { - //是否同意隐私协议 - if (!MyApplication.allowPrivacyPolicy) return; - - //未开启转发 - if (!SettingUtils.getSwitchEnableAppNotify()) return; - //异常通知跳过 - if (sbn.getNotification() == null) return; - if (sbn.getNotification().extras == null) return; - - //仅锁屏状态转发APP通知 - if (SettingUtils.getSwitchNotUserPresent() && MyApplication.isUserPresent) return; - - //推送通知的应用包名 - String packageName = sbn.getPackageName(); - //自身通知跳过 - if ("com.idormy.sms.forwarder".equals(packageName)) return; - - try { - //通知标题 - String title = ""; - if (sbn.getNotification().extras.get("android.title") != null) { - title = sbn.getNotification().extras.get("android.title").toString(); - } - //通知内容 - String text = ""; - if (sbn.getNotification().extras.get("android.text") != null) { - text = sbn.getNotification().extras.get("android.text").toString(); - } - if (text.isEmpty() && sbn.getNotification().tickerText != null) { - text = sbn.getNotification().tickerText.toString(); - } - //不处理空消息(标题跟内容都为空) - if (title.isEmpty() && text.isEmpty()) return; - - //通知时间 - String time = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.CHINESE).format(new Date(sbn.getPostTime())); - Log.d(TAG, String.format( - Locale.getDefault(), - "onNotificationPosted:\n应用包名:%s\n消息标题:%s\n消息内容:%s\n消息时间:%s\n", - packageName, title, text, time) - ); - - //自动关闭通知 - if (SettingUtils.getSwitchCancelAppNotify()) { - String key = sbn.getKey(); - cancelNotification(key); - } - - //重复通知不再处理 - String prevHash = SettingUtils.getPrevNoticeHash(packageName); - String currHash = CommonUtils.MD5(packageName + title + text + time); - Log.d(TAG, "prevHash=" + prevHash + " currHash=" + currHash); - if (prevHash != null && prevHash.equals(currHash)) { - Log.w(TAG, "重复通知不再处理"); - return; - } - SettingUtils.setPrevNoticeHash(packageName, currHash); - - SmsVo smsVo = new SmsVo(packageName, text, new Date(), title); - Log.d(TAG, "send_msg" + smsVo); - SendUtil.send_msg(this, smsVo, 1, "app"); - } catch (Exception e) { - Log.e(TAG, "onNotificationPosted:", e); - } - - } - - /** - * 通知已删除 - * - * @param sbn 状态栏通知 - */ - @Override - public void onNotificationRemoved(StatusBarNotification sbn) { - //未开启转发 - if (!SettingUtils.getSwitchEnableAppNotify()) return; - //异常通知跳过 - if (sbn.getNotification() == null) return; - - Log.d(TAG, sbn.getPackageName()); - } - - /** - * 监听断开 - */ - @Override - public void onListenerDisconnected() { - //未开启转发 - if (!SettingUtils.getSwitchEnableAppNotify()) return; - - Log.d(TAG, "通知侦听器断开连接 - 请求重新绑定"); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { - requestRebind(new ComponentName(this, NotificationListenerService.class)); - } - } -} diff --git a/app/src/main/java/com/idormy/sms/forwarder/service/NotifyService.kt b/app/src/main/java/com/idormy/sms/forwarder/service/NotifyService.kt new file mode 100644 index 00000000..753ccea0 --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/service/NotifyService.kt @@ -0,0 +1,123 @@ +package com.idormy.sms.forwarder.service + +import android.content.ComponentName +import android.content.Context +import android.os.Build +import android.service.notification.NotificationListenerService +import android.service.notification.StatusBarNotification +import android.util.Log +import androidx.work.OneTimeWorkRequestBuilder +import androidx.work.WorkManager +import androidx.work.workDataOf +import com.google.gson.Gson +import com.idormy.sms.forwarder.entity.MsgInfo +import com.idormy.sms.forwarder.utils.PACKAGE_NAME +import com.idormy.sms.forwarder.utils.SettingUtils +import com.idormy.sms.forwarder.utils.Worker +import com.idormy.sms.forwarder.workers.SendWorker +import com.xuexiang.xrouter.utils.TextUtils +import com.xuexiang.xutil.display.ScreenUtils +import java.util.* + + +@Suppress("PrivatePropertyName", "DEPRECATION") +class NotifyService : NotificationListenerService()/*, LifecycleOwner*/ { + + private lateinit var context: Context + + /*private val mRegistry = LifecycleRegistry(this)*/ + private val TAG: String = "NotifyService" + + override fun onCreate() { + super.onCreate() + context = applicationContext + } + + override fun onListenerConnected() { + //super.onListenerConnected() + // Check if Notification Listener Permission is allowed + Log.d(TAG, "onListenerConnected") + } + + override fun onListenerDisconnected() { + //总开关 + if (!SettingUtils.enableAppNotify) return + + Log.d(TAG, "通知侦听器断开连接 - 请求重新绑定") + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + requestRebind(ComponentName(this, NotificationListenerService::class.java)) + } + } + + override fun onNotificationPosted(sbn: StatusBarNotification?) { + try { + //总开关 + if (!SettingUtils.enableAppNotify) return + + //异常通知跳过 + if (sbn!!.notification == null) return + if (sbn.notification.extras == null) return + + //仅锁屏状态转发APP通知 + if (SettingUtils.enableNotUserPresent && !ScreenUtils.isScreenLock()) return + + val from = sbn.packageName + //自身通知跳过 + if (PACKAGE_NAME == sbn.packageName) return + + //通知标题 + var title = "" + if (sbn.notification.extras["android.title"] != null) { + title = sbn.notification.extras["android.title"].toString() + } + //通知内容 + var text = "" + if (sbn.notification.extras["android.text"] != null) { + text = sbn.notification.extras["android.text"].toString() + } + if (text.isEmpty() && sbn.notification.tickerText != null) { + text = sbn.notification.tickerText.toString() + } + + //不处理空消息(标题跟内容都为空) + if (TextUtils.isEmpty(title) && TextUtils.isEmpty(text)) return + + val msgInfo = MsgInfo("app", from, text, Date(), title, -1) + val request = OneTimeWorkRequestBuilder() + .setInputData( + workDataOf( + Worker.sendMsgInfo to Gson().toJson(msgInfo), + //Worker.sendSbnId to sbn.id + ) + ) + .build() + WorkManager.getInstance(context).enqueue(request) + + //TODO:收不到返回信息,自动消除待解决 + /*WorkManager.getInstance(context).getWorkInfoByIdLiveData(request.id) + .observe(this) { info: WorkInfo? -> + Log.e(TAG, info.toString()) + if (info != null && info.state == WorkInfo.State.SUCCEEDED) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + cancelNotification(sbn.key) + } else { + cancelNotification(sbn.packageName, sbn.tag, sbn.id) + } + } + }*/ + + } catch (e: Exception) { + Log.e(TAG, "Parsing Notification failed: " + e.message.toString()) + } + + } + + override fun onNotificationRemoved(sbn: StatusBarNotification?) { + //super.onNotificationRemoved(sbn) + Log.d(TAG, "Removed Package Name : ${sbn?.packageName}") + } + + /*override fun getLifecycle(): Lifecycle { + return mRegistry + }*/ +} \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/utils/AnimationUtils.java b/app/src/main/java/com/idormy/sms/forwarder/utils/AnimationUtils.java deleted file mode 100644 index 851a36cf..00000000 --- a/app/src/main/java/com/idormy/sms/forwarder/utils/AnimationUtils.java +++ /dev/null @@ -1,224 +0,0 @@ -package com.idormy.sms.forwarder.utils; - -import android.view.View; -import android.view.animation.AlphaAnimation; -import android.view.animation.Animation; -import android.view.animation.Animation.AnimationListener; -import android.view.animation.LinearInterpolator; -import android.view.animation.TranslateAnimation; -import android.widget.TextView; - -/** - * 动画工具 - */ -public class AnimationUtils { - public enum AnimationState { - STATE_SHOW, - STATE_HIDDEN - } - - /** - * 渐隐渐现动画 - * - * @param viewGroup 需要实现动画的对象 - * @param state 需要实现的状态 - * @param duration 动画实现的时长(ms) - */ - public static void showAndHiddenCenterAnimation(final View viewGroup, AnimationState state, long duration) { - float start = 0f; - float end = 0f; - if (state == AnimationState.STATE_SHOW) { - end = 1.0f; - viewGroup.setVisibility(View.VISIBLE); - } else if (state == AnimationState.STATE_HIDDEN) { - start = 1.0f; - viewGroup.setVisibility(View.GONE); - } - AlphaAnimation animation = new AlphaAnimation(start, end); - animation.setDuration(duration); - animation.setFillAfter(true); - animation.setAnimationListener(new AnimationListener() { - - @Override - public void onAnimationStart(Animation animation) { - } - - @Override - public void onAnimationRepeat(Animation animation) { - } - - @Override - public void onAnimationEnd(Animation animation) { - viewGroup.clearAnimation(); - } - }); - viewGroup.setAnimation(animation); - animation.start(); - } - - public static void showAndHiddenCloseVideoAnimation(final View view, AnimationState state, long duration) { - float start = 0f; - float end = 0f; - if (state == AnimationState.STATE_SHOW) { - end = 1.0f; - view.setVisibility(View.VISIBLE); - } else if (state == AnimationState.STATE_HIDDEN) { - start = 1.0f; - view.setVisibility(View.GONE); - } - AlphaAnimation animation = new AlphaAnimation(start, end); - animation.setDuration(duration); - animation.setFillAfter(true); - animation.setAnimationListener(new AnimationListener() { - - @Override - public void onAnimationStart(Animation animation) { - } - - @Override - public void onAnimationRepeat(Animation animation) { - } - - @Override - public void onAnimationEnd(Animation animation) { - view.clearAnimation(); - } - }); - view.setAnimation(animation); - animation.start(); - } - - /** - * 移动动画 - * - * @param viewGroup 需要实现动画的对象 - * @param state 需要实现的状态 - * @param duration 动画实现的时长(ms) - */ - public static void showAndHiddenTopAnimation(final View viewGroup, AnimationState state, long duration, boolean isTop) { - float start = 0.0f; - float end = 0.0f; - if (state == AnimationState.STATE_SHOW) { - if (isTop) { - start = -1.0f; - } else { - start = 1.0f; - } - viewGroup.setVisibility(View.VISIBLE); - } else if (state == AnimationState.STATE_HIDDEN) { - if (isTop) { - end = -1.0f; - } else { - end = 1.0f; - } - viewGroup.setVisibility(View.GONE); - } - Animation translateAnimation; - translateAnimation = new TranslateAnimation(Animation.RELATIVE_TO_SELF, 0.0f, - Animation.RELATIVE_TO_SELF, 0.0f, Animation.RELATIVE_TO_SELF, - start, Animation.RELATIVE_TO_SELF, end); - translateAnimation.setDuration(duration); - translateAnimation.setFillEnabled(true);//使其可以填充效果从而不回到原地 - translateAnimation.setFillAfter(true);//不回到起始位置 - //如果不添加setFillEnabled和setFillAfter则动画执行结束后会自动回到远点 - translateAnimation.setAnimationListener(new AnimationListener() { - - @Override - public void onAnimationStart(Animation animation) { - } - - @Override - public void onAnimationRepeat(Animation animation) { - } - - @Override - public void onAnimationEnd(Animation animation) { - viewGroup.clearAnimation(); - } - }); - viewGroup.setAnimation(translateAnimation); - translateAnimation.start(); - } - - public static void liveGiftAnimation(final View viewGroup, AnimationState state, long duration) { - float start = 0.0f; - float end = 0.0f; - if (state == AnimationState.STATE_SHOW) { - start = -1.0f; - viewGroup.setVisibility(View.VISIBLE); - } else if (state == AnimationState.STATE_HIDDEN) { - end = -1.0f; - viewGroup.setVisibility(View.GONE); - } - Animation translateAnimation; - translateAnimation = new TranslateAnimation(Animation.RELATIVE_TO_SELF, start, - Animation.RELATIVE_TO_SELF, end, Animation.RELATIVE_TO_SELF, - 0.0f, Animation.RELATIVE_TO_SELF, 0.0f); - translateAnimation.setDuration(duration); - translateAnimation.setFillEnabled(true);//使其可以填充效果从而不回到原地 - translateAnimation.setFillAfter(true);//不回到起始位置 - //如果不添加setFillEnabled和setFillAfter则动画执行结束后会自动回到远点 - translateAnimation.setAnimationListener(new AnimationListener() { - - @Override - public void onAnimationStart(Animation animation) { - } - - @Override - public void onAnimationRepeat(Animation animation) { - } - - @Override - public void onAnimationEnd(Animation animation) { - viewGroup.clearAnimation(); - } - }); - viewGroup.setAnimation(translateAnimation); - translateAnimation.start(); - } - - //通知滚动 - public static void liveLotteryAnimation(final TextView viewGroup, AnimationState state, long duration) { - - float start = 0.0f; - float end = 0.0f; - if (state == AnimationState.STATE_SHOW) { - start = 1.0f; - end = -1.0f; - viewGroup.setVisibility(View.VISIBLE); - } else if (state == AnimationState.STATE_HIDDEN) { - end = -1.0f; - viewGroup.setVisibility(View.GONE); - } - Animation translateAnimation; - translateAnimation = new TranslateAnimation(Animation.RELATIVE_TO_PARENT, start, - Animation.RELATIVE_TO_PARENT, end, Animation.RELATIVE_TO_SELF, - 0.0f, Animation.RELATIVE_TO_SELF, 0.0f); - translateAnimation.setDuration(duration); - translateAnimation.setFillEnabled(true);//使其可以填充效果从而不回到原地 - translateAnimation.setFillAfter(true);//不回到起始位置 - translateAnimation.setInterpolator(new LinearInterpolator()); - if (viewGroup.getAnimation() != null) { - translateAnimation.setStartOffset(duration); - } - //如果不添加setFillEnabled和setFillAfter则动画执行结束后会自动回到远点 - translateAnimation.setAnimationListener(new AnimationListener() { - - @Override - public void onAnimationStart(Animation animation) { - } - - @Override - public void onAnimationRepeat(Animation animation) { - } - - @Override - public void onAnimationEnd(Animation animation) { - viewGroup.clearAnimation(); - viewGroup.setVisibility(View.GONE); - } - }); - viewGroup.setAnimation(translateAnimation); - translateAnimation.start(); - } -} diff --git a/app/src/main/java/com/idormy/sms/forwarder/utils/BatteryUtils.java b/app/src/main/java/com/idormy/sms/forwarder/utils/BatteryUtils.java deleted file mode 100644 index f2b6a480..00000000 --- a/app/src/main/java/com/idormy/sms/forwarder/utils/BatteryUtils.java +++ /dev/null @@ -1,94 +0,0 @@ -package com.idormy.sms.forwarder.utils; - -import android.annotation.SuppressLint; -import android.content.Intent; -import android.os.BatteryManager; -import android.util.Log; - -public class BatteryUtils { - private static final String TAG = "BatteryUtils"; - - @SuppressLint("DefaultLocale") - public static String getBatteryInfo(Intent intent) { - Log.i(TAG, "getBatteryInfo--------------"); - String action = intent.getAction(); - Log.i(TAG, " 0 action:" + action); - Log.i(TAG, "ACTION_BATTERY_CHANGED"); - int status = intent.getIntExtra("status", 0); - int health = intent.getIntExtra("health", 0); - //boolean present = intent.getBooleanExtra("present", false); - int levelCur = intent.getIntExtra("level", 0); - int scale = intent.getIntExtra("scale", 0); - //int icon_small = intent.getIntExtra("icon-small", 0); - int plugged = intent.getIntExtra("plugged", 0); - int voltage = intent.getIntExtra("voltage", 0); - int temperature = intent.getIntExtra("temperature", 0); - //String technology = intent.getStringExtra("technology"); - - String msg = ""; - msg += "\n剩余电量:" + levelCur + "%"; - - if (scale > 0) msg += "\n充满电量:" + scale + "%"; - - if (voltage > 0) msg += "\n当前电压:" + String.format("%.2f", voltage / 1000F) + "V"; - - if (temperature > 0) msg += "\n当前温度:" + String.format("%.2f", temperature / 10F) + "℃"; - - msg += "\n电池状态:" + getStatus(status); - - if (health > 0) msg += "\n健康度:" + getHealth(health); - - switch (plugged) { - case BatteryManager.BATTERY_PLUGGED_AC: - msg += "\n充电器:AC"; - break; - case BatteryManager.BATTERY_PLUGGED_USB: - msg += "\n充电器:USB"; - break; - case BatteryManager.BATTERY_PLUGGED_WIRELESS: - msg += "\n充电器:无线"; - break; - } - - Log.i(TAG, msg); - return msg; - } - - //电池状态 - public static String getStatus(int status) { - switch (status) { - case BatteryManager.BATTERY_STATUS_CHARGING: - return "充电中"; - case BatteryManager.BATTERY_STATUS_DISCHARGING: - return "放电中"; - case BatteryManager.BATTERY_STATUS_NOT_CHARGING: - return "未充电"; - case BatteryManager.BATTERY_STATUS_FULL: - return "充满电"; - case BatteryManager.BATTERY_STATUS_UNKNOWN: - default: - return "未知"; - } - } - - //健康度 - public static String getHealth(int health) { - switch (health) { - case 2: - return "良好"; - case 3: - return "过热"; - case 4: - return "没电"; - case 5: - return "过电压"; - case 6: - return "未知错误"; - case 7: - return "温度过低"; - default: - case 1: - return "未知"; - } - } -} diff --git a/app/src/main/java/com/idormy/sms/forwarder/utils/BatteryUtils.kt b/app/src/main/java/com/idormy/sms/forwarder/utils/BatteryUtils.kt new file mode 100644 index 00000000..59ca3f7e --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/utils/BatteryUtils.kt @@ -0,0 +1,61 @@ +package com.idormy.sms.forwarder.utils + +import android.content.Intent +import android.os.BatteryManager +import android.util.Log +import com.idormy.sms.forwarder.R +import com.idormy.sms.forwarder.entity.BatteryInfo +import com.xuexiang.xui.utils.ResUtils.getString + +object BatteryUtils { + private const val TAG = "BatteryUtils" + + fun getBatteryInfo(intent: Intent?): BatteryInfo { + val batteryInfo = BatteryInfo() + if (intent == null) return batteryInfo + + val level = intent.getIntExtra("level", 0) + val scale = intent.getIntExtra("scale", 0) + val voltage = intent.getIntExtra("voltage", 0) + val temperature = intent.getIntExtra("temperature", 0) + val status = intent.getIntExtra("status", 0) + val health = intent.getIntExtra("health", 0) + val plugged = intent.getIntExtra("plugged", 0) + + batteryInfo.level = "$level%" + if (scale > 0) batteryInfo.scale = "$scale%" + if (voltage > 0) batteryInfo.voltage = "${String.format("%.2f", voltage / 1000f)}V" + if (temperature > 0) batteryInfo.temperature = "${String.format("%.2f", temperature / 10f)}℃" + batteryInfo.status = getStatus(status) + batteryInfo.health = when (health) { + BatteryManager.BATTERY_HEALTH_UNKNOWN -> getString(R.string.battery_unknown) + BatteryManager.BATTERY_HEALTH_GOOD -> getString(R.string.battery_good) + BatteryManager.BATTERY_HEALTH_OVERHEAT -> getString(R.string.battery_overheat) + BatteryManager.BATTERY_HEALTH_DEAD -> getString(R.string.battery_dead) + BatteryManager.BATTERY_HEALTH_OVER_VOLTAGE -> getString(R.string.battery_over_voltage) + BatteryManager.BATTERY_HEALTH_UNSPECIFIED_FAILURE -> getString(R.string.battery_unspecified_failure) + BatteryManager.BATTERY_HEALTH_COLD -> getString(R.string.battery_cold) + else -> getString(R.string.battery_unknown) + } + batteryInfo.plugged = when (plugged) { + BatteryManager.BATTERY_PLUGGED_AC -> getString(R.string.battery_ac) + BatteryManager.BATTERY_PLUGGED_USB -> getString(R.string.battery_usb) + BatteryManager.BATTERY_PLUGGED_WIRELESS -> getString(R.string.battery_wireless) + else -> getString(R.string.battery_unknown) + } + Log.i(TAG, batteryInfo.toString()) + return batteryInfo + } + + fun getStatus(status: Int): String { + return when (status) { + BatteryManager.BATTERY_STATUS_CHARGING -> getString(R.string.battery_charging) + BatteryManager.BATTERY_STATUS_DISCHARGING -> getString(R.string.battery_discharging) + BatteryManager.BATTERY_STATUS_NOT_CHARGING -> getString(R.string.battery_not_charging) + BatteryManager.BATTERY_STATUS_FULL -> getString(R.string.battery_full) + BatteryManager.BATTERY_STATUS_UNKNOWN -> getString(R.string.battery_unknown) + else -> getString(R.string.battery_unknown) + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/utils/BuildProperties.java b/app/src/main/java/com/idormy/sms/forwarder/utils/BuildProperties.java deleted file mode 100644 index c250cb33..00000000 --- a/app/src/main/java/com/idormy/sms/forwarder/utils/BuildProperties.java +++ /dev/null @@ -1,68 +0,0 @@ -package com.idormy.sms.forwarder.utils; - -import android.os.Environment; - -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; -import java.util.Collection; -import java.util.Enumeration; -import java.util.Map; -import java.util.Properties; -import java.util.Set; - -@SuppressWarnings("rawtypes") -public class BuildProperties { - private static BuildProperties ourInstance; - private final Properties properties; - - private BuildProperties() throws IOException { - properties = new Properties(); - properties.load(new FileInputStream(new File(Environment.getRootDirectory(), "build.prop"))); - } - - public static BuildProperties getInstance() throws IOException { - if (ourInstance == null) ourInstance = new BuildProperties(); - return ourInstance; - } - - public boolean containsKey(final Object key) { - return properties.containsKey(key); - } - - public boolean containsValue(final Object value) { - return properties.containsValue(value); - } - - public String getProperty(final String name) { - return properties.getProperty(name); - } - - public String getProperty(final String name, final String defaultValue) { - return properties.getProperty(name, defaultValue); - } - - public Set> entrySet() { - return properties.entrySet(); - } - - public boolean isEmpty() { - return properties.isEmpty(); - } - - public Enumeration keys() { - return properties.keys(); - } - - public Set keySet() { - return properties.keySet(); - } - - public int size() { - return properties.size(); - } - - public Collection values() { - return properties.values(); - } -} \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/utils/CacheUtils.java b/app/src/main/java/com/idormy/sms/forwarder/utils/CacheUtils.java deleted file mode 100644 index be6e0b12..00000000 --- a/app/src/main/java/com/idormy/sms/forwarder/utils/CacheUtils.java +++ /dev/null @@ -1,106 +0,0 @@ -package com.idormy.sms.forwarder.utils; - -import android.content.Context; -import android.os.Environment; - -import java.io.File; -import java.math.BigDecimal; - -public class CacheUtils { - /** - * 获取缓存大小 - * - * @param context 上下文 - * @return 缓存大小 - */ - public static String getTotalCacheSize(Context context) { - long cacheSize = getFolderSize(context.getCacheDir()); - if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) { - cacheSize += getFolderSize(context.getExternalCacheDir()); - } - return getFormatSize(cacheSize); - } - - /*** - * 清理所有缓存 - * @param context 上下文 - */ - public static void clearAllCache(Context context) { - deleteDir(context.getCacheDir()); - if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) { - deleteDir(context.getExternalCacheDir()); - } - } - - private static boolean deleteDir(File dir) { - if (dir != null && dir.isDirectory()) { - String[] children = dir.list(); - assert children != null; - for (String child : children) { - boolean success = deleteDir(new File(dir, child)); - if (!success) { - return false; - } - } - } - assert dir != null; - return dir.delete(); - } - - // 获取文件 - //Context.getExternalFilesDir() --> SDCard/Android/data/你的应用的包名/files/ 目录,一般放一些长时间保存的数据 - //Context.getExternalCacheDir() --> SDCard/Android/data/你的应用包名/cache/目录,一般存放临时缓存数据 - public static long getFolderSize(File file) { - long size = 0; - try { - File[] fileList = file.listFiles(); - assert fileList != null; - for (File value : fileList) { - // 如果下面还有文件 - if (value.isDirectory()) { - size = size + getFolderSize(value); - } else { - size = size + value.length(); - } - } - } catch (Exception e) { - e.printStackTrace(); - } - return size; - } - - /** - * 格式化单位 - * - * @param size 文件大小 - * @return 结果 - */ - public static String getFormatSize(double size) { - double kiloByte = size / 1024; - if (kiloByte < 1) { - return "0KB"; - } - - double megaByte = kiloByte / 1024; - if (megaByte < 1) { - BigDecimal result1 = new BigDecimal(Double.toString(kiloByte)); - return result1.setScale(2, BigDecimal.ROUND_HALF_UP).toPlainString() + "KB"; - } - - double gigaByte = megaByte / 1024; - if (gigaByte < 1) { - BigDecimal result2 = new BigDecimal(Double.toString(megaByte)); - return result2.setScale(2, BigDecimal.ROUND_HALF_UP).toPlainString() + "MB"; - } - - double teraBytes = gigaByte / 1024; - if (teraBytes < 1) { - BigDecimal result3 = new BigDecimal(Double.toString(gigaByte)); - return result3.setScale(2, BigDecimal.ROUND_HALF_UP).toPlainString() + "GB"; - } - - BigDecimal result4 = new BigDecimal(teraBytes); - return result4.setScale(2, BigDecimal.ROUND_HALF_UP).toPlainString() + "TB"; - } - -} \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/utils/CacheUtils.kt b/app/src/main/java/com/idormy/sms/forwarder/utils/CacheUtils.kt new file mode 100644 index 00000000..7789cb53 --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/utils/CacheUtils.kt @@ -0,0 +1,105 @@ +package com.idormy.sms.forwarder.utils + +import android.content.Context +import android.os.Environment +import java.io.File +import java.math.BigDecimal + +class CacheUtils private constructor() { + companion object { + /** + * 获取缓存大小 + * + * @param context 上下文 + * @return 缓存大小 + */ + fun getTotalCacheSize(context: Context): String { + return try { + var cacheSize = getFolderSize(context.cacheDir) + if (Environment.getExternalStorageState() == Environment.MEDIA_MOUNTED) { + cacheSize += getFolderSize(context.externalCacheDir) + } + getFormatSize(cacheSize.toDouble()) + } catch (e: Exception) { + e.printStackTrace() + "0KB" + } + } + + /*** + * 清理所有缓存 + * @param context 上下文 + */ + fun clearAllCache(context: Context) { + deleteDir(context.cacheDir) + if (Environment.getExternalStorageState() == Environment.MEDIA_MOUNTED) { + deleteDir(context.externalCacheDir) + } + } + + private fun deleteDir(dir: File?): Boolean { + if (dir != null && dir.isDirectory) { + val children = dir.list()!! + for (child in children) { + val success = deleteDir(File(dir, child)) + if (!success) { + return false + } + } + } + assert(dir != null) + return dir!!.delete() + } + + // 获取文件 + //Context.getExternalFilesDir() --> SDCard/Android/data/你的应用的包名/files/ 目录,一般放一些长时间保存的数据 + //Context.getExternalCacheDir() --> SDCard/Android/data/你的应用包名/cache/目录,一般存放临时缓存数据 + private fun getFolderSize(file: File?): Long { + var size: Long = 0 + try { + val fileList = file!!.listFiles()!! + for (value in fileList) { + // 如果下面还有文件 + size = if (value.isDirectory) { + size + getFolderSize(value) + } else { + size + value.length() + } + } + } catch (e: Exception) { + e.printStackTrace() + } + return size + } + + /** + * 格式化单位 + * + * @param size 文件大小 + * @return 结果 + */ + private fun getFormatSize(size: Double): String { + val kiloByte = size / 1024 + if (kiloByte < 1) { + return "0KB" + } + val megaByte = kiloByte / 1024 + if (megaByte < 1) { + val result1 = BigDecimal(kiloByte.toString()) + return result1.setScale(2, BigDecimal.ROUND_HALF_UP).toPlainString() + "KB" + } + val gigaByte = megaByte / 1024 + if (gigaByte < 1) { + val result2 = BigDecimal(megaByte.toString()) + return result2.setScale(2, BigDecimal.ROUND_HALF_UP).toPlainString() + "MB" + } + val teraBytes = gigaByte / 1024 + if (teraBytes < 1) { + val result3 = BigDecimal(gigaByte.toString()) + return result3.setScale(2, BigDecimal.ROUND_HALF_UP).toPlainString() + "GB" + } + val result4 = BigDecimal(teraBytes) + return result4.setScale(2, BigDecimal.ROUND_HALF_UP).toPlainString() + "TB" + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/utils/CactusSave.kt b/app/src/main/java/com/idormy/sms/forwarder/utils/CactusSave.kt new file mode 100644 index 00000000..158565e2 --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/utils/CactusSave.kt @@ -0,0 +1,32 @@ +package com.idormy.sms.forwarder.utils + +object CactusSave { + //Cactus存活时间 + var timer: Long + get() = MMKVUtils.getLong(CACTUS_TIMER, 0L) + set(timer) { + MMKVUtils.put(CACTUS_TIMER, timer) + } + + //Cactus上次存活时间 + var lastTimer: Long + get() = MMKVUtils.getLong(CACTUS_LAST_TIMER, 0L) + set(timer) { + MMKVUtils.put(CACTUS_LAST_TIMER, timer) + } + + //Cactus运行时间 + var date: String? + get() = MMKVUtils.getString(SP_EXTRA_DEVICE_MARK, "0000-01-01 00:00:00") + set(extraDeviceMark) { + MMKVUtils.put(SP_EXTRA_DEVICE_MARK, extraDeviceMark) + } + + //Cactus结束时间 + var endDate: String? + get() = MMKVUtils.getString(CACTUS_DATE, "0000-01-01 00:00:00") + set(extraDeviceMark) { + MMKVUtils.put(CACTUS_END_DATE, extraDeviceMark) + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/utils/CertUtils.java b/app/src/main/java/com/idormy/sms/forwarder/utils/CertUtils.java deleted file mode 100644 index 878b2265..00000000 --- a/app/src/main/java/com/idormy/sms/forwarder/utils/CertUtils.java +++ /dev/null @@ -1,73 +0,0 @@ -package com.idormy.sms.forwarder.utils; - -import android.annotation.SuppressLint; - -import java.security.KeyStore; -import java.security.SecureRandom; -import java.security.cert.X509Certificate; -import java.util.Arrays; - -import javax.net.ssl.HostnameVerifier; -import javax.net.ssl.SSLContext; -import javax.net.ssl.SSLSocketFactory; -import javax.net.ssl.TrustManager; -import javax.net.ssl.TrustManagerFactory; -import javax.net.ssl.X509TrustManager; - -@SuppressLint("ALL") -public class CertUtils { - - //获取这个SSLSocketFactory - public static SSLSocketFactory getSSLSocketFactory() { - try { - SSLContext sslContext = SSLContext.getInstance("SSL"); - sslContext.init(null, getTrustManager(), new SecureRandom()); - return sslContext.getSocketFactory(); - } catch (Exception e) { - throw new RuntimeException(e); - } - } - - //获取TrustManager - private static TrustManager[] getTrustManager() { - return new TrustManager[]{ - new X509TrustManager() { - @Override - public void checkClientTrusted(X509Certificate[] chain, String authType) { - } - - @Override - public void checkServerTrusted(X509Certificate[] chain, String authType) { - } - - @Override - public X509Certificate[] getAcceptedIssuers() { - return new X509Certificate[]{}; - } - } - }; - } - - //获取HostnameVerifier - public static HostnameVerifier getHostnameVerifier() { - return (s, sslSession) -> true; - } - - public static X509TrustManager getX509TrustManager() { - X509TrustManager trustManager = null; - try { - TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); - trustManagerFactory.init((KeyStore) null); - TrustManager[] trustManagers = trustManagerFactory.getTrustManagers(); - if (trustManagers.length != 1 || !(trustManagers[0] instanceof X509TrustManager)) { - throw new IllegalStateException("Unexpected default trust managers:" + Arrays.toString(trustManagers)); - } - trustManager = (X509TrustManager) trustManagers[0]; - } catch (Exception e) { - e.printStackTrace(); - } - - return trustManager; - } - -} \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/utils/CertUtils.kt b/app/src/main/java/com/idormy/sms/forwarder/utils/CertUtils.kt new file mode 100644 index 00000000..b477859c --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/utils/CertUtils.kt @@ -0,0 +1,51 @@ +package com.idormy.sms.forwarder.utils + +import android.annotation.SuppressLint +import java.security.KeyStore +import java.security.SecureRandom +import java.security.cert.X509Certificate +import java.util.* +import javax.net.ssl.* + +@SuppressLint("ALL") +object CertUtils { + //获取这个SSLSocketFactory + val sSLSocketFactory: SSLSocketFactory + get() = try { + val sslContext = SSLContext.getInstance("SSL") + sslContext.init(null, trustManager, SecureRandom()) + sslContext.socketFactory + } catch (e: Exception) { + throw RuntimeException(e) + } + + //获取TrustManager + private val trustManager: Array + get() = arrayOf( + object : X509TrustManager { + override fun checkClientTrusted(chain: Array, authType: String) {} + override fun checkServerTrusted(chain: Array, authType: String) {} + override fun getAcceptedIssuers(): Array { + return arrayOf() + } + } + ) + + //获取HostnameVerifier + val hostnameVerifier: HostnameVerifier + get() = HostnameVerifier { _: String?, _: SSLSession? -> true } + val x509TrustManager: X509TrustManager? + get() { + var trustManager: X509TrustManager? = null + try { + val trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()) + trustManagerFactory.init(null as KeyStore?) + val trustManagers = trustManagerFactory.trustManagers + check(!(trustManagers.size != 1 || trustManagers[0] !is X509TrustManager)) { "Unexpected default trust managers:" + Arrays.toString(trustManagers) } + trustManager = trustManagers[0] as X509TrustManager + } catch (e: Exception) { + e.printStackTrace() + } + return trustManager + } +} \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/utils/CloneUtils.java b/app/src/main/java/com/idormy/sms/forwarder/utils/CloneUtils.java deleted file mode 100644 index e6b48a77..00000000 --- a/app/src/main/java/com/idormy/sms/forwarder/utils/CloneUtils.java +++ /dev/null @@ -1,86 +0,0 @@ -package com.idormy.sms.forwarder.utils; - -import com.alibaba.fastjson.JSON; -import com.idormy.sms.forwarder.model.RuleModel; -import com.idormy.sms.forwarder.model.SenderModel; -import com.idormy.sms.forwarder.model.vo.CloneInfoVo; -import com.idormy.sms.forwarder.sender.SenderUtil; - -import java.util.List; - -/** - * 一键克隆工具类 - */ -public class CloneUtils { - - //导出设置 - public static String exportSettings() { - CloneInfoVo cloneInfo = new CloneInfoVo(); - try { - cloneInfo.setVersionCode(SettingUtils.getVersionCode()); - cloneInfo.setVersionName(SettingUtils.getVersionName()); - cloneInfo.setEnableSms(SettingUtils.getSwitchEnableSms()); - cloneInfo.setEnablePhone(SettingUtils.getSwitchEnablePhone()); - cloneInfo.setCallType1(SettingUtils.getSwitchCallType1()); - cloneInfo.setCallType2(SettingUtils.getSwitchCallType2()); - cloneInfo.setCallType3(SettingUtils.getSwitchCallType3()); - cloneInfo.setEnableAppNotify(SettingUtils.getSwitchEnableAppNotify()); - cloneInfo.setCancelAppNotify(SettingUtils.getSwitchCancelAppNotify()); - cloneInfo.setBatteryLevelAlarmMin(SettingUtils.getBatteryLevelAlarmMin()); - cloneInfo.setBatteryLevelAlarmMax(SettingUtils.getBatteryLevelAlarmMax()); - cloneInfo.setBatteryLevelAlarmOnce(SettingUtils.getBatteryLevelAlarmOnce()); - cloneInfo.setRetryTimes(SettingUtils.getRetryTimes()); - cloneInfo.setDelayTime(SettingUtils.getDelayTime()); - cloneInfo.setEnableSmsTemplate(SettingUtils.getSwitchSmsTemplate()); - cloneInfo.setSmsTemplate(SettingUtils.getSmsTemplate()); - cloneInfo.setSenderList(SenderUtil.getSender(null, null)); - cloneInfo.setRuleList(RuleUtils.getRule(null, null)); - } catch (Exception e) { - e.printStackTrace(); - } - - return JSON.toJSONString(cloneInfo); - } - - //还原设置 - public static boolean restoreSettings(CloneInfoVo cloneInfoVo) { - - try { - //应用配置 - //SettingUtils.init(context); - SettingUtils.switchEnableSms(cloneInfoVo.isEnableSms()); - SettingUtils.switchEnablePhone(cloneInfoVo.isEnablePhone()); - SettingUtils.switchCallType1(cloneInfoVo.isCallType1()); - SettingUtils.switchCallType2(cloneInfoVo.isCallType2()); - SettingUtils.switchCallType3(cloneInfoVo.isCallType3()); - SettingUtils.switchEnableAppNotify(cloneInfoVo.isEnableAppNotify()); - SettingUtils.switchCancelAppNotify(cloneInfoVo.isCancelAppNotify()); - SettingUtils.setBatteryLevelAlarmMin(cloneInfoVo.getBatteryLevelAlarmMin()); - SettingUtils.setBatteryLevelAlarmMax(cloneInfoVo.getBatteryLevelAlarmMax()); - SettingUtils.switchBatteryLevelAlarmOnce(cloneInfoVo.isBatteryLevelAlarmOnce()); - SettingUtils.setRetryTimes(cloneInfoVo.getRetryTimes()); - SettingUtils.setDelayTime(cloneInfoVo.getDelayTime()); - SettingUtils.switchSmsTemplate(cloneInfoVo.isEnableSmsTemplate()); - SettingUtils.setSmsTemplate(cloneInfoVo.getSmsTemplate()); - - SenderUtil.delSender(null); - List senderList = cloneInfoVo.getSenderList(); - for (SenderModel senderModel : senderList) { - SenderUtil.addSender(senderModel); - } - - RuleUtils.delRule(null); - List ruleList = cloneInfoVo.getRuleList(); - for (RuleModel ruleModel : ruleList) { - RuleUtils.addRule(ruleModel); - } - - LogUtils.delLog(null, null); - - return true; - } catch (Exception e) { - e.printStackTrace(); - return false; - } - } -} diff --git a/app/src/main/java/com/idormy/sms/forwarder/utils/CommonUtils.java b/app/src/main/java/com/idormy/sms/forwarder/utils/CommonUtils.java deleted file mode 100644 index 4f1a69ee..00000000 --- a/app/src/main/java/com/idormy/sms/forwarder/utils/CommonUtils.java +++ /dev/null @@ -1,189 +0,0 @@ -package com.idormy.sms.forwarder.utils; - -import android.Manifest; -import android.annotation.SuppressLint; -import android.app.Activity; -import android.content.ComponentName; -import android.content.Context; -import android.content.Intent; -import android.content.pm.PackageInfo; -import android.content.pm.PackageManager; -import android.text.TextUtils; -import android.widget.EditText; -import android.widget.ListView; -import android.widget.RelativeLayout; -import android.widget.ScrollView; - -import androidx.core.app.ActivityCompat; -import androidx.core.app.NotificationManagerCompat; - -import com.google.android.material.floatingactionbutton.FloatingActionButton; -import com.idormy.sms.forwarder.MyApplication; -import com.idormy.sms.forwarder.service.NotifyService; - -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; -import java.util.Set; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -/** - * 常用工具类 - */ -public class CommonUtils { - public static final int NOTIFICATION_REQUEST_CODE = 9527; - private static final String ACTION_NOTIFICATION_LISTENER_SETTINGS = "android.settings.ACTION_NOTIFICATION_LISTENER_SETTINGS"; - - //是否启用通知监听服务 - public static boolean isNotificationListenerServiceEnabled(Context context) { - Set packageNames = NotificationManagerCompat.getEnabledListenerPackages(context); - return packageNames.contains(context.getPackageName()); - } - - //开关通知监听服务 - public static void toggleNotificationListenerService(Context context) { - PackageManager pm = context.getPackageManager(); - pm.setComponentEnabledSetting(new ComponentName(context.getApplicationContext(), NotifyService.class), - PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager.DONT_KILL_APP); - - pm.setComponentEnabledSetting(new ComponentName(context.getApplicationContext(), NotifyService.class), - PackageManager.COMPONENT_ENABLED_STATE_ENABLED, PackageManager.DONT_KILL_APP); - } - - //跳转通知使用权设置界面 - public static void openNotificationAccess(Activity activity) { - Intent intent = new Intent(ACTION_NOTIFICATION_LISTENER_SETTINGS); - activity.startActivityForResult(intent, NOTIFICATION_REQUEST_CODE); - } - - //获取当前版本名称 - public static String getVersionName(Context context) throws Exception { - // 获取PackageManager的实例 - PackageManager packageManager = context.getPackageManager(); - // getPackageName()是你当前类的包名,0代表是获取版本信息 - PackageInfo packInfo = packageManager.getPackageInfo(context.getPackageName(), 0); - return packInfo.versionName; - } - - //获取当前版本号 - public static Integer getVersionCode(Context context) throws Exception { - // 获取PackageManager的实例 - PackageManager packageManager = context.getPackageManager(); - // getPackageName()是你当前类的包名,0代表是获取版本信息 - PackageInfo packInfo = packageManager.getPackageInfo(context.getPackageName(), 0); - return packInfo.versionCode; - } - - // 检查权限是否获取(android6.0及以上系统可能默认关闭权限,且没提示) - @SuppressLint("InlinedApi") - public static void CheckPermission(PackageManager pm, Context that) { - //PackageManager pm = getPackageManager(); - boolean permission_internet = (PackageManager.PERMISSION_GRANTED == pm.checkPermission("android.permission.INTERNET", that.getPackageName())); - boolean permission_receive_boot = (PackageManager.PERMISSION_GRANTED == pm.checkPermission("android.permission.RECEIVE_BOOT_COMPLETED", that.getPackageName())); - boolean permission_foreground_service = (PackageManager.PERMISSION_GRANTED == pm.checkPermission("android.permission.FOREGROUND_SERVICE", that.getPackageName())); - boolean permission_read_external_storage = (PackageManager.PERMISSION_GRANTED == pm.checkPermission("android.permission.READ_EXTERNAL_STORAGE", that.getPackageName())); - boolean permission_write_external_storage = (PackageManager.PERMISSION_GRANTED == pm.checkPermission("android.permission.WRITE_EXTERNAL_STORAGE", that.getPackageName())); - boolean permission_receive_sms = (PackageManager.PERMISSION_GRANTED == pm.checkPermission("android.permission.RECEIVE_SMS", that.getPackageName())); - boolean permission_read_sms = (PackageManager.PERMISSION_GRANTED == pm.checkPermission("android.permission.READ_SMS", that.getPackageName())); - boolean permission_send_sms = (PackageManager.PERMISSION_GRANTED == pm.checkPermission("android.permission.SEND_SMS", that.getPackageName())); - boolean permission_read_phone_state = (PackageManager.PERMISSION_GRANTED == pm.checkPermission("android.permission.READ_PHONE_STATE", that.getPackageName())); - boolean permission_read_phone_numbers = (PackageManager.PERMISSION_GRANTED == pm.checkPermission("android.permission.READ_PHONE_NUMBERS", that.getPackageName())); - boolean permission_read_call_log = (PackageManager.PERMISSION_GRANTED == pm.checkPermission("android.permission.READ_CALL_LOG", that.getPackageName())); - boolean permission_read_contacts = (PackageManager.PERMISSION_GRANTED == pm.checkPermission("android.permission.READ_CONTACTS", that.getPackageName())); - boolean permission_battery_stats = (PackageManager.PERMISSION_GRANTED == pm.checkPermission("android.permission.BATTERY_STATS", that.getPackageName())); - boolean permission_bind_notification_listener_service = (PackageManager.PERMISSION_GRANTED == pm.checkPermission("android.permission.BIND_NOTIFICATION_LISTENER_SERVICE", that.getPackageName())); - boolean permission_query_all_packages = (PackageManager.PERMISSION_GRANTED == pm.checkPermission("android.permission.QUERY_ALL_PACKAGES", that.getPackageName())); - - if (!(permission_internet && permission_receive_boot && permission_foreground_service && - permission_read_external_storage && permission_write_external_storage && - permission_receive_sms && permission_read_sms && permission_send_sms && - permission_read_call_log && permission_read_contacts && - permission_read_phone_state && permission_read_phone_numbers && permission_battery_stats && - permission_bind_notification_listener_service && permission_query_all_packages)) { - ActivityCompat.requestPermissions((Activity) that, new String[]{ - Manifest.permission.INTERNET, - Manifest.permission.RECEIVE_BOOT_COMPLETED, - Manifest.permission.READ_EXTERNAL_STORAGE, - Manifest.permission.WRITE_EXTERNAL_STORAGE, - Manifest.permission.RECEIVE_SMS, - Manifest.permission.READ_SMS, - Manifest.permission.SEND_SMS, - Manifest.permission.READ_PHONE_STATE, - Manifest.permission.READ_CALL_LOG, - Manifest.permission.READ_CONTACTS, - Manifest.permission.READ_PHONE_NUMBERS, - Manifest.permission.FOREGROUND_SERVICE, - Manifest.permission.BATTERY_STATS, - Manifest.permission.BIND_NOTIFICATION_LISTENER_SERVICE, - Manifest.permission.QUERY_ALL_PACKAGES, - }, 0x01); - } - } - - //计算MD5 - public static String MD5(String input) { - if (input == null || input.length() == 0) return null; - - try { - MessageDigest md5 = MessageDigest.getInstance("MD5"); - md5.update(input.getBytes()); - byte[] byteArray = md5.digest(); - StringBuilder sb = new StringBuilder(); - for (byte b : byteArray) { - // 一个byte格式化成两位的16进制,不足两位高位补零 - sb.append(String.format("%02x", b)); - } - return sb.toString().toUpperCase(); - } catch (NoSuchAlgorithmException e) { - e.printStackTrace(); - } - - return null; - } - - //屏幕像素转换 - public static int dp2px(Context context, float dipValue) { - final float scale = context.getResources().getDisplayMetrics().density; - return (int) (dipValue * scale + 0.5f); - } - - //是否合法的url - public static boolean checkUrl(String urls, boolean emptyResult) { - if (TextUtils.isEmpty(urls)) return emptyResult; - - String regex = "(ht|f)tp(s?)\\:\\/\\/[0-9a-zA-Z]([-.\\w]*[0-9a-zA-Z])*(:(0-9)*)*(\\/?)([a-zA-Z0-9\\-\\.\\?\\,\\'\\/\\\\&%\\+\\$#_=]*)?"; - Pattern pat = Pattern.compile(regex); - Matcher mat = pat.matcher(urls.trim()); - return mat.matches(); - } - - //焦点位置插入文本 - public static void insertOrReplaceText2Cursor(EditText editText, String str) { - int start = Math.max(editText.getSelectionStart(), 0); - int end = Math.max(editText.getSelectionEnd(), 0); - editText.getText().replace(Math.min(start, end), Math.max(start, end), str, 0, str.length()); - } - - //计算浮动按钮位置 - public static void calcMarginBottom(Context context, FloatingActionButton btnFloat, ListView viewList, ScrollView scrollView) { - - int marginBottom = MyApplication.showHelpTip ? 85 : 65; - if (btnFloat != null) { - RelativeLayout.LayoutParams btnLayoutParams = (RelativeLayout.LayoutParams) btnFloat.getLayoutParams(); - btnLayoutParams.bottomMargin = dp2px(context, marginBottom + 10); - btnFloat.setLayoutParams(btnLayoutParams); - } - - if (viewList != null) { - RelativeLayout.LayoutParams listLayoutParams = (RelativeLayout.LayoutParams) viewList.getLayoutParams(); - listLayoutParams.bottomMargin = dp2px(context, marginBottom); - viewList.setLayoutParams(listLayoutParams); - } - - if (scrollView != null) { - RelativeLayout.LayoutParams listLayoutParams = (RelativeLayout.LayoutParams) scrollView.getLayoutParams(); - listLayoutParams.bottomMargin = dp2px(context, marginBottom); - scrollView.setLayoutParams(listLayoutParams); - } - } -} diff --git a/app/src/main/java/com/idormy/sms/forwarder/utils/CommonUtils.kt b/app/src/main/java/com/idormy/sms/forwarder/utils/CommonUtils.kt new file mode 100644 index 00000000..bd29fe42 --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/utils/CommonUtils.kt @@ -0,0 +1,271 @@ +package com.idormy.sms.forwarder.utils + +import android.app.Dialog +import android.content.ComponentName +import android.content.Context +import android.content.Intent +import android.content.pm.PackageManager +import android.graphics.Rect +import android.text.SpannableString +import android.text.SpannableStringBuilder +import android.text.Spanned +import android.text.TextUtils +import android.text.method.LinkMovementMethod +import android.text.style.ClickableSpan +import android.view.View +import android.widget.EditText +import androidx.annotation.ColorInt +import androidx.core.app.NotificationManagerCompat +import androidx.fragment.app.Fragment +import com.idormy.sms.forwarder.R +import com.idormy.sms.forwarder.core.webview.AgentWebActivity +import com.idormy.sms.forwarder.core.webview.AgentWebFragment +import com.idormy.sms.forwarder.entity.ImageInfo +import com.idormy.sms.forwarder.fragment.MarkdownFragment +import com.idormy.sms.forwarder.fragment.ServiceProtocolFragment +import com.idormy.sms.forwarder.service.NotifyService +import com.xuexiang.xpage.base.XPageFragment +import com.xuexiang.xpage.core.PageOption +import com.xuexiang.xui.utils.ColorUtils +import com.xuexiang.xui.utils.ResUtils +import com.xuexiang.xui.widget.dialog.DialogLoader +import com.xuexiang.xui.widget.dialog.materialdialog.DialogAction +import com.xuexiang.xui.widget.dialog.materialdialog.MaterialDialog +import com.xuexiang.xui.widget.dialog.materialdialog.MaterialDialog.SingleButtonCallback +import com.xuexiang.xui.widget.imageview.preview.PreviewBuilder +import com.xuexiang.xutil.XUtil +import com.xuexiang.xutil.common.StringUtils +import java.util.regex.Pattern +import kotlin.math.max +import kotlin.math.min + +/** + * 常用工具类 + */ +@Suppress("RegExpRedundantEscape", "unused") +class CommonUtils private constructor() { + companion object { + /** + * 这里填写你的应用隐私政策网页地址 + */ + private const val PRIVACY_URL = "https://gitee.com/pp/SmsForwarder/raw/main/PRIVACY" + + /** + * 显示隐私政策的提示 + * + * @param context + * @param submitListener 同意的监听 + * @return + */ + @Suppress("SameParameterValue", "NAME_SHADOWING") + @JvmStatic + fun showPrivacyDialog(context: Context, submitListener: SingleButtonCallback?): Dialog { + val dialog = + MaterialDialog.Builder(context).title(R.string.title_reminder).autoDismiss(false) + .cancelable(false) + .positiveText(R.string.lab_agree) + .onPositive { dialog1: MaterialDialog, which: DialogAction? -> + if (submitListener != null) { + submitListener.onClick(dialog1, which!!) + } else { + dialog1.dismiss() + } + } + .negativeText(R.string.lab_disagree).onNegative { dialog, _ -> + dialog.dismiss() + DialogLoader.getInstance().showConfirmDialog( + context, + ResUtils.getString(R.string.title_reminder), + String.format( + ResUtils.getString(R.string.content_privacy_explain_again), + ResUtils.getString(R.string.app_name) + ), + ResUtils.getString(R.string.lab_look_again), + { dialog, _ -> + dialog.dismiss() + showPrivacyDialog(context, submitListener) + }, + ResUtils.getString(R.string.lab_still_disagree) + ) { dialog, _ -> + dialog.dismiss() + DialogLoader.getInstance().showConfirmDialog( + context, + ResUtils.getString(R.string.content_think_about_it_again), + ResUtils.getString(R.string.lab_look_again), + { dialog, _ -> + dialog.dismiss() + showPrivacyDialog(context, submitListener) + }, + ResUtils.getString(R.string.lab_exit_app) + ) { dialog, _ -> + dialog.dismiss() + XUtil.exitApp() + } + } + }.build() + dialog.setContent(getPrivacyContent(context)) + //开始响应点击事件 + dialog.contentView!!.movementMethod = LinkMovementMethod.getInstance() + dialog.show() + return dialog + } + + /** + * @return 隐私政策说明 + */ + private fun getPrivacyContent(context: Context): SpannableStringBuilder { + return SpannableStringBuilder() + .append(" ").append(ResUtils.getString(R.string.privacy_content_1)).append(" ").append(ResUtils.getString(R.string.app_name)).append("!\n") + .append(" ").append(ResUtils.getString(R.string.privacy_content_2)) + .append(" ").append(ResUtils.getString(R.string.privacy_content_3)) + .append(getPrivacyLink(context, PRIVACY_URL)) + .append(ResUtils.getString(R.string.privacy_content_4)) + .append(" ").append(ResUtils.getString(R.string.privacy_content_5)) + .append(getPrivacyLink(context, PRIVACY_URL)) + .append(ResUtils.getString(R.string.privacy_content_6)) + } + + /** + * @param context 隐私政策的链接 + * @return + */ + @Suppress("SameParameterValue") + private fun getPrivacyLink(context: Context, privacyUrl: String): SpannableString { + val privacyName = String.format( + ResUtils.getString(R.string.lab_privacy_name), + ResUtils.getString(R.string.app_name) + ) + val spannableString = SpannableString(privacyName) + spannableString.setSpan(object : ClickableSpan() { + override fun onClick(widget: View) { + goWeb(context, privacyUrl) + } + }, 0, privacyName.length, Spanned.SPAN_MARK_MARK) + return spannableString + } + + /** + * 请求浏览器 + * + * @param url + */ + @JvmStatic + fun goWeb(context: Context, url: String?) { + val intent = Intent(context, AgentWebActivity::class.java) + intent.putExtra(AgentWebFragment.KEY_URL, url) + context.startActivity(intent) + } + + /** + * 打开用户协议和隐私协议 + * + * @param fragment + * @param isPrivacy 是否是隐私协议 + * @param isImmersive 是否沉浸式 + */ + @JvmStatic + fun gotoProtocol(fragment: XPageFragment?, isPrivacy: Boolean, isImmersive: Boolean) { + PageOption.to(ServiceProtocolFragment::class.java) + .putString( + ServiceProtocolFragment.KEY_PROTOCOL_TITLE, + if (isPrivacy) ResUtils.getString(R.string.title_privacy_protocol) else ResUtils.getString( + R.string.title_user_protocol + ) + ) + .putBoolean(ServiceProtocolFragment.KEY_IS_IMMERSIVE, isImmersive) + .open(fragment!!) + } + + /** + * 是否是深色的颜色 + * + * @param color + * @return + */ + @JvmStatic + fun isColorDark(@ColorInt color: Int): Boolean { + return ColorUtils.isColorDark(color, 0.382) + } + + //焦点位置插入文本 + fun insertOrReplaceText2Cursor(editText: EditText, str: String) { + editText.isFocusable = true + editText.requestFocus() + val start = max(editText.selectionStart, 0) + val end = max(editText.selectionEnd, 0) + editText.text.replace(min(start, end), max(start, end), str, 0, str.length) + } + + //==========图片预览===========// + /** + * 大图预览 + * + * @param fragment + * @param url 图片资源 + * @param view 小图加载控件 + */ + fun previewPicture(fragment: Fragment?, url: String, view: View?) { + if (fragment == null || StringUtils.isEmpty(url)) { + return + } + val bounds = Rect() + view?.getGlobalVisibleRect(bounds) + PreviewBuilder.from(fragment) + .setImgs(ImageInfo.newInstance(url, bounds)) + .setCurrentIndex(0) + .setSingleFling(true) + .setProgressColor(R.color.xui_config_color_main_theme) + .setType(PreviewBuilder.IndicatorType.Number) + .start() + } + + /** + * 打开Markdown链接并渲染 + * + * @param fragment + * @param url Markdown链接 + * @param isImmersive 是否沉浸式 + */ + @JvmStatic + fun previewMarkdown(fragment: XPageFragment?, title: String, url: String, isImmersive: Boolean) { + PageOption.to(MarkdownFragment::class.java) + .putString(MarkdownFragment.KEY_MD_TITLE, title) + .putString(MarkdownFragment.KEY_MD_URL, url) + .putBoolean(MarkdownFragment.KEY_IS_IMMERSIVE, isImmersive) + .open(fragment!!) + } + + //是否合法的url + fun checkUrl(urls: String, emptyResult: Boolean): Boolean { + if (TextUtils.isEmpty(urls)) return emptyResult + val regex = "(ht|f)tp(s?)\\:\\/\\/[\\da-zA-Z]([-.\\w]*[\\da-zA-Z])*(:(0-9)*)*(\\/?)([a-zA-Z\\d\\-\\.\\?\\,\\'\\/\\\\&%\\+\\$#_=]*)?" + val pat = Pattern.compile(regex) + val mat = pat.matcher(urls.trim()) + return mat.matches() + } + + //是否启用通知监听服务 + fun isNotificationListenerServiceEnabled(context: Context): Boolean { + val packageNames = NotificationManagerCompat.getEnabledListenerPackages(context) + return packageNames.contains(context.packageName) + } + + //开关通知监听服务 + fun toggleNotificationListenerService(context: Context) { + val pm = context.packageManager + pm.setComponentEnabledSetting( + ComponentName(context.applicationContext, NotifyService::class.java), + PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager.DONT_KILL_APP + ) + pm.setComponentEnabledSetting( + ComponentName(context.applicationContext, NotifyService::class.java), + PackageManager.COMPONENT_ENABLED_STATE_ENABLED, PackageManager.DONT_KILL_APP + ) + } + + } + + init { + throw UnsupportedOperationException("u can't instantiate me...") + } +} \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/utils/Constants.kt b/app/src/main/java/com/idormy/sms/forwarder/utils/Constants.kt new file mode 100644 index 00000000..eab2191d --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/utils/Constants.kt @@ -0,0 +1,241 @@ +@file:Suppress("unused") + +package com.idormy.sms.forwarder.utils + +import com.idormy.sms.forwarder.R +import com.xuexiang.xpage.enums.CoreAnim +import com.xuexiang.xpage.model.PageInfo +import com.xuexiang.xui.utils.ResUtils.getString + +object Worker { + const val sendMsgInfo = "send_msg_info" + const val sendLogId = "send_log_id" + const val sendSbnId = "send_sbn_id" + const val updateLogs = "update_logs" +} + +//初始化相关 +const val IS_FIRST_OPEN_KEY = "is_first_open_key" +const val IS_AGREE_PRIVACY_KEY = "is_agree_privacy_key" + +//数据库 +const val DATABASE_NAME = "sms_forwarder.db" +const val PACKAGE_NAME = "com.idormy.sms.forwarder" + +//通用设置 +const val SP_ENABLE_SMS = "enable_sms" + +const val SP_ENABLE_PHONE = "enable_phone" +const val SP_ENABLE_CALL_TYPE_1 = "enable_call_type_1" +const val SP_ENABLE_CALL_TYPE_2 = "enable_call_type_2" +const val SP_ENABLE_CALL_TYPE_3 = "enable_call_type_3" + +const val SP_ENABLE_APP_NOTIFY = "enable_app_notify" +const val SP_ENABLE_CANCEL_APP_NOTIFY = "enable_cancel_app_notify" +const val SP_ENABLE_NOT_USER_PRESENT = "enable_not_user_present" + +const val SP_DUPLICATE_MESSAGES_LIMITS = "duplicate_messages_limits" + +const val SP_BATTERY_RECEIVER = "enable_battery_receiver" +const val SP_BATTERY_STATUS = "battery_status" +const val SP_BATTERY_LEVEL_MIN = "battery_level_min" +const val SP_BATTERY_LEVEL_MAX = "battery_level_max" +const val SP_BATTERY_LEVEL_ONCE = "battery_level_once" +const val SP_BATTERY_LEVEL_CURRENT = "battery_level_current" + +const val SP_BATTERY_CRON = "enable_battery_cron" +const val SP_BATTERY_CRON_START_TIME = "battery_cron_start_time" +const val SP_BATTERY_CRON_INTERVAL = "battery_cron_interval" + +const val SP_ENABLE_EXCLUDE_FROM_RECENTS = "enable_exclude_from_recents" +const val SP_ENABLE_PLAY_SILENCE_MUSIC = "enable_play_silence_music" +const val SP_ENABLE_ONE_PIXEL_ACTIVITY = "enable_one_pixel_activity" + +const val SP_REQUEST_RETRY_TIMES = "request_retry_times" +const val SP_REQUEST_DELAY_TIME = "request_delay_time" +const val SP_REQUEST_TIMEOUT = "request_timeout" + +const val SP_NOTIFY_CONTENT = "notify_content" +const val SP_EXTRA_DEVICE_MARK = "extra_device_mark" +const val SP_EXTRA_SIM1 = "extra_sim1" +const val SP_EXTRA_SIM2 = "extra_sim2" +const val SP_ENABLE_SMS_TEMPLATE = "enable_sms_template" +const val SP_SMS_TEMPLATE = "sms_template" + +const val SP_ENABLE_HELP_TIP = "enable_help_tip" + +const val CACTUS_TIMER = "cactus_timer" +const val CACTUS_LAST_TIMER = "cactus_last_timer" +const val CACTUS_DATE = "cactus_date" +const val CACTUS_END_DATE = "cactus_end_date" + +//OkHttp 请求超时时间 +const val REQUEST_TIMEOUT_SECONDS = 5 + +//规则相关 +const val STATUS_ON = 1 +const val STATUS_OFF = 0 +const val FILED_TRANSPOND_ALL = "transpond_all" +const val FILED_PHONE_NUM = "phone_num" +const val FILED_PACKAGE_NAME = "package_name" +const val FILED_MSG_CONTENT = "msg_content" +const val FILED_INFORM_CONTENT = "inform_content" +const val FILED_MULTI_MATCH = "multi_match" +const val CHECK_IS = "is" +const val CHECK_CONTAIN = "contain" +const val CHECK_NOT_CONTAIN = "notcontain" +const val CHECK_START_WITH = "startwith" +const val CHECK_END_WITH = "endwith" +const val CHECK_NOT_IS = "notis" +const val CHECK_REGEX = "regex" +const val CHECK_SIM_SLOT_ALL = "ALL" +const val CHECK_SIM_SLOT_1 = "SIM1" +const val CHECK_SIM_SLOT_2 = "SIM2" +val TYPE_MAP = object : HashMap() { + init { + put("sms", getString(R.string.rule_sms)) + put("call", getString(R.string.rule_call)) + put("app", getString(R.string.rule_app)) + } +} +val FILED_MAP = object : HashMap() { + init { + put("transpond_all", getString(R.string.rule_transpond_all)) + put("phone_num", getString(R.string.rule_phone_num)) + put("msg_content", getString(R.string.rule_msg_content)) + put("multi_match", getString(R.string.rule_multi_match)) + put("package_name", getString(R.string.rule_package_name)) + put("inform_content", getString(R.string.rule_inform_content)) + } +} +val CHECK_MAP = object : HashMap() { + init { + put("is", getString(R.string.rule_is)) + put("notis", getString(R.string.rule_notis)) + put("contain", getString(R.string.rule_contain)) + put("startwith", getString(R.string.rule_startwith)) + put("endwith", getString(R.string.rule_endwith)) + put("notcontain", getString(R.string.rule_notcontain)) + put("regex", getString(R.string.rule_regex)) + } +} +val SIM_SLOT_MAP = object : HashMap() { + init { + put("ALL", getString(R.string.rule_all)) + put("SIM1", "SIM1") + put("SIM2", "SIM2") + } +} +val FORWARD_STATUS_MAP = object : HashMap() { + init { + put(0, getString(R.string.failed)) + put(1, getString(R.string.processing)) + put(2, getString(R.string.success)) + } +} +val BARK_LEVEL_MAP = mapOf( + "active" to getString(R.string.bark_level_active), + "timeSensitive" to getString(R.string.bark_level_timeSensitive), + "passive" to getString(R.string.bark_level_passive) +) + +//发送通道 +const val TYPE_DINGDING = 0 +const val TYPE_EMAIL = 1 +const val TYPE_BARK = 2 +const val TYPE_WEBHOOK = 3 +const val TYPE_WEWORK_ROBOT = 4 +const val TYPE_WEWORK_AGENT = 5 +const val TYPE_SERVERCHAN = 6 +const val TYPE_TELEGRAM = 7 +const val TYPE_SMS = 8 +const val TYPE_FEISHU = 9 +const val TYPE_PUSHPLUS = 10 +const val TYPE_GOTIFY = 11 +var SENDER_FRAGMENT_LIST = listOf( + PageInfo(getString(R.string.dingtalk_robot), "com.idormy.sms.forwarder.fragment.senders.DingtalkFragment", "{\"\":\"\"}", CoreAnim.slide, R.drawable.icon_dingtalk), + PageInfo(getString(R.string.email), "com.idormy.sms.forwarder.fragment.senders.EmailFragment", "{\"\":\"\"}", CoreAnim.slide, R.drawable.icon_email), + PageInfo(getString(R.string.bark), "com.idormy.sms.forwarder.fragment.senders.BarkFragment", "{\"\":\"\"}", CoreAnim.slide, R.drawable.icon_bark), + PageInfo(getString(R.string.webhook), "com.idormy.sms.forwarder.fragment.senders.WebhookFragment", "{\"\":\"\"}", CoreAnim.slide, R.drawable.icon_webhook), + PageInfo(getString(R.string.wework_robot), "com.idormy.sms.forwarder.fragment.senders.WeworkRobotFragment", "{\"\":\"\"}", CoreAnim.slide, R.drawable.icon_wework_robot), + PageInfo(getString(R.string.wework_agent), "com.idormy.sms.forwarder.fragment.senders.WeworkAgentFragment", "{\"\":\"\"}", CoreAnim.slide, R.drawable.icon_wework_agent), + PageInfo(getString(R.string.server_chan), "com.idormy.sms.forwarder.fragment.senders.ServerchanFragment", "{\"\":\"\"}", CoreAnim.slide, R.drawable.icon_serverchan), + PageInfo(getString(R.string.telegram), "com.idormy.sms.forwarder.fragment.senders.TelegramFragment", "{\"\":\"\"}", CoreAnim.slide, R.drawable.icon_telegram), + PageInfo(getString(R.string.sms_menu), "com.idormy.sms.forwarder.fragment.senders.SmsFragment", "{\"\":\"\"}", CoreAnim.slide, R.drawable.icon_sms), + PageInfo(getString(R.string.feishu), "com.idormy.sms.forwarder.fragment.senders.FeishuFragment", "{\"\":\"\"}", CoreAnim.slide, R.drawable.icon_feishu), + PageInfo(getString(R.string.pushplus), "com.idormy.sms.forwarder.fragment.senders.PushplusFragment", "{\"\":\"\"}", CoreAnim.slide, R.drawable.icon_pushplus), + PageInfo(getString(R.string.gotify), "com.idormy.sms.forwarder.fragment.senders.GotifyFragment", "{\"\":\"\"}", CoreAnim.slide, R.drawable.icon_gotify), +) + +//前台服务 +const val FRONT_NOTIFY_ID = 0x1010 +const val FRONT_CHANNEL_ID = "com.idormy.sms.forwarder" +const val FRONT_CHANNEL_NAME = "SmsForwarder Foreground Service" + +//Frp内网穿透 +const val FRPC_LIB_VERSION = "0.42.0" +const val EVENT_FRPC_UPDATE_CONFIG = "EVENT_FRPC_UPDATE_CONFIG" +const val EVENT_FRPC_DELETE_CONFIG = "EVENT_FRPC_DELETE_CONFIG" +const val EVENT_FRPC_RUNNING_ERROR = "EVENT_FRPC_RUNNING_ERROR" +const val EVENT_FRPC_RUNNING_SUCCESS = "EVENT_FRPC_RUNNING_SUCCESS" +const val INTENT_FRPC_EDIT_FILE = "INTENT_FRPC_EDIT_FILE" +const val INTENT_FRPC_APPLY_FILE = "INTENT_FRPC_APPLY_FILE" + +//来电监听 +const val ACTION_CALL_IN = "android.intent.action.PHONE_STATE" +const val ACTION_CALL_OUT = "android.intent.action.NEW_OUTGOING_CALL" +const val EXTRA_PHONE_NUMBER = "android.intent.extra.PHONE_NUMBER" + +//Markdown 查看页面 +const val KEY_TITLE = "key_title" +const val KEY_URL = "key_url" + +//主页监听时间 +const val EVENT_UPDATE_LOGS_TYPE = "key_logs_type" +const val EVENT_UPDATE_RULE_TYPE = "key_status" +const val EVENT_UPDATE_NOTIFY = "key_notify" + +const val KEY_SENDER_ID = "key_sender_id" +const val KEY_SENDER_TYPE = "key_sender_type" +const val KEY_SENDER_CLONE = "key_sender_clone" + +const val KEY_RULE_ID = "key_rule_id" +const val KEY_RULE_TYPE = "key_rule_type" +const val KEY_RULE_CLONE = "key_rule_clone" + +const val EVENT_KEY_SIM_SLOT = "EVENT_KEY_SIM_SLOT" +const val EVENT_KEY_PHONE_NUMBERS = "EVENT_KEY_PHONE_NUMBERS" + +//在线升级URL +const val KEY_UPDATE_URL = "https://xupdate.bms.ink/update/checkVersion" + +//HttpServer相关 +const val ENABLE_HTTP_SERVER = "enable_http_server" +const val HTTP_SERVER_PORT = 5000 +const val HTTP_SERVER_TIME_OUT = 10 +const val HTTP_SERVER_NOTIFY_ID = 0x1011 +const val HTTP_SERVER_CHANNEL_ID = "http_server_notification_channel" +const val HTTP_SERVER_CHANNEL_NAME = "Http-Server Service" +const val START_ACTION = "start" +const val STOP_ACTION = "stop" +const val HTTP_SUCCESS_CODE: Int = 200 +const val HTTP_FAILURE_CODE: Int = 500 +const val SP_ENABLE_SERVER = "enable_server" +const val SP_ENABLE_SERVER_AUTORUN = "enable_server_autorun" +const val SP_SERVER_SIGN_KEY = "server_sign_key" +const val SP_ENABLE_API_CLONE = "enable_api_clone" +const val SP_ENABLE_API_SMS_SEND = "enable_api_sms_send" +const val SP_ENABLE_API_SMS_QUERY = "enable_api_sms_query" +const val SP_ENABLE_API_CALL_QUERY = "enable_api_call_query" +const val SP_ENABLE_API_CONTACT_QUERY = "enable_api_contact_query" +const val SP_ENABLE_API_BATTERY_QUERY = "enable_api_battery_query" +const val SP_SERVER_ADDRESS = "server_address" +const val SP_CLIENT_SIGN_KEY = "client_sign_key" +var CLIENT_FRAGMENT_LIST = listOf( + PageInfo(getString(R.string.api_clone), "com.idormy.sms.forwarder.fragment.client.CloneFragment", "{\"\":\"\"}", CoreAnim.slide, R.drawable.icon_api_clone), + PageInfo(getString(R.string.api_sms_send), "com.idormy.sms.forwarder.fragment.client.SmsSendFragment", "{\"\":\"\"}", CoreAnim.slide, R.drawable.icon_api_sms_send), + PageInfo(getString(R.string.api_sms_query), "com.idormy.sms.forwarder.fragment.client.SmsQueryFragment", "{\"\":\"\"}", CoreAnim.slide, R.drawable.icon_api_sms_query), + PageInfo(getString(R.string.api_call_query), "com.idormy.sms.forwarder.fragment.client.CallQueryFragment", "{\"\":\"\"}", CoreAnim.slide, R.drawable.icon_api_call_query), + PageInfo(getString(R.string.api_contact_query), "com.idormy.sms.forwarder.fragment.client.ContactQueryFragment", "{\"\":\"\"}", CoreAnim.slide, R.drawable.icon_api_contact_query), + PageInfo(getString(R.string.api_battery_query), "com.idormy.sms.forwarder.fragment.client.BatteryQueryFragment", "{\"\":\"\"}", CoreAnim.slide, R.drawable.icon_api_battery_query), +) \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/utils/ContactHelper.java b/app/src/main/java/com/idormy/sms/forwarder/utils/ContactHelper.java deleted file mode 100644 index 0c3a15f5..00000000 --- a/app/src/main/java/com/idormy/sms/forwarder/utils/ContactHelper.java +++ /dev/null @@ -1,191 +0,0 @@ -package com.idormy.sms.forwarder.utils; - -import android.content.ContentResolver; -import android.content.Context; -import android.database.Cursor; -import android.provider.ContactsContract; - -import com.idormy.sms.forwarder.model.PhoneBookEntity; - -import java.util.ArrayList; -import java.util.List; - -/** - * 获取联系人工具类 - */ -@SuppressWarnings("unused") -public class ContactHelper { - - private static final String[] PROJECTION = new String[]{ - ContactsContract.Contacts.DISPLAY_NAME, - ContactsContract.CommonDataKinds.Phone.NUMBER - }; - - private final List contacts = new ArrayList<>(); - - private ContactHelper() { - - } - - public static ContactHelper getInstance() { - return InstanceHolder.INSTANCE; - } - - private static class InstanceHolder { - private static final ContactHelper INSTANCE = new ContactHelper(); - } - - /** - * 获取所有联系人 - * - * @param context 上下文 - * @return 联系人集合 - */ - public List getContacts(Context context) { - contacts.clear(); - ContentResolver cr = context.getContentResolver(); - try (Cursor cursor = cr.query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI, PROJECTION, null, null, "sort_key")) { - if (cursor != null) { - final int displayNameIndex = cursor.getColumnIndex(ContactsContract.Contacts.DISPLAY_NAME); - final int mobileNoIndex = cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER); - String mobileNo, displayName; - while (cursor.moveToNext()) { - mobileNo = cursor.getString(mobileNoIndex); - displayName = cursor.getString(displayNameIndex); - contacts.add(new PhoneBookEntity(displayName, mobileNo)); - } - } - } catch (Exception e) { - e.printStackTrace(); - } - return contacts; - } - - /** - * 通过姓名获取联系人 - * - * @param context 上下文 - * @param contactName 联系人姓名 - * @return 联系人集合 - */ - public List getContactByName(Context context, String contactName) { - contacts.clear(); - - ContentResolver cr = context.getContentResolver(); - String selection = ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME + " like ? "; - String[] selectionArgs = new String[]{"%" + contactName + "%"}; - try (Cursor cursor = cr.query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI, PROJECTION, selection, selectionArgs, "sort_key")) { - if (cursor != null) { - final int displayNameIndex = cursor.getColumnIndex(ContactsContract.Contacts.DISPLAY_NAME); - final int mobileNoIndex = cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER); - String mobileNo, displayName; - while (cursor.moveToNext()) { - mobileNo = cursor.getString(mobileNoIndex); - displayName = cursor.getString(displayNameIndex); - contacts.add(new PhoneBookEntity(displayName, mobileNo)); - } - } - } catch (Exception e) { - e.printStackTrace(); - } - return contacts; - } - - /** - * 通过手机号获取联系人 - * - * @param context 上下文 - * @param phoneNum 手机号码 - * @return 联系人集合 - */ - public List getContactByNumber(Context context, String phoneNum) { - contacts.clear(); - Cursor cursor = null; - if (phoneNum.length() < 11) { - return null; - } - try { - ContentResolver cr = context.getContentResolver(); - String selection = ContactsContract.CommonDataKinds.Phone.NUMBER + " in(?,?,?) "; - String phone1 = phoneNum.subSequence(0, 3) + " " + phoneNum.substring(3, 7) + - " " + phoneNum.substring(7, 11); - String phone2 = phoneNum.subSequence(0, 3) + "-" + phoneNum.substring(3, 7) + - "-" + phoneNum.substring(7, 11); - - String[] selectionArgs = new String[]{phoneNum, phone1, phone2}; - - cursor = cr.query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI, PROJECTION, selection, selectionArgs, "sort_key"); - if (cursor != null) { - final int displayNameIndex = cursor.getColumnIndex(ContactsContract.Contacts.DISPLAY_NAME); - final int mobileNoIndex = cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER); - String mobileNo, displayName; - while (cursor.moveToNext()) { - mobileNo = cursor.getString(mobileNoIndex); - displayName = cursor.getString(displayNameIndex); - contacts.add(new PhoneBookEntity(displayName, mobileNo)); - } - } - } catch (Exception e) { - e.printStackTrace(); - } finally { - if (cursor != null) { - cursor.close(); - } - } - return contacts; - } - - /** - * 分页查询联系人 - * - * @param context 上下文 - * @param page 页数 - * @param pageSize 每页数据量 - * @return 联系人集合 - */ - public List getContactsByPage(Context context, int page, int pageSize) { - contacts.clear(); - - Cursor cursor = null; - ContentResolver cr = context.getContentResolver(); - try { - String sortOrder = "_id limit " + page + "," + pageSize; - cursor = cr.query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI, PROJECTION, null, null, sortOrder); - if (cursor != null) { - final int displayNameIndex = cursor.getColumnIndex(ContactsContract.Contacts.DISPLAY_NAME); - final int mobileNoIndex = cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER); - String mobileNo, displayName; - while (cursor.moveToNext()) { - mobileNo = cursor.getString(mobileNoIndex); - displayName = cursor.getString(displayNameIndex); - contacts.add(new PhoneBookEntity(displayName, mobileNo)); - } - } - } catch (Exception e) { - e.printStackTrace(); - } finally { - if (cursor != null) { - cursor.close(); - } - } - return contacts; - } - - /** - * 获取联系人总数 - * - * @param context 上下文 - * @return 数量 - */ - public int getContactCount(Context context) { - ContentResolver cr = context.getContentResolver(); - try (Cursor cursor = cr.query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI, PROJECTION, null, null, "sort_key")) { - if (cursor != null) { - return cursor.getCount(); - } - } catch (Exception e) { - e.printStackTrace(); - } - return 0; - } -} \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/utils/CrashHandler.java b/app/src/main/java/com/idormy/sms/forwarder/utils/CrashHandler.java deleted file mode 100644 index 07cd9561..00000000 --- a/app/src/main/java/com/idormy/sms/forwarder/utils/CrashHandler.java +++ /dev/null @@ -1,186 +0,0 @@ -package com.idormy.sms.forwarder.utils; - -import android.annotation.SuppressLint; -import android.content.Context; -import android.content.pm.PackageInfo; -import android.content.pm.PackageManager; -import android.os.Build; -import android.os.Looper; -import android.util.Log; - -import androidx.annotation.NonNull; - -import com.hjq.toast.ToastUtils; -import com.idormy.sms.forwarder.R; - -import java.io.File; -import java.io.FileOutputStream; -import java.io.PrintWriter; -import java.io.StringWriter; -import java.io.Writer; -import java.lang.Thread.UncaughtExceptionHandler; -import java.lang.reflect.Field; -import java.text.DateFormat; -import java.text.SimpleDateFormat; -import java.util.Date; -import java.util.HashMap; -import java.util.Map; -import java.util.Objects; - -/** - * UncaughtException处理类,当程序发生Uncaught异常的时候,有该类来接管程序,并记录发送错误报告 - */ -public class CrashHandler implements UncaughtExceptionHandler { - private static final String TAG = "CrashHandler"; - //系统默认的UncaughtException处理类 - private Thread.UncaughtExceptionHandler mDefaultHandler; - //CrashHandler实例 - @SuppressLint("StaticFieldLeak") - private static final CrashHandler INSTANCE = new CrashHandler(); - //程序的Context对象 - private Context mContext; - //用来存储设备信息和异常信息 - private final Map infos = new HashMap<>(); - //用于格式化日期,作为日志文件名的一部分 - @SuppressLint("SimpleDateFormat") - private final DateFormat formatter = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss"); - - /** - * 保证只有一个CrashHandler实例 - */ - private CrashHandler() { - } - - /** - * 获取CrashHandler实例 ,单例模式 - */ - public static CrashHandler getInstance() { - return INSTANCE; - } - - /** - * 初始化 - */ - public void init(Context context) { - mContext = context; - //获取系统默认的UncaughtException处理器 - mDefaultHandler = Thread.getDefaultUncaughtExceptionHandler(); - //设置该CrashHandler为程序的默认处理器 - Thread.setDefaultUncaughtExceptionHandler(this); - } - - /** - * 当UncaughtException发生时会转入该函数来处理 - */ - @Override - public void uncaughtException(@NonNull Thread thread, @NonNull Throwable ex) { - if (!handleException(ex) && mDefaultHandler != null) { - //如果用户没有处理则让系统默认的异常处理器来处理 - mDefaultHandler.uncaughtException(thread, ex); - } else { - try { - Thread.sleep(3000); - } catch (InterruptedException e) { - Log.e(TAG, "error : ", e); - } //退出程序 - android.os.Process.killProcess(android.os.Process.myPid()); - System.exit(1); - } - } - - /** - * 自定义错误处理,收集错误信息 发送错误报告等操作均在此完成 - * - * @return true:如果处理了该异常信息;否则返回false. - */ - private boolean handleException(Throwable ex) { - if (ex == null) { - return false; - } //使用Toast来显示异常信息 - new Thread() { - @Override - public void run() { - Looper.prepare(); - ToastUtils.delayedShow(R.string.crash_tip, 3000); - Looper.loop(); - } - }.start(); - //收集设备参数信息 - collectDeviceInfo(mContext); - //保存日志文件 - saveCrashInfo2File(ex); - return true; - } - - /** - * 收集设备参数信息 - */ - public void collectDeviceInfo(Context ctx) { - try { - PackageManager pm = ctx.getPackageManager(); - PackageInfo pi = pm.getPackageInfo(ctx.getPackageName(), PackageManager.GET_ACTIVITIES); - if (pi != null) { - String versionName = pi.versionName == null ? "null" : pi.versionName; - String versionCode = pi.versionCode + ""; - infos.put("versionName", versionName); - infos.put("versionCode", versionCode); - } - } catch (PackageManager.NameNotFoundException e) { - Log.e(TAG, "an error occured when collect package info", e); - } - Field[] fields = Build.class.getDeclaredFields(); - for (Field field : fields) { - try { - field.setAccessible(true); - infos.put(field.getName(), Objects.requireNonNull(field.get(null)).toString()); - Log.d(TAG, field.getName() + " : " + field.get(null)); - } catch (Exception e) { - Log.e(TAG, "an error occured when collect crash info", e); - } - } - } - - /** - * 保存错误信息到文件中 - */ - @SuppressWarnings({"ResultOfMethodCallIgnored", "UnusedReturnValue"}) - private String saveCrashInfo2File(Throwable ex) { - StringBuilder sb = new StringBuilder(); - for (Map.Entry entry : infos.entrySet()) { - String key = entry.getKey(); - String value = entry.getValue(); - sb.append(key).append("=").append(value).append("\n"); - } - Writer writer = new StringWriter(); - PrintWriter printWriter = new PrintWriter(writer); - ex.printStackTrace(printWriter); - Throwable cause = ex.getCause(); - while (cause != null) { - cause.printStackTrace(printWriter); - cause = cause.getCause(); - } - printWriter.close(); - String result = writer.toString(); - sb.append(result); - try { - long timestamp = System.currentTimeMillis(); - String time = formatter.format(new Date()); - String fileName = "crash-" + time + "-" + timestamp + ".log"; - - String path = mContext.getCacheDir().getPath() + File.separator + "crash" + File.separator; - Log.e(TAG, path); - File dir = new File(path); - if (!dir.exists()) { - dir.mkdirs(); - } - FileOutputStream fos = new FileOutputStream(path + fileName); - fos.write(sb.toString().getBytes()); - fos.close(); - - return fileName; - } catch (Exception e) { - Log.e(TAG, "an error occured while writing file...", e); - } - return null; - } -} \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/utils/DataProvider.kt b/app/src/main/java/com/idormy/sms/forwarder/utils/DataProvider.kt new file mode 100644 index 00000000..31b62e3e --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/utils/DataProvider.kt @@ -0,0 +1,45 @@ +package com.idormy.sms.forwarder.utils + +import com.idormy.sms.forwarder.entity.CallInfo +import com.idormy.sms.forwarder.entity.ContactInfo +import com.idormy.sms.forwarder.entity.SmsInfo +import com.xuexiang.xaop.annotation.MemoryCache + +object DataProvider { + + //用于占位的空信息 + @JvmStatic + @get:MemoryCache + val emptySmsInfo: List + get() { + val list: MutableList = ArrayList() + for (i in 0..5) { + list.add(SmsInfo()) + } + return list + } + + //用于占位的空信息 + @JvmStatic + @get:MemoryCache + val emptyCallInfo: List + get() { + val list: MutableList = ArrayList() + for (i in 0..5) { + list.add(CallInfo()) + } + return list + } + + //用于占位的空信息 + @JvmStatic + @get:MemoryCache + val emptyContactInfo: List + get() { + val list: MutableList = ArrayList() + for (i in 0..5) { + list.add(ContactInfo()) + } + return list + } +} \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/utils/DbHelper.java b/app/src/main/java/com/idormy/sms/forwarder/utils/DbHelper.java deleted file mode 100644 index 25d1316d..00000000 --- a/app/src/main/java/com/idormy/sms/forwarder/utils/DbHelper.java +++ /dev/null @@ -1,128 +0,0 @@ -package com.idormy.sms.forwarder.utils; - -import android.content.Context; -import android.database.sqlite.SQLiteDatabase; -import android.database.sqlite.SQLiteOpenHelper; -import android.util.Log; - -import com.idormy.sms.forwarder.model.LogTable; -import com.idormy.sms.forwarder.model.RuleTable; -import com.idormy.sms.forwarder.model.SenderTable; - -import java.util.Arrays; -import java.util.List; - -@SuppressWarnings("unused") -public class DbHelper extends SQLiteOpenHelper { - // If you change the database schema, you must increment the database version. - public static final String TAG = "DbHelper"; - public static final int DATABASE_VERSION = 9; - public static final String DATABASE_NAME = "sms_forwarder.db"; - - private static final List SQL_CREATE_ENTRIES = - Arrays.asList( - "CREATE TABLE " + LogTable.LogEntry.TABLE_NAME + " (" + - LogTable.LogEntry._ID + " INTEGER PRIMARY KEY," + - LogTable.LogEntry.COLUMN_NAME_TYPE + " TEXT NOT NULL DEFAULT 'sms'," + - LogTable.LogEntry.COLUMN_NAME_FROM + " TEXT," + - LogTable.LogEntry.COLUMN_NAME_CONTENT + " TEXT," + - LogTable.LogEntry.COLUMN_NAME_SIM_INFO + " TEXT," + - LogTable.LogEntry.COLUMN_NAME_RULE_ID + " INTEGER," + - LogTable.LogEntry.COLUMN_NAME_FORWARD_STATUS + " INTEGER NOT NULL DEFAULT 1," + - LogTable.LogEntry.COLUMN_NAME_FORWARD_RESPONSE + " TEXT NOT NULL DEFAULT 'ok'," + - LogTable.LogEntry.COLUMN_NAME_TIME + " TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP)" - , "CREATE TABLE " + RuleTable.RuleEntry.TABLE_NAME + " (" + - RuleTable.RuleEntry._ID + " INTEGER PRIMARY KEY," + - RuleTable.RuleEntry.COLUMN_NAME_TYPE + " TEXT NOT NULL DEFAULT 'sms'," + - RuleTable.RuleEntry.COLUMN_NAME_FILED + " TEXT," + - RuleTable.RuleEntry.COLUMN_NAME_CHECK + " TEXT," + - RuleTable.RuleEntry.COLUMN_NAME_VALUE + " TEXT," + - RuleTable.RuleEntry.COLUMN_NAME_SENDER_ID + " INTEGER," + - RuleTable.RuleEntry.COLUMN_NAME_TIME + " TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP," + - RuleTable.RuleEntry.COLUMN_SMS_TEMPLATE + " TEXT NOT NULL DEFAULT ''," + - RuleTable.RuleEntry.COLUMN_REGEX_REPLACE + " TEXT NOT NULL DEFAULT ''," + - RuleTable.RuleEntry.COLUMN_NAME_STATUS + " INTEGER NOT NULL DEFAULT 1," + - RuleTable.RuleEntry.COLUMN_NAME_SIM_SLOT + " TEXT NOT NULL DEFAULT 'ALL')" - , "CREATE TABLE " + SenderTable.SenderEntry.TABLE_NAME + " (" + - SenderTable.SenderEntry._ID + " INTEGER PRIMARY KEY," + - SenderTable.SenderEntry.COLUMN_NAME_NAME + " TEXT," + - SenderTable.SenderEntry.COLUMN_NAME_STATUS + " INTEGER NOT NULL DEFAULT 1," + - SenderTable.SenderEntry.COLUMN_NAME_TYPE + " INTEGER NOT NULL DEFAULT 1," + - SenderTable.SenderEntry.COLUMN_NAME_JSON_SETTING + " TEXT," + - SenderTable.SenderEntry.COLUMN_NAME_TIME + " TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP)" - ); - - private static final List SQL_DELETE_ENTRIES = - Arrays.asList( - "DROP TABLE IF EXISTS " + LogTable.LogEntry.TABLE_NAME + " ; " - , "DROP TABLE IF EXISTS " + RuleTable.RuleEntry.TABLE_NAME + " ; " - , "DROP TABLE IF EXISTS " + SenderTable.SenderEntry.TABLE_NAME + " ; " - - ); - - - public DbHelper(Context context) { - super(context, DATABASE_NAME, null, DATABASE_VERSION); - } - - public void onCreate(SQLiteDatabase db) { - for (String createEntries : SQL_CREATE_ENTRIES - ) { - Log.d(TAG, "onCreate:createEntries " + createEntries); - db.execSQL(createEntries); - } - } - - public void delCreateTable(SQLiteDatabase db) { - for (String delCreateEntries : SQL_DELETE_ENTRIES - ) { - Log.d(TAG, "delCreateTable:delCreateEntries " + delCreateEntries); - db.execSQL(delCreateEntries); - } - } - - public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { - if (oldVersion < 2) { //转发日志添加SIM卡槽信息 - String sql = "Alter table " + LogTable.LogEntry.TABLE_NAME + " add column " + LogTable.LogEntry.COLUMN_NAME_SIM_INFO + " TEXT "; - db.execSQL(sql); - } - if (oldVersion < 3) { //转发规则添加SIM卡槽信息 - String sql = "Alter table " + RuleTable.RuleEntry.TABLE_NAME + " add column " + RuleTable.RuleEntry.COLUMN_NAME_SIM_SLOT + " TEXT NOT NULL DEFAULT 'ALL' "; - db.execSQL(sql); - } - if (oldVersion < 4) { //转发日志添加转发状态与返回信息 - String sql = "Alter table " + LogTable.LogEntry.TABLE_NAME + " add column " + LogTable.LogEntry.COLUMN_NAME_FORWARD_STATUS + " INTEGER NOT NULL DEFAULT 1 "; - db.execSQL(sql); - sql = "Alter table " + LogTable.LogEntry.TABLE_NAME + " add column " + LogTable.LogEntry.COLUMN_NAME_FORWARD_RESPONSE + " TEXT NOT NULL DEFAULT 'ok' "; - db.execSQL(sql); - } - if (oldVersion < 5) { //转发规则添加规则自定义信息模板 - String sql = "Alter table " + RuleTable.RuleEntry.TABLE_NAME + " add column " + RuleTable.RuleEntry.COLUMN_SMS_TEMPLATE + " TEXT NOT NULL DEFAULT '' "; - db.execSQL(sql); - } - if (oldVersion < 6) { //增加转发规则与日志的分类 - String sql = "Alter table " + RuleTable.RuleEntry.TABLE_NAME + " add column " + RuleTable.RuleEntry.COLUMN_NAME_TYPE + " TEXT NOT NULL DEFAULT 'sms' "; - db.execSQL(sql); - sql = "Alter table " + LogTable.LogEntry.TABLE_NAME + " add column " + RuleTable.RuleEntry.COLUMN_NAME_TYPE + " TEXT NOT NULL DEFAULT 'sms' "; - db.execSQL(sql); - } - if (oldVersion < 7) { //转发规则添加正则替换内容 - String sql = "Alter table " + RuleTable.RuleEntry.TABLE_NAME + " add column " + RuleTable.RuleEntry.COLUMN_REGEX_REPLACE + " TEXT NOT NULL DEFAULT '' "; - db.execSQL(sql); - } - if (oldVersion < 8) { //更新日志表状态:0=失败,1=待处理,2=成功 - String sql = "update " + LogTable.LogEntry.TABLE_NAME + " set " + LogTable.LogEntry.COLUMN_NAME_FORWARD_STATUS + " = 2 where " + LogTable.LogEntry.COLUMN_NAME_FORWARD_STATUS + " = 1 "; - db.execSQL(sql); - } - if (oldVersion < 9) { //规则/通道状态:0=禁用,1=启用 - String sql = "Alter table " + RuleTable.RuleEntry.TABLE_NAME + " add column " + RuleTable.RuleEntry.COLUMN_NAME_STATUS + " INTEGER NOT NULL DEFAULT 1 "; - db.execSQL(sql); - sql = "update " + SenderTable.SenderEntry.TABLE_NAME + " set " + SenderTable.SenderEntry.COLUMN_NAME_STATUS + " = 1 "; - db.execSQL(sql); - } - } - - public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) { - onUpgrade(db, oldVersion, newVersion); - } -} diff --git a/app/src/main/java/com/idormy/sms/forwarder/utils/Define.java b/app/src/main/java/com/idormy/sms/forwarder/utils/Define.java deleted file mode 100644 index 60aca5b4..00000000 --- a/app/src/main/java/com/idormy/sms/forwarder/utils/Define.java +++ /dev/null @@ -1,49 +0,0 @@ -package com.idormy.sms.forwarder.utils; - -public class Define { - public static final String SP_CONFIG = "forwarder_config"; - public static final String SP_CONFIG_SWITCH_HELP_TIP = "forwarder_config_switch_help_tip"; - - public static final String SP_MSG_KEY_SWITCH_ADD_EXTRA = "tsms_msg_key_switch_add_extra"; - public static final String SP_MSG_KEY_STRING_ADD_EXTRA_DEVICE_NAME = "tsms_msg_key_switch_add_extra_device_name"; - public static final String SP_MSG_KEY_STRING_ENABLE_SMS = "tsms_msg_key_switch_enable_sms"; - public static final String SP_MSG_KEY_STRING_ENABLE_PHONE = "tsms_msg_key_switch_enable_phone"; - public static final String SP_MSG_KEY_STRING_ENABLE_APP_NOTIFY = "tsms_msg_key_switch_enable_app_notify"; - public static final String SP_MSG_KEY_STRING_CANCEL_APP_NOTIFY = "tsms_msg_key_switch_cancel_app_notify"; - public static final String SP_MSG_KEY_STRING_NOT_USER_PRESENT = "tsms_msg_key_switch_not_user_present"; - public static final String SP_MSG_KEY_STRING_ENABLE_EXCLUDE_FROM_RECENTS = "tsms_msg_key_switch_enable_exclude_from_recents"; - public static final String SP_MSG_KEY_STRING_ENABLE_PLAY_SILENCE_MUSIC = "tsms_msg_key_switch_enable_play_silence_music"; - public static final String SP_MSG_KEY_STRING_ENABLE_ONE_PIXEL_ACTIVITY = "tsms_msg_key_switch_enable_one_pixel_activity"; - public static final String SP_MSG_KEY_STRING_ADD_EXTRA_DEVICE_MARK = "tsms_msg_key_string_add_extra_device_mark"; - public static final String SP_MSG_KEY_STRING_ADD_EXTRA_SIM1 = "tsms_msg_key_string_add_extra_sim1"; - public static final String SP_MSG_KEY_STRING_ADD_EXTRA_SIM2 = "tsms_msg_key_string_add_extra_sim2"; - public static final String SP_MSG_KEY_SWITCH_SMS_TEMPLATE = "tsms_msg_key_switch_sms_template"; - public static final String SP_MSG_KEY_STRING_SMS_TEMPLATE = "tsms_msg_key_string_sms_template"; - public static final String SP_MSG_KEY_STRING_BATTERY_STATUS = "tsms_msg_key_string_battery_status"; - public static final String SP_MSG_KEY_STRING_BATTERY_RECEIVER = "tsms_msg_key_switch_battery_receiver"; - public static final String SP_MSG_KEY_STRING_BATTERY_CRON = "tsms_msg_key_switch_battery_cron"; - public static final String SP_MSG_KEY_STRING_BATTERY_CRON_START_TIME = "tsms_msg_key_switch_battery_cron_start_time"; - public static final String SP_MSG_KEY_STRING_BATTERY_CRON_INTERVAL = "tsms_msg_key_switch_battery_cron_interval"; - public static final String SP_MSG_KEY_STRING_BATTERY_LEVEL_CURRENT = "tsms_msg_key_string_battery_level_current"; - public static final String SP_MSG_KEY_STRING_BATTERY_LEVEL_ALARM = "tsms_msg_key_string_battery_level_alarm"; - public static final String SP_MSG_KEY_STRING_BATTERY_LEVEL_MAX = "tsms_msg_key_string_battery_level_max"; - public static final String SP_MSG_KEY_STRING_BATTERY_LEVEL_ONCE = "tsms_msg_key_string_battery_level_once"; - public static final String SP_MSG_KEY_STRING_RETRY_TIMES = "tsms_msg_key_retry_times"; - public static final String SP_MSG_KEY_STRING_DELAY_TIME = "tsms_msg_key_delay_time"; - public static final String SP_MSG_KEY_STRING_ENABLE_HTTP_SERVER = "tsms_msg_key_string_enable_http_server"; - public static final String SP_MSG_KEY_STRING_ENABLE_CALL_TYPE_1 = "tsms_msg_key_string_enable_call_type_1"; - public static final String SP_MSG_KEY_STRING_ENABLE_CALL_TYPE_2 = "tsms_msg_key_string_enable_call_type_2"; - public static final String SP_MSG_KEY_STRING_ENABLE_CALL_TYPE_3 = "tsms_msg_key_string_enable_call_type_3"; - - public static final String SP_MSG = "forwarder_msg"; - public static final String SP_MSG_SET_KEY = "forwarder_msg_set_key"; - - //OkHttp 请求超时时间 - public static final int REQUEST_TIMEOUT_SECONDS = 5; - //HttpServer 服务端口 - public static final int HTTP_SERVER_PORT = 5000; - - //FRPC相关 - public static final String EVENT_UPDATE_CONFIG = "EVENT_UPDATE_CONFIG"; - public static final String EVENT_RUNNING_ERROR = "EVENT_RUNNING_ERROR"; -} diff --git a/app/src/main/java/com/idormy/sms/forwarder/utils/DrawableUtils.kt b/app/src/main/java/com/idormy/sms/forwarder/utils/DrawableUtils.kt new file mode 100644 index 00000000..cb99703b --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/utils/DrawableUtils.kt @@ -0,0 +1,59 @@ +package com.idormy.sms.forwarder.utils + +import android.graphics.drawable.GradientDrawable + +/** + * @author xuexiang + * @since 2019/4/7 下午12:57 + */ +@Suppress("unused") +class DrawableUtils private constructor() { + companion object { + /** + * 矩形 + */ + fun createRectangleDrawable(color: Int, cornerRadius: Float): GradientDrawable { + val gradientDrawable = GradientDrawable() + gradientDrawable.shape = GradientDrawable.RECTANGLE + gradientDrawable.cornerRadius = cornerRadius + gradientDrawable.setColor(color) + return gradientDrawable + } + + /** + * 矩形 + */ + fun createRectangleDrawable(colors: IntArray?, cornerRadius: Float): GradientDrawable { + val gradientDrawable = GradientDrawable() + gradientDrawable.shape = GradientDrawable.RECTANGLE + gradientDrawable.cornerRadius = cornerRadius + gradientDrawable.colors = colors + return gradientDrawable + } + + /** + * 圆形 + */ + fun createOvalDrawable(color: Int): GradientDrawable { + val gradientDrawable = GradientDrawable() + gradientDrawable.shape = GradientDrawable.OVAL + gradientDrawable.setColor(color) + return gradientDrawable + } + + /** + * 圆形 + */ + fun createOvalDrawable(colors: IntArray?): GradientDrawable { + val gradientDrawable = GradientDrawable() + gradientDrawable.shape = GradientDrawable.OVAL + gradientDrawable.colors = colors + return gradientDrawable + } + } + + init { + throw UnsupportedOperationException("Can not be instantiated.") + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/utils/FileUtils.java b/app/src/main/java/com/idormy/sms/forwarder/utils/FileUtils.java deleted file mode 100644 index b70aa3df..00000000 --- a/app/src/main/java/com/idormy/sms/forwarder/utils/FileUtils.java +++ /dev/null @@ -1,253 +0,0 @@ -package com.idormy.sms.forwarder.utils; - -import android.content.Context; -import android.os.Environment; -import android.util.Log; - -import java.io.BufferedReader; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStreamReader; -import java.io.OutputStreamWriter; -import java.io.RandomAccessFile; -import java.nio.charset.StandardCharsets; - -/* - * 文件读写工具类 - * external storage - 外部存储 Environment.getExternalStorageDirectory() SD根目录:/mnt/sdcard/ (6.0后写入需要用户授权) - context.getExternalFilesDir(dir) 路径为:/mnt/sdcard/Android/data/< package name >/files/… - context.getExternalCacheDir() 路径为:/mnt/sdcard//Android/data/< package name >/cach/… - * - internal storage - 内部存储 - context.getFilesDir() 路径是:/data/data/< package name >/files/… - context.getCacheDir() 路径是:/data/data/< package name >/cach/… -*/ -@SuppressWarnings({"UnusedReturnValue", "ResultOfMethodCallIgnored"}) -public class FileUtils { - private static final String TAG = FileUtils.class.getSimpleName(); - - /** - * @return .外部储存sd卡 根路径 - */ - public static String getRootPath() { - // /storage/emulated/0 - return Environment.getExternalStorageDirectory().getAbsolutePath(); - } - - /** - * @param context . - * @return . 外部储存sd卡 :/mnt/sdcard/Android/data/< package name >/files/… - */ - public static String getAppRootPth(Context context) { - // /storage/emulated/0/Android/data/pack_name/files - return context.getExternalFilesDir("").getAbsolutePath(); - } - - /** - * @return .内部存储 - */ - public static String getInternalPath() { - // /data - return Environment.getDataDirectory().getAbsolutePath(); - } - - /** - * @param context . - * @return .内部储存:/data/data/< package name >/files/ - */ - public static String getInternalAppPath(Context context) { - return context.getFilesDir().getAbsolutePath(); - } - - /** - * @param path 路径 - * @param fileName 文件名称 - * @return . - */ - public static boolean createFile(String path, String fileName) { - File file = new File(path + File.separator + fileName); - - //先创建文件夹 保证文件创建成功 - createDirs(path); - - if (!file.exists()) { - try { - file.createNewFile(); - } catch (IOException e) { - e.printStackTrace(); - } - return true; - } else { - // - return false; - } - } - - /** - * @param folder 创建多级文件夹 - * @return . - */ - public static boolean createDirs(String folder) { - File file = new File(folder); - - if (!file.exists()) { - boolean mkdirs = file.mkdirs(); - Log.i(TAG, "createDirs: 不存在文件夹 开始创建" + mkdirs + "--" + folder); - return true; - } else { - Log.i(TAG, "createDirs: 文件夹已存在"); - } - return false; - } - - /** - * =======================================文件读写============================================= - * - * @param content 写入字符串 - * @param path . 目录 - * @param fileName .文件名 - * @param isRewrite 是否覆盖 - */ - //1.RandomAccessFile 读写 - public static boolean writeFileR(String content, String path, String fileName, boolean isRewrite) { - //路径非斜杆结尾 - if (!path.endsWith("/")) path += File.separator; - - File file = new File(path + fileName); - - //文件目录不存在,先创建 - if (!file.exists()) createFile(path, fileName); - - RandomAccessFile randomAccessFile; - try { - randomAccessFile = new RandomAccessFile(file, "rw"); - if (isRewrite) { - randomAccessFile.setLength(content.length()); - randomAccessFile.seek(0); - } else { - randomAccessFile.seek(randomAccessFile.length()); - } - randomAccessFile.write(content.getBytes()); - randomAccessFile.close(); - return true; - } catch (IOException e) { - e.printStackTrace(); - return false; - } - } - - public static boolean writeFileR(String content, String path, String fileName) { - return writeFileR(content, path, fileName, false); - } - - /** - * 读文件 - * - * @param path . - * @param fileName . - * @return . - */ - public static String readFileR(String path, String fileName) { - //路径非斜杆结尾 - if (!path.endsWith("/")) path += File.separator; - - File file = new File(path + fileName); - if (!file.exists()) { - Log.i(TAG, "readFileR: return null"); - return null; - } - - StringBuilder buffer = new StringBuilder(); - try { - RandomAccessFile randomAccessFile = new RandomAccessFile(file, "rw"); - randomAccessFile.seek(0); - byte[] buf = new byte[(int) randomAccessFile.length()]; - if (randomAccessFile.read(buf) != -1) { - buffer.append(new String(buf)); - Log.i(TAG, "readFileR: length" + randomAccessFile.length()); - //buffer.append(new String(buf, StandardCharsets.UTF_8)); - } - randomAccessFile.close(); - } catch (IOException e) { - Log.i(TAG, "readFileR: " + e.getMessage()); - e.printStackTrace(); - } - - return buffer.toString(); - } - - /** - * 读文件 - * - * @param path .文件路径 - * @param name .名称 - * @return . - */ - public static String readFileI(String path, String name) { - //路径非斜杆结尾 - if (!path.endsWith("/")) path += File.separator; - - //默认编码格式 StandardCharsets.UTF_8; - File file = new File(path, name); - if (!file.exists()) { - return null; - } - StringBuilder builder = new StringBuilder(); - try { - BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(file), StandardCharsets.UTF_8)); - String line; - while ((line = reader.readLine()) != null) { - builder.append(line); - builder.append("\n"); - } - reader.close(); - } catch (IOException e) { - e.printStackTrace(); - } - return builder.toString(); - - } - - /** - * 写入文件 - * - * @param content 内容. - * @param path 目录. - * @param fileName 文件名 . - */ - public static void writeFileO(String content, String path, String fileName) { - writeFileO(content, path, fileName, false); - } - - /** - * @param content . - * @param path . - * @param fileName . - * @param isReWrite .是否追加 - */ - public static void writeFileO(String content, String path, String fileName, boolean isReWrite) { - //路径非斜杆结尾 - if (!path.endsWith("/")) path += File.separator; - - File file = new File(path + fileName); - - //文件目录不存在,先创建 - if (!file.exists()) createFile(path, fileName); - - try { - FileOutputStream ops = new FileOutputStream(file, isReWrite); - OutputStreamWriter opsw = new OutputStreamWriter(ops, StandardCharsets.UTF_8); - // byte[] bytes = content.getBytes(); - opsw.write(content); - opsw.close(); - ops.close(); - } catch (IOException e) { - e.printStackTrace(); - } - } - -} \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/utils/FrpcUtils.kt b/app/src/main/java/com/idormy/sms/forwarder/utils/FrpcUtils.kt new file mode 100644 index 00000000..4f275dd2 --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/utils/FrpcUtils.kt @@ -0,0 +1,51 @@ +package com.idormy.sms.forwarder.utils + +import android.app.ActivityManager +import android.content.Context +import io.reactivex.Completable +import io.reactivex.Observable +import io.reactivex.ObservableEmitter +import java.io.BufferedReader +import java.io.InputStreamReader +import java.util.concurrent.TimeUnit + +@Suppress("DEPRECATION", "MemberVisibilityCanBePrivate") +class FrpcUtils private constructor() { + + companion object { + + fun waitService(serviceName: String, context: Context): Completable { + return Completable.fromObservable( + Observable.interval(3, 1, TimeUnit.SECONDS) + .takeUntil { isServiceRunning(serviceName, context) } + ) + } + + fun isServiceRunning(serviceName: String, context: Context): Boolean { + val am = context.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager + val runningServices = am.getRunningServices(Int.MAX_VALUE) //获取运行的服务,参数表示最多返回的数量 + for (runningServiceInfo in runningServices) { + val className = runningServiceInfo.service.className + if (className == serviceName) { + return true + } + } + return false + } + + fun getStringFromRaw(context: Context, rawName: Int): Observable { + return Observable.create { emitter: ObservableEmitter -> + val reader = BufferedReader(InputStreamReader(context.resources.openRawResource(rawName))) + var line: String? + val result = StringBuilder() + while (reader.readLine().also { line = it } != null) { + result.append(line).append("\n") + } + reader.close() + emitter.onNext(result.toString()) + emitter.onComplete() + } + } + + } +} \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/utils/HistoryUtils.kt b/app/src/main/java/com/idormy/sms/forwarder/utils/HistoryUtils.kt new file mode 100644 index 00000000..8a280e30 --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/utils/HistoryUtils.kt @@ -0,0 +1,274 @@ +package com.idormy.sms.forwarder.utils + +import android.content.Context +import android.os.Parcelable +import com.tencent.mmkv.MMKV + +/** + * 转发历史工具类 + * + * @author pppscn + * @since 2022年5月9日 + */ +@Suppress("PropertyName", "UNCHECKED_CAST", "MemberVisibilityCanBePrivate", "unused") +class HistoryUtils private constructor() { + + companion object { + private var sMMKV: MMKV? = null + + /** + * 初始化 + * + * @param context + */ + fun init(context: Context) { + MMKV.initialize(context.applicationContext) + sMMKV = MMKV.mmkvWithID("History") + } + + fun getsMMKV(): MMKV? { + if (sMMKV == null) { + sMMKV = MMKV.mmkvWithID("History") + } + return sMMKV + } + //=======================================键值保存==================================================// + /** + * 保存键值 + * + * @param key + * @param value + * @return + */ + fun put(key: String?, value: Any?): Boolean { + when (value) { + is Int -> { + return getsMMKV()!!.encode(key, (value as Int?)!!) + } + is Float -> { + return getsMMKV()!!.encode(key, (value as Float?)!!) + } + is String -> { + return getsMMKV()!!.encode(key, value as String?) + } + is Boolean -> { + return getsMMKV()!!.encode(key, (value as Boolean?)!!) + } + is Long -> { + return getsMMKV()!!.encode(key, (value as Long?)!!) + } + is Double -> { + return getsMMKV()!!.encode(key, (value as Double?)!!) + } + is Parcelable -> { + return getsMMKV()!!.encode(key, value as Parcelable?) + } + is ByteArray -> { + return getsMMKV()!!.encode(key, value as ByteArray?) + } + is Set<*> -> { + return getsMMKV()!!.encode(key, value as Set?) + } + else -> return false + } + } + //=======================================键值获取==================================================// + /** + * 获取键值 + * + * @param key + * @param defaultValue + * @return + */ + operator fun get(key: String?, defaultValue: Any?): Any? { + when (defaultValue) { + is Int -> { + return getsMMKV()!! + .decodeInt(key, (defaultValue as Int?)!!) + } + is Float -> { + return getsMMKV()!! + .decodeFloat(key, (defaultValue as Float?)!!) + } + is String -> { + return getsMMKV()!!.decodeString(key, defaultValue as String?) + } + is Boolean -> { + return getsMMKV()!! + .decodeBool(key, (defaultValue as Boolean?)!!) + } + is Long -> { + return getsMMKV()!! + .decodeLong(key, (defaultValue as Long?)!!) + } + is Double -> { + return getsMMKV()!! + .decodeDouble(key, (defaultValue as Double?)!!) + } + is ByteArray -> { + return getsMMKV()!!.decodeBytes(key) + } + is Set<*> -> { + return getsMMKV()!!.decodeStringSet(key, defaultValue as Set?) + } + else -> return null + } + } + + /** + * 根据key获取boolean值 + * + * @param key + * @param defValue + * @return + */ + fun getBoolean(key: String?, defValue: Boolean): Boolean { + try { + return getsMMKV()!!.getBoolean(key, defValue) + } catch (e: Exception) { + e.printStackTrace() + } + return defValue + } + + /** + * 根据key获取long值 + * + * @param key + * @param defValue + * @return + */ + fun getLong(key: String?, defValue: Long): Long { + try { + return getsMMKV()!!.getLong(key, defValue) + } catch (e: Exception) { + e.printStackTrace() + } + return defValue + } + + /** + * 根据key获取float值 + * + * @param key + * @param defValue + * @return + */ + fun getFloat(key: String?, defValue: Float): Float { + try { + return getsMMKV()!!.getFloat(key, defValue) + } catch (e: Exception) { + e.printStackTrace() + } + return defValue + } + + /** + * 根据key获取String值 + * + * @param key + * @param defValue + * @return + */ + fun getString(key: String?, defValue: String?): String? { + try { + return getsMMKV()!!.getString(key, defValue) + } catch (e: Exception) { + e.printStackTrace() + } + return defValue + } + + /** + * 根据key获取int值 + * + * @param key + * @param defValue + * @return + */ + fun getInt(key: String?, defValue: Int): Int { + try { + return getsMMKV()!!.getInt(key, defValue) + } catch (e: Exception) { + e.printStackTrace() + } + return defValue + } + + /** + * 根据key获取double值 + * + * @param key + * @param defValue + * @return + */ + fun getDouble(key: String?, defValue: Double): Double { + try { + return getsMMKV()!!.decodeDouble(key, defValue) + } catch (e: Exception) { + e.printStackTrace() + } + return defValue + } + + /** + * 获取对象 + * + * @param key + * @param tClass 类型 + * @param + * @return + */ + fun getObject(key: String?, tClass: Class?): T? { + return getsMMKV()!!.decodeParcelable(key, tClass) + } + + /** + * 获取对象 + * + * @param key + * @param tClass 类型 + * @param + * @return + */ + fun getObject(key: String?, tClass: Class?, defValue: T): T? { + try { + return getsMMKV()!!.decodeParcelable(key, tClass, defValue) + } catch (e: Exception) { + e.printStackTrace() + } + return defValue + } + + /** + * 判断键值对是否存在 + * + * @param key 键 + * @return 键值对是否存在 + */ + fun containsKey(key: String?): Boolean { + return getsMMKV()!!.containsKey(key) + } + + /** + * 清除指定键值对 + * + * @param key 键 + */ + fun remove(key: String?) { + getsMMKV()!!.remove(key).apply() + } + + /** + * 清除所有键值对 + */ + fun clear() { + getsMMKV()!!.clearAll() + } + + } + + init { + throw UnsupportedOperationException("u can't instantiate me...") + } +} \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/utils/HttpI.java b/app/src/main/java/com/idormy/sms/forwarder/utils/HttpI.java deleted file mode 100644 index ef0b0f29..00000000 --- a/app/src/main/java/com/idormy/sms/forwarder/utils/HttpI.java +++ /dev/null @@ -1,95 +0,0 @@ -package com.idormy.sms.forwarder.utils; - - -import androidx.annotation.NonNull; - -import java.io.File; -import java.io.Serializable; -import java.util.Map; - -/** - * app版本更新接口 - */ -@SuppressWarnings("unused") -public interface HttpI extends Serializable { - /** - * 异步get - * - * @param url get请求地址 - * @param params get参数 - * @param callBack 回调 - */ - void asyncGet(@NonNull String url, @NonNull Map params, @NonNull Callback callBack); - - - /** - * 异步post - * - * @param url post请求地址 - * @param params post请求参数 - * @param callBack 回调 - */ - void asyncPost(@NonNull String url, @NonNull Map params, @NonNull Callback callBack); - - /** - * 下载 - * - * @param url 下载地址 - * @param path 文件保存路径 - * @param fileName 文件名称 - * @param callback 回调 - */ - void download(@NonNull String url, @NonNull String path, @NonNull String fileName, @NonNull FileCallback callback); - - /** - * 下载回调 - */ - interface FileCallback { - /** - * 进度 - * - * @param progress 进度0.00 - 0.50 - 1.00 - * @param total 文件总大小 单位字节 - */ - void onProgress(float progress, long total); - - /** - * 错误回调 - * - * @param error 错误提示 - */ - void onError(String error); - - /** - * 结果回调 - * - * @param file 下载好的文件 - */ - void onResponse(File file); - - /** - * 请求之前 - */ - void onBefore(); - } - - /** - * 网络请求回调 - */ - interface Callback { - /** - * 结果回调 - * - * @param result 结果 - */ - void onResponse(String result); - - /** - * 错误回调 - * - * @param error 错误提示 - */ - void onError(String error); - } -} - diff --git a/app/src/main/java/com/idormy/sms/forwarder/utils/HttpServerUtils.kt b/app/src/main/java/com/idormy/sms/forwarder/utils/HttpServerUtils.kt new file mode 100644 index 00000000..10586374 --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/utils/HttpServerUtils.kt @@ -0,0 +1,245 @@ +package com.idormy.sms.forwarder.utils + + +import android.text.TextUtils +import android.util.Base64 +import com.google.gson.Gson +import com.idormy.sms.forwarder.core.Core +import com.idormy.sms.forwarder.entity.CloneInfo +import com.idormy.sms.forwarder.server.model.BaseRequest +import com.xuexiang.xutil.app.AppUtils +import com.yanzhenjie.andserver.error.HttpException +import java.net.URLEncoder +import java.nio.charset.StandardCharsets +import javax.crypto.Mac +import javax.crypto.spec.SecretKeySpec + +/** + * HttpServer工具类 + */ +class HttpServerUtils private constructor() { + + companion object { + + //是否启用HttpServer开机自启 + @JvmStatic + var enableServerAutorun: Boolean + get() = MMKVUtils.getBoolean(SP_ENABLE_SERVER_AUTORUN, false) + set(enableServerAutorun) { + MMKVUtils.put(SP_ENABLE_SERVER_AUTORUN, enableServerAutorun) + } + + //服务端签名密钥 + @JvmStatic + var serverSignKey: String? + get() = MMKVUtils.getString(SP_SERVER_SIGN_KEY, "") + set(serverSignKey) { + MMKVUtils.put(SP_SERVER_SIGN_KEY, serverSignKey) + } + + //服务地址 + @JvmStatic + var serverAddress: String? + get() = MMKVUtils.getString(SP_SERVER_ADDRESS, "") + set(clientSignKey) { + MMKVUtils.put(SP_SERVER_ADDRESS, clientSignKey) + } + + //客户端签名密钥 + @JvmStatic + var clientSignKey: String? + get() = MMKVUtils.getString(SP_CLIENT_SIGN_KEY, "") + set(clientSignKey) { + MMKVUtils.put(SP_CLIENT_SIGN_KEY, clientSignKey) + } + + //是否启用一键克隆 + @JvmStatic + var enableApiClone: Boolean + get() = MMKVUtils.getBoolean(SP_ENABLE_API_CLONE, false) + set(enableApiClone) { + MMKVUtils.put(SP_ENABLE_API_CLONE, enableApiClone) + } + + //是否启用远程发短信 + @JvmStatic + var enableApiSmsSend: Boolean + get() = MMKVUtils.getBoolean(SP_ENABLE_API_SMS_SEND, false) + set(enableApiSendSms) { + MMKVUtils.put(SP_ENABLE_API_SMS_SEND, enableApiSendSms) + } + + //是否启用远程查短信 + @JvmStatic + var enableApiSmsQuery: Boolean + get() = MMKVUtils.getBoolean(SP_ENABLE_API_SMS_QUERY, false) + set(enableApiQuerySms) { + MMKVUtils.put(SP_ENABLE_API_SMS_QUERY, enableApiQuerySms) + } + + //是否启用远程查通话 + @JvmStatic + var enableApiCallQuery: Boolean + get() = MMKVUtils.getBoolean(SP_ENABLE_API_CALL_QUERY, false) + set(enableApiQueryCall) { + MMKVUtils.put(SP_ENABLE_API_CALL_QUERY, enableApiQueryCall) + } + + //是否启用远程查话簿 + @JvmStatic + var enableApiContactQuery: Boolean + get() = MMKVUtils.getBoolean(SP_ENABLE_API_CONTACT_QUERY, false) + set(enableApiQueryLinkman) { + MMKVUtils.put(SP_ENABLE_API_CONTACT_QUERY, enableApiQueryLinkman) + } + + //是否启用远程查电量 + @JvmStatic + var enableApiBatteryQuery: Boolean + get() = MMKVUtils.getBoolean(SP_ENABLE_API_BATTERY_QUERY, false) + set(enableApiQueryBattery) { + MMKVUtils.put(SP_ENABLE_API_BATTERY_QUERY, enableApiQueryBattery) + } + + //计算签名 + fun calcSign(timestamp: String, signSecret: String): String { + val stringToSign = "$timestamp\n" + signSecret + val mac = Mac.getInstance("HmacSHA256") + mac.init(SecretKeySpec(signSecret.toByteArray(StandardCharsets.UTF_8), "HmacSHA256")) + val signData = mac.doFinal(stringToSign.toByteArray(StandardCharsets.UTF_8)) + return URLEncoder.encode(String(Base64.encode(signData, Base64.NO_WRAP)), "UTF-8") + } + + //校验签名 + @Throws(HttpException::class) + fun checkSign(req: BaseRequest<*>) { + val signSecret = serverSignKey + if (TextUtils.isEmpty(signSecret)) return + + if (TextUtils.isEmpty(req.sign)) throw HttpException(500, "服务端启用签名密钥,sign节点必传") + if (req.timestamp == 0L) throw HttpException(500, "服务端启用签名密钥,timestamp节点必传") + + val timestamp = System.currentTimeMillis() + val diffTime = kotlin.math.abs(timestamp - req.timestamp) + if (diffTime > 3600000L) { + throw HttpException(500, "timestamp校验失败,与服务器时间($timestamp)误差不能超过1小时(diffTime=$diffTime)") + } + + val sign = calcSign(req.timestamp.toString(), signSecret.toString()) + if (sign != req.sign) throw HttpException(500, "签名校验失败") + } + + //判断版本是否一致 + @Throws(HttpException::class) + fun compareVersion(cloneInfo: CloneInfo) { + val versionCodeRequest = cloneInfo.versionCode + if (versionCodeRequest == 0) throw HttpException(500, "version_code节点必传") + val versionCodeLocal = AppUtils.getAppVersionCode().toString().substring(1) + if (!versionCodeRequest.toString().endsWith(versionCodeLocal)) throw HttpException(500, "客户端与服务端的App版本不一致") + } + + //导出设置 + fun exportSettings(): CloneInfo { + val cloneInfo = CloneInfo() + cloneInfo.versionCode = AppUtils.getAppVersionCode() + cloneInfo.versionName = AppUtils.getAppVersionName() + cloneInfo.enableSms = SettingUtils.enableSms + cloneInfo.enablePhone = SettingUtils.enablePhone + cloneInfo.callType1 = SettingUtils.enableCallType1 + cloneInfo.callType2 = SettingUtils.enableCallType2 + cloneInfo.callType3 = SettingUtils.enableCallType3 + cloneInfo.enableAppNotify = SettingUtils.enableAppNotify + cloneInfo.cancelAppNotify = SettingUtils.enableCancelAppNotify + cloneInfo.enableNotUserPresent = SettingUtils.enableNotUserPresent + cloneInfo.duplicateMessagesLimits = SettingUtils.duplicateMessagesLimits + cloneInfo.enableBatteryReceiver = SettingUtils.enableBatteryReceiver + cloneInfo.batteryLevelMin = SettingUtils.batteryLevelMin + cloneInfo.batteryLevelMax = SettingUtils.batteryLevelMax + cloneInfo.batteryLevelOnce = SettingUtils.batteryLevelOnce + cloneInfo.enableBatteryCron = SettingUtils.enableBatteryCron + cloneInfo.batteryCronStartTime = SettingUtils.batteryCronStartTime + cloneInfo.batteryCronInterval = SettingUtils.batteryCronInterval + cloneInfo.enableExcludeFromRecents = SettingUtils.enableExcludeFromRecents + cloneInfo.enablePlaySilenceMusic = SettingUtils.enablePlaySilenceMusic + cloneInfo.requestRetryTimes = SettingUtils.requestRetryTimes + cloneInfo.requestDelayTime = SettingUtils.requestDelayTime + cloneInfo.requestTimeout = SettingUtils.requestTimeout + cloneInfo.notifyContent = SettingUtils.notifyContent + cloneInfo.enableSmsTemplate = SettingUtils.enableSmsTemplate + cloneInfo.smsTemplate = SettingUtils.smsTemplate + cloneInfo.enableHelpTip = SettingUtils.enableHelpTip + cloneInfo.senderList = Core.sender.all + cloneInfo.ruleList = Core.rule.all + + return cloneInfo + } + + //还原设置 + fun restoreSettings(cloneInfo: CloneInfo): Boolean { + return try { + //应用配置 + SettingUtils.enableSms = cloneInfo.enableSms + SettingUtils.enablePhone = cloneInfo.enablePhone + SettingUtils.enableCallType1 = cloneInfo.callType1 + SettingUtils.enableCallType2 = cloneInfo.callType2 + SettingUtils.enableCallType3 = cloneInfo.callType3 + SettingUtils.enableAppNotify = cloneInfo.enableAppNotify + SettingUtils.enableCancelAppNotify = cloneInfo.cancelAppNotify + SettingUtils.enableNotUserPresent = cloneInfo.enableNotUserPresent + SettingUtils.duplicateMessagesLimits = cloneInfo.duplicateMessagesLimits + SettingUtils.enableBatteryReceiver = cloneInfo.enableBatteryReceiver + SettingUtils.batteryLevelMin = cloneInfo.batteryLevelMin + SettingUtils.batteryLevelMax = cloneInfo.batteryLevelMax + SettingUtils.batteryLevelOnce = cloneInfo.batteryLevelOnce + SettingUtils.enableBatteryCron = cloneInfo.enableBatteryCron + SettingUtils.batteryCronStartTime = cloneInfo.batteryCronStartTime + SettingUtils.batteryCronInterval = cloneInfo.batteryCronInterval + SettingUtils.enableExcludeFromRecents = cloneInfo.enableExcludeFromRecents + SettingUtils.enablePlaySilenceMusic = cloneInfo.enablePlaySilenceMusic + SettingUtils.requestRetryTimes = cloneInfo.requestRetryTimes + SettingUtils.requestDelayTime = cloneInfo.requestDelayTime + SettingUtils.requestTimeout = cloneInfo.requestTimeout + SettingUtils.notifyContent = cloneInfo.notifyContent + SettingUtils.enableSmsTemplate = cloneInfo.enableSmsTemplate + SettingUtils.smsTemplate = cloneInfo.smsTemplate + SettingUtils.enableHelpTip = cloneInfo.enableHelpTip + //删除发送通道、转发规则、转发日志 + Core.sender.deleteAll() + //发送通道 + for (sender in cloneInfo.senderList!!) { + Core.sender.insert(sender) + } + //转发规则 + for (rule in cloneInfo.ruleList!!) { + Core.rule.insert(rule) + } + true + } catch (e: Exception) { + e.printStackTrace() + throw HttpException(500, e.message) + //false + } + } + + //返回统一结构报文 + fun response(output: Any?): String { + val resp: MutableMap = mutableMapOf() + if (output is String && output != "success") { + resp["code"] = HTTP_FAILURE_CODE + resp["msg"] = output + } else { + resp["code"] = HTTP_SUCCESS_CODE + resp["msg"] = "success" + if (output != null) resp["data"] = output + } + + val timestamp = System.currentTimeMillis() + resp["timestamp"] = timestamp + if (!TextUtils.isEmpty(serverSignKey)) { + resp["sign"] = calcSign(timestamp.toString(), serverSignKey.toString()) + } + + return Gson().toJson(resp) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/utils/HttpUtils.java b/app/src/main/java/com/idormy/sms/forwarder/utils/HttpUtils.java deleted file mode 100644 index f3229bdf..00000000 --- a/app/src/main/java/com/idormy/sms/forwarder/utils/HttpUtils.java +++ /dev/null @@ -1,188 +0,0 @@ -package com.idormy.sms.forwarder.utils; - -import android.annotation.SuppressLint; -import android.content.Context; -import android.util.Log; - -import androidx.annotation.NonNull; - -import com.alibaba.fastjson.JSON; -import com.alibaba.fastjson.TypeReference; -import com.hjq.toast.ToastUtils; -import com.idormy.sms.forwarder.sender.RetryIntercepter; - -import java.io.IOException; -import java.net.URLEncoder; -import java.util.Map; -import java.util.Objects; -import java.util.concurrent.TimeUnit; - -import okhttp3.Call; -import okhttp3.Callback; -import okhttp3.MediaType; -import okhttp3.OkHttpClient; -import okhttp3.Request; -import okhttp3.RequestBody; -import okhttp3.Response; - -@SuppressWarnings("unchecked") -public class HttpUtils { - private static OkHttpClient client; - private static OkHttpClient retryClient; - private static final String TAG = "HttpUtils"; - private static Boolean hasInit = false; - @SuppressLint("StaticFieldLeak") - private static Context context; - private static final MediaType MEDIA_TYPE_JSON = MediaType.parse("application/json;charset=utf-8"); - - @SuppressLint("HandlerLeak") - public static void init(Context context) { - //noinspection SynchronizeOnNonFinalField - synchronized (hasInit) { - if (hasInit) return; - - hasInit = true; - HttpUtils.context = context; - OkHttpClient.Builder clientBuilder = new OkHttpClient.Builder(); - //设置读取超时时间 - clientBuilder - .readTimeout(Define.REQUEST_TIMEOUT_SECONDS, TimeUnit.SECONDS) - .writeTimeout(Define.REQUEST_TIMEOUT_SECONDS, TimeUnit.SECONDS) - .connectTimeout(Define.REQUEST_TIMEOUT_SECONDS, TimeUnit.SECONDS); - client = clientBuilder.build(); - //设置重试拦截器 - int retryTimes = SettingUtils.getRetryTimes(); - int delayTime = SettingUtils.getDelayTime(); - if (retryTimes > 0) - clientBuilder.addInterceptor(new RetryIntercepter.Builder().executionCount(retryTimes).retryInterval(delayTime).build()); - retryClient = clientBuilder.build(); - } - } - - public static void asyncGet(String tag, String url, Object param, Lamda.Consumer onResponse, Lamda.Consumer onFailure, boolean doRetry) { - StringBuilder resUrl = appendQueryStr(tag, url, param); - Request request = new Request.Builder().url(resUrl.toString()).get().build(); - Lamda.Func func = call -> { - call.enqueue(new Callback0(tag, onResponse, onFailure)); - return null; - }; - callAndCatch(tag, request, func, doRetry); - } - - public static void asyncPostJson(String tag, String url, Object param, Lamda.Consumer onResponse, Lamda.Consumer onFailure, boolean doRetry) { - String jsonString = JSON.toJSONString(param); - Request request = new Request.Builder().url(url).post(RequestBody.create(jsonString, MEDIA_TYPE_JSON)).build(); - Lamda.Func func = call -> { - call.enqueue(new Callback0(tag, onResponse, onFailure)); - return null; - }; - callAndCatch(tag, request, func, doRetry); - } - - public static String postJson(String tag, String url, Object param, boolean doRetry) { - String jsonString = JSON.toJSONString(param); - Request request = new Request.Builder().url(url).post(RequestBody.create(jsonString, MEDIA_TYPE_JSON)).build(); - Lamda.Func func = call -> { - Response response = call.execute(); - if (response.code() == 200) { - return Objects.requireNonNull(response.body()).toString(); - } - return null; - }; - return callAndCatch(tag, request, func, doRetry); - } - - public static String get(String tag, String url, Object param, boolean doRetry) { - StringBuilder resUrl = appendQueryStr(tag, url, param); - Request request = new Request.Builder().url(resUrl.toString()).get().build(); - Lamda.Func func = call -> { - Response response = call.execute(); - if (response.code() == 200) { - return Objects.requireNonNull(response.body()).toString(); - } - return null; - }; - return callAndCatch(tag, request, func, doRetry); - } - - public static void Toast(String Tag, String data) { - Log.i(Tag, data); - try { - ToastUtils.delayedShow(Tag + "-" + data, 3000); - } catch (Exception e) { - e.printStackTrace(); - } - } - - @SuppressWarnings("deprecation") - @NonNull - public static StringBuilder appendQueryStr(String tag, String url, Object param) { - StringBuilder resUrl = new StringBuilder(url); - if (!url.contains("?")) { - resUrl.append("?"); - } else { - resUrl.append("&"); - } - Map paramMap = param instanceof Map ? (Map) param - : JSON.parseObject(JSON.toJSONString(param), new TypeReference>() { - }.getType()); - for (Map.Entry entry : paramMap.entrySet()) { - if (entry.getValue() != null) { - resUrl.append(URLEncoder.encode(entry.getKey())).append("=").append(URLEncoder.encode(entry.getValue())); - } - } - Log.i(tag, "url:" + resUrl); - return resUrl; - } - - public static String callAndCatch(String tag, Request request, Lamda.Func func, boolean doRetry) { - try { - Call call = (doRetry ? retryClient : client).newCall(request); - return func.execute(call); - } catch (Exception e) { - Toast(tag, "请求失败:" + e.getMessage()); - Log.e(tag, "请求失败:" + e.getMessage()); - e.printStackTrace(); - } - return null; - } - - @SuppressWarnings("RedundantThrows") - public static class Callback0 implements Callback { - public Callback0(String tag, Lamda.Consumer onResponse, Lamda.Consumer onFailure) { - this.tag = tag; - this.onResponse = onResponse; - this.onFailure = onFailure; - } - - public Callback0(String tag, Lamda.Consumer onResponse) { - this.tag = tag; - this.onResponse = onResponse; - } - - private final String tag; - private final Lamda.Consumer onResponse; - private Lamda.Consumer onFailure; - - @Override - public void onFailure(@NonNull Call call, @NonNull final IOException e) { - Toast(tag, "onFailure:" + e.getMessage()); - Log.d(tag, "onFailure:" + e.getMessage()); - if (onFailure != null) { - onFailure.executeThrowRunTimeExcp(e); - } - } - - @Override - public void onResponse(@NonNull Call call, @NonNull Response response) throws IOException { - Log.d(tag, "onResponse:" + response.code() + ":" + JSON.toJSONString(Objects.requireNonNull(response.body()))); - if (onResponse != null) - onResponse.executeThrowRunTimeExcp(response); - } - - public String getTag() { - return tag; - } - } - -} diff --git a/app/src/main/java/com/idormy/sms/forwarder/utils/InitUtils.java b/app/src/main/java/com/idormy/sms/forwarder/utils/InitUtils.java deleted file mode 100644 index ae412b6c..00000000 --- a/app/src/main/java/com/idormy/sms/forwarder/utils/InitUtils.java +++ /dev/null @@ -1,26 +0,0 @@ -package com.idormy.sms.forwarder.utils; - -import android.annotation.SuppressLint; -import android.content.Context; -import android.util.Log; - -@SuppressWarnings("SynchronizeOnNonFinalField") -public class InitUtils { - static Boolean hasInit = false; - private static final String TAG = "InitUtils"; - @SuppressLint("StaticFieldLeak") - private static Context context = null; - - public static void init(Context context1) { - Log.d(TAG, "SmsForwarder init"); - //noinspection SynchronizeOnNonFinalField - synchronized (hasInit) { - if (hasInit) return; - hasInit = true; - context = context1; - Log.d(TAG, "init context"); - SettingUtils.init(context); - } - } - -} diff --git a/app/src/main/java/com/idormy/sms/forwarder/utils/KeepAliveUtils.java b/app/src/main/java/com/idormy/sms/forwarder/utils/KeepAliveUtils.java deleted file mode 100644 index 77cea331..00000000 --- a/app/src/main/java/com/idormy/sms/forwarder/utils/KeepAliveUtils.java +++ /dev/null @@ -1,56 +0,0 @@ -package com.idormy.sms.forwarder.utils; - -import android.annotation.SuppressLint; -import android.app.Activity; -import android.content.Context; -import android.content.Intent; -import android.content.pm.ResolveInfo; -import android.net.Uri; -import android.os.Build; -import android.os.PowerManager; -import android.provider.Settings; - -import androidx.annotation.RequiresApi; - -import com.hjq.toast.ToastUtils; -import com.idormy.sms.forwarder.R; - -public class KeepAliveUtils { - - public static boolean isIgnoreBatteryOptimization(Activity activity) { - //安卓6.0以下没有忽略电池优化 - if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.M) { - return true; - } - try { - PowerManager powerManager = (PowerManager) activity.getSystemService(Context.POWER_SERVICE); - if (powerManager != null) { - return powerManager.isIgnoringBatteryOptimizations(activity.getPackageName()); - } else { - return true; - } - } catch (Exception e) { - ToastUtils.show(R.string.unsupport); - return false; - } - } - - @RequiresApi(api = Build.VERSION_CODES.M) - public static void ignoreBatteryOptimization(Activity activity) { - try { - if (isIgnoreBatteryOptimization(activity)) { - return; - } - @SuppressLint("BatteryLife") Intent intent = new Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS); - intent.setData(Uri.parse("package:" + activity.getPackageName())); - ResolveInfo resolveInfo = activity.getPackageManager().resolveActivity(intent, 0); - if (resolveInfo != null) { - activity.startActivity(intent); - } else { - ToastUtils.show(R.string.unsupport); - } - } catch (Exception e) { - ToastUtils.show(R.string.unsupport); - } - } -} diff --git a/app/src/main/java/com/idormy/sms/forwarder/utils/KeepAliveUtils.kt b/app/src/main/java/com/idormy/sms/forwarder/utils/KeepAliveUtils.kt new file mode 100644 index 00000000..1e6dddda --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/utils/KeepAliveUtils.kt @@ -0,0 +1,51 @@ +package com.idormy.sms.forwarder.utils + +import android.annotation.SuppressLint +import android.app.Activity +import android.content.Context +import android.content.Intent +import android.content.pm.ResolveInfo +import android.net.Uri +import android.os.Build +import android.os.PowerManager +import android.provider.Settings +import androidx.annotation.RequiresApi +import com.idormy.sms.forwarder.R + +class KeepAliveUtils private constructor() { + + companion object { + fun isIgnoreBatteryOptimization(activity: Activity): Boolean { + //安卓6.0以下没有忽略电池优化 + return if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { + true + } else try { + val powerManager: PowerManager = activity.getSystemService(Context.POWER_SERVICE) as PowerManager + powerManager.isIgnoringBatteryOptimizations(activity.packageName) + } catch (e: Exception) { + XToastUtils.error(R.string.unsupport) + false + } + } + + @RequiresApi(api = Build.VERSION_CODES.M) + fun ignoreBatteryOptimization(activity: Activity) { + try { + if (isIgnoreBatteryOptimization(activity)) { + return + } + @SuppressLint("BatteryLife") val intent = Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS) + intent.data = Uri.parse("package:" + activity.packageName) + val resolveInfo: ResolveInfo? = activity.packageManager.resolveActivity(intent, 0) + if (resolveInfo != null) { + activity.startActivity(intent) + } else { + XToastUtils.error(R.string.unsupport) + } + } catch (e: Exception) { + XToastUtils.error(R.string.unsupport) + } + } + + } +} \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/utils/Lamda.java b/app/src/main/java/com/idormy/sms/forwarder/utils/Lamda.java deleted file mode 100644 index 78af2ccd..00000000 --- a/app/src/main/java/com/idormy/sms/forwarder/utils/Lamda.java +++ /dev/null @@ -1,41 +0,0 @@ -package com.idormy.sms.forwarder.utils; - -import java.util.Objects; - -public class Lamda { - @SuppressWarnings("RedundantThrows") - public interface Consumer extends Func { - void accept(T t) throws Exception; - - default T execute(T t) throws Exception { - accept(t); - return t; - } - } - - @SuppressWarnings("UnusedReturnValue") - public interface Func { - R execute(T t) throws Exception; - - default R executeThrowRunTimeExcp(T t) { - try { - return execute(t); - } catch (Exception e) { - throw new RuntimeException(e); - } - } - - default R executeIgnoreExcp(T t) { - try { - return execute(t); - } catch (Exception ignored) { - } - return null; - } - - default Func andThen(Func after) { - Objects.requireNonNull(after); - return (T t) -> after.execute(execute(t)); - } - } -} diff --git a/app/src/main/java/com/idormy/sms/forwarder/utils/LogUtils.java b/app/src/main/java/com/idormy/sms/forwarder/utils/LogUtils.java deleted file mode 100644 index ff355270..00000000 --- a/app/src/main/java/com/idormy/sms/forwarder/utils/LogUtils.java +++ /dev/null @@ -1,258 +0,0 @@ -package com.idormy.sms.forwarder.utils; - -import android.annotation.SuppressLint; -import android.content.ContentValues; -import android.content.Context; -import android.database.Cursor; -import android.database.sqlite.SQLiteDatabase; -import android.provider.BaseColumns; -import android.util.Log; - -import com.idormy.sms.forwarder.model.LogModel; -import com.idormy.sms.forwarder.model.LogTable; -import com.idormy.sms.forwarder.model.RuleModel; -import com.idormy.sms.forwarder.model.RuleTable; -import com.idormy.sms.forwarder.model.SenderModel; -import com.idormy.sms.forwarder.model.SenderTable; -import com.idormy.sms.forwarder.model.vo.LogVo; - -import java.text.SimpleDateFormat; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Date; -import java.util.List; -import java.util.Locale; - -@SuppressWarnings("UnusedReturnValue") -public class LogUtils { - static final String TAG = "LogUtils"; - static Boolean hasInit = false; - - @SuppressLint("StaticFieldLeak") - static Context context; - static DbHelper dbHelper; - static SQLiteDatabase db; - - public static void init(Context context1) { - //noinspection SynchronizeOnNonFinalField - synchronized (hasInit) { - if (hasInit) return; - hasInit = true; - context = context1; - dbHelper = new DbHelper(context); - // Gets the data repository in write mode - db = dbHelper.getReadableDatabase(); - } - - } - - public static long addLog(LogModel logModel) { - Log.i(TAG, "addLog logModel: " + logModel); - //不保存转发消息 - if (logModel == null) return 0; - - // Create a new map of values, where column names are the keys - ContentValues values = new ContentValues(); - values.put(LogTable.LogEntry.COLUMN_NAME_TYPE, logModel.getType()); - values.put(LogTable.LogEntry.COLUMN_NAME_FROM, logModel.getFrom()); - values.put(LogTable.LogEntry.COLUMN_NAME_CONTENT, logModel.getContent()); - values.put(LogTable.LogEntry.COLUMN_NAME_SIM_INFO, logModel.getSimInfo()); - values.put(LogTable.LogEntry.COLUMN_NAME_RULE_ID, logModel.getRuleId()); - - // Insert the new row, returning the primary key value of the new row - - return db.insert(LogTable.LogEntry.TABLE_NAME, null, values); - } - - public static void delLog(Long id, String key) { - // Define 'where' part of query. - String selection = " 1 "; - // Specify arguments in placeholder order. - List selectionArgList = new ArrayList<>(); - if (id != null) { - // Define 'where' part of query. - selection += " and " + LogTable.LogEntry._ID + " = ? "; - // Specify arguments in placeholder order. - selectionArgList.add(String.valueOf(id)); - } - - if (key != null) { - // Define 'where' part of query. - selection = " and (" + LogTable.LogEntry.COLUMN_NAME_FROM + " LIKE ? or " + LogTable.LogEntry.COLUMN_NAME_CONTENT + " LIKE ? ) "; - // Specify arguments in placeholder order. - selectionArgList.add(key); - selectionArgList.add(key); - } - String[] selectionArgs = selectionArgList.toArray(new String[0]); - // Issue SQL statement. - db.delete(LogTable.LogEntry.TABLE_NAME, selection, selectionArgs); - - } - - public static void updateLog(Long id, int forward_status, String forward_response) { - if (id == null || id <= 0) return; - if (forward_response == null) forward_response = ""; - - Date date = new Date(); - SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.CHINESE); - forward_response = forward_response + "\nAt " + dateFormat.format(date); - - @SuppressWarnings("StringBufferReplaceableByString") String sql = new StringBuilder().append("UPDATE ").append(LogTable.LogEntry.TABLE_NAME) - .append(" SET ").append(LogTable.LogEntry.COLUMN_NAME_FORWARD_STATUS).append(" = ? , ") - .append(LogTable.LogEntry.COLUMN_NAME_FORWARD_RESPONSE) - .append(" = CASE WHEN (trim(").append(LogTable.LogEntry.COLUMN_NAME_FORWARD_RESPONSE) - .append(") = '' or trim(").append(LogTable.LogEntry.COLUMN_NAME_FORWARD_RESPONSE).append(") = 'ok') THEN ? ELSE ") - .append(LogTable.LogEntry.COLUMN_NAME_FORWARD_RESPONSE).append(" || '\n ---------- \n' || ? END ") - .append(" WHERE ").append(LogTable.LogEntry._ID).append(" = ? ") - .toString(); - db.execSQL(sql, new Object[]{forward_status, forward_response, forward_response, id}); - } - - public static List getLog(Long id, String key, String type) { - // Define a projection that specifies which columns from the database - // you will actually use after this query. - String[] projection = { - LogTable.LogEntry.TABLE_NAME + "." + BaseColumns._ID + " AS " + BaseColumns._ID, - LogTable.LogEntry.TABLE_NAME + "." + LogTable.LogEntry.COLUMN_NAME_FROM + " AS " + LogTable.LogEntry.COLUMN_NAME_FROM, - LogTable.LogEntry.TABLE_NAME + "." + LogTable.LogEntry.COLUMN_NAME_TIME + " AS " + LogTable.LogEntry.COLUMN_NAME_TIME, - LogTable.LogEntry.TABLE_NAME + "." + LogTable.LogEntry.COLUMN_NAME_CONTENT + " AS " + LogTable.LogEntry.COLUMN_NAME_CONTENT, - LogTable.LogEntry.TABLE_NAME + "." + LogTable.LogEntry.COLUMN_NAME_SIM_INFO + " AS " + LogTable.LogEntry.COLUMN_NAME_SIM_INFO, - LogTable.LogEntry.TABLE_NAME + "." + LogTable.LogEntry.COLUMN_NAME_FORWARD_STATUS + " AS " + LogTable.LogEntry.COLUMN_NAME_FORWARD_STATUS, - LogTable.LogEntry.TABLE_NAME + "." + LogTable.LogEntry.COLUMN_NAME_FORWARD_RESPONSE + " AS " + LogTable.LogEntry.COLUMN_NAME_FORWARD_RESPONSE, - RuleTable.RuleEntry.TABLE_NAME + "." + RuleTable.RuleEntry.COLUMN_NAME_FILED + " AS " + RuleTable.RuleEntry.COLUMN_NAME_FILED, - RuleTable.RuleEntry.TABLE_NAME + "." + RuleTable.RuleEntry.COLUMN_NAME_CHECK + " AS " + RuleTable.RuleEntry.COLUMN_NAME_CHECK, - RuleTable.RuleEntry.TABLE_NAME + "." + RuleTable.RuleEntry.COLUMN_NAME_VALUE + " AS " + RuleTable.RuleEntry.COLUMN_NAME_VALUE, - RuleTable.RuleEntry.TABLE_NAME + "." + RuleTable.RuleEntry.COLUMN_NAME_SIM_SLOT + " AS " + RuleTable.RuleEntry.COLUMN_NAME_SIM_SLOT, - SenderTable.SenderEntry.TABLE_NAME + "." + SenderTable.SenderEntry.COLUMN_NAME_NAME + " AS " + SenderTable.SenderEntry.COLUMN_NAME_NAME, - SenderTable.SenderEntry.TABLE_NAME + "." + SenderTable.SenderEntry.COLUMN_NAME_TYPE + " AS " + SenderTable.SenderEntry.COLUMN_NAME_TYPE - }; - // Define 'where' part of query. - String selection = " 1 "; - // Specify arguments in placeholder order. - List selectionArgList = new ArrayList<>(); - if (id != null) { - // Define 'where' part of query. - selection += " and " + LogTable.LogEntry.TABLE_NAME + "." + LogTable.LogEntry._ID + " = ? "; - // Specify arguments in placeholder order. - selectionArgList.add(String.valueOf(id)); - } - - if (type != null) { - // Define 'where' part of query. - selection += " and " + LogTable.LogEntry.TABLE_NAME + "." + LogTable.LogEntry.COLUMN_NAME_TYPE + " = ? "; - selectionArgList.add(type); - } - - if (key != null) { - // Define 'where' part of query. - selection += " and (" + LogTable.LogEntry.TABLE_NAME + "." + LogTable.LogEntry.COLUMN_NAME_FROM + " LIKE ? or " + LogTable.LogEntry.TABLE_NAME + "." + LogTable.LogEntry.COLUMN_NAME_CONTENT + " LIKE ? ) "; - // Specify arguments in placeholder order. - selectionArgList.add(key); - selectionArgList.add(key); - } - String[] selectionArgs = selectionArgList.toArray(new String[0]); - - // How you want the results sorted in the resulting Cursor - String sortOrder = - LogTable.LogEntry.TABLE_NAME + "." + LogTable.LogEntry._ID + " DESC"; - - Cursor cursor = db.query( - // The table to query - LogTable.LogEntry.TABLE_NAME - + " LEFT JOIN " + RuleTable.RuleEntry.TABLE_NAME + " ON " + LogTable.LogEntry.TABLE_NAME + "." + LogTable.LogEntry.COLUMN_NAME_RULE_ID + "=" + RuleTable.RuleEntry.TABLE_NAME + "." + RuleTable.RuleEntry._ID - + " LEFT JOIN " + SenderTable.SenderEntry.TABLE_NAME + " ON " + SenderTable.SenderEntry.TABLE_NAME + "." + SenderTable.SenderEntry._ID + "=" + RuleTable.RuleEntry.TABLE_NAME + "." + RuleTable.RuleEntry.COLUMN_NAME_SENDER_ID, - projection, // The array of columns to return (pass null to get all) - selection, // The columns for the WHERE clause - selectionArgs, // The values for the WHERE clause - null, // don't group the rows - null, // don't filter by row groups - sortOrder // The sort order - ); - - - Log.d(TAG, "getLog: " + db.getPath()); - List LogVos = new ArrayList<>(); - - Log.d(TAG, "getLog: itemId cursor" + Arrays.toString(cursor.getColumnNames())); - while (cursor.moveToNext()) { - try { - Long itemId = cursor.getLong( - cursor.getColumnIndexOrThrow(BaseColumns._ID)); - String itemFrom = cursor.getString( - cursor.getColumnIndexOrThrow(LogTable.LogEntry.COLUMN_NAME_FROM)); - String content = cursor.getString( - cursor.getColumnIndexOrThrow(LogTable.LogEntry.COLUMN_NAME_CONTENT)); - String simInfo = cursor.getString( - cursor.getColumnIndexOrThrow(LogTable.LogEntry.COLUMN_NAME_SIM_INFO)); - String time = cursor.getString( - cursor.getColumnIndexOrThrow(LogTable.LogEntry.COLUMN_NAME_TIME)); - int forwardStatus = cursor.getInt( - cursor.getColumnIndexOrThrow(LogTable.LogEntry.COLUMN_NAME_FORWARD_STATUS)); - String forwardResponse = cursor.getString( - cursor.getColumnIndexOrThrow(LogTable.LogEntry.COLUMN_NAME_FORWARD_RESPONSE)); - String ruleFiled = cursor.getString( - cursor.getColumnIndexOrThrow(RuleTable.RuleEntry.COLUMN_NAME_FILED)); - String ruleCheck = cursor.getString( - cursor.getColumnIndexOrThrow(RuleTable.RuleEntry.COLUMN_NAME_CHECK)); - String ruleValue = cursor.getString( - cursor.getColumnIndexOrThrow(RuleTable.RuleEntry.COLUMN_NAME_VALUE)); - String ruleSimSlot = cursor.getString( - cursor.getColumnIndexOrThrow(RuleTable.RuleEntry.COLUMN_NAME_SIM_SLOT)); - String senderName = cursor.getString( - cursor.getColumnIndexOrThrow(SenderTable.SenderEntry.COLUMN_NAME_NAME)); - int senderType = cursor.getInt( - cursor.getColumnIndexOrThrow(SenderTable.SenderEntry.COLUMN_NAME_TYPE)); - - String rule = RuleModel.getRuleMatch(ruleFiled, ruleCheck, ruleValue, ruleSimSlot); - if (senderName != null) rule += senderName.trim(); - - int senderImageId = SenderModel.getImageId(senderType); - LogVo logVo = new LogVo(itemId, type, itemFrom, content, simInfo, time, rule, senderImageId, forwardStatus, forwardResponse); - LogVos.add(logVo); - } catch (Exception e) { - Log.e(TAG, "getLog e:" + e.getMessage()); - } - - } - - cursor.close(); - - return LogVos; - } - - public static int countLog(String status, String type, String value) { - String[] projection = {}; - String selection = " 1 "; - List selectionArgList = new ArrayList<>(); - - if (status != null && !status.isEmpty()) { - selection += " and " + LogTable.LogEntry.COLUMN_NAME_FORWARD_STATUS + " = ? "; - selectionArgList.add(status); - } - - if (type != null && !type.isEmpty()) { - selection += " and " + LogTable.LogEntry.COLUMN_NAME_TYPE + " = ? "; - selectionArgList.add(status); - } - - if (value != null && !value.isEmpty()) { - selection += " and " + LogTable.LogEntry.COLUMN_NAME_CONTENT + " LIKE ? "; - selectionArgList.add(value); - } - - String[] selectionArgs = selectionArgList.toArray(new String[0]); - Cursor cursor = db.query( - LogTable.LogEntry.TABLE_NAME, // The table to query - projection, // The array of columns to return (pass null to get all) - selection, // The columns for the WHERE clause - selectionArgs, // The values for the WHERE clause - null, // don't group the rows - null, // don't filter by row groups - null // The sort order - ); - - int count = cursor.getCount(); - cursor.close(); - return count; - } -} diff --git a/app/src/main/java/com/idormy/sms/forwarder/utils/Logger.kt b/app/src/main/java/com/idormy/sms/forwarder/utils/Logger.kt new file mode 100644 index 00000000..745a6577 --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/utils/Logger.kt @@ -0,0 +1,56 @@ +/* + * Copyright 2018 Zhenjie Yan. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.idormy.sms.forwarder.utils + +import android.util.Log + +/** + * Created by Zhenjie Yan on 2018/9/12. + */ +@Suppress("unused") +object Logger { + private const val TAG = "AndServer" + private const val DEBUG = true + fun i(obj: Any?) { + if (DEBUG) { + Log.i(TAG, obj?.toString() ?: "null") + } + } + + fun d(obj: Any?) { + if (DEBUG) { + Log.d(TAG, obj?.toString() ?: "null") + } + } + + fun v(obj: Any?) { + if (DEBUG) { + Log.v(TAG, obj?.toString() ?: "null") + } + } + + fun w(obj: Any?) { + if (DEBUG) { + Log.w(TAG, obj?.toString() ?: "null") + } + } + + fun e(obj: Any?) { + if (DEBUG) { + Log.e(TAG, obj?.toString() ?: "null") + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/utils/MMKVUtils.kt b/app/src/main/java/com/idormy/sms/forwarder/utils/MMKVUtils.kt new file mode 100644 index 00000000..47ba6ace --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/utils/MMKVUtils.kt @@ -0,0 +1,332 @@ +package com.idormy.sms.forwarder.utils + +import android.content.Context +import android.os.Parcelable +import android.util.Log +import androidx.preference.PreferenceManager +import com.tencent.mmkv.MMKV + +/** + * MMKV工具类 + * + * @author xuexiang + * @since 2019-07-04 10:20 + */ +@Suppress("PropertyName", "UNCHECKED_CAST", "MemberVisibilityCanBePrivate", "unused") +class MMKVUtils private constructor() { + + companion object { + private var TAG: String = "MMKVUtils" + private var sMMKV: MMKV? = null + + /** + * 初始化 + * + * @param context + */ + fun init(context: Context) { + MMKV.initialize(context.applicationContext) + sMMKV = MMKV.defaultMMKV() + } + + fun getsMMKV(): MMKV? { + if (sMMKV == null) { + sMMKV = MMKV.defaultMMKV() + } + return sMMKV + } + //=======================================键值保存==================================================// + /** + * 保存键值 + * + * @param key + * @param value + * @return + */ + fun put(key: String?, value: Any?): Boolean { + when (value) { + is Int -> { + return getsMMKV()!!.encode(key, (value as Int?)!!) + } + is Float -> { + return getsMMKV()!!.encode(key, (value as Float?)!!) + } + is String -> { + return getsMMKV()!!.encode(key, value as String?) + } + is Boolean -> { + return getsMMKV()!!.encode(key, (value as Boolean?)!!) + } + is Long -> { + return getsMMKV()!!.encode(key, (value as Long?)!!) + } + is Double -> { + return getsMMKV()!!.encode(key, (value as Double?)!!) + } + is Parcelable -> { + return getsMMKV()!!.encode(key, value as Parcelable?) + } + is ByteArray -> { + return getsMMKV()!!.encode(key, value as ByteArray?) + } + is Set<*> -> { + return getsMMKV()!!.encode(key, value as Set?) + } + else -> return false + } + } + //=======================================键值获取==================================================// + /** + * 获取键值 + * + * @param key + * @param defaultValue + * @return + */ + operator fun get(key: String?, defaultValue: Any?): Any? { + when (defaultValue) { + is Int -> { + return getsMMKV()!! + .decodeInt(key, (defaultValue as Int?)!!) + } + is Float -> { + return getsMMKV()!! + .decodeFloat(key, (defaultValue as Float?)!!) + } + is String -> { + return getsMMKV()!!.decodeString(key, defaultValue as String?) + } + is Boolean -> { + return getsMMKV()!! + .decodeBool(key, (defaultValue as Boolean?)!!) + } + is Long -> { + return getsMMKV()!! + .decodeLong(key, (defaultValue as Long?)!!) + } + is Double -> { + return getsMMKV()!! + .decodeDouble(key, (defaultValue as Double?)!!) + } + is ByteArray -> { + return getsMMKV()!!.decodeBytes(key) + } + is Set<*> -> { + return getsMMKV()!!.decodeStringSet(key, defaultValue as Set?) + } + else -> return null + } + } + + /** + * 根据key获取boolean值 + * + * @param key + * @param defValue + * @return + */ + fun getBoolean(key: String?, defValue: Boolean): Boolean { + try { + return getsMMKV()!!.getBoolean(key, defValue) + } catch (e: Exception) { + e.printStackTrace() + } + return defValue + } + + /** + * 根据key获取long值 + * + * @param key + * @param defValue + * @return + */ + fun getLong(key: String?, defValue: Long): Long { + try { + return getsMMKV()!!.getLong(key, defValue) + } catch (e: Exception) { + e.printStackTrace() + } + return defValue + } + + /** + * 根据key获取float值 + * + * @param key + * @param defValue + * @return + */ + fun getFloat(key: String?, defValue: Float): Float { + try { + return getsMMKV()!!.getFloat(key, defValue) + } catch (e: Exception) { + e.printStackTrace() + } + return defValue + } + + /** + * 根据key获取String值 + * + * @param key + * @param defValue + * @return + */ + fun getString(key: String?, defValue: String?): String? { + try { + return getsMMKV()!!.getString(key, defValue) + } catch (e: Exception) { + e.printStackTrace() + } + return defValue + } + + /** + * 根据key获取int值 + * + * @param key + * @param defValue + * @return + */ + fun getInt(key: String?, defValue: Int): Int { + try { + return getsMMKV()!!.getInt(key, defValue) + } catch (e: Exception) { + e.printStackTrace() + } + return defValue + } + + /** + * 根据key获取double值 + * + * @param key + * @param defValue + * @return + */ + fun getDouble(key: String?, defValue: Double): Double { + try { + return getsMMKV()!!.decodeDouble(key, defValue) + } catch (e: Exception) { + e.printStackTrace() + } + return defValue + } + + /** + * 获取对象 + * + * @param key + * @param tClass 类型 + * @param + * @return + */ + fun getObject(key: String?, tClass: Class?): T? { + return getsMMKV()!!.decodeParcelable(key, tClass) + } + + /** + * 获取对象 + * + * @param key + * @param tClass 类型 + * @param + * @return + */ + fun getObject(key: String?, tClass: Class?, defValue: T): T? { + try { + return getsMMKV()!!.decodeParcelable(key, tClass, defValue) + } catch (e: Exception) { + e.printStackTrace() + } + return defValue + } + + /** + * 判断键值对是否存在 + * + * @param key 键 + * @return 键值对是否存在 + */ + fun containsKey(key: String?): Boolean { + return getsMMKV()!!.containsKey(key) + } + + /** + * 清除指定键值对 + * + * @param key 键 + */ + fun remove(key: String?) { + getsMMKV()!!.remove(key).apply() + } + + /** + * 从SP迁移数据 + */ + fun importSharedPreferences(context: Context) { + Log.d(TAG, "从SP迁移数据") + val preferences = PreferenceManager.getDefaultSharedPreferences(context) + val editor = preferences.edit() + getsMMKV()!!.importFromSharedPreferences(preferences) + editor.clear().apply() + + Log.d(TAG, "转换旧的SP配置") + loop@ for (key: String in getsMMKV()!!.allKeys()!!) { + when { + key.startsWith("tsms_msg_key_switch_") || key.startsWith("tsms_msg_key_string_enable_") || key.endsWith("battery_level_once") -> { + val newKey = key.replace("tsms_msg_key_switch_", "enable_") + .replace("tsms_msg_key_string_", "enable_") + .replace("enable_enable_", "enable_") + val value = getBoolean(key, false) + Log.d(TAG, String.format("oldKey=%s, newKey=%s, value=%s", key, newKey, value.toString())) + put(newKey, value) + remove(key) + continue@loop + } + key.endsWith("battery_level_alarm") || key.endsWith("battery_level_max") || key.endsWith("battery_level_current") || key.endsWith("battery_status") || key.endsWith("battery_cron_interval") -> { + val newKey = key.replace("tsms_msg_key_switch_", "") + .replace("tsms_msg_key_string_", "") + .replace("alarm", "min") + .replace("tsms_msg_key_", "request_") + val value = getInt(key, 0) + Log.d(TAG, String.format("oldKey=%s, newKey=%s, value=%s", key, newKey, value.toString())) + put(newKey, value) + remove(key) + continue@loop + } + key.startsWith("tsms_msg_key_") -> { + val newKey = key.replace("tsms_msg_key_string_", "") + .replace("add_", "") + .replace("tsms_msg_key_", "request_") + val value = getString(key, "") + Log.d(TAG, String.format("oldKey=%s, newKey=%s, value=%s", key, newKey, value.toString())) + put(newKey, value) + remove(key) + continue@loop + } + } + } + + Log.d(TAG, "转换后的数据") + for (key: String in getsMMKV()!!.allKeys()!!) { + when { + key.startsWith("enable_") -> { + Log.d(TAG, String.format("key=%s, value=%s", key, getBoolean(key, false).toString())) + } + key.startsWith("battery_") || key.startsWith("request_") -> { + Log.d(TAG, String.format("key=%s, value=%s", key, getInt(key, 0).toString())) + } + else -> { + Log.d(TAG, String.format("key=%s, value=%s", key, getString(key, "").toString())) + } + } + } + } + } + + init { + throw UnsupportedOperationException("u can't instantiate me...") + } +} \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/utils/NetUtils.java b/app/src/main/java/com/idormy/sms/forwarder/utils/NetUtils.java deleted file mode 100644 index 556c398b..00000000 --- a/app/src/main/java/com/idormy/sms/forwarder/utils/NetUtils.java +++ /dev/null @@ -1,72 +0,0 @@ -package com.idormy.sms.forwarder.utils; - -import android.annotation.SuppressLint; -import android.content.Context; -import android.net.ConnectivityManager; -import android.net.NetworkInfo; -import android.net.wifi.WifiInfo; -import android.net.wifi.WifiManager; - -import com.hjq.toast.ToastUtils; -import com.idormy.sms.forwarder.R; - -public class NetUtils { - //没有网络 - public static final int NETWORK_NONE = 0; - //移动网络 - public static final int NETWORK_MOBILE = 1; - //无线网络 - public static final int NETWORK_WIFI = 2; - - static Boolean hasInit = false; - @SuppressLint("StaticFieldLeak") - static Context context; - - public static void init(Context context1) { - //noinspection SynchronizeOnNonFinalField - synchronized (hasInit) { - if (hasInit) return; - hasInit = true; - context = context1; - } - } - - //获取网络启动 - public static int getNetWorkStatus() { - //连接服务 CONNECTIVITY_SERVICE - ConnectivityManager connectivityManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); - //网络信息 NetworkInfo - NetworkInfo activeNetworkInfo = connectivityManager.getActiveNetworkInfo(); - - if (activeNetworkInfo != null && activeNetworkInfo.isConnected()) { - //判断是否是wifi - if (activeNetworkInfo.getType() == (ConnectivityManager.TYPE_WIFI)) { - //返回无线网络 - ToastUtils.show(R.string.on_wireless_network); - return NETWORK_WIFI; - //判断是否移动网络 - } else if (activeNetworkInfo.getType() == (ConnectivityManager.TYPE_MOBILE)) { - ToastUtils.show(R.string.on_mobile_network); - //返回移动网络 - return NETWORK_MOBILE; - } - } else { - //没有网络 - ToastUtils.show(R.string.no_network); - return NETWORK_NONE; - } - //默认返回 没有网络 - return NETWORK_NONE; - } - - public static String getLocalIp(Context context) { - if (NETWORK_WIFI != getNetWorkStatus()) return context.getString(R.string.not_connected_wifi); - - WifiManager wifiManager = (WifiManager) context.getApplicationContext().getSystemService(Context.WIFI_SERVICE); - WifiInfo wifiInfo = wifiManager.getConnectionInfo(); - int ipAddress = wifiInfo.getIpAddress(); - if (ipAddress == 0) return context.getString(R.string.failed_to_get_ip); - return ((ipAddress & 0xff) + "." + (ipAddress >> 8 & 0xff) + "." - + (ipAddress >> 16 & 0xff) + "." + (ipAddress >> 24 & 0xff)); - } -} diff --git a/app/src/main/java/com/idormy/sms/forwarder/utils/NetUtils.kt b/app/src/main/java/com/idormy/sms/forwarder/utils/NetUtils.kt new file mode 100644 index 00000000..cf412385 --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/utils/NetUtils.kt @@ -0,0 +1,74 @@ +/* + * Copyright © 2018 Zhenjie Yan. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.idormy.sms.forwarder.utils + +import java.net.InetAddress +import java.net.NetworkInterface +import java.net.SocketException +import java.util.* +import java.util.regex.Pattern + +/** + * Created by Zhenjie Yan on 2018/6/9. + */ +@Suppress("unused") +object NetUtils { + /** + * Ipv4 address check. + */ + private val IPV4_PATTERN = Pattern.compile( + "^(" + "([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}" + + "([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$" + ) + + /** + * Check if valid IPV4 address. + * + * @param input the address string to check for validity. + * @return True if the input parameter is a valid IPv4 address. + */ + private fun isIPv4Address(input: String?): Boolean { + return IPV4_PATTERN.matcher(input.toString()).matches() + } + + /** + * Get local Ip address. + */ + val localIPAddress: InetAddress? + get() { + var enumeration: Enumeration? = null + try { + enumeration = NetworkInterface.getNetworkInterfaces() + } catch (e: SocketException) { + e.printStackTrace() + } + if (enumeration != null) { + while (enumeration.hasMoreElements()) { + val nif = enumeration.nextElement() + val inetAddresses = nif.inetAddresses + if (inetAddresses != null) { + while (inetAddresses.hasMoreElements()) { + val inetAddress = inetAddresses.nextElement() + if (!inetAddress.isLoopbackAddress && isIPv4Address(inetAddress.hostAddress)) { + return inetAddress + } + } + } + } + } + return null + } +} \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/utils/OSUtils.java b/app/src/main/java/com/idormy/sms/forwarder/utils/OSUtils.java deleted file mode 100644 index e5b8c3d4..00000000 --- a/app/src/main/java/com/idormy/sms/forwarder/utils/OSUtils.java +++ /dev/null @@ -1,132 +0,0 @@ -package com.idormy.sms.forwarder.utils; - -import android.os.Environment; -import android.text.TextUtils; - -import com.alibaba.fastjson.util.IOUtils; - -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; -import java.util.Properties; - -/** - * 使用方法: - * OSUtils.ROM_TYPE romType = OSUtils.getRomType(); - * 可能您需要对其他的ROM进行区分,那么只需三步: - * 一:使用BuildProperties获取到所有的key,遍历获取到所有的value(getProperty),或者直接找到build.prop文件。 - * 二:找到定制ROM特征的标识(key/value) - * 三:增加ROM_TYPE枚举类型,getRomType方法加入识别比对即可 - * 作者:YouAreMyShine - * 链接:https://www.jianshu.com/p/bb1f765a425f - */ -public class OSUtils { - - /** - * 判断是否为MIUI系统,参考http://blog.csdn.net/xx326664162/article/details/52438706 - * - * @return 返回结果 - */ - @SuppressWarnings("unused") - public static boolean isMIUI() { - File file = new File(Environment.getRootDirectory(), "build.prop"); - FileInputStream fis = null; - try { - fis = new FileInputStream(file); - String KEY_MIUI_VERSION_CODE = "ro.miui.ui.version.code"; - String KEY_MIUI_VERSION_NAME = "ro.miui.ui.version.name"; - String KEY_MIUI_INTERNAL_STORAGE = "ro.miui.internal.storage"; - Properties prop = new Properties(); - prop.load(fis); - - return prop.getProperty(KEY_MIUI_VERSION_CODE, null) != null - || prop.getProperty(KEY_MIUI_VERSION_NAME, null) != null - || prop.getProperty(KEY_MIUI_INTERNAL_STORAGE, null) != null; - } catch (final IOException e) { - return false; - } finally { - IOUtils.close(fis); - } - } - - /** - * MIUI ROM标识 - *

- * "ro.miui.ui.version.code" -> "5" - *

- * "ro.miui.ui.version.name" -> "V7" - *

- * "ro.miui.has_handy_mode_sf" -> "1" - *

- * "ro.miui.has_real_blur" -> "1" - *

- *

- *

- * Flyme ROM标识 - *

- * "ro.build.user" -> "flyme" - *

- * "persist.sys.use.flyme.icon" -> "true" - *

- * "ro.flyme.published" -> "true" - *

- * "ro.build.display.id" -> "Flyme OS 5.1.2.0U" - *

- * "ro.meizu.setupwizard.flyme" -> "true" - *

- *

- *

- * EMUI ROM标识 - *

- * "ro.build.version.emui" -> "EmotionUI_1.6" - */ - - //MIUI标识 - private static final String KEY_MIUI_VERSION_CODE = "ro.miui.ui.version.code"; - private static final String KEY_MIUI_VERSION_NAME = "ro.miui.ui.version.name"; - //EMUI标识 - private static final String KEY_EMUI_VERSION_CODE = "ro.build.version.emui"; - //Flyme标识 - private static final String KEY_FLYME_ID_FLAG_KEY = "ro.build.display.id"; - private static final String KEY_FLYME_ID_FLAG_VALUE_KEYWORD = "Flyme"; - private static final String KEY_FLYME_ICON_FLAG = "persist.sys.use.flyme.icon"; - private static final String KEY_FLYME_SETUP_FLAG = "ro.meizu.setupwizard.flyme"; - private static final String KEY_FLYME_PUBLISH_FLAG = "ro.flyme.published"; - - /** - * 获取ROM类型,MIUI_ROM, *FLYME_ROM, * EMUI_ROM, * OTHER_ROM - * - * @return ROM_TYPE ROM类型的枚举 - */ - public static ROM_TYPE getRomType() { - ROM_TYPE rom_type = ROM_TYPE.OTHER_ROM; - try { - BuildProperties buildProperties = BuildProperties.getInstance(); - if (buildProperties.containsKey(KEY_EMUI_VERSION_CODE)) { - return ROM_TYPE.EMUI_ROM; - } - if (buildProperties.containsKey(KEY_MIUI_VERSION_CODE) || buildProperties.containsKey(KEY_MIUI_VERSION_NAME)) { - return ROM_TYPE.MIUI_ROM; - } - if (buildProperties.containsKey(KEY_FLYME_ICON_FLAG) || buildProperties.containsKey(KEY_FLYME_SETUP_FLAG) || buildProperties.containsKey(KEY_FLYME_PUBLISH_FLAG)) { - return ROM_TYPE.FLYME_ROM; - } - if (buildProperties.containsKey(KEY_FLYME_ID_FLAG_KEY)) { - String romName = buildProperties.getProperty(KEY_FLYME_ID_FLAG_KEY); - if (!TextUtils.isEmpty(romName) && romName.contains(KEY_FLYME_ID_FLAG_VALUE_KEYWORD)) { - return ROM_TYPE.FLYME_ROM; - } - } - } catch (IOException e) { - e.printStackTrace(); - } - return rom_type; - } - - public enum ROM_TYPE { - MIUI_ROM, - FLYME_ROM, - EMUI_ROM, - OTHER_ROM - } -} diff --git a/app/src/main/java/com/idormy/sms/forwarder/utils/OnePixelManager.java b/app/src/main/java/com/idormy/sms/forwarder/utils/OnePixelManager.java deleted file mode 100644 index fdeccbdb..00000000 --- a/app/src/main/java/com/idormy/sms/forwarder/utils/OnePixelManager.java +++ /dev/null @@ -1,72 +0,0 @@ -package com.idormy.sms.forwarder.utils; - -import android.app.Activity; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; - -import com.idormy.sms.forwarder.OnePixelActivity; -import com.idormy.sms.forwarder.receiver.ScreenBroadcastReceiver; - -import java.lang.ref.WeakReference; - -public class OnePixelManager { - private static final String TAG = "OnePixelManager"; - private WeakReference mActivity; - private ScreenBroadcastReceiver screenBroadcastReceiver; - - /** - * 一像素广播接收者注册方法。该方法中初始化OnePixelReceiver,并添加了过滤条件 - * 屏幕息屏和亮屏。然后注册该广播接收者 - */ - public void registerOnePixelReceiver(Context context) { - IntentFilter filter = new IntentFilter(); - filter.addAction(Intent.ACTION_SCREEN_OFF); - filter.addAction(Intent.ACTION_SCREEN_ON); - filter.addAction(Intent.ACTION_USER_PRESENT); - screenBroadcastReceiver = new ScreenBroadcastReceiver(); - context.registerReceiver(screenBroadcastReceiver, filter); - } - - /** - * 对广播接收者进行解注册 - */ - public void unregisterOnePixelReceiver(Context context) { - if (null != screenBroadcastReceiver) { - context.unregisterReceiver(screenBroadcastReceiver); - } - } - - /** - * 开启一像素Activity - */ - public void startOnePixelActivity(Context context) { - Intent intent = new Intent(); - intent.setClass(context, OnePixelActivity.class); - context.startActivity(intent); - } - - /** - * 关闭一像素Activity - */ - public void finishOnePixelActivity() { - if (null != mActivity) { - Activity activity = mActivity.get(); - if (null != activity) { - activity.finish(); - } - mActivity = null; - } - } - - /** - * 使用弱引用获取一像素的上下文 - */ - public void setKeepAliveReference(OnePixelActivity activity) { - mActivity = new WeakReference<>(activity); - } - -} - - - diff --git a/app/src/main/java/com/idormy/sms/forwarder/utils/PermissionInterceptor.java b/app/src/main/java/com/idormy/sms/forwarder/utils/PermissionInterceptor.java deleted file mode 100644 index 1bf5fe14..00000000 --- a/app/src/main/java/com/idormy/sms/forwarder/utils/PermissionInterceptor.java +++ /dev/null @@ -1,282 +0,0 @@ -package com.idormy.sms.forwarder.utils; - -import android.app.Activity; -import android.content.Context; -import android.os.Build; - -import androidx.appcompat.app.AlertDialog; - -import com.hjq.permissions.IPermissionInterceptor; -import com.hjq.permissions.OnPermissionCallback; -import com.hjq.permissions.Permission; -import com.hjq.permissions.XXPermissions; -import com.hjq.toast.ToastUtils; -import com.idormy.sms.forwarder.R; - -import java.util.ArrayList; -import java.util.List; - -/** - * author : Android 轮子哥 - * github : https://github.com/getActivity/XXPermissions - * time : 2021/01/04 - * desc : 权限申请拦截器 - */ -@SuppressWarnings({"deprecation", "CommentedOutCode"}) -public final class PermissionInterceptor implements IPermissionInterceptor { - -// @Override -// public void requestPermissions(Activity activity, OnPermissionCallback callback, List allPermissions) { -// // 这里的 Dialog 只是示例,没有用 DialogFragment 来处理 Dialog 生命周期 -// new AlertDialog.Builder(activity) -// .setTitle(R.string.common_permission_hint) -// .setMessage(R.string.common_permission_message) -// .setPositiveButton(R.string.common_permission_granted, new DialogInterface.OnClickListener() { -// -// @Override -// public void onClick(DialogInterface dialog, int which) { -// dialog.dismiss(); -// PermissionFragment.beginRequest(activity, new ArrayList<>(allPermissions), PermissionInterceptor.this, callback); -// } -// }) -// .setNegativeButton(R.string.common_permission_denied, new DialogInterface.OnClickListener() { -// -// @Override -// public void onClick(DialogInterface dialog, int which) { -// dialog.dismiss(); -// } -// }) -// .show(); -// } - - @Override - public void grantedPermissions(Activity activity, List allPermissions, List grantedPermissions, - boolean all, OnPermissionCallback callback) { - if (callback != null) { - callback.onGranted(grantedPermissions, all); - } - } - - @SuppressWarnings("ConstantConditions") - @Override - public void deniedPermissions(Activity activity, List allPermissions, List deniedPermissions, - boolean never, OnPermissionCallback callback) { - if (callback != null) { - callback.onDenied(deniedPermissions, never); - } - - if (never) { - showPermissionDialog(activity, deniedPermissions); - return; - } - - if (deniedPermissions.size() == 1 && Permission.ACCESS_BACKGROUND_LOCATION.equals(deniedPermissions.get(0))) { - ToastUtils.show(R.string.common_permission_fail_4); - return; - } - - ToastUtils.show(R.string.common_permission_fail_1); - - if (callback == null) { - return; - } - callback.onDenied(deniedPermissions, never); - } - - /** - * 显示授权对话框 - */ - protected void showPermissionDialog(Activity activity, List permissions) { - // 这里的 Dialog 只是示例,没有用 DialogFragment 来处理 Dialog 生命周期 - new AlertDialog.Builder(activity) - .setTitle(R.string.common_permission_alert) - .setCancelable(false) - .setMessage(getPermissionHint(activity, permissions)) - .setPositiveButton(R.string.common_permission_goto, (dialog, which) -> { - dialog.dismiss(); - XXPermissions.startPermissionActivity(activity, permissions); - }) - .show(); - } - - /** - * 根据权限获取提示 - */ - protected String getPermissionHint(Context context, List permissions) { - if (permissions == null || permissions.isEmpty()) { - return context.getString(R.string.common_permission_fail_2); - } - - List hints = new ArrayList<>(); - for (String permission : permissions) { - switch (permission) { - case Permission.READ_EXTERNAL_STORAGE: - case Permission.WRITE_EXTERNAL_STORAGE: - case Permission.MANAGE_EXTERNAL_STORAGE: { - String hint = context.getString(R.string.common_permission_storage); - if (!hints.contains(hint)) { - hints.add(hint); - } - break; - } - case Permission.CAMERA: { - String hint = context.getString(R.string.common_permission_camera); - if (!hints.contains(hint)) { - hints.add(hint); - } - break; - } - case Permission.RECORD_AUDIO: { - String hint = context.getString(R.string.common_permission_microphone); - if (!hints.contains(hint)) { - hints.add(hint); - } - break; - } - case Permission.ACCESS_FINE_LOCATION: - case Permission.ACCESS_COARSE_LOCATION: - case Permission.ACCESS_BACKGROUND_LOCATION: { - String hint; - if (!permissions.contains(Permission.ACCESS_FINE_LOCATION) && - !permissions.contains(Permission.ACCESS_COARSE_LOCATION)) { - hint = context.getString(R.string.common_permission_location_background); - } else { - hint = context.getString(R.string.common_permission_location); - } - if (!hints.contains(hint)) { - hints.add(hint); - } - break; - } - case Permission.BLUETOOTH_SCAN: - case Permission.BLUETOOTH_CONNECT: - case Permission.BLUETOOTH_ADVERTISE: { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { - String hint = context.getString(R.string.common_permission_bluetooth); - if (!hints.contains(hint)) { - hints.add(hint); - } - } - break; - } - case Permission.READ_PHONE_STATE: - case Permission.CALL_PHONE: - case Permission.ADD_VOICEMAIL: - case Permission.USE_SIP: - case Permission.READ_PHONE_NUMBERS: - case Permission.ANSWER_PHONE_CALLS: { - String hint = context.getString(R.string.common_permission_phone); - if (!hints.contains(hint)) { - hints.add(hint); - } - break; - } - case Permission.GET_ACCOUNTS: - case Permission.READ_CONTACTS: - case Permission.WRITE_CONTACTS: { - String hint = context.getString(R.string.common_permission_contacts); - if (!hints.contains(hint)) { - hints.add(hint); - } - break; - } - case Permission.READ_CALENDAR: - case Permission.WRITE_CALENDAR: { - String hint = context.getString(R.string.common_permission_calendar); - if (!hints.contains(hint)) { - hints.add(hint); - } - break; - } - case Permission.READ_CALL_LOG: - case Permission.WRITE_CALL_LOG: - case Permission.PROCESS_OUTGOING_CALLS: { - String hint = context.getString(Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q ? - R.string.common_permission_call_log : R.string.common_permission_phone); - if (!hints.contains(hint)) { - hints.add(hint); - } - break; - } - case Permission.BODY_SENSORS: { - String hint = context.getString(R.string.common_permission_sensors); - if (!hints.contains(hint)) { - hints.add(hint); - } - break; - } - case Permission.ACTIVITY_RECOGNITION: { - String hint = context.getString(R.string.common_permission_activity_recognition); - if (!hints.contains(hint)) { - hints.add(hint); - } - break; - } - case Permission.SEND_SMS: - case Permission.RECEIVE_SMS: - case Permission.READ_SMS: - case Permission.RECEIVE_WAP_PUSH: - case Permission.RECEIVE_MMS: { - String hint = context.getString(R.string.common_permission_sms); - if (!hints.contains(hint)) { - hints.add(hint); - } - break; - } - case Permission.REQUEST_INSTALL_PACKAGES: { - String hint = context.getString(R.string.common_permission_install); - if (!hints.contains(hint)) { - hints.add(hint); - } - break; - } - case Permission.SYSTEM_ALERT_WINDOW: { - String hint = context.getString(R.string.common_permission_window); - if (!hints.contains(hint)) { - hints.add(hint); - } - break; - } - case Permission.WRITE_SETTINGS: { - String hint = context.getString(R.string.common_permission_setting); - if (!hints.contains(hint)) { - hints.add(hint); - } - break; - } - case Permission.NOTIFICATION_SERVICE: { - String hint = context.getString(R.string.common_permission_notification); - if (!hints.contains(hint)) { - hints.add(hint); - } - break; - } - case Permission.PACKAGE_USAGE_STATS: { - String hint = context.getString(R.string.common_permission_task); - if (!hints.contains(hint)) { - hints.add(hint); - } - break; - } - default: - break; - } - } - - if (!hints.isEmpty()) { - StringBuilder builder = new StringBuilder(); - for (String text : hints) { - if (builder.length() == 0) { - builder.append(text); - } else { - builder.append("、") - .append(text); - } - } - builder.append(" "); - return context.getString(R.string.common_permission_fail_3, builder.toString()); - } - - return context.getString(R.string.common_permission_fail_2); - } -} \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/utils/PhoneUtils.java b/app/src/main/java/com/idormy/sms/forwarder/utils/PhoneUtils.java deleted file mode 100644 index 0acad513..00000000 --- a/app/src/main/java/com/idormy/sms/forwarder/utils/PhoneUtils.java +++ /dev/null @@ -1,659 +0,0 @@ -package com.idormy.sms.forwarder.utils; - -import android.Manifest; -import android.annotation.SuppressLint; -import android.content.ContentResolver; -import android.content.Context; -import android.content.pm.PackageManager; -import android.database.Cursor; -import android.net.Uri; -import android.os.Build; -import android.provider.CallLog; -import android.provider.Settings; -import android.telephony.SubscriptionInfo; -import android.telephony.SubscriptionManager; -import android.telephony.TelephonyManager; -import android.text.TextUtils; -import android.util.Log; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.core.app.ActivityCompat; - -import com.idormy.sms.forwarder.model.CallInfo; - -import java.io.File; -import java.lang.reflect.Method; -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; -import java.util.Objects; -import java.util.Set; -import java.util.UUID; - -@SuppressWarnings({"deprecation", "rawtypes", "unchecked", "CommentedOutCode", "SynchronizeOnNonFinalField", "unused", "SameReturnValue"}) -public class PhoneUtils { - private static final String TAG = "PhoneUtils"; - static Boolean hasInit = false; - @SuppressLint("StaticFieldLeak") - static Context context; - - /** - * 构造类 - */ - private PhoneUtils() { - throw new UnsupportedOperationException("u can't instantiate me..."); - } - - public static void init(Context context1) { - synchronized (hasInit) { - if (hasInit) return; - hasInit = true; - context = context1; - } - } - - /** - * 判断设备是否是手机 - * - * @return {@code true}: 是
{@code false}: 否 - */ - public static boolean isPhone() { - TelephonyManager tm = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE); - return tm != null && tm.getPhoneType() != TelephonyManager.PHONE_TYPE_NONE; - } - - /** - * 判断设备是否root - * - * @return the boolean{@code true}: 是
{@code false}: 否 - */ - public static boolean isDeviceRoot() { - String su = "su"; - String[] locations = {"/system/bin/", "/system/xbin/", "/sbin/", "/system/sd/xbin/", "/system/bin/failsafe/", - "/data/local/xbin/", "/data/local/bin/", "/data/local/"}; - for (String location : locations) { - if (new File(location + su).exists()) { - return true; - } - } - return false; - } - - /** - * 获取设备系统版本号 - * - * @return 设备系统版本号 - */ - public static int getSDKVersion() { - return android.os.Build.VERSION.SDK_INT; - } - - /** - * 获取设备AndroidID - * - * @return AndroidID - */ - @SuppressLint("HardwareIds") - public static String getAndroidID() { - return Settings.Secure.getString(context.getContentResolver(), Settings.Secure.ANDROID_ID); - } - - /** - * 获取IMEI码 - *

需添加权限 {@code }

- * - * @return IMEI码 - */ - @SuppressLint("HardwareIds") - public static String getIMEI() { - TelephonyManager tm = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE); - try { - return tm != null ? tm.getDeviceId() : null; - } catch (Exception ignored) { - - } - return getUniquePseudoID(); - } - - /** - * 通过读取设备的ROM版本号、厂商名、CPU型号和其他硬件信息来组合出一串15位的号码 - * 其中“Build.SERIAL”这个属性来保证ID的独一无二,当API < 9 无法读取时,使用AndroidId - * - * @return 伪唯一ID - */ - public static String getUniquePseudoID() { - String m_szDevIDShort = "35" + - Build.BOARD.length() % 10 + Build.BRAND.length() % 10 + - Build.CPU_ABI.length() % 10 + Build.DEVICE.length() % 10 + - Build.DISPLAY.length() % 10 + Build.HOST.length() % 10 + - Build.ID.length() % 10 + Build.MANUFACTURER.length() % 10 + - Build.MODEL.length() % 10 + Build.PRODUCT.length() % 10 + - Build.TAGS.length() % 10 + Build.TYPE.length() % 10 + - Build.USER.length() % 10; - - String serial; - try { - serial = Objects.requireNonNull(Build.class.getField("SERIAL").get(null)).toString(); - return new UUID(m_szDevIDShort.hashCode(), serial.hashCode()).toString(); - } catch (Exception e) { - //获取失败,使用AndroidId - serial = getAndroidID(); - if (TextUtils.isEmpty(serial)) { - serial = "serial"; - } - } - - return new UUID(m_szDevIDShort.hashCode(), serial.hashCode()).toString(); - } - - /** - * 获取IMSI码 - *

需添加权限 {@code }

- * - * @return IMSI码 - */ - @SuppressLint("HardwareIds") - public static String getIMSI() { - TelephonyManager tm = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE); - try { - return tm != null ? tm.getSubscriberId() : null; - } catch (Exception ignored) { - } - return null; - } - - /** - * 判断sim卡是否准备好 - * - * @return {@code true}: 是
{@code false}: 否 - */ - public static boolean isSimCardReady() { - TelephonyManager tm = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE); - return tm != null && tm.getSimState() == TelephonyManager.SIM_STATE_READY; - } - - /** - * 获取Sim卡运营商名称 - *

中国移动、如中国联通、中国电信

- * - * @return sim卡运营商名称 - */ - public static String getSimOperatorName() { - TelephonyManager tm = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE); - return tm != null ? tm.getSimOperatorName() : null; - } - - /** - * 获取Sim卡运营商名称 - *

中国移动、如中国联通、中国电信

- * - * @return 移动网络运营商名称 - */ - public static String getSimOperatorByMnc() { - TelephonyManager tm = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE); - String operator = tm != null ? tm.getSimOperator() : null; - if (operator == null) { - return null; - } - switch (operator) { - case "46000": - case "46002": - case "46007": - return "中国移动"; - case "46001": - return "中国联通"; - case "46003": - return "中国电信"; - default: - return operator; - } - } - - /** - * 获取Sim卡序列号 - *

- * Requires Permission: - * {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE} - * - * @return 序列号 - */ - public static String getSimSerialNumber() { - try { - TelephonyManager tm = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE); - @SuppressLint("HardwareIds") String serialNumber = tm != null ? tm.getSimSerialNumber() : null; - - return serialNumber != null ? serialNumber : ""; - } catch (Exception ignored) { - } - - return ""; - } - - /** - * 获取Sim卡的国家代码 - * - * @return 国家代码 - */ - public static String getSimCountryIso() { - TelephonyManager tm = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE); - return tm != null ? tm.getSimCountryIso() : null; - } - - /** - * 读取电话号码 - *

- * Requires Permission: - * {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE} - * OR - * {@link android.Manifest.permission#READ_SMS} - *

- * - * @return 电话号码 - */ - @SuppressLint({"MissingPermission", "HardwareIds"}) - public static String getPhoneNumber() { - TelephonyManager tm = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE); - try { - if (ActivityCompat.checkSelfPermission(context, Manifest.permission.READ_SMS) != PackageManager.PERMISSION_GRANTED && - ActivityCompat.checkSelfPermission(context, Manifest.permission.READ_PHONE_NUMBERS) != PackageManager.PERMISSION_GRANTED && - ActivityCompat.checkSelfPermission(context, Manifest.permission.READ_PHONE_STATE) != PackageManager.PERMISSION_GRANTED) { - return null; - } - return tm != null ? tm.getLine1Number() : null; - } catch (Exception ignored) { - } - return null; - } - - /** - * 获得卡槽数,默认为1 - * - * @return 返回卡槽数 - */ - @SuppressLint("ObsoleteSdkInt") - public static int getSimCount() { - int count = 1; - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1) { - try { - SubscriptionManager mSubscriptionManager = (SubscriptionManager) context.getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE); - if (mSubscriptionManager != null) { - count = mSubscriptionManager.getActiveSubscriptionInfoCountMax(); - return count; - } - } catch (Exception ignored) { - } - } - try { - count = Integer.parseInt(getReflexMethod(context)); - } catch (MethodNotFoundException ignored) { - } - return count; - } - - /** - * 获取Sim卡使用的数量 - * - * @return 0, 1, 2 - */ - @SuppressLint("ObsoleteSdkInt") - public static int getSimUsedCount() { - int count = 0; - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1) { - try { - SubscriptionManager mSubscriptionManager = (SubscriptionManager) context.getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE); - if (ActivityCompat.checkSelfPermission(context, Manifest.permission.READ_PHONE_STATE) != PackageManager.PERMISSION_GRANTED) { - return count; - } - count = mSubscriptionManager.getActiveSubscriptionInfoCount(); - return count; - } catch (Exception ignored) { - } - } - - TelephonyManager tm = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE); - if (tm.getSimState() == TelephonyManager.SIM_STATE_READY) { - count = 1; - } - try { - if (Integer.parseInt(getReflexMethodWithId(context, "getSimState", "1")) == TelephonyManager.SIM_STATE_READY) { - count = 2; - } - } catch (MethodNotFoundException ignored) { - } - return count; - } - - /** - * 获取多卡信息 - * - * @return 多Sim卡的具体信息 - */ - @SuppressLint({"ObsoleteSdkInt", "Range"}) - public static List getSimMultiInfo() { - List infos = new ArrayList<>(); - try { - //Log.d(TAG, "Build.VERSION.SDK_INT = " + Build.VERSION.SDK_INT); - //Log.d(TAG, "Build.VERSION_CODES.LOLLIPOP_MR1 = " + Build.VERSION_CODES.LOLLIPOP_MR1); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1) { - Log.d(TAG, "1.版本超过5.1,调用系统方法"); - //1.版本超过5.1,调用系统方法 - SubscriptionManager mSubscriptionManager = (SubscriptionManager) context.getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE); - List activeSubscriptionInfoList = null; - if (mSubscriptionManager != null) { - ActivityCompat.checkSelfPermission(context, Manifest.permission.READ_PHONE_STATE); - activeSubscriptionInfoList = mSubscriptionManager.getActiveSubscriptionInfoList(); - } - if (activeSubscriptionInfoList != null && activeSubscriptionInfoList.size() > 0) { - //1.1.1 有使用的卡,就遍历所有卡 - for (SubscriptionInfo subscriptionInfo : activeSubscriptionInfoList) { - SimInfo simInfo = new SimInfo(); - simInfo.mCarrierName = subscriptionInfo.getCarrierName(); - simInfo.mIccId = subscriptionInfo.getIccId(); - simInfo.mSimSlotIndex = subscriptionInfo.getSimSlotIndex(); - simInfo.mNumber = subscriptionInfo.getNumber(); - simInfo.mCountryIso = subscriptionInfo.getCountryIso(); - simInfo.mSubscriptionId = subscriptionInfo.getSubscriptionId(); - /*try { - simInfo.mImei = getReflexMethodWithId(context, "getDeviceId", String.valueOf(simInfo.mSimSlotIndex)); - simInfo.mImsi = getReflexMethodWithId(context, "getSubscriberId", String.valueOf(subscriptionInfo.getSubscriptionId())); - } catch (MethodNotFoundException ignored) { - }*/ - Log.d(TAG, String.valueOf(simInfo)); - infos.add(simInfo); - } - } - } else { - Log.d(TAG, "2.版本低于5.1的系统,首先调用数据库,看能不能访问到"); - //2.版本低于5.1的系统,首先调用数据库,看能不能访问到 - Uri uri = Uri.parse("content://telephony/siminfo"); //访问raw_contacts表 - ContentResolver resolver = context.getContentResolver(); - Cursor cursor = resolver.query(uri, new String[]{"_id", "icc_id", "sim_id", "display_name", "carrier_name", "name_source", "color", "number", "display_number_format", "data_roaming", "mcc", "mnc"}, null, null, null); - if (cursor != null && cursor.moveToFirst()) { - do { - SimInfo simInfo = new SimInfo(); - simInfo.mCarrierName = cursor.getString(cursor.getColumnIndex("carrier_name")); - simInfo.mIccId = cursor.getString(cursor.getColumnIndex("icc_id")); - simInfo.mSimSlotIndex = cursor.getInt(cursor.getColumnIndex("sim_id")); - simInfo.mNumber = cursor.getString(cursor.getColumnIndex("number")); - simInfo.mCountryIso = cursor.getString(cursor.getColumnIndex("mcc")); - String id = cursor.getString(cursor.getColumnIndex("_id")); - /*try { - simInfo.mImei = getReflexMethodWithId(context, "getDeviceId", String.valueOf(simInfo.mSimSlotIndex)); - simInfo.mImsi = getReflexMethodWithId(context, "getSubscriberId", String.valueOf(id)); - } catch (MethodNotFoundException ignored) { - }*/ - Log.d(TAG, String.valueOf(simInfo)); - infos.add(simInfo); - } while (cursor.moveToNext()); - cursor.close(); - } - } - - /*Log.d(TAG, "3.通过反射读取卡槽信息,最后通过IMEI去重"); - //3.通过反射读取卡槽信息,最后通过IMEI去重 - for (int i = 0; i < getSimCount(); i++) { - infos.add(getReflexSimInfo(context, i)); - } - List simInfos = removeDuplicateWithOrder(infos); - if (simInfos.size() < getSimCount()) { - for (int i = simInfos.size(); i < getSimCount(); i++) { - simInfos.add(new SimInfo()); - } - } - return simInfos;*/ - } catch (Exception e) { - e.printStackTrace(); - } - - return infos; - } - - @Nullable - public static String getSecondIMSI() { - int maxCount = 20; - if (TextUtils.isEmpty(getIMSI())) { - return null; - } - for (int i = 0; i < maxCount; i++) { - String imsi = null; - try { - imsi = getReflexMethodWithId(context, "getSubscriberId", String.valueOf(i)); - } catch (MethodNotFoundException e) { - Log.d(TAG, String.valueOf(e)); - } - if (!TextUtils.isEmpty(imsi) && !Objects.equals(imsi, getIMSI())) { - return imsi; - } - } - return null; - } - - /** - * 通过反射获得SimInfo的信息 - * 当index为0时,读取默认信息 - * - * @param index 位置,用来当subId和phoneId - * @return {@link SimInfo} sim信息 - */ - @NonNull - private static SimInfo getReflexSimInfo(Context context, int index) { - SimInfo simInfo = new SimInfo(); - simInfo.mSimSlotIndex = index; - try { - simInfo.mImei = getReflexMethodWithId(context, "getDeviceId", String.valueOf(simInfo.mSimSlotIndex)); - //slotId,比较准确 - simInfo.mImsi = getReflexMethodWithId(context, "getSubscriberId", String.valueOf(simInfo.mSimSlotIndex)); - //subId,很不准确 - simInfo.mCarrierName = getReflexMethodWithId(context, "getSimOperatorNameForPhone", String.valueOf(simInfo.mSimSlotIndex)); - //PhoneId,基本准确 - simInfo.mCountryIso = getReflexMethodWithId(context, "getSimCountryIso", String.valueOf(simInfo.mSimSlotIndex)); - //subId,很不准确 - simInfo.mIccId = getReflexMethodWithId(context, "getSimSerialNumber", String.valueOf(simInfo.mSimSlotIndex)); - //subId,很不准确 - simInfo.mNumber = getReflexMethodWithId(context, "getLine1Number", String.valueOf(simInfo.mSimSlotIndex)); - //subId,很不准确 - } catch (MethodNotFoundException ignored) { - } - return simInfo; - } - - /** - * 通过反射调取@hide的方法 - * - * @return 返回方法调用的结果 - * @throws MethodNotFoundException 方法没有找到 - */ - private static String getReflexMethod(Context context) throws MethodNotFoundException { - String result = null; - TelephonyManager telephony = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE); - try { - Class telephonyClass = Class.forName(telephony.getClass().getName()); - Method getSimID = telephonyClass.getMethod("getPhoneCount"); - Object ob_phone = getSimID.invoke(telephony); - if (ob_phone != null) { - result = ob_phone.toString(); - } - } catch (Exception e) { - Log.d(TAG, String.valueOf(e.fillInStackTrace())); - throw new MethodNotFoundException("getPhoneCount"); - } - return result; - } - - /** - * 通过反射调取@hide的方法 - * - * @param predictedMethodName 方法名 - * @param id 参数 - * @return 返回方法调用的结果 - * @throws MethodNotFoundException 方法没有找到 - */ - private static String getReflexMethodWithId(Context context, String predictedMethodName, String id) throws MethodNotFoundException { - String result = null; - TelephonyManager telephony = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE); - try { - Class telephonyClass = Class.forName(telephony.getClass().getName()); - Class[] parameter = new Class[1]; - parameter[0] = int.class; - Method getSimID = telephonyClass.getMethod(predictedMethodName, parameter); - Class[] parameterTypes = getSimID.getParameterTypes(); - Object[] obParameter = new Object[parameterTypes.length]; - if (parameterTypes[0].getSimpleName().equals("int")) { - obParameter[0] = Integer.valueOf(id); - } else if (parameterTypes[0].getSimpleName().equals("long")) { - obParameter[0] = Long.valueOf(id); - } else { - obParameter[0] = id; - } - Object ob_phone = getSimID.invoke(telephony, obParameter); - if (ob_phone != null) { - result = ob_phone.toString(); - } - } catch (Exception e) { - Log.d(TAG, String.valueOf(e.fillInStackTrace())); - throw new MethodNotFoundException(predictedMethodName); - } - return result; - } - - // 删除ArrayList中重复元素,保持顺序 - public static List removeDuplicateWithOrder(List list) { - Set set = new HashSet(); - List newList = new ArrayList(); - for (Object element : list) { - if (set.add(element)) - newList.add(element); - } - list.clear(); - list.addAll(newList); - return list; - } - - /** - * 获取后一条通话记录 - */ - @SuppressLint("Range") - public static CallInfo getLastCallInfo(String phoneNumber) { - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP_MR1) { - return null; - } - - try { - /*String[] columns = {CallLog.Calls.CACHED_NAME, //通话记录的联系人 - CallLog.Calls.NUMBER, //通话记录的电话号码 - CallLog.Calls.DATE, //通话记录的日期 - CallLog.Calls.DURATION, //通话时长 - CallLog.Calls.TYPE, //通话类型 - (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N ? CallLog.Calls.VIA_NUMBER : ""), //来源号码 - "simid" //卡槽ID - };*/ - - CallInfo callInfo; - Cursor cursor = context.getContentResolver().query(CallLog.Calls.CONTENT_URI, null, - CallLog.Calls.NUMBER + " like ?", - new String[]{phoneNumber + "%"}, CallLog.Calls.DEFAULT_SORT_ORDER); - Log.i(TAG, "cursor count:" + cursor.getCount()); - - //noinspection LoopStatementThatDoesntLoop - while (cursor.moveToNext()) { - int simColumnIndex = -1; - if (cursor.getColumnIndex("simid") != -1) { - simColumnIndex = cursor.getColumnIndex("simid"); - } else if (cursor.getColumnIndex(CallLog.Calls.PHONE_ACCOUNT_ID) != -1) { - simColumnIndex = cursor.getColumnIndex(CallLog.Calls.PHONE_ACCOUNT_ID); - } - - callInfo = new CallInfo( - cursor.getString(cursor.getColumnIndex(CallLog.Calls.CACHED_NAME)), //姓名 - cursor.getString(cursor.getColumnIndex(CallLog.Calls.NUMBER)), //号码 - cursor.getLong(cursor.getColumnIndex(CallLog.Calls.DATE)), //获取通话日期 - cursor.getInt(cursor.getColumnIndex(CallLog.Calls.DURATION)),//获取通话时长,值为多少秒 - cursor.getInt(cursor.getColumnIndex(CallLog.Calls.TYPE)), //获取通话类型:1.呼入2.呼出3.未接 - (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N && cursor.getColumnIndex("via_number") != -1 ? cursor.getString(cursor.getColumnIndex(CallLog.Calls.VIA_NUMBER)) : null), //来源号码 - simColumnIndex != -1 ? cursor.getInt(simColumnIndex) : -1 //卡槽id - ); - Log.d(TAG, callInfo.toString()); - cursor.close(); - return callInfo; - } - } catch (Exception e) { - Log.e(TAG, "getLastCallInfo:", e); - } - - return null; - } - - /** - * SIM 卡信息 - */ - public static class SimInfo { - /** - * 运营商信息:中国移动 中国联通 中国电信 - */ - public CharSequence mCarrierName; - /** - * 卡槽ID,SimSerialNumber - */ - public CharSequence mIccId; - /** - * 卡槽id, -1 - 没插入、 0 - 卡槽1 、1 - 卡槽2 - */ - public int mSimSlotIndex; - /** - * 号码 - */ - public CharSequence mNumber; - /** - * 城市 - */ - public CharSequence mCountryIso; - /** - * 设备唯一识别码 - */ - public CharSequence mImei = getIMEI(); - /** - * SIM的编号 - */ - public CharSequence mImsi; - /** - * SIM的 Subscription Id (SIM插入顺序) - */ - public int mSubscriptionId; - - /** - * 通过 IMEI 判断是否相等 - */ - @Override - public boolean equals(Object obj) { - return obj instanceof SimInfo && (TextUtils.isEmpty(((SimInfo) obj).mImei) || ((SimInfo) obj).mImei.equals(mImei)); - } - - @NonNull - @Override - public String toString() { - return "SimInfo{" + - "mCarrierName=" + mCarrierName + - ", mIccId=" + mIccId + - ", mSimSlotIndex=" + mSimSlotIndex + - ", mNumber=" + mNumber + - ", mCountryIso=" + mCountryIso + - ", mImei=" + mImei + - ", mImsi=" + mImsi + - ", mSubscriptionId=" + mSubscriptionId + - '}'; - } - } - - /** - * 反射未找到方法 - */ - private static class MethodNotFoundException extends Exception { - - public static final long serialVersionUID = -3241033488141442594L; - - MethodNotFoundException(String info) { - super(info); - } - } -} diff --git a/app/src/main/java/com/idormy/sms/forwarder/utils/PhoneUtils.kt b/app/src/main/java/com/idormy/sms/forwarder/utils/PhoneUtils.kt new file mode 100644 index 00000000..7464350f --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/utils/PhoneUtils.kt @@ -0,0 +1,460 @@ +package com.idormy.sms.forwarder.utils + +import android.Manifest.permission +import android.annotation.SuppressLint +import android.app.PendingIntent +import android.content.ContentResolver +import android.content.Context +import android.content.Intent +import android.net.Uri +import android.os.Build +import android.os.Bundle +import android.provider.CallLog +import android.provider.ContactsContract +import android.provider.Settings +import android.telephony.SmsManager +import android.telephony.SubscriptionInfo +import android.telephony.SubscriptionManager +import android.text.TextUtils +import android.util.Log +import androidx.annotation.RequiresPermission +import androidx.core.app.ActivityCompat +import com.idormy.sms.forwarder.R +import com.idormy.sms.forwarder.core.Core +import com.idormy.sms.forwarder.entity.CallInfo +import com.idormy.sms.forwarder.entity.ContactInfo +import com.idormy.sms.forwarder.entity.SimInfo +import com.idormy.sms.forwarder.entity.SmsInfo +import com.xuexiang.xutil.XUtil +import com.xuexiang.xutil.app.IntentUtils +import com.xuexiang.xutil.data.DateUtils +import com.xuexiang.xutil.resource.ResUtils +import java.text.SimpleDateFormat +import java.util.* + + +@Suppress("PropertyName") +class PhoneUtils private constructor() { + + companion object { + const val TAG = "PhoneUtils" + + //获取多卡信息 + @SuppressLint("Range") + fun getSimMultiInfo(): MutableMap { + val infoList = HashMap() + try { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1) { + println("1.版本超过5.1,调用系统方法") + val mSubscriptionManager = XUtil.getContext().getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE) as SubscriptionManager + ActivityCompat.checkSelfPermission(XUtil.getContext(), permission.READ_PHONE_STATE) + val activeSubscriptionInfoList: List? = mSubscriptionManager.activeSubscriptionInfoList + if (activeSubscriptionInfoList != null && activeSubscriptionInfoList.isNotEmpty()) { + //1.1.1 有使用的卡,就遍历所有卡 + for (subscriptionInfo in activeSubscriptionInfoList) { + val simInfo = SimInfo() + simInfo.mCarrierName = subscriptionInfo.carrierName + simInfo.mIccId = subscriptionInfo.iccId + simInfo.mSimSlotIndex = subscriptionInfo.simSlotIndex + simInfo.mNumber = subscriptionInfo.number + simInfo.mCountryIso = subscriptionInfo.countryIso + simInfo.mSubscriptionId = subscriptionInfo.subscriptionId + println(simInfo.toString()) + infoList[simInfo.mSimSlotIndex] = simInfo + } + } + } else { + println("2.版本低于5.1的系统,首先调用数据库,看能不能访问到") + val uri = Uri.parse("content://telephony/siminfo") //访问raw_contacts表 + val resolver: ContentResolver = XUtil.getContext().contentResolver + val cursor = resolver.query(uri, arrayOf("_id", "icc_id", "sim_id", "display_name", "carrier_name", "name_source", "color", "number", "display_number_format", "data_roaming", "mcc", "mnc"), null, null, null) + if (cursor != null && cursor.moveToFirst()) { + do { + val simInfo = SimInfo() + simInfo.mCarrierName = cursor.getString(cursor.getColumnIndex("carrier_name")) + simInfo.mIccId = cursor.getString(cursor.getColumnIndex("icc_id")) + simInfo.mSimSlotIndex = cursor.getInt(cursor.getColumnIndex("sim_id")) + simInfo.mNumber = cursor.getString(cursor.getColumnIndex("number")) + simInfo.mCountryIso = cursor.getString(cursor.getColumnIndex("mcc")) + //val id = cursor.getString(cursor.getColumnIndex("_id")) + println(simInfo.toString()) + infoList[simInfo.mSimSlotIndex] = simInfo + } while (cursor.moveToNext()) + cursor.close() + } + } + } catch (e: java.lang.Exception) { + e.printStackTrace() + } + return infoList + } + + //获取设备名称 + fun getDeviceName(): String { + return try { + Settings.Secure.getString(XUtil.getContentResolver(), "bluetooth_name") + } catch (e: Exception) { + e.printStackTrace() + Build.BRAND + " " + Build.MODEL + } + } + + /** + * 发送短信 + *

需添加权限 {@code }

+ * + * @param subId 发送卡的subId,传入 -1 则 SmsManager.getDefault() + * @param mobileList 接收号码列表 + * @param message 短信内容 + */ + @Suppress("DEPRECATION") + @SuppressLint("SoonBlockedPrivateApi", "DiscouragedPrivateApi") + @RequiresPermission(permission.SEND_SMS) + fun sendSms(subId: Int, mobileList: String, message: String): String? { + val mobiles = mobileList.replace(";", ";").replace(",", ";").replace(",", ";") + Log.d(TAG, "subId = $subId, mobiles = $mobiles, message = $message") + val mobileArray = mobiles.split(";".toRegex()).toTypedArray() + for (mobile in mobileArray) { + try { + val sendFlags = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) PendingIntent.FLAG_IMMUTABLE else PendingIntent.FLAG_ONE_SHOT + val sendPI = PendingIntent.getBroadcast(XUtil.getContext(), 0, Intent(), sendFlags) + + val smsManager = if (subId > -1 && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1) SmsManager.getSmsManagerForSubscriptionId(subId) else SmsManager.getDefault() + //TODO: Android 5.1.1 以下使用反射指定卡槽 + if (subId > -1 && Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP_MR1) { + Log.d(TAG, "Android 5.1.1 以下使用反射指定卡槽") + val clz = SmsManager::class.java + val field = clz.getDeclaredField("mSubId") // 反射拿到变量 + field.isAccessible = true // 修改权限为可读写 + field.set(smsManager, subId) + } + + //TODO: 切割长短信 + if (message.length >= 70) { + val deliverFlags = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) PendingIntent.FLAG_IMMUTABLE else 0 + val deliverPI = PendingIntent.getBroadcast(XUtil.getContext(), 0, Intent("DELIVERED_SMS_ACTION"), deliverFlags) + + val sentPendingIntents = ArrayList() + val deliveredPendingIntents = ArrayList() + val divideContents = smsManager.divideMessage(message) + + for (i in divideContents.indices) { + sentPendingIntents.add(i, sendPI) + deliveredPendingIntents.add(i, deliverPI) + } + smsManager.sendMultipartTextMessage(mobile, null, divideContents, sentPendingIntents, deliveredPendingIntents) + } else { + smsManager.sendTextMessage(mobile, null, message, sendPI, null) + } + } catch (e: Exception) { + Log.e(TAG, e.message.toString()) + return e.message.toString() + } + } + + return null + } + + //获取通话记录列表 + fun getCallInfoList(type: Int, limit: Int, offset: Int, phoneNumber: String?): MutableList { + val callInfoList: MutableList = mutableListOf() + try { + var selection = "1=1" + val selectionArgs = ArrayList() + if (type > 0) { + selection += " and " + CallLog.Calls.TYPE + " = ?" + selectionArgs.add("$type") + } + if (!TextUtils.isEmpty(phoneNumber)) { + selection += " and " + CallLog.Calls.NUMBER + " like ?" + selectionArgs.add("%$phoneNumber%") + } + Log.d(TAG, "selection = $selection") + Log.d(TAG, "selectionArgs = $selectionArgs") + + //TODO:避免超过总数后循环取出 + val cursorTotal = Core.app.contentResolver.query( + CallLog.Calls.CONTENT_URI, + null, + selection, + selectionArgs.toTypedArray(), + CallLog.Calls.DEFAULT_SORT_ORDER + ) ?: return callInfoList + if (offset >= cursorTotal.count) return callInfoList + + val cursor = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + Core.app.contentResolver.query( + CallLog.Calls.CONTENT_URI, + null, + Bundle().apply { + putString(ContentResolver.QUERY_ARG_SQL_SELECTION, selection) + putStringArray(ContentResolver.QUERY_ARG_SQL_SELECTION_ARGS, selectionArgs.toTypedArray()) + putStringArray(ContentResolver.QUERY_ARG_SORT_COLUMNS, arrayOf(CallLog.Calls.DATE)) + putInt(ContentResolver.QUERY_ARG_SORT_DIRECTION, ContentResolver.QUERY_SORT_DIRECTION_DESCENDING) + putInt(ContentResolver.QUERY_ARG_LIMIT, limit) + putInt(ContentResolver.QUERY_ARG_OFFSET, offset) + }, + null) + } else { + Core.app.contentResolver.query( + CallLog.Calls.CONTENT_URI, + null, + selection, + selectionArgs.toTypedArray(), + CallLog.Calls.DEFAULT_SORT_ORDER + " limit $limit offset $offset" + ) + } ?: return callInfoList + + Log.i(TAG, "cursor count:" + cursor.count) + if (cursor.count == 0) return callInfoList + + if (cursor.moveToFirst()) { + val indexName = cursor.getColumnIndex(CallLog.Calls.CACHED_NAME) + val indexNumber = cursor.getColumnIndex(CallLog.Calls.NUMBER) + val indexDate = cursor.getColumnIndex(CallLog.Calls.DATE) + val indexDuration = cursor.getColumnIndex(CallLog.Calls.DURATION) + val indexType = cursor.getColumnIndex(CallLog.Calls.TYPE) + val indexViaNumber = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && cursor.getColumnIndex("via_number") != -1) cursor.getColumnIndex("via_number") else -1 + var indexSimId = -1 + if (cursor.getColumnIndex("simid") != -1) { + indexSimId = cursor.getColumnIndex("simid") + } else if (cursor.getColumnIndex(CallLog.Calls.PHONE_ACCOUNT_ID) != -1 + && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP + ) { + indexSimId = cursor.getColumnIndex(CallLog.Calls.PHONE_ACCOUNT_ID) + } + do { + val callInfo = CallInfo( + cursor.getString(indexName) ?: "", //姓名 + cursor.getString(indexNumber) ?: "", //号码 + cursor.getLong(indexDate), //获取通话日期 + cursor.getInt(indexDuration), //获取通话时长,值为多少秒 + cursor.getInt(indexType), //获取通话类型:1.呼入 2.呼出 3.未接 + if (indexViaNumber != -1) cursor.getString(indexViaNumber) else "", //来源号码 + if (indexSimId != -1) cursor.getInt(indexSimId) else -1 //卡槽id + ) + Log.d(TAG, callInfo.toString()) + callInfoList.add(callInfo) + } while (cursor.moveToNext()) + if (!cursor.isClosed) cursor.close() + } + } catch (e: java.lang.Exception) { + Log.e(TAG, "getCallInfoList:", e) + } + + return callInfoList + } + + //获取后一条通话记录 + @SuppressLint("Range") + fun getLastCallInfo(callType: Int, phoneNumber: String?): CallInfo? { + val callInfoList = getCallInfoList(callType, 1, 0, phoneNumber) + if (callInfoList.isNotEmpty()) return callInfoList[0] + return null + } + + //获取联系人列表 + fun getContactInfoList(limit: Int, offset: Int, phoneNumber: String?, name: String?): MutableList { + val contactInfoList: MutableList = mutableListOf() + + try { + var selection = "1=1" + val selectionArgs = ArrayList() + if (!TextUtils.isEmpty(phoneNumber)) { + /*selection += " and " + ContactsContract.CommonDataKinds.Phone.NUMBER + " in (?,?,?) " + val phone1 = phoneNumber?.subSequence(0, 3).toString() + " " + phoneNumber?.substring(3, 7) + + " " + phoneNumber?.substring(7) + val phone2 = phoneNumber?.subSequence(0, 3).toString() + "-" + phoneNumber?.substring(3, 7) + + "-" + phoneNumber?.substring(7) + selectionArgs.add("%$phoneNumber%") + selectionArgs.add("%$phone1%") + selectionArgs.add("%$phone2%")*/ + selection += " and replace(replace(" + ContactsContract.CommonDataKinds.Phone.NUMBER + ",' ',''),'-','') like ?" + selectionArgs.add("%$phoneNumber%") + } + if (!TextUtils.isEmpty(name)) { + selection += " and " + ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME + " like ?" + selectionArgs.add("%$name%") + } + Log.d(TAG, "selection = $selection") + Log.d(TAG, "selectionArgs = $selectionArgs") + + //TODO:避免超过总数后循环取出 + val cursorTotal = Core.app.contentResolver.query( + ContactsContract.CommonDataKinds.Phone.CONTENT_URI, + null, + selection, + selectionArgs.toTypedArray(), + ContactsContract.CommonDataKinds.Phone.SORT_KEY_PRIMARY + ) ?: return contactInfoList + if (offset >= cursorTotal.count) return contactInfoList + + val cursor = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + Core.app.contentResolver.query( + ContactsContract.CommonDataKinds.Phone.CONTENT_URI, + null, + Bundle().apply { + putString(ContentResolver.QUERY_ARG_SQL_SELECTION, selection) + putStringArray(ContentResolver.QUERY_ARG_SQL_SELECTION_ARGS, selectionArgs.toTypedArray()) + putStringArray(ContentResolver.QUERY_ARG_SORT_COLUMNS, arrayOf(ContactsContract.CommonDataKinds.Phone.SORT_KEY_PRIMARY)) + putInt(ContentResolver.QUERY_ARG_SORT_DIRECTION, ContentResolver.QUERY_SORT_DIRECTION_ASCENDING) + putInt(ContentResolver.QUERY_ARG_LIMIT, limit) + putInt(ContentResolver.QUERY_ARG_OFFSET, offset) + }, + null) + } else { + Core.app.contentResolver.query( + ContactsContract.CommonDataKinds.Phone.CONTENT_URI, + null, + selection, + selectionArgs.toTypedArray(), + ContactsContract.CommonDataKinds.Phone.SORT_KEY_PRIMARY + " limit $limit offset $offset" + ) + } ?: return contactInfoList + + Log.i(TAG, "cursor count:" + cursor.count) + if (cursor.count == 0) return contactInfoList + + if (cursor.moveToFirst()) { + val displayNameIndex = cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME) + val mobileNoIndex = cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER) + do { + val contactInfo = ContactInfo( + cursor.getString(displayNameIndex), //姓名 + cursor.getString(mobileNoIndex), //号码 + ) + Log.d(TAG, contactInfo.toString()) + contactInfoList.add(contactInfo) + } while (cursor.moveToNext()) + if (!cursor.isClosed) cursor.close() + } + } catch (e: java.lang.Exception) { + Log.e(TAG, "getContactInfoList:", e) + } + + return contactInfoList + } + + //获取联系人姓名 + fun getContactByNumber(phoneNumber: String?): MutableList { + val contactInfoList = mutableListOf() + if (TextUtils.isEmpty(phoneNumber)) return contactInfoList + return getContactInfoList(1, 0, phoneNumber, null) + } + + //获取通话记录转发内容 + fun getCallMsg(callInfo: CallInfo): String { + val sb = StringBuilder() + sb.append(ResUtils.getString(R.string.linkman)).append(callInfo.name).append("\n") + if (!TextUtils.isEmpty(callInfo.viaNumber)) sb.append(ResUtils.getString(R.string.via_number)).append(callInfo.viaNumber).append("\n") + if (callInfo.dateLong > 0L) sb.append(ResUtils.getString(R.string.call_date)).append(DateUtils.millis2String(callInfo.dateLong, SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault()))).append("\n") + if (callInfo.duration > 0) sb.append(ResUtils.getString(R.string.call_duration)).append(callInfo.duration).append("s\n") + sb.append(ResUtils.getString(R.string.mandatory_type)) + //通话类型:1.呼入 2.呼出 3.未接 + when (callInfo.type) { + 1 -> sb.append(ResUtils.getString(R.string.received_call)) + 2 -> sb.append(ResUtils.getString(R.string.local_outgoing_call)) + else -> sb.append(ResUtils.getString(R.string.missed_call)) + } + return sb.toString() + } + + // 获取用户短信列表 + fun getSmsList(type: Int, limit: Int, offset: Int, keyword: String): MutableList { + val smsInfoList: MutableList = mutableListOf() + try { + var selection = "1=1" + val selectionArgs = ArrayList() + if (type > 0) { + selection += " and type = ?" + selectionArgs.add("$type") + } + if (!TextUtils.isEmpty(keyword)) { + selection += " and body like ?" + selectionArgs.add("%$keyword%") + } + Log.d(TAG, "selection = $selection") + Log.d(TAG, "selectionArgs = $selectionArgs") + + //TODO:避免超过总数后循环取出 + val cursorTotal = Core.app.contentResolver.query( + Uri.parse("content://sms/"), + null, + selection, + selectionArgs.toTypedArray(), + "date desc" + ) ?: return smsInfoList + if (offset >= cursorTotal.count) return smsInfoList + + val cursor = Core.app.contentResolver.query( + Uri.parse("content://sms/"), + null, + selection, + selectionArgs.toTypedArray(), + "date desc limit $limit offset $offset" + ) ?: return smsInfoList + + Log.i(TAG, "cursor count:" + cursor.count) + if (cursor.count == 0) return smsInfoList + + if (cursor.moveToFirst()) { + val indexAddress = cursor.getColumnIndex("address") + val indexBody = cursor.getColumnIndex("body") + val indexDate = cursor.getColumnIndex("date") + val indexType = cursor.getColumnIndex("type") + var indexSimId = -1 + if (cursor.getColumnIndex("sim_id") != -1) { + indexSimId = cursor.getColumnIndex("sim_id") + } + do { + val smsInfo = SmsInfo() + val phoneNumber = cursor.getString(indexAddress) + // 根据手机号码查询用户名 + val contacts = getContactByNumber(phoneNumber) + smsInfo.name = if (contacts.isNotEmpty()) contacts[0].name else ResUtils.getString(R.string.unknown_number) + // 联系人号码 + smsInfo.number = phoneNumber + // 短信内容 + smsInfo.content = cursor.getString(indexBody) + // 短信时间 + smsInfo.date = cursor.getLong(indexDate) + // 短信类型: 1=接收, 2=发送 + smsInfo.type = cursor.getInt(indexType) + // 卡槽id + smsInfo.simId = if (indexSimId != -1) cursor.getInt(indexSimId) else -1 + smsInfoList.add(smsInfo) + } while (cursor.moveToNext()) + if (!cursor.isClosed) cursor.close() + } + } catch (e: java.lang.Exception) { + e.printStackTrace() + } + return smsInfoList + } + + /** + * 跳至拨号界面 + * + * @param phoneNumber 电话号码 + */ + fun dial(phoneNumber: String?) { + XUtil.getContext().startActivity(IntentUtils.getDialIntent(phoneNumber, true)) + } + + /** + * 拨打电话 + * + * 需添加权限 `` + * + * @param phoneNumber 电话号码 + */ + fun call(phoneNumber: String?) { + XUtil.getContext().startActivity(IntentUtils.getCallIntent(phoneNumber, true)) + } + + } + + init { + throw UnsupportedOperationException("u can't instantiate me...") + } +} \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/utils/PlaceholderHelper.kt b/app/src/main/java/com/idormy/sms/forwarder/utils/PlaceholderHelper.kt new file mode 100644 index 00000000..60c92eec --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/utils/PlaceholderHelper.kt @@ -0,0 +1,105 @@ +package com.idormy.sms.forwarder.utils + +import android.graphics.Color +import android.graphics.drawable.GradientDrawable +import android.view.View +import android.view.animation.Animation +import android.view.animation.ScaleAnimation +import com.idormy.sms.forwarder.R +import me.samlss.broccoli.PlaceholderParameter + +/** + * 占位控件 + * + * @author xuexiang + * @since 2019/4/7 下午1:02 + */ +@Suppress("SameParameterValue", "unused") +class PlaceholderHelper private constructor() { + companion object { + fun getParameter(view: View?): PlaceholderParameter? { + return if (view == null) { + null + } else getParameter(view.id, view) + } + + private fun getParameter(viewId: Int, view: View): PlaceholderParameter? { + val placeHolderColor = Color.parseColor("#DDDDDD") + when (viewId) { + R.id.tv_ver_name, R.id.tv_time -> { + val summaryAnimation: Animation = ScaleAnimation(0.4f, 1f, 1f, 1f) + summaryAnimation.duration = 600 + return getAnimationRectanglePlaceholder(view, summaryAnimation, placeHolderColor, 5) + } + R.id.tv_app_name, R.id.tv_from, R.id.tv_name -> { + val titleAnimation: Animation = ScaleAnimation(0.3f, 1f, 1f, 1f) + titleAnimation.duration = 600 + return getAnimationRectanglePlaceholder(view, titleAnimation, placeHolderColor, 5) + } + R.id.tv_pkg_name, R.id.tv_duration, R.id.tv_phone_number, R.id.tv_content -> { + val summaryAnimation2: Animation = ScaleAnimation(0.5f, 1f, 1f, 1f) + summaryAnimation2.duration = 400 + return getAnimationRectanglePlaceholder(view, summaryAnimation2, placeHolderColor, 5) + } + R.id.iv_app_icon, R.id.iv_image, R.id.iv_sim_image, R.id.sb_letter, R.id.iv_copy, R.id.iv_call, R.id.iv_reply -> { + val imageAnimation: Animation = ScaleAnimation(0.5f, 1f, 0.5f, 1f, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f) + imageAnimation.duration = 800 + return getAnimationOvalPlaceholder(view, imageAnimation, placeHolderColor) + } + else -> {} + } + return null + } + + /** + * 圆形的动画占位 + */ + private fun getAnimationOvalPlaceholder(view: View, animation: Animation, placeHolderColor: Int): PlaceholderParameter { + animation.repeatMode = Animation.REVERSE + animation.repeatCount = Animation.INFINITE + return PlaceholderParameter.Builder() + .setView(view) + .setAnimation(animation) + .setDrawable(DrawableUtils.createOvalDrawable(placeHolderColor)) + .build() + } + + /** + * 矩形的动画占位 + */ + private fun getAnimationRectanglePlaceholder(view: View, animation: Animation, placeHolderColor: Int, cornerRadius: Int): PlaceholderParameter { + animation.repeatMode = Animation.REVERSE + animation.repeatCount = Animation.INFINITE + return PlaceholderParameter.Builder() + .setView(view) + .setAnimation(animation) + .setDrawable(DrawableUtils.createRectangleDrawable(placeHolderColor, cornerRadius.toFloat())) + .build() + } + + /** + * 圆形的占位 + */ + private fun getOvalPlaceholder(view: View, placeHolderColor: Int): PlaceholderParameter { + return getPlaceholder(view, DrawableUtils.createOvalDrawable(placeHolderColor)) + } + + /** + * 矩形的占位 + */ + private fun getRectanglePlaceholder(view: View, placeHolderColor: Int, cornerRadius: Int): PlaceholderParameter { + return getPlaceholder(view, DrawableUtils.createRectangleDrawable(placeHolderColor, cornerRadius.toFloat())) + } + + private fun getPlaceholder(view: View, ovalDrawable: GradientDrawable): PlaceholderParameter { + return PlaceholderParameter.Builder() + .setView(view) + .setDrawable(ovalDrawable) + .build() + } + } + + init { + throw UnsupportedOperationException("Can not be instantiated.") + } +} \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/utils/RandomUtils.kt b/app/src/main/java/com/idormy/sms/forwarder/utils/RandomUtils.kt new file mode 100644 index 00000000..9e902f6e --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/utils/RandomUtils.kt @@ -0,0 +1,275 @@ +package com.idormy.sms.forwarder.utils + +import android.graphics.Color +import android.text.TextUtils +import java.util.* + +/** + *
+ * desc   : Random Utils
+ * author : xuexiang
+ * time   : 2018/4/28 上午12:41
+
* + * + * Shuffling algorithm + * * [.shuffle] Shuffling algorithm, Randomly permutes the specified array using a default source of + * randomness + * * [.shuffle] Shuffling algorithm, Randomly permutes the specified array + * * [.shuffle] Shuffling algorithm, Randomly permutes the specified int array using a default source of + * randomness + * * [.shuffle] Shuffling algorithm, Randomly permutes the specified int array + * + * + * get random int + * * [.getRandom] get random int between 0 and max + * * [.getRandom] get random int between min and max + * + * + * get random numbers or letters + * * [.getRandomCapitalLetters] get a fixed-length random string, its a mixture of uppercase letters + * * [.getRandomLetters] get a fixed-length random string, its a mixture of uppercase and lowercase letters + * + * * [.getRandomLowerCaseLetters] get a fixed-length random string, its a mixture of lowercase letters + * * [.getRandomNumbers] get a fixed-length random string, its a mixture of numbers + * * [.getRandomNumbersAndLetters] get a fixed-length random string, its a mixture of uppercase, lowercase + * letters and numbers + * * [.getRandom] get a fixed-length random string, its a mixture of chars in source + * * [.getRandom] get a fixed-length random string, its a mixture of chars in sourceChar + * + * + */ +@Suppress("MemberVisibilityCanBePrivate", "unused") +class RandomUtils private constructor() { + companion object { + private const val NUMBERS_AND_LETTERS = + "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" + private const val NUMBERS = "0123456789" + private const val LETTERS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" + private const val CAPITAL_LETTERS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + private const val LOWER_CASE_LETTERS = "abcdefghijklmnopqrstuvwxyz" + + /** + * 在数字和英文字母中获取一个定长的随机字符串 + * + * @param length 长度 + * @return 随机字符串 + * @see RandomUtils.getRandom + */ + @JvmStatic + fun getRandomNumbersAndLetters(length: Int): String? { + return getRandom(NUMBERS_AND_LETTERS, length) + } + + /** + * 在数字中获取一个定长的随机字符串 + * + * @param length 长度 + * @return 随机数字符串 + * @see RandomUtils.getRandom + */ + fun getRandomNumbers(length: Int): String? { + return getRandom(NUMBERS, length) + } + + /** + * 在英文字母中获取一个定长的随机字符串 + * + * @param length 长度 + * @return 随机字母字符串 + * @see RandomUtils.getRandom + */ + fun getRandomLetters(length: Int): String? { + return getRandom(LETTERS, length) + } + + /** + * 在大写英文字母中获取一个定长的随机字符串 + * + * @param length 长度 + * @return 随机字符串 只包含大写字母 + * @see RandomUtils.getRandom + */ + fun getRandomCapitalLetters(length: Int): String? { + return getRandom(CAPITAL_LETTERS, length) + } + + /** + * 在小写英文字母中获取一个定长的随机字符串 + * + * @param length 长度 + * @return 随机字符串 只包含小写字母 + * @see RandomUtils.getRandom + */ + fun getRandomLowerCaseLetters(length: Int): String? { + return getRandom(LOWER_CASE_LETTERS, length) + } + + /** + * 在一个字符数组源中获取一个定长的随机字符串 + * + * @param source 源字符串 + * @param length 长度 + * @return + * * if source is null or empty, return null + * * else see [RandomUtils.getRandom] + * + */ + fun getRandom(source: String, length: Int): String? { + return if (TextUtils.isEmpty(source)) null else getRandom(source.toCharArray(), length) + } + + /** + * 在一个字符数组源中获取一个定长的随机字符串 + * + * @param sourceChar 字符数组源 + * @param length 长度 + * @return + * * if sourceChar is null or empty, return null + * * if length less than 0, return null + * + */ + fun getRandom(sourceChar: CharArray?, length: Int): String? { + if (sourceChar == null || sourceChar.isEmpty() || length < 0) { + return null + } + val str = StringBuilder(length) + val random = Random() + for (i in 0 until length) { + str.append(sourceChar[random.nextInt(sourceChar.size)]) + } + return str.toString() + } + + /** + * get random int between 0 and max + * + * @param max 最大随机数 + * @return + * * if max <= 0, return 0 + * * else return random int between 0 and max + * + */ + fun getRandom(max: Int): Int { + return getRandom(0, max) + } + + /** + * get random int between min and max + * + * @param min 最小随机数 + * @param max 最大随机数 + * @return + * * if min > max, return 0 + * * if min == max, return min + * * else return random int between min and max + * + */ + fun getRandom(min: Int, max: Int): Int { + if (min > max) { + return 0 + } + return if (min == max) { + min + } else min + Random().nextInt(max - min) + } + + /** + * 获取随机颜色 + * + * @return + */ + val randomColor: Int + get() { + val random = Random() + val r = random.nextInt(256) + val g = random.nextInt(256) + val b = random.nextInt(256) + return Color.rgb(r, g, b) + } + + /** + * 随机打乱数组中的内容 + * + * @param objArray + * @return + */ + fun shuffle(objArray: Array?): Boolean { + return if (objArray == null) { + false + } else shuffle( + objArray, + getRandom(objArray.size) + ) + } + + /** + * 随机打乱数组中的内容 + * + * @param objArray + * @param shuffleCount + * @return + */ + private fun shuffle(objArray: Array?, shuffleCount: Int): Boolean { + var length = 0 + if (objArray == null || shuffleCount < 0 || objArray.size.also { + length = it + } < shuffleCount) { + return false + } + for (i in 1..shuffleCount) { + val random = getRandom(length - i) + val temp = objArray[length - i] + objArray[length - i] = objArray[random] + objArray[random] = temp + } + return true + } + + /** + * 随机打乱数组中的内容 + * + * @param intArray + * @return + */ + fun shuffle(intArray: IntArray?): IntArray? { + return if (intArray == null) { + null + } else shuffle( + intArray, + getRandom(intArray.size) + ) + } + + /** + * 随机打乱数组中的内容 + * + * @param intArray + * @param shuffleCount + * @return + */ + fun shuffle(intArray: IntArray?, shuffleCount: Int): IntArray? { + var length = 0 + if (intArray == null || shuffleCount < 0 || intArray.size.also { + length = it + } < shuffleCount) { + return null + } + val out = IntArray(shuffleCount) + for (i in 1..shuffleCount) { + val random = getRandom(length - i) + out[i - 1] = intArray[random] + val temp = intArray[length - i] + intArray[length - i] = intArray[random] + intArray[random] = temp + } + return out + } + } + + /** + * Don't let anyone instantiate this class. + */ + init { + throw Error("Do not need instantiate!") + } +} \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/utils/RuleLine.java b/app/src/main/java/com/idormy/sms/forwarder/utils/RuleLine.java deleted file mode 100644 index 7026da84..00000000 --- a/app/src/main/java/com/idormy/sms/forwarder/utils/RuleLine.java +++ /dev/null @@ -1,461 +0,0 @@ -package com.idormy.sms.forwarder.utils; - - -import android.util.Log; - -import androidx.annotation.NonNull; - -import com.idormy.sms.forwarder.model.vo.SmsVo; - -import java.util.ArrayList; -import java.util.List; -import java.util.regex.Matcher; -import java.util.regex.Pattern; -import java.util.regex.PatternSyntaxException; - -@SuppressWarnings({"unused"}) -class RuleLine { - public static final String CONJUNCTION_AND = "并且"; - public static final String CONJUNCTION_OR = "或者"; - public static final String FILED_PHONE_NUM = "手机号"; - public static final String FILED_PACKAGE_NAME = "APP包名"; - public static final String FILED_MSG_CONTENT = "短信内容"; - public static final String FILED_INFORM_CONTENT = "通知内容"; - public static final String FILED_INFORM_TITLE = "通知标题"; - public static final String FILED_SIM_SLOT_INFO = "卡槽信息"; - public static final String SURE_YES = "是"; - public static final String SURE_NOT = "不是"; - public static final String CHECK_EQUALS = "相等"; - public static final String CHECK_CONTAIN = "包含"; - public static final String CHECK_NOT_CONTAIN = "不包含"; - public static final String CHECK_START_WITH = "开头"; - public static final String CHECK_END_WITH = "结尾"; - public static final String CHECK_REGEX = "正则匹配"; - public static final List CONJUNCTION_LIST = new ArrayList<>(); - public static final List FILED_LIST = new ArrayList<>(); - public static final List SURE_LIST = new ArrayList<>(); - public static final List CHECK_LIST = new ArrayList<>(); - static final String TAG = "RuleLine"; - static Boolean STARTLOG = true; - - static { - CONJUNCTION_LIST.add("and"); - CONJUNCTION_LIST.add("or"); - CONJUNCTION_LIST.add(CONJUNCTION_AND); - CONJUNCTION_LIST.add(CONJUNCTION_OR); - } - - static { - FILED_LIST.add(FILED_PHONE_NUM); - FILED_LIST.add(FILED_PACKAGE_NAME); - FILED_LIST.add(FILED_MSG_CONTENT); - FILED_LIST.add(FILED_INFORM_CONTENT); - FILED_LIST.add(FILED_INFORM_TITLE); - FILED_LIST.add(FILED_SIM_SLOT_INFO); - } - - static { - SURE_LIST.add(SURE_YES); - SURE_LIST.add(SURE_NOT); - } - - static { - CHECK_LIST.add(CHECK_EQUALS); - CHECK_LIST.add(CHECK_CONTAIN); - CHECK_LIST.add(CHECK_NOT_CONTAIN); - CHECK_LIST.add(CHECK_START_WITH); - CHECK_LIST.add(CHECK_END_WITH); - CHECK_LIST.add(CHECK_REGEX); - } - - //开头有几个空格 - int headSpaceNum = 0; - RuleLine beforeRuleLine; - RuleLine nextRuleLine; - RuleLine parentRuleLine; - RuleLine childRuleLine; - //and or - String conjunction; - //手机号 短信内容 - String field; - // 是否 - String sure; - String check; - String value; - - public RuleLine(String line, int lineNum, RuleLine beforeRuleLine) throws Exception { - logg("----------" + lineNum + "-----------------"); - logg(line); - //规则检验: - //并且 是 手机号 相等 10086 - //[并且, 是, 手机号, 相等, 10086] - // 并且 是 内容 包含 asfas - //[, , 并且, 是, 内容, 包含, asfas] - - //处理头空格数用来确认跟上一行节点的相对位置:是同级还是子级 - //处理4个字段,之后的全部当做value - - //标记3个阶段 - boolean isCountHeading = false; - boolean isDealMiddle = false; - boolean isDealValue = false; - - //用于保存4个中间体: 并且, 是, 内容, 包含 - List middleList = new ArrayList<>(4); - //保存每个中间体字符串 - StringBuilder buildMiddleWord = new StringBuilder(); - StringBuilder valueBuilder = new StringBuilder(); - - for (int i = 0; i < line.length(); i++) { - String w = String.valueOf(line.charAt(i)); - logg("walk over:" + w); - - //控制阶段 - //开始处理头 - if (i == 0) { - if (" ".equals(w)) { - logg("start to isCountHeading:"); - - isCountHeading = true; - } else { - //直接进入处理中间体阶段 - isDealMiddle = true; - logg("start to isDealMiddle:"); - } - - } - //正在数空格头,但是遇到非空格,阶段变更:由处理空头阶段 变为 处理 中间体阶段 - if (isCountHeading && (!" ".equals(w))) { - logg("isCountHeading to isDealMiddle:"); - - isCountHeading = false; - isDealMiddle = true; - } - - //正在处理中间体,中间体数量够了,阶段变更:由处理中间体 变为 处理 value - if (isDealMiddle && middleList.size() == 4) { - logg("isDealMiddle done middleList:" + middleList); - logg("isDealMiddle to isDealValue:"); - isDealMiddle = false; - isDealValue = true; - } - - logg("isCountHeading:" + isCountHeading); - logg("isDealMiddle:" + isDealMiddle); - logg("isDealValue:" + isDealValue); - - if (isCountHeading) { - logg("headSpaceNum++:" + headSpaceNum); - headSpaceNum++; - } - - if (isDealMiddle) { - //遇到空格 - if (" ".equals(w)) { - if (buildMiddleWord.length() == 0) { - throw new Exception(lineNum + "行:语法错误不允许出现连续空格!"); - } else { - //生成了一个中间体 - middleList.add(buildMiddleWord.toString()); - logg("get Middle++:" + buildMiddleWord); - - buildMiddleWord = new StringBuilder(); - } - } else { - //把w拼接到中间体上 - buildMiddleWord.append(w); - logg("buildMiddleWord length:" + buildMiddleWord.length() + "buildMiddleWord:" + buildMiddleWord); - - } - } - - if (isDealValue) { - //把余下的所有字符都拼接给value - valueBuilder.append(w); - } - - } - logg("isDealValue done valueBuilder:" + valueBuilder); - - - if (middleList.size() != 4) { - throw new Exception(lineNum + "行配置错误:每行必须有4段组成,例如: 并且 手机号 是 相等 "); - } - - - //规则对齐 - if (beforeRuleLine != null) { - logg("beforeRuleLine :" + beforeRuleLine); - logg("thisRuleLine :" + this); - - //同级别 - if (headSpaceNum == beforeRuleLine.headSpaceNum) { - logg("同级别"); - this.beforeRuleLine = beforeRuleLine; - beforeRuleLine.nextRuleLine = this; - } - //子级 - if (headSpaceNum - 1 == beforeRuleLine.headSpaceNum) { - logg("子级"); - this.parentRuleLine = beforeRuleLine; - beforeRuleLine.childRuleLine = this; - } - //查找父级别 - if (headSpaceNum < beforeRuleLine.headSpaceNum) { - //匹配到最近一个同级 - RuleLine fBeforeRuleLine = beforeRuleLine.getBeforeRuleLine(); - if (fBeforeRuleLine == null) { - fBeforeRuleLine = beforeRuleLine.getParentRuleLine(); - } - - while (fBeforeRuleLine != null) { - logg("fBeforeRuleLine" + fBeforeRuleLine); - - //查找到同级别 - if (headSpaceNum == fBeforeRuleLine.headSpaceNum) { - logg("父级别"); - this.beforeRuleLine = fBeforeRuleLine; - fBeforeRuleLine.nextRuleLine = this; - break; - } else { - //向上查找 - RuleLine testfBeforeRuleLine = fBeforeRuleLine.getBeforeRuleLine(); - if (testfBeforeRuleLine == null) { - testfBeforeRuleLine = fBeforeRuleLine.getParentRuleLine(); - } - fBeforeRuleLine = testfBeforeRuleLine; - - } - } - } - - } else { - logg("根级别"); - } - - - this.conjunction = middleList.get(0); - this.sure = middleList.get(1); - this.field = middleList.get(2); - this.check = middleList.get(3); - this.value = valueBuilder.toString(); - - if (!CONJUNCTION_LIST.contains(this.conjunction)) { - throw new Exception(lineNum + "行配置错误:连接词只支持:" + CONJUNCTION_LIST + " 但提供了" + this.conjunction); - } - if (!FILED_LIST.contains(this.field)) { - throw new Exception(lineNum + "行配置错误:字段只支持:" + FILED_LIST + " 但提供了" + this.field); - } - if (!SURE_LIST.contains(this.sure)) { - throw new Exception(lineNum + "行配置错误 " + this.sure + " 确认词只支持:" + SURE_LIST + " 但提供了" + this.sure); - } - if (!CHECK_LIST.contains(this.check)) { - throw new Exception(lineNum + "行配置错误:比较词只支持:" + CHECK_LIST + " 但提供了" + this.check); - } - - logg("----------" + lineNum + "==" + this); - - } - - public static void startLog(boolean startLog) { - STARTLOG = startLog; - } - - public static void logg(String msg) { - if (STARTLOG) { - Log.i(TAG, msg); - } - } - - //字段分支 - public boolean checkMsg(SmsVo msg) { - - //检查这一行和上一行合并的结果是否命中 - boolean mixChecked = false; - - //先检查规则是否命中 - switch (this.field) { - case FILED_PHONE_NUM: - case FILED_PACKAGE_NAME: - mixChecked = checkValue(msg.getMobile()); - break; - case FILED_MSG_CONTENT: - case FILED_INFORM_CONTENT: - mixChecked = checkValue(msg.getContent()); - break; - case FILED_INFORM_TITLE: - case FILED_SIM_SLOT_INFO: - mixChecked = checkValue(msg.getSimInfo()); - break; - default: - break; - } - - //整合肯定词 - switch (this.sure) { - case SURE_YES: - break; - case SURE_NOT: - mixChecked = !mixChecked; - break; - default: - mixChecked = false; - break; - } - - logg("rule:" + this + " checkMsg:" + msg + " checked:" + mixChecked); - return mixChecked; - - } - - //内容分支 - public boolean checkValue(String msgValue) { - boolean checked = false; - - switch (this.check) { - case CHECK_EQUALS: - checked = this.value.equals(msgValue); - break; - case CHECK_CONTAIN: - if (msgValue != null) { - checked = msgValue.contains(this.value); - } - break; - case CHECK_NOT_CONTAIN: - if (msgValue != null) { - checked = !msgValue.contains(this.value); - } - break; - case CHECK_START_WITH: - if (msgValue != null) { - checked = msgValue.startsWith(this.value); - } - break; - case CHECK_END_WITH: - if (msgValue != null) { - checked = msgValue.endsWith(this.value); - } - break; - case CHECK_REGEX: - if (msgValue != null) { - try { - //checked = Pattern.matches(this.value, msgValue); - Pattern pattern = Pattern.compile(this.value, Pattern.CASE_INSENSITIVE); - Matcher matcher = pattern.matcher(msgValue); - //noinspection LoopStatementThatDoesntLoop - while (matcher.find()) { - checked = true; - break; - } - } catch (PatternSyntaxException e) { - logg("PatternSyntaxException: "); - logg("Description: " + e.getDescription()); - logg("Index: " + e.getIndex()); - logg("Message: " + e.getMessage()); - logg("Pattern: " + e.getPattern()); - } - } - break; - default: - break; - } - logg("checkValue " + msgValue + " " + this.check + " " + this.value + " checked:" + checked); - - return checked; - - } - - @NonNull - @Override - public String toString() { - return "RuleLine{" + - "headSpaceNum='" + headSpaceNum + '\'' + - "conjunction='" + conjunction + '\'' + - ", field='" + field + '\'' + - ", sure='" + sure + '\'' + - ", check='" + check + '\'' + - ", value='" + value + '\'' + - '}'; - } - - public int getHeadSpaceNum() { - return headSpaceNum; - } - - public void setHeadSpaceNum(int headSpaceNum) { - this.headSpaceNum = headSpaceNum; - } - - public RuleLine getBeforeRuleLine() { - return beforeRuleLine; - } - - public void setBeforeRuleLine(RuleLine beforeRuleLine) { - this.beforeRuleLine = beforeRuleLine; - } - - public RuleLine getNextRuleLine() { - return nextRuleLine; - } - - public void setNextRuleLine(RuleLine nextRuleLine) { - this.nextRuleLine = nextRuleLine; - } - - public RuleLine getParentRuleLine() { - return parentRuleLine; - } - - public void setParentRuleLine(RuleLine parentRuleLine) { - this.parentRuleLine = parentRuleLine; - } - - public RuleLine getChildRuleLine() { - return childRuleLine; - } - - public void setChildRuleLine(RuleLine childRuleLine) { - this.childRuleLine = childRuleLine; - } - - public String getConjunction() { - return conjunction; - } - - public void setConjunction(String conjunction) { - this.conjunction = conjunction; - } - - public String getField() { - return field; - } - - public void setField(String field) { - this.field = field; - } - - public String getSure() { - return sure; - } - - public void setSure(String sure) { - this.sure = sure; - } - - public String getCheck() { - return check; - } - - public void setCheck(String check) { - this.check = check; - } - - public String getValue() { - return value; - } - - public void setValue(String value) { - this.value = value; - } - -} \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/utils/RuleLine.kt b/app/src/main/java/com/idormy/sms/forwarder/utils/RuleLine.kt new file mode 100644 index 00000000..92a9f70e --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/utils/RuleLine.kt @@ -0,0 +1,334 @@ +package com.idormy.sms.forwarder.utils + +import android.util.Log +import com.idormy.sms.forwarder.R +import com.idormy.sms.forwarder.entity.MsgInfo +import com.xuexiang.xui.utils.ResUtils.getString +import java.util.regex.Pattern +import java.util.regex.PatternSyntaxException + +@Suppress("unused") +class RuleLine(line: String, lineNum: Int, beforeRuleLine: RuleLine?) { + companion object { + val CONJUNCTION_AND: String = getString(R.string.CONJUNCTION_AND) + val CONJUNCTION_OR: String = getString(R.string.CONJUNCTION_OR) + val FILED_PHONE_NUM: String = getString(R.string.FILED_PHONE_NUM) + val FILED_MSG_CONTENT: String = getString(R.string.FILED_MSG_CONTENT) + val FILED_PACKAGE_NAME: String = getString(R.string.FILED_PACKAGE_NAME) + val FILED_INFORM_TITLE: String = getString(R.string.FILED_INFORM_TITLE) + val FILED_INFORM_CONTENT: String = getString(R.string.FILED_INFORM_CONTENT) + val FILED_SIM_SLOT_INFO: String = getString(R.string.FILED_SIM_SLOT_INFO) + val SURE_YES: String = getString(R.string.SURE_YES) + val SURE_NOT: String = getString(R.string.SURE_NOT) + val CHECK_EQUALS: String = getString(R.string.CHECK_EQUALS) + val CHECK_CONTAIN: String = getString(R.string.CHECK_CONTAIN) + val CHECK_NOT_CONTAIN: String = getString(R.string.CHECK_NOT_CONTAIN) + val CHECK_START_WITH: String = getString(R.string.CHECK_START_WITH) + val CHECK_END_WITH: String = getString(R.string.CHECK_END_WITH) + val CHECK_REGEX: String = getString(R.string.CHECK_REGEX) + val CONJUNCTION_LIST: MutableList = ArrayList() + val FILED_LIST: MutableList = ArrayList() + val SURE_LIST: MutableList = ArrayList() + val CHECK_LIST: MutableList = ArrayList() + + const val TAG = "RuleLine" + private var START_LOG = true + fun startLog(startLog: Boolean) { + START_LOG = startLog + } + + fun logg(msg: String?) { + if (START_LOG) { + Log.i(TAG, msg!!) + } + } + + init { + CONJUNCTION_LIST.add("and") + CONJUNCTION_LIST.add("or") + CONJUNCTION_LIST.add(CONJUNCTION_AND) + CONJUNCTION_LIST.add(CONJUNCTION_OR) + } + + init { + FILED_LIST.add(FILED_PHONE_NUM) + FILED_LIST.add(FILED_PACKAGE_NAME) + FILED_LIST.add(FILED_MSG_CONTENT) + FILED_LIST.add(FILED_INFORM_CONTENT) + FILED_LIST.add(FILED_INFORM_TITLE) + FILED_LIST.add(FILED_SIM_SLOT_INFO) + } + + init { + SURE_LIST.add(SURE_YES) + SURE_LIST.add(SURE_NOT) + } + + init { + CHECK_LIST.add(CHECK_EQUALS) + CHECK_LIST.add(CHECK_CONTAIN) + CHECK_LIST.add(CHECK_NOT_CONTAIN) + CHECK_LIST.add(CHECK_START_WITH) + CHECK_LIST.add(CHECK_END_WITH) + CHECK_LIST.add(CHECK_REGEX) + } + } + + //开头有几个空格 + private var headSpaceNum = 0 + private var beforeRuleLine: RuleLine? = null + private var nextRuleLine: RuleLine? = null + private var parentRuleLine: RuleLine? = null + private var childRuleLine: RuleLine? = null + + //and or + var conjunction: String + + //手机号 短信内容 APP包名 通知标题 通知内容 卡槽信息 + private var field: String + + // 是否 + private var sure: String + private var check: String + private var value: String + + //字段分支 + fun checkMsg(msg: MsgInfo): Boolean { + + //检查这一行和上一行合并的结果是否命中 + var mixChecked = false + when (field) { + FILED_PHONE_NUM, FILED_PACKAGE_NAME -> mixChecked = checkValue(msg.from) + FILED_MSG_CONTENT, FILED_INFORM_CONTENT -> mixChecked = checkValue(msg.content) + FILED_INFORM_TITLE, FILED_SIM_SLOT_INFO -> mixChecked = checkValue(msg.simInfo) + else -> {} + } + when (sure) { + SURE_YES -> {} + SURE_NOT -> mixChecked = !mixChecked + else -> mixChecked = false + } + logg("rule:$this checkMsg:$msg checked:$mixChecked") + return mixChecked + } + + //内容分支 + private fun checkValue(msgValue: String?): Boolean { + var checked = false + when (check) { + CHECK_EQUALS -> checked = value == msgValue + CHECK_CONTAIN -> if (msgValue != null) { + checked = msgValue.contains(value) + } + CHECK_NOT_CONTAIN -> if (msgValue != null) { + checked = !msgValue.contains(value) + } + CHECK_START_WITH -> if (msgValue != null) { + checked = msgValue.startsWith(value) + } + CHECK_END_WITH -> if (msgValue != null) { + checked = msgValue.endsWith(value) + } + CHECK_REGEX -> if (msgValue != null) { + try { + //checked = Pattern.matches(this.value, msgValue); + val pattern = Pattern.compile(value, Pattern.CASE_INSENSITIVE) + val matcher = pattern.matcher(msgValue) + while (matcher.find()) { + checked = true + break + } + } catch (e: PatternSyntaxException) { + logg("PatternSyntaxException: ") + logg("Description: " + e.description) + logg("Index: " + e.index) + logg("Message: " + e.message) + logg("Pattern: " + e.pattern) + } + } + else -> {} + } + logg("checkValue $msgValue $check $value checked:$checked") + return checked + } + + override fun toString(): String { + return "RuleLine{" + + "headSpaceNum='" + headSpaceNum + '\'' + + "conjunction='" + conjunction + '\'' + + ", field='" + field + '\'' + + ", sure='" + sure + '\'' + + ", check='" + check + '\'' + + ", value='" + value + '\'' + + '}' + } + + fun getNextRuleLine(): RuleLine? { + return nextRuleLine + } + + fun setNextRuleLine(nextRuleLine: RuleLine?) { + this.nextRuleLine = nextRuleLine + } + + fun getChildRuleLine(): RuleLine? { + return childRuleLine + } + + fun setChildRuleLine(childRuleLine: RuleLine?) { + this.childRuleLine = childRuleLine + } + + init { + logg("----------$lineNum-----------------") + logg(line) + //规则检验: + //并且 是 手机号 相等 10086 + //[并且, 是, 手机号, 相等, 10086] + // 并且 是 内容 包含 test + //[, , 并且, 是, 内容, 包含, sms] + + //处理头空格数用来确认跟上一行节点的相对位置:是同级还是子级 + //处理4个字段,之后的全部当做value + + //标记3个阶段 + var isCountHeading = false + var isDealMiddle = false + var isDealValue = false + + //用于保存4个中间体: 并且, 是, 内容, 包含 + val middleList: MutableList = ArrayList(4) + //保存每个中间体字符串 + var buildMiddleWord = StringBuilder() + val valueBuilder = StringBuilder() + for (i in line.indices) { + val w = line[i].toString() + logg("walk over:$w") + + //控制阶段 + //开始处理头 + if (i == 0) { + if (" " == w) { + logg("start to isCountHeading:") + isCountHeading = true + } else { + //直接进入处理中间体阶段 + isDealMiddle = true + logg("start to isDealMiddle:") + } + } + //正在数空格头,但是遇到非空格,阶段变更:由处理空头阶段 变为 处理 中间体阶段 + if (isCountHeading && " " != w) { + logg("isCountHeading to isDealMiddle:") + isCountHeading = false + isDealMiddle = true + } + + //正在处理中间体,中间体数量够了,阶段变更:由处理中间体 变为 处理 value + if (isDealMiddle && middleList.size == 4) { + logg("isDealMiddle done middleList:$middleList") + logg("isDealMiddle to isDealValue:") + isDealMiddle = false + isDealValue = true + } + logg("isCountHeading:$isCountHeading") + logg("isDealMiddle:$isDealMiddle") + logg("isDealValue:$isDealValue") + if (isCountHeading) { + logg("headSpaceNum++:$headSpaceNum") + headSpaceNum++ + } + if (isDealMiddle) { + //遇到空格 + if (" " == w) { + buildMiddleWord = if (buildMiddleWord.isEmpty()) { + throw Exception(lineNum.toString() + "行:语法错误不允许出现连续空格!") + } else { + //生成了一个中间体 + middleList.add(buildMiddleWord.toString()) + logg("get Middle++:$buildMiddleWord") + StringBuilder() + } + } else { + //把w拼接到中间体上 + buildMiddleWord.append(w) + logg("buildMiddleWord length:" + buildMiddleWord.length + "buildMiddleWord:" + buildMiddleWord) + } + } + if (isDealValue) { + //把余下的所有字符都拼接给value + valueBuilder.append(w) + } + } + logg("isDealValue done valueBuilder:$valueBuilder") + if (middleList.size != 4) { + throw Exception(lineNum.toString() + "行配置错误:每行必须有4段组成,例如: 并且 手机号 是 相等 ") + } + + + //规则对齐 + if (beforeRuleLine != null) { + logg("beforeRuleLine :$beforeRuleLine") + logg("thisRuleLine :$this") + + //同级别 + if (headSpaceNum == beforeRuleLine.headSpaceNum) { + logg("同级别") + this.beforeRuleLine = beforeRuleLine + beforeRuleLine.nextRuleLine = this + } + //子级 + if (headSpaceNum - 1 == beforeRuleLine.headSpaceNum) { + logg("子级") + parentRuleLine = beforeRuleLine + beforeRuleLine.childRuleLine = this + } + //查找父级别 + if (headSpaceNum < beforeRuleLine.headSpaceNum) { + //匹配到最近一个同级 + var fBeforeRuleLine = beforeRuleLine.beforeRuleLine + if (fBeforeRuleLine == null) { + fBeforeRuleLine = beforeRuleLine.parentRuleLine + } + while (fBeforeRuleLine != null) { + logg("fBeforeRuleLine$fBeforeRuleLine") + + //查找到同级别 + if (headSpaceNum == fBeforeRuleLine.headSpaceNum) { + logg("父级别") + this.beforeRuleLine = fBeforeRuleLine + fBeforeRuleLine.nextRuleLine = this + break + } else { + //向上查找 + var pBeforeRuleLine = fBeforeRuleLine.beforeRuleLine + if (pBeforeRuleLine == null) { + pBeforeRuleLine = fBeforeRuleLine.parentRuleLine + } + fBeforeRuleLine = pBeforeRuleLine + } + } + } + } else { + logg("根级别") + } + conjunction = middleList[0] + sure = middleList[1] + field = middleList[2] + check = middleList[3] + value = valueBuilder.toString() + if (!CONJUNCTION_LIST.contains(conjunction)) { + throw Exception(lineNum.toString() + "行配置错误:连接词只支持:" + CONJUNCTION_LIST + " 但提供了" + conjunction) + } + if (!FILED_LIST.contains(field)) { + throw Exception(lineNum.toString() + "行配置错误:字段只支持:" + FILED_LIST + " 但提供了" + field) + } + if (!SURE_LIST.contains(sure)) { + throw Exception(lineNum.toString() + "行配置错误 " + sure + " 确认词只支持:" + SURE_LIST + " 但提供了" + sure) + } + if (!CHECK_LIST.contains(check)) { + throw Exception(lineNum.toString() + "行配置错误:比较词只支持:" + CHECK_LIST + " 但提供了" + check) + } + logg("----------$lineNum==$this") + } +} \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/utils/RuleLineUtils.java b/app/src/main/java/com/idormy/sms/forwarder/utils/RuleLineUtils.java deleted file mode 100644 index 2f4892d6..00000000 --- a/app/src/main/java/com/idormy/sms/forwarder/utils/RuleLineUtils.java +++ /dev/null @@ -1,134 +0,0 @@ -package com.idormy.sms.forwarder.utils; - -import static com.idormy.sms.forwarder.utils.RuleLine.CONJUNCTION_AND; -import static com.idormy.sms.forwarder.utils.RuleLine.CONJUNCTION_OR; - -import android.util.Log; - -import com.idormy.sms.forwarder.model.vo.SmsVo; - -import java.util.Date; -import java.util.Scanner; - -@SuppressWarnings("ALL") -public class RuleLineUtils { - static final String TAG = "RuleLineUtils"; - static Boolean STARTLOG = false; - - public static void main(String[] args) throws Exception { - String a = "并且 是 手机号 相等 10086\n" + - " 或者 是 手机号 结尾 哈哈哈\n" + - " 并且 是 短信内容 包含 asfas\n" + - " 或者 是 手机号 结尾 aaaa\n" + - "并且 是 手机号 相等 100861\n" + - "并且 是 手机号 相等 100861"; - - SmsVo msg = new SmsVo("10086", "哈哈哈", new Date(), "15888888888"); - logg("check:" + checkRuleLines(msg, a)); - } - - public static void startLog(boolean startLog) { - STARTLOG = startLog; - } - - public static void logg(String msg) { - if (STARTLOG) { - Log.i(TAG, msg); - } - - } - - public static boolean checkRuleLines(SmsVo msg, String RuleLines) throws Exception { - - Scanner scanner = new Scanner(RuleLines); - - int lineNum = 0; - RuleLine headRuleLine = null; - - RuleLine beforeRuleLine = null; - - while (scanner.hasNextLine()) { - String line = scanner.nextLine(); - logg(lineNum + " : " + line); - //第一行 - if (lineNum == 0) { - //第一行不允许缩进 - if (line.startsWith(" ")) { - throw new Exception("第一行不允许缩进"); - } - } - - // process the line - - - beforeRuleLine = RuleLineUtils.generateRuleTree(line, lineNum, beforeRuleLine); - if (lineNum == 0) { - headRuleLine = beforeRuleLine; - } - - lineNum++; - } - - assert headRuleLine != null; - return checkRuleTree(msg, headRuleLine); - - } - - - /** - * 使用规则树判断消息是否命中规则 - * Rule节点是否命中取决于:该节点是否命中、该节点子结点(如果有的话)是否命中、该节点下节点(如果有的话)是否命中 - * 递归检查 - */ - public static boolean checkRuleTree(SmsVo msg, RuleLine currentRuleLine) throws Exception { - //该节点是否命中 - boolean currentAll = currentRuleLine.checkMsg(msg); - logg("current:" + currentRuleLine + " checked:" + currentAll); - - //该节点子结点(如果有的话)是否命中 - if (currentRuleLine.getChildRuleLine() != null) { - logg(" child:" + currentRuleLine.getChildRuleLine()); - - //根据情况连接结果 - switch (currentRuleLine.getChildRuleLine().conjunction) { - case CONJUNCTION_AND: - currentAll = currentAll && checkRuleTree(msg, currentRuleLine.getChildRuleLine()); - break; - case CONJUNCTION_OR: - currentAll = currentAll || checkRuleTree(msg, currentRuleLine.getChildRuleLine()); - break; - default: - throw new Exception("child wrong conjunction"); - } - } - - //该节点下节点(如果有的话)是否命中 - if (currentRuleLine.getNextRuleLine() != null) { - logg("next:" + currentRuleLine.getNextRuleLine()); - //根据情况连接结果 - switch (currentRuleLine.getNextRuleLine().conjunction) { - case CONJUNCTION_AND: - currentAll = currentAll && checkRuleTree(msg, currentRuleLine.getNextRuleLine()); - break; - case CONJUNCTION_OR: - currentAll = currentAll || checkRuleTree(msg, currentRuleLine.getNextRuleLine()); - break; - default: - throw new Exception("next wrong conjunction"); - } - } - - return currentAll; - } - - /** - * 生成规则树 - * 一行代表一个规则 - */ - public static RuleLine generateRuleTree(String line, int lineNum, RuleLine parentRuleLine) throws Exception { - String[] words = line.split(" "); - - return new RuleLine(line, lineNum, parentRuleLine); - } - -} diff --git a/app/src/main/java/com/idormy/sms/forwarder/utils/RuleLineUtils.kt b/app/src/main/java/com/idormy/sms/forwarder/utils/RuleLineUtils.kt new file mode 100644 index 00000000..fa2b0692 --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/utils/RuleLineUtils.kt @@ -0,0 +1,105 @@ +package com.idormy.sms.forwarder.utils + +import android.util.Log +import com.idormy.sms.forwarder.entity.MsgInfo +import java.util.* + +@Suppress("unused") +object RuleLineUtils { + const val TAG = "RuleLineUtils" + private var START_LOG = false + + @Throws(Exception::class) + @JvmStatic + fun main(args: Array) { + val a = """并且 是 手机号 相等 10086 + 或者 是 手机号 结尾 哈哈哈 + 并且 是 短信内容 包含 test + 或者 是 手机号 结尾 pppscn +并且 是 手机号 相等 100861 +并且 是 手机号 相等 100861""" + val msg = MsgInfo("sms", "10086", "哈哈哈", Date(), "15888888888") + logg("check:" + checkRuleLines(msg, a)) + } + + fun startLog(startLog: Boolean) { + START_LOG = startLog + } + + private fun logg(msg: String?) { + if (START_LOG) { + Log.i(TAG, msg!!) + } + } + + @Throws(Exception::class) + fun checkRuleLines(msg: MsgInfo, RuleLines: String?): Boolean { + val scanner = Scanner(RuleLines) + var lineNum = 0 + var headRuleLine: RuleLine? = null + var beforeRuleLine: RuleLine? = null + while (scanner.hasNextLine()) { + val line = scanner.nextLine() + logg("$lineNum : $line") + //第一行 + if (lineNum == 0) { + //第一行不允许缩进 + if (line.startsWith(" ")) { + throw Exception("第一行不允许缩进") + } + } + + // process the line + beforeRuleLine = generateRuleTree(line, lineNum, beforeRuleLine) + if (lineNum == 0) { + headRuleLine = beforeRuleLine + } + lineNum++ + } + assert(headRuleLine != null) + return checkRuleTree(msg, headRuleLine) + } + + /** + * 使用规则树判断消息是否命中规则 + * Rule节点是否命中取决于:该节点是否命中、该节点子结点(如果有的话)是否命中、该节点下节点(如果有的话)是否命中 + * 递归检查 + */ + @Throws(Exception::class) + fun checkRuleTree(msg: MsgInfo, currentRuleLine: RuleLine?): Boolean { + //该节点是否命中 + var currentAll = currentRuleLine!!.checkMsg(msg) + logg("current:$currentRuleLine checked:$currentAll") + + //该节点子结点(如果有的话)是否命中 + if (currentRuleLine.getChildRuleLine() != null) { + logg(" child:" + currentRuleLine.getChildRuleLine()) + currentAll = when (currentRuleLine.getChildRuleLine()!!.conjunction) { + RuleLine.CONJUNCTION_AND -> currentAll && checkRuleTree(msg, currentRuleLine.getChildRuleLine()) + RuleLine.CONJUNCTION_OR -> currentAll || checkRuleTree(msg, currentRuleLine.getChildRuleLine()) + else -> throw Exception("child wrong conjunction") + } + } + + //该节点下节点(如果有的话)是否命中 + if (currentRuleLine.getNextRuleLine() != null) { + logg("next:" + currentRuleLine.getNextRuleLine()) + currentAll = when (currentRuleLine.getNextRuleLine()!!.conjunction) { + RuleLine.CONJUNCTION_AND -> currentAll && checkRuleTree(msg, currentRuleLine.getNextRuleLine()) + RuleLine.CONJUNCTION_OR -> currentAll || checkRuleTree(msg, currentRuleLine.getNextRuleLine()) + else -> throw Exception("next wrong conjunction") + } + } + return currentAll + } + + /** + * 生成规则树 + * 一行代表一个规则 + */ + @Throws(Exception::class) + fun generateRuleTree(line: String, lineNum: Int, parentRuleLine: RuleLine?): RuleLine { + //val words = line.split(" ".toRegex()).toTypedArray() + return RuleLine(line, lineNum, parentRuleLine) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/utils/RuleUtils.java b/app/src/main/java/com/idormy/sms/forwarder/utils/RuleUtils.java deleted file mode 100644 index b1cb9d27..00000000 --- a/app/src/main/java/com/idormy/sms/forwarder/utils/RuleUtils.java +++ /dev/null @@ -1,252 +0,0 @@ -package com.idormy.sms.forwarder.utils; - -import android.annotation.SuppressLint; -import android.content.ContentValues; -import android.content.Context; -import android.database.Cursor; -import android.database.sqlite.SQLiteDatabase; -import android.provider.BaseColumns; -import android.util.Log; - -import com.idormy.sms.forwarder.model.RuleModel; -import com.idormy.sms.forwarder.model.RuleTable; - -import java.util.ArrayList; -import java.util.List; - -@SuppressWarnings({"UnusedReturnValue", "SynchronizeOnNonFinalField"}) -public class RuleUtils { - static final String TAG = "RuleUtils"; - static Boolean hasInit = false; - @SuppressLint("StaticFieldLeak") - static Context context; - static DbHelper dbHelper; - static SQLiteDatabase db; - - public static void init(Context context1) { - synchronized (hasInit) { - if (hasInit) return; - hasInit = true; - context = context1; - dbHelper = new DbHelper(context); - // Gets the data repository in write mode - db = dbHelper.getReadableDatabase(); - } - } - - public static long addRule(RuleModel ruleModel) { - - // Create a new map of values, where column names are the keys - ContentValues values = new ContentValues(); - values.put(RuleTable.RuleEntry.COLUMN_NAME_TYPE, ruleModel.getType()); - values.put(RuleTable.RuleEntry.COLUMN_NAME_FILED, ruleModel.getFiled()); - values.put(RuleTable.RuleEntry.COLUMN_NAME_CHECK, ruleModel.getCheck()); - values.put(RuleTable.RuleEntry.COLUMN_NAME_VALUE, ruleModel.getValue()); - values.put(RuleTable.RuleEntry.COLUMN_NAME_SENDER_ID, ruleModel.getSenderId()); - values.put(RuleTable.RuleEntry.COLUMN_NAME_SIM_SLOT, ruleModel.getSimSlot()); - values.put(RuleTable.RuleEntry.COLUMN_SMS_TEMPLATE, ruleModel.getSmsTemplate()); - values.put(RuleTable.RuleEntry.COLUMN_REGEX_REPLACE, ruleModel.getRegexReplace()); - values.put(RuleTable.RuleEntry.COLUMN_NAME_STATUS, ruleModel.getStatus()); - - if (null != ruleModel.getId()) { - values.put(BaseColumns._ID, ruleModel.getId()); - return db.replace(RuleTable.RuleEntry.TABLE_NAME, null, values); - } else { - // Insert the new row, returning the primary key value of the new row - return db.insert(RuleTable.RuleEntry.TABLE_NAME, null, values); - } - } - - public static long updateRule(RuleModel ruleModel) { - if (ruleModel == null) return 0; - - // Create a new map of values, where column names are the keys - ContentValues values = new ContentValues(); - values.put(RuleTable.RuleEntry.COLUMN_NAME_FILED, ruleModel.getFiled()); - values.put(RuleTable.RuleEntry.COLUMN_NAME_CHECK, ruleModel.getCheck()); - values.put(RuleTable.RuleEntry.COLUMN_NAME_VALUE, ruleModel.getValue()); - values.put(RuleTable.RuleEntry.COLUMN_NAME_SENDER_ID, ruleModel.getSenderId()); - values.put(RuleTable.RuleEntry.COLUMN_NAME_SIM_SLOT, ruleModel.getSimSlot()); - values.put(RuleTable.RuleEntry.COLUMN_SMS_TEMPLATE, ruleModel.getSmsTemplate()); - values.put(RuleTable.RuleEntry.COLUMN_REGEX_REPLACE, ruleModel.getRegexReplace()); - values.put(RuleTable.RuleEntry.COLUMN_NAME_STATUS, ruleModel.getStatus()); - - String selection = RuleTable.RuleEntry._ID + " = ? "; - String[] whereArgs = {String.valueOf(ruleModel.getId())}; - - return db.update(RuleTable.RuleEntry.TABLE_NAME, values, selection, whereArgs); - } - - public static int delRule(Long id) { - // Define 'where' part of query. - String selection = " 1 "; - // Specify arguments in placeholder order. - List selectionArgList = new ArrayList<>(); - if (id != null) { - // Define 'where' part of query. - selection += " and " + RuleTable.RuleEntry._ID + " = ? "; - // Specify arguments in placeholder order. - selectionArgList.add(String.valueOf(id)); - - } - String[] selectionArgs = selectionArgList.toArray(new String[0]); - // Issue SQL statement. - return db.delete(RuleTable.RuleEntry.TABLE_NAME, selection, selectionArgs); - - } - - public static List getRule(Long id, String key) { - return getRule(id, key, null, null); - } - - public static List getRule(Long id, String key, String type) { - return getRule(id, key, type, null); - } - - public static List getRule(Long id, String key, String type, String status) { - // Define a projection that specifies which columns from the database - // you will actually use after this query. - String[] projection = { - BaseColumns._ID, - RuleTable.RuleEntry.COLUMN_NAME_TYPE, - RuleTable.RuleEntry.COLUMN_NAME_FILED, - RuleTable.RuleEntry.COLUMN_NAME_CHECK, - RuleTable.RuleEntry.COLUMN_NAME_VALUE, - RuleTable.RuleEntry.COLUMN_NAME_SENDER_ID, - RuleTable.RuleEntry.COLUMN_NAME_TIME, - RuleTable.RuleEntry.COLUMN_NAME_SIM_SLOT, - RuleTable.RuleEntry.COLUMN_SMS_TEMPLATE, - RuleTable.RuleEntry.COLUMN_REGEX_REPLACE, - RuleTable.RuleEntry.COLUMN_NAME_STATUS, - }; - // Define 'where' part of query. - String selection = " 1 = 1 "; - // Specify arguments in placeholder order. - List selectionArgList = new ArrayList<>(); - if (id != null) { - // Define 'where' part of query. - selection += " and " + RuleTable.RuleEntry._ID + " = ? "; - // Specify arguments in placeholder order. - selectionArgList.add(String.valueOf(id)); - } - - if (type != null) { - selection += " and " + RuleTable.RuleEntry.COLUMN_NAME_TYPE + " = ? "; - selectionArgList.add(type); - } - - if (status != null) { - selection += " and " + RuleTable.RuleEntry.COLUMN_NAME_STATUS + " = ? "; - selectionArgList.add(status); - } - - if (key != null) { - // Define 'where' part of query. - if (key.equals("SIM1") || key.equals("SIM2")) { - selection += " and " + RuleTable.RuleEntry.COLUMN_NAME_SIM_SLOT + " IN ( 'ALL', ? ) "; - } else { - selection += " and " + RuleTable.RuleEntry.COLUMN_NAME_VALUE + " LIKE ? "; - } - // Specify arguments in placeholder order. - selectionArgList.add(key); - } - String[] selectionArgs = selectionArgList.toArray(new String[0]); - - // How you want the results sorted in the resulting Cursor - String sortOrder = - RuleTable.RuleEntry._ID + " DESC"; - - Cursor cursor = db.query( - RuleTable.RuleEntry.TABLE_NAME, // The table to query - projection, // The array of columns to return (pass null to get all) - selection, // The columns for the WHERE clause - selectionArgs, // The values for the WHERE clause - null, // don't group the rows - null, // don't filter by row groups - sortOrder // The sort order - ); - List tRules = new ArrayList<>(); - while (cursor.moveToNext()) { - - long itemId = cursor.getLong( - cursor.getColumnIndexOrThrow(RuleTable.RuleEntry._ID)); - String itemType = cursor.getString( - cursor.getColumnIndexOrThrow(RuleTable.RuleEntry.COLUMN_NAME_TYPE)); - String itemFiled = cursor.getString( - cursor.getColumnIndexOrThrow(RuleTable.RuleEntry.COLUMN_NAME_FILED)); - String itemCheck = cursor.getString( - cursor.getColumnIndexOrThrow(RuleTable.RuleEntry.COLUMN_NAME_CHECK)); - String itemValue = cursor.getString( - cursor.getColumnIndexOrThrow(RuleTable.RuleEntry.COLUMN_NAME_VALUE)); - long itemSenderId = cursor.getLong( - cursor.getColumnIndexOrThrow(RuleTable.RuleEntry.COLUMN_NAME_SENDER_ID)); - long itemTime = cursor.getLong( - cursor.getColumnIndexOrThrow(RuleTable.RuleEntry.COLUMN_NAME_TIME)); - String itemSimSlot = cursor.getString( - cursor.getColumnIndexOrThrow(RuleTable.RuleEntry.COLUMN_NAME_SIM_SLOT)); - String smsTemplate = cursor.getString( - cursor.getColumnIndexOrThrow(RuleTable.RuleEntry.COLUMN_SMS_TEMPLATE)); - String regexReplace = cursor.getString( - cursor.getColumnIndexOrThrow(RuleTable.RuleEntry.COLUMN_REGEX_REPLACE)); - int itemStatus = cursor.getInt( - cursor.getColumnIndexOrThrow(RuleTable.RuleEntry.COLUMN_NAME_STATUS)); - - Log.d(TAG, "getRule: itemId" + itemId); - RuleModel ruleModel = new RuleModel(); - ruleModel.setId(itemId); - ruleModel.setType(itemType); - ruleModel.setFiled(itemFiled); - ruleModel.setCheck(itemCheck); - ruleModel.setValue(itemValue); - ruleModel.setSenderId(itemSenderId); - ruleModel.setTime(itemTime); - ruleModel.setSimSlot(itemSimSlot); - ruleModel.setSwitchSmsTemplate(!smsTemplate.trim().isEmpty()); - ruleModel.setSmsTemplate(smsTemplate); - ruleModel.setSwitchRegexReplace(!regexReplace.trim().isEmpty()); - ruleModel.setRegexReplace(regexReplace); - ruleModel.setStatus(itemStatus); - - tRules.add(ruleModel); - } - cursor.close(); - return tRules; - } - - public static int countRule(String status, String type, String value) { - String[] projection = {}; - String selection = " 1 "; - List selectionArgList = new ArrayList<>(); - - if (status != null && !status.isEmpty()) { - selection += " and " + RuleTable.RuleEntry.COLUMN_NAME_STATUS + " = ? "; - selectionArgList.add(status); - } - - if (type != null && !type.isEmpty()) { - selection += " and " + RuleTable.RuleEntry.COLUMN_NAME_TYPE + " = ? "; - selectionArgList.add(status); - } - - if (value != null && !value.isEmpty()) { - selection += " and " + RuleTable.RuleEntry.COLUMN_NAME_VALUE + " LIKE ? "; - selectionArgList.add(value); - } - - String[] selectionArgs = selectionArgList.toArray(new String[0]); - Cursor cursor = db.query( - RuleTable.RuleEntry.TABLE_NAME, // The table to query - projection, // The array of columns to return (pass null to get all) - selection, // The columns for the WHERE clause - selectionArgs, // The values for the WHERE clause - null, // don't group the rows - null, // don't filter by row groups - null // The sort order - ); - - int count = cursor.getCount(); - cursor.close(); - return count; - } - -} diff --git a/app/src/main/java/com/idormy/sms/forwarder/utils/SendUtils.kt b/app/src/main/java/com/idormy/sms/forwarder/utils/SendUtils.kt new file mode 100644 index 00000000..18f1cf7d --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/utils/SendUtils.kt @@ -0,0 +1,137 @@ +package com.idormy.sms.forwarder.utils + +import android.util.Log +import androidx.work.OneTimeWorkRequestBuilder +import androidx.work.WorkManager +import androidx.work.workDataOf +import com.google.gson.Gson +import com.idormy.sms.forwarder.database.entity.LogsAndRuleAndSender +import com.idormy.sms.forwarder.database.entity.Rule +import com.idormy.sms.forwarder.database.entity.Sender +import com.idormy.sms.forwarder.entity.MsgInfo +import com.idormy.sms.forwarder.entity.result.SendResponse +import com.idormy.sms.forwarder.entity.setting.* +import com.idormy.sms.forwarder.utils.sender.* +import com.idormy.sms.forwarder.workers.SendWorker +import com.idormy.sms.forwarder.workers.UpdateLogsWorker +import com.xuexiang.xutil.XUtil +import com.xuexiang.xutil.data.DateUtils +import java.text.SimpleDateFormat +import java.util.* + + +object SendUtils { + private const val TAG = "SendUtils" + + //批量发送消息 + /*fun sendMsgList(infoList: List, type: String) { + for (msgInfo in infoList) { + sendMsg(msgInfo, type) + } + }*/ + + //发送消息 + fun sendMsg(msgInfo: MsgInfo) { + val request = OneTimeWorkRequestBuilder() + .setInputData(workDataOf(Worker.sendMsgInfo to Gson().toJson(msgInfo))) + .build() + WorkManager.getInstance(XUtil.getContext()).enqueue(request) + } + + /** + * 重发消息:从日志获取消息内容并尝试重发 + * 根据当前rule和sender来重发,而不是失败时设置的规则 + */ + fun resendMsg(item: LogsAndRuleAndSender, rematch: Boolean) { + Log.d(TAG, item.logs.toString()) + + val date: Date = try { + DateUtils.string2Date(item.logs.time.toString(), SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault())) + } catch (e: Exception) { + e.printStackTrace() + Date() + } + val simInfo: String = item.logs.simInfo + val simSlot: Int = if (simInfo.startsWith("SIM2")) 2 else 1 + val msgInfo = MsgInfo(item.logs.type, item.logs.from, item.logs.content, date, simInfo, simSlot) + Log.d(TAG, "resendMsg msgInfo:$msgInfo") + + if (rematch) { + sendMsg(msgInfo) + return + } + + sendMsgSender(msgInfo, item.relation.rule, item.relation.sender, item.logs.id) + } + + //匹配发送通道发送消息 + fun sendMsgSender(msgInfo: MsgInfo, rule: Rule, sender: Sender, logId: Long) { + when (sender.type) { + TYPE_DINGDING -> { + val settingVo = Gson().fromJson(sender.jsonSetting, DingtalkSetting::class.java) + DingtalkUtils.sendMsg(settingVo, msgInfo, rule, logId) + } + TYPE_EMAIL -> { + val settingVo = Gson().fromJson(sender.jsonSetting, EmailSetting::class.java) + EmailUtils.sendMsg(settingVo, msgInfo, rule, logId) + } + TYPE_BARK -> { + val settingVo = Gson().fromJson(sender.jsonSetting, BarkSetting::class.java) + BarkUtils.sendMsg(settingVo, msgInfo, rule, logId) + } + TYPE_WEBHOOK -> { + val settingVo = Gson().fromJson(sender.jsonSetting, WebhookSetting::class.java) + WebhookUtils.sendMsg(settingVo, msgInfo, rule, logId) + } + TYPE_WEWORK_ROBOT -> { + val settingVo = Gson().fromJson(sender.jsonSetting, WeworkRobotSetting::class.java) + WeworkRobotUtils.sendMsg(settingVo, msgInfo, rule, logId) + } + TYPE_WEWORK_AGENT -> { + val settingVo = Gson().fromJson(sender.jsonSetting, WeworkAgentSetting::class.java) + WeworkAgentUtils.sendMsg(settingVo, msgInfo, rule, logId) + } + TYPE_SERVERCHAN -> { + val settingVo = Gson().fromJson(sender.jsonSetting, ServerchanSetting::class.java) + ServerchanUtils.sendMsg(settingVo, msgInfo, rule, logId) + } + TYPE_TELEGRAM -> { + val settingVo = Gson().fromJson(sender.jsonSetting, TelegramSetting::class.java) + TelegramUtils.sendMsg(settingVo, msgInfo, rule, logId) + } + TYPE_SMS -> { + val settingVo = Gson().fromJson(sender.jsonSetting, SmsSetting::class.java) + SmsUtils.sendMsg(settingVo, msgInfo, rule, logId) + } + TYPE_FEISHU -> { + val settingVo = Gson().fromJson(sender.jsonSetting, FeishuSetting::class.java) + FeishuUtils.sendMsg(settingVo, msgInfo, rule, logId) + } + TYPE_PUSHPLUS -> { + val settingVo = Gson().fromJson(sender.jsonSetting, PushplusSetting::class.java) + PushplusUtils.sendMsg(settingVo, msgInfo, rule, logId) + } + TYPE_GOTIFY -> { + val settingVo = Gson().fromJson(sender.jsonSetting, GotifySetting::class.java) + GotifyUtils.sendMsg(settingVo, msgInfo, rule, logId) + } + else -> {} + } + } + + //更新转发日志状态 + fun updateLogs(logId: Long?, status: Int, response: String) { + if (logId == null) return + + val sendResponse = SendResponse(logId, status, response) + val request = OneTimeWorkRequestBuilder() + .setInputData( + workDataOf( + Worker.updateLogs to Gson().toJson(sendResponse) + ) + ) + .build() + WorkManager.getInstance(XUtil.getContext()).enqueue(request) + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/utils/SettingUtils.java b/app/src/main/java/com/idormy/sms/forwarder/utils/SettingUtils.java deleted file mode 100644 index 4047453c..00000000 --- a/app/src/main/java/com/idormy/sms/forwarder/utils/SettingUtils.java +++ /dev/null @@ -1,332 +0,0 @@ -package com.idormy.sms.forwarder.utils; - -import android.annotation.SuppressLint; -import android.content.Context; -import android.content.SharedPreferences; -import android.content.pm.PackageInfo; -import android.content.pm.PackageManager; -import android.preference.PreferenceManager; -import android.util.Log; - -import com.idormy.sms.forwarder.MyApplication; -import com.idormy.sms.forwarder.R; - -@SuppressWarnings({"SynchronizeOnNonFinalField", "unused"}) -public class SettingUtils { - static Boolean hasInit = false; - private static final String TAG = "SettingUtils"; - private static SharedPreferences sp_setting = null; - @SuppressLint("StaticFieldLeak") - private static Context context = null; - - public static void init(Context context1) { - synchronized (hasInit) { - if (hasInit) return; - hasInit = true; - context = context1; - Log.d(TAG, "SettingUtils init "); - sp_setting = PreferenceManager.getDefaultSharedPreferences(context1); - } - } - - public static void switchAddExtra(Boolean switchAddExtra) { - sp_setting.edit().putBoolean(Define.SP_MSG_KEY_SWITCH_ADD_EXTRA, switchAddExtra).apply(); - } - - public static boolean getSwitchAddExtra() { - return sp_setting.getBoolean(Define.SP_MSG_KEY_SWITCH_ADD_EXTRA, false); - } - - public static void switchAddDeviceName(Boolean switchAddDeviceName) { - sp_setting.edit().putBoolean(Define.SP_MSG_KEY_STRING_ADD_EXTRA_DEVICE_NAME, switchAddDeviceName).apply(); - } - - public static boolean getSwitchAddDeviceName() { - return sp_setting.getBoolean(Define.SP_MSG_KEY_STRING_ADD_EXTRA_DEVICE_NAME, false); - } - - public static void switchEnableSms(Boolean enable) { - sp_setting.edit().putBoolean(Define.SP_MSG_KEY_STRING_ENABLE_SMS, enable).apply(); - } - - public static boolean getSwitchEnableSms() { - return sp_setting.getBoolean(Define.SP_MSG_KEY_STRING_ENABLE_SMS, false); - } - - public static void switchEnablePhone(Boolean enable) { - sp_setting.edit().putBoolean(Define.SP_MSG_KEY_STRING_ENABLE_PHONE, enable).apply(); - } - - public static boolean getSwitchEnablePhone() { - return sp_setting.getBoolean(Define.SP_MSG_KEY_STRING_ENABLE_PHONE, false); - } - - public static void switchEnableAppNotify(Boolean enable) { - sp_setting.edit().putBoolean(Define.SP_MSG_KEY_STRING_ENABLE_APP_NOTIFY, enable).apply(); - } - - public static boolean getSwitchEnableAppNotify() { - return sp_setting.getBoolean(Define.SP_MSG_KEY_STRING_ENABLE_APP_NOTIFY, false); - } - - public static void switchCancelAppNotify(Boolean enable) { - sp_setting.edit().putBoolean(Define.SP_MSG_KEY_STRING_CANCEL_APP_NOTIFY, enable).apply(); - } - - public static boolean getSwitchCancelAppNotify() { - return sp_setting.getBoolean(Define.SP_MSG_KEY_STRING_CANCEL_APP_NOTIFY, false); - } - - public static void switchNotUserPresent(Boolean enable) { - sp_setting.edit().putBoolean(Define.SP_MSG_KEY_STRING_NOT_USER_PRESENT, enable).apply(); - } - - public static boolean getSwitchNotUserPresent() { - return sp_setting.getBoolean(Define.SP_MSG_KEY_STRING_NOT_USER_PRESENT, false); - } - - public static void switchEnableBatteryReceiver(Boolean enable) { - sp_setting.edit().putBoolean(Define.SP_MSG_KEY_STRING_BATTERY_RECEIVER, enable).apply(); - } - - public static boolean getSwitchEnableBatteryReceiver() { - return sp_setting.getBoolean(Define.SP_MSG_KEY_STRING_BATTERY_RECEIVER, false); - } - - public static void switchExcludeFromRecents(Boolean enable) { - sp_setting.edit().putBoolean(Define.SP_MSG_KEY_STRING_ENABLE_EXCLUDE_FROM_RECENTS, enable).apply(); - } - - public static boolean getExcludeFromRecents() { - return sp_setting.getBoolean(Define.SP_MSG_KEY_STRING_ENABLE_EXCLUDE_FROM_RECENTS, false); - } - - public static void switchPlaySilenceMusic(Boolean enable) { - sp_setting.edit().putBoolean(Define.SP_MSG_KEY_STRING_ENABLE_PLAY_SILENCE_MUSIC, enable).apply(); - } - - public static boolean getPlaySilenceMusic() { - return sp_setting.getBoolean(Define.SP_MSG_KEY_STRING_ENABLE_PLAY_SILENCE_MUSIC, false); - } - - public static void switchOnePixelActivity(Boolean enable) { - sp_setting.edit().putBoolean(Define.SP_MSG_KEY_STRING_ENABLE_ONE_PIXEL_ACTIVITY, enable).apply(); - } - - public static boolean getOnePixelActivity() { - return sp_setting.getBoolean(Define.SP_MSG_KEY_STRING_ENABLE_ONE_PIXEL_ACTIVITY, false); - } - - public static void switchSmsTemplate(Boolean switchSmsTemplate) { - sp_setting.edit().putBoolean(Define.SP_MSG_KEY_SWITCH_SMS_TEMPLATE, switchSmsTemplate).apply(); - } - - public static boolean getSwitchSmsTemplate() { - return sp_setting.getBoolean(Define.SP_MSG_KEY_SWITCH_SMS_TEMPLATE, false); - } - - public static String getAddExtraDeviceMark() { - String res = sp_setting.getString(Define.SP_MSG_KEY_STRING_ADD_EXTRA_DEVICE_MARK, ""); - if (res == null || res.equals("")) { - res = android.os.Build.MODEL; - } - return res; - } - - public static void setAddExtraDeviceMark(String addExtraDeviceMark) { - sp_setting.edit().putString(Define.SP_MSG_KEY_STRING_ADD_EXTRA_DEVICE_MARK, addExtraDeviceMark).apply(); - } - - public static String getSmsTemplate() { - return sp_setting.getString(Define.SP_MSG_KEY_STRING_SMS_TEMPLATE, - getString(R.string.tag_from) + "\n" + - getString(R.string.tag_sms) + "\n" + - getString(R.string.tag_card_slot) + "\n" + - getString(R.string.tag_receive_time) + "\n" + - getString(R.string.tag_device_name)); - } - - public static void setSmsTemplate(String textSmsTemplate) { - sp_setting.edit().putString(Define.SP_MSG_KEY_STRING_SMS_TEMPLATE, textSmsTemplate).apply(); - } - - public static String getAddExtraSim1() { - String res = sp_setting.getString(Define.SP_MSG_KEY_STRING_ADD_EXTRA_SIM1, ""); - if (res == null || res.equals("")) { - res = SimUtils.getSimInfo(1); - } - return res; - } - - public static void setAddExtraSim1(String sim1) { - sp_setting.edit().putString(Define.SP_MSG_KEY_STRING_ADD_EXTRA_SIM1, sim1).apply(); - } - - public static String getAddExtraSim2() { - String res = sp_setting.getString(Define.SP_MSG_KEY_STRING_ADD_EXTRA_SIM2, ""); - if (res == null || res.equals("")) { - res = SimUtils.getSimInfo(2); - } - return res; - } - - public static void setAddExtraSim2(String sim2) { - sp_setting.edit().putString(Define.SP_MSG_KEY_STRING_ADD_EXTRA_SIM2, sim2).apply(); - } - - public static int getBatteryLevelAlarmMin() { - return sp_setting.getInt(Define.SP_MSG_KEY_STRING_BATTERY_LEVEL_ALARM, 0); - } - - public static void setBatteryLevelAlarmMin(int battery_level) { - sp_setting.edit().putInt(Define.SP_MSG_KEY_STRING_BATTERY_LEVEL_ALARM, battery_level).apply(); - } - - public static int getBatteryLevelAlarmMax() { - return sp_setting.getInt(Define.SP_MSG_KEY_STRING_BATTERY_LEVEL_MAX, 0); - } - - public static void switchBatteryLevelAlarmOnce(Boolean enable) { - sp_setting.edit().putBoolean(Define.SP_MSG_KEY_STRING_BATTERY_LEVEL_ONCE, enable).apply(); - } - - public static boolean getBatteryLevelAlarmOnce() { - return sp_setting.getBoolean(Define.SP_MSG_KEY_STRING_BATTERY_LEVEL_ONCE, false); - } - - public static void setBatteryLevelAlarmMax(int battery_level) { - sp_setting.edit().putInt(Define.SP_MSG_KEY_STRING_BATTERY_LEVEL_MAX, battery_level).apply(); - } - - public static int getBatteryLevelCurrent() { - return sp_setting.getInt(Define.SP_MSG_KEY_STRING_BATTERY_LEVEL_CURRENT, 0); - } - - public static void setBatteryLevelCurrent(int battery_level) { - sp_setting.edit().putInt(Define.SP_MSG_KEY_STRING_BATTERY_LEVEL_CURRENT, battery_level).apply(); - } - - public static int getBatteryStatus() { - return sp_setting.getInt(Define.SP_MSG_KEY_STRING_BATTERY_STATUS, 0); - } - - public static void setBatteryStatus(int battery_status) { - sp_setting.edit().putInt(Define.SP_MSG_KEY_STRING_BATTERY_STATUS, battery_status).apply(); - } - - public static boolean saveMsgHistory() { - return !sp_setting.getBoolean("option_save_history_on", false); - } - - public static String getPrevNoticeHash(String key) { - return sp_setting.getString(key, ""); - } - - public static void setPrevNoticeHash(String key, String value) { - sp_setting.edit().putString(key, value).apply(); - } - - public static void switchEnableHttpServer(Boolean enable) { - sp_setting.edit().putBoolean(Define.SP_MSG_KEY_STRING_ENABLE_HTTP_SERVER, enable).apply(); - } - - public static boolean getSwitchEnableHttpServer() { - return sp_setting.getBoolean(Define.SP_MSG_KEY_STRING_ENABLE_HTTP_SERVER, false); - } - - public static void switchCallType1(Boolean switchCallType) { - sp_setting.edit().putBoolean(Define.SP_MSG_KEY_STRING_ENABLE_CALL_TYPE_1, switchCallType).apply(); - } - - public static boolean getSwitchCallType1() { - return sp_setting.getBoolean(Define.SP_MSG_KEY_STRING_ENABLE_CALL_TYPE_1, false); - } - - public static void switchCallType2(Boolean switchCallType) { - sp_setting.edit().putBoolean(Define.SP_MSG_KEY_STRING_ENABLE_CALL_TYPE_2, switchCallType).apply(); - } - - public static boolean getSwitchCallType2() { - return sp_setting.getBoolean(Define.SP_MSG_KEY_STRING_ENABLE_CALL_TYPE_2, false); - } - - public static void switchCallType3(Boolean switchCallType) { - sp_setting.edit().putBoolean(Define.SP_MSG_KEY_STRING_ENABLE_CALL_TYPE_3, switchCallType).apply(); - } - - public static boolean getSwitchCallType3() { - return sp_setting.getBoolean(Define.SP_MSG_KEY_STRING_ENABLE_CALL_TYPE_3, true); - } - - public static int getRetryTimes() { - return sp_setting.getInt(Define.SP_MSG_KEY_STRING_RETRY_TIMES, 0); - } - - public static void setRetryTimes(int retry_times) { - sp_setting.edit().putInt(Define.SP_MSG_KEY_STRING_RETRY_TIMES, retry_times).apply(); - } - - public static int getDelayTime() { - return sp_setting.getInt(Define.SP_MSG_KEY_STRING_DELAY_TIME, 1); - } - - public static void setDelayTime(int delay_time) { - sp_setting.edit().putInt(Define.SP_MSG_KEY_STRING_DELAY_TIME, delay_time).apply(); - } - - public static void switchEnableBatteryCron(Boolean enable) { - sp_setting.edit().putBoolean(Define.SP_MSG_KEY_STRING_BATTERY_CRON, enable).apply(); - } - - public static boolean getSwitchEnableBatteryCron() { - return sp_setting.getBoolean(Define.SP_MSG_KEY_STRING_BATTERY_CRON, false); - } - - public static void setBatteryCronStartTime(String startTime) { - sp_setting.edit().putString(Define.SP_MSG_KEY_STRING_BATTERY_CRON_START_TIME, startTime).apply(); - } - - public static String getBatteryCronStartTime() { - return sp_setting.getString(Define.SP_MSG_KEY_STRING_BATTERY_CRON_START_TIME, "00:00"); - } - - public static void setBatteryCronInterval(int interval) { - sp_setting.edit().putInt(Define.SP_MSG_KEY_STRING_BATTERY_CRON_INTERVAL, interval).apply(); - } - - public static int getBatteryCronInterval() { - return sp_setting.getInt(Define.SP_MSG_KEY_STRING_BATTERY_CRON_INTERVAL, 60); - } - - //获取当前版本名称 - public static String getVersionName() { - // 获取PackageManager的实例 - PackageManager packageManager = context.getPackageManager(); - // getPackageName()是你当前类的包名,0代表是获取版本信息 - PackageInfo packInfo; - try { - packInfo = packageManager.getPackageInfo(context.getPackageName(), 0); - return packInfo.versionName; - } catch (PackageManager.NameNotFoundException e) { - e.printStackTrace(); - return ""; - } - } - - //获取应用的版本号 - public static int getVersionCode() { - PackageManager manager = context.getPackageManager(); - int code = 0; - try { - PackageInfo info = manager.getPackageInfo(context.getPackageName(), 0); - code = info.versionCode; - } catch (PackageManager.NameNotFoundException e) { - e.printStackTrace(); - } - return code; - } - - private static String getString(int resId) { - return MyApplication.getContext().getString(resId); - } -} diff --git a/app/src/main/java/com/idormy/sms/forwarder/utils/SettingUtils.kt b/app/src/main/java/com/idormy/sms/forwarder/utils/SettingUtils.kt new file mode 100644 index 00000000..59d9c3e5 --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/utils/SettingUtils.kt @@ -0,0 +1,276 @@ +package com.idormy.sms.forwarder.utils + +import com.idormy.sms.forwarder.R +import com.xuexiang.xui.utils.ResUtils.getString + +class SettingUtils private constructor() { + companion object { + + //是否是第一次启动 + var isFirstOpen: Boolean + get() = MMKVUtils.getBoolean(IS_FIRST_OPEN_KEY, true) + set(isFirstOpen) { + MMKVUtils.put(IS_FIRST_OPEN_KEY, isFirstOpen) + } + + //是否同意隐私政策 + @JvmStatic + var isAgreePrivacy: Boolean + get() = MMKVUtils.getBoolean(IS_AGREE_PRIVACY_KEY, false) + set(isAgreePrivacy) { + MMKVUtils.put(IS_AGREE_PRIVACY_KEY, isAgreePrivacy) + } + + //是否转发短信 + @JvmStatic + var enableSms: Boolean + get() = MMKVUtils.getBoolean(SP_ENABLE_SMS, false) + set(enableSms) { + MMKVUtils.put(SP_ENABLE_SMS, enableSms) + } + + //是否转发通话 + @JvmStatic + var enablePhone: Boolean + get() = MMKVUtils.getBoolean(SP_ENABLE_PHONE, false) + set(enablePhone) { + MMKVUtils.put(SP_ENABLE_PHONE, enablePhone) + } + + //是否转发通话——已接来电 + @JvmStatic + var enableCallType1: Boolean + get() = MMKVUtils.getBoolean(SP_ENABLE_CALL_TYPE_1, false) + set(enableCallType1) { + MMKVUtils.put(SP_ENABLE_CALL_TYPE_1, enableCallType1) + } + + //是否转发通话——本机去电 + @JvmStatic + var enableCallType2: Boolean + get() = MMKVUtils.getBoolean(SP_ENABLE_CALL_TYPE_2, false) + set(enableCallType2) { + MMKVUtils.put(SP_ENABLE_CALL_TYPE_2, enableCallType2) + } + + //是否转发通话——未接来电 + @JvmStatic + var enableCallType3: Boolean + get() = MMKVUtils.getBoolean(SP_ENABLE_CALL_TYPE_3, false) + set(enableCallType3) { + MMKVUtils.put(SP_ENABLE_CALL_TYPE_3, enableCallType3) + } + + //是否转发应用通知 + @JvmStatic + var enableAppNotify: Boolean + get() = MMKVUtils.getBoolean(SP_ENABLE_APP_NOTIFY, false) + set(enableAppNotify) { + MMKVUtils.put(SP_ENABLE_APP_NOTIFY, enableAppNotify) + } + + //是否转发应用通知——自动消除通知 + @JvmStatic + var enableCancelAppNotify: Boolean + get() = MMKVUtils.getBoolean(SP_ENABLE_CANCEL_APP_NOTIFY, false) + set(enableCancelAppNotify) { + MMKVUtils.put(SP_ENABLE_CANCEL_APP_NOTIFY, enableCancelAppNotify) + } + + //是否转发应用通知——仅锁屏状态 + @JvmStatic + var enableNotUserPresent: Boolean + get() = MMKVUtils.getBoolean(SP_ENABLE_NOT_USER_PRESENT, false) + set(enableNotUserPresent) { + MMKVUtils.put(SP_ENABLE_NOT_USER_PRESENT, enableNotUserPresent) + } + + //过滤多久内重复消息 + @JvmStatic + var duplicateMessagesLimits: Int + get() = MMKVUtils.getInt(SP_DUPLICATE_MESSAGES_LIMITS, 0) + set(duplicateMessagesLimits) { + MMKVUtils.put(SP_DUPLICATE_MESSAGES_LIMITS, duplicateMessagesLimits) + } + + //是否监听电池状态变化 + @JvmStatic + var enableBatteryReceiver: Boolean + get() = MMKVUtils.getBoolean(SP_BATTERY_RECEIVER, false) + set(enableBatteryReceiver) { + MMKVUtils.put(SP_BATTERY_RECEIVER, enableBatteryReceiver) + } + + //电量预警当前状态 + @JvmStatic + var batteryStatus: Int + get() = MMKVUtils.getInt(SP_BATTERY_STATUS, 0) + set(batteryStatus) { + MMKVUtils.put(SP_BATTERY_STATUS, batteryStatus) + } + + //电量预警当前值 + @JvmStatic + var batteryLevelCurrent: Int + get() = MMKVUtils.getInt(SP_BATTERY_LEVEL_CURRENT, 0) + set(batteryLevelCurrent) { + MMKVUtils.put(SP_BATTERY_LEVEL_CURRENT, batteryLevelCurrent) + } + + //电量预警最低值 + @JvmStatic + var batteryLevelMin: Int + get() = MMKVUtils.getInt(SP_BATTERY_LEVEL_MIN, 0) + set(batteryLevelMin) { + MMKVUtils.put(SP_BATTERY_LEVEL_MIN, batteryLevelMin) + } + + //电量预警最高值 + @JvmStatic + var batteryLevelMax: Int + get() = MMKVUtils.getInt(SP_BATTERY_LEVEL_MAX, 100) + set(batteryLevelMax) { + MMKVUtils.put(SP_BATTERY_LEVEL_MAX, batteryLevelMax) + } + + //是否持续电量预警 + @JvmStatic + var batteryLevelOnce: Boolean + get() = MMKVUtils.getBoolean(SP_BATTERY_LEVEL_ONCE, false) + set(batteryLevelOnce) { + MMKVUtils.put(SP_BATTERY_LEVEL_ONCE, batteryLevelOnce) + } + + //是否定时推送电池状态 + @JvmStatic + var enableBatteryCron: Boolean + get() = MMKVUtils.getBoolean(SP_BATTERY_CRON, false) + set(enableBatteryCron) { + MMKVUtils.put(SP_BATTERY_CRON, enableBatteryCron) + } + + //是否定时推送电池状态——开始时间 + @JvmStatic + var batteryCronStartTime: String? + get() = MMKVUtils.getString(SP_BATTERY_CRON_START_TIME, "00:00") + set(batteryCronStartTime) { + MMKVUtils.put(SP_BATTERY_CRON_START_TIME, batteryCronStartTime) + } + + //是否定时推送电池状态——间隔时间(分钟) + @JvmStatic + var batteryCronInterval: Int + get() = MMKVUtils.getInt(SP_BATTERY_CRON_INTERVAL, 60) + set(batteryCronInterval) { + MMKVUtils.put(SP_BATTERY_CRON_INTERVAL, batteryCronInterval) + } + + //是否不在最近任务列表中显示 + @JvmStatic + var enableExcludeFromRecents: Boolean + get() = MMKVUtils.getBoolean(SP_ENABLE_EXCLUDE_FROM_RECENTS, false) + set(enableExcludeFromRecents) { + MMKVUtils.put(SP_ENABLE_EXCLUDE_FROM_RECENTS, enableExcludeFromRecents) + } + + //是否播放静音音乐 + @JvmStatic + var enablePlaySilenceMusic: Boolean + get() = MMKVUtils.getBoolean(SP_ENABLE_PLAY_SILENCE_MUSIC, false) + set(enablePlaySilenceMusic) { + MMKVUtils.put(SP_ENABLE_PLAY_SILENCE_MUSIC, enablePlaySilenceMusic) + } + + //是否不在最近任务列表中显示 + @JvmStatic + var enableOnePixelActivity: Boolean + get() = MMKVUtils.getBoolean(SP_ENABLE_ONE_PIXEL_ACTIVITY, false) + set(enableOnePixelActivity) { + MMKVUtils.put(SP_ENABLE_ONE_PIXEL_ACTIVITY, enableOnePixelActivity) + } + + //请求接口失败重试次数 + @JvmStatic + var requestRetryTimes: Int + get() = MMKVUtils.getInt(SP_REQUEST_RETRY_TIMES, 0) + set(requestRetryTimes) { + MMKVUtils.put(SP_REQUEST_RETRY_TIMES, requestRetryTimes) + } + + //请求接口失败重试间隔(秒) + @JvmStatic + var requestDelayTime: Int + get() = MMKVUtils.getInt(SP_REQUEST_DELAY_TIME, 1) + set(requestDelayTime) { + MMKVUtils.put(SP_REQUEST_DELAY_TIME, requestDelayTime) + } + + //请求接口失败超时时间(秒) + @JvmStatic + var requestTimeout: Int + get() = MMKVUtils.getInt(SP_REQUEST_TIMEOUT, 10) + set(requestTimeout) { + MMKVUtils.put(SP_REQUEST_TIMEOUT, requestTimeout) + } + + //通知内容 + @JvmStatic + var notifyContent: String? + get() = MMKVUtils.getString(SP_NOTIFY_CONTENT, getString(R.string.notification_content)) + set(notificationContent) { + MMKVUtils.put(SP_NOTIFY_CONTENT, notificationContent) + } + + //设备名称 + @JvmStatic + var extraDeviceMark: String? + get() = MMKVUtils.getString(SP_EXTRA_DEVICE_MARK, "") + set(extraDeviceMark) { + MMKVUtils.put(SP_EXTRA_DEVICE_MARK, extraDeviceMark) + } + + //SM1备注 + @JvmStatic + var extraSim1: String? + get() = MMKVUtils.getString(SP_EXTRA_SIM1, "") + set(extraSim1) { + MMKVUtils.put(SP_EXTRA_SIM1, extraSim1) + } + + //SM2备注 + @JvmStatic + var extraSim2: String? + get() = MMKVUtils.getString(SP_EXTRA_SIM2, "") + set(extraSim2) { + MMKVUtils.put(SP_EXTRA_SIM2, extraSim2) + } + + //是否启用自定义模板 + @JvmStatic + var enableSmsTemplate: Boolean + get() = MMKVUtils.getBoolean(SP_ENABLE_SMS_TEMPLATE, false) + set(enableSmsTemplate) { + MMKVUtils.put(SP_ENABLE_SMS_TEMPLATE, enableSmsTemplate) + } + + //自定义模板 + @JvmStatic + var smsTemplate: String? + get() = MMKVUtils.getString(SP_SMS_TEMPLATE, "") + set(smsTemplate) { + MMKVUtils.put(SP_SMS_TEMPLATE, smsTemplate) + } + + //是否显示页面帮助 + @JvmStatic + var enableHelpTip: Boolean + get() = MMKVUtils.getBoolean(SP_ENABLE_HELP_TIP, false) + set(enableHelpTip) { + MMKVUtils.put(SP_ENABLE_HELP_TIP, enableHelpTip) + } + } + + init { + throw UnsupportedOperationException("u can't instantiate me...") + } +} \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/utils/SharedPreferencesHelper.java b/app/src/main/java/com/idormy/sms/forwarder/utils/SharedPreferencesHelper.java deleted file mode 100644 index c1c98722..00000000 --- a/app/src/main/java/com/idormy/sms/forwarder/utils/SharedPreferencesHelper.java +++ /dev/null @@ -1,87 +0,0 @@ -package com.idormy.sms.forwarder.utils; - -import android.content.Context; -import android.content.SharedPreferences; - -import java.util.Map; - -public class SharedPreferencesHelper { - - private final SharedPreferences sharedPreferences; - private final SharedPreferences.Editor editor; - - public SharedPreferencesHelper(Context context, String FILE_NAME) { - sharedPreferences = context.getSharedPreferences(FILE_NAME, - Context.MODE_PRIVATE); - editor = sharedPreferences.edit(); - } - - /** - * 存储 - */ - public void put(String key, Object object) { - if (object instanceof String) { - editor.putString(key, (String) object); - } else if (object instanceof Integer) { - editor.putInt(key, (Integer) object); - } else if (object instanceof Boolean) { - editor.putBoolean(key, (Boolean) object); - } else if (object instanceof Float) { - editor.putFloat(key, (Float) object); - } else if (object instanceof Long) { - editor.putLong(key, (Long) object); - } else { - editor.putString(key, object.toString()); - } - editor.commit(); - } - - /** - * 获取保存的数据 - */ - public Object getSharedPreference(String key, Object defaultObject) { - if (defaultObject instanceof String) { - return sharedPreferences.getString(key, (String) defaultObject); - } else if (defaultObject instanceof Integer) { - return sharedPreferences.getInt(key, (Integer) defaultObject); - } else if (defaultObject instanceof Boolean) { - return sharedPreferences.getBoolean(key, (Boolean) defaultObject); - } else if (defaultObject instanceof Float) { - return sharedPreferences.getFloat(key, (Float) defaultObject); - } else if (defaultObject instanceof Long) { - return sharedPreferences.getLong(key, (Long) defaultObject); - } else { - return sharedPreferences.getString(key, null); - } - } - - /** - * 移除某个key值已经对应的值 - */ - public void remove(String key) { - editor.remove(key); - editor.commit(); - } - - /** - * 清除所有数据 - */ - public void clear() { - editor.clear(); - editor.commit(); - } - - /** - * 查询某个key是否存在 - */ - public Boolean contain(String key) { - return sharedPreferences.contains(key); - } - - /** - * 返回所有的键值对 - */ - public Map getAll() { - return sharedPreferences.getAll(); - } -} diff --git a/app/src/main/java/com/idormy/sms/forwarder/utils/SimUtils.java b/app/src/main/java/com/idormy/sms/forwarder/utils/SimUtils.java deleted file mode 100644 index c2d1f7a2..00000000 --- a/app/src/main/java/com/idormy/sms/forwarder/utils/SimUtils.java +++ /dev/null @@ -1,91 +0,0 @@ -package com.idormy.sms.forwarder.utils; - -import android.os.Bundle; -import android.util.Log; - -import com.idormy.sms.forwarder.MyApplication; - - -@SuppressWarnings("unused") -public class SimUtils { - private static final String TAG = "SimUtils"; - - //获取卡槽信息ID - public static int getSimId(Bundle bundle) { - int whichSIM = -1; - if (bundle == null) { - return whichSIM; - } - - if (bundle.containsKey("simId")) { - whichSIM = bundle.getInt("simId"); - Log.d(TAG, "simId = " + whichSIM); - } else if (bundle.containsKey("com.android.phone.extra.slot")) { - whichSIM = bundle.getInt("com.android.phone.extra.slot"); - Log.d(TAG, "com.android.phone.extra.slot = " + whichSIM); - } else { - String keyName = ""; - for (String key : bundle.keySet()) { - if (key.contains("sim")) - keyName = key; - } - if (bundle.containsKey(keyName)) { - whichSIM = bundle.getInt(keyName); - } - } - - Log.d(TAG, "Slot Number " + whichSIM); - return whichSIM + 1; - } - - //通过SubscriptionId获取卡槽信息ID - public static int getSimIdBySubscriptionId(int subscriptionId) { - try { - Log.d(TAG, MyApplication.SimInfoList.toString()); - for (PhoneUtils.SimInfo simInfo : MyApplication.SimInfoList) { - Log.d(TAG, simInfo.toString()); - if (simInfo.mSubscriptionId == subscriptionId) { - return simInfo.mSimSlotIndex + 1; - } - } - } catch (Exception e) { - Log.d(TAG, "getSimExtra Fail: " + e.getMessage()); - } - - return 1; - } - - //通过卡槽ID获取SubscriptionId - public static int getSubscriptionIdBySimId(int simId) { - try { - for (PhoneUtils.SimInfo simInfo : MyApplication.SimInfoList) { - Log.d(TAG, "mSimSlotIndex = " + simInfo.mSimSlotIndex); - if (simInfo.mSimSlotIndex != -1 && simInfo.mSimSlotIndex == simId) { - return simInfo.mSubscriptionId; - } - } - } catch (Exception e) { - Log.d(TAG, "getSimExtra Fail: " + e.getMessage()); - } - - return 0; - } - - //获取卡槽备注 - public static String getSimInfo(int simId) { - String res = ""; - try { - for (PhoneUtils.SimInfo simInfo : MyApplication.SimInfoList) { - Log.d(TAG, String.valueOf(simInfo)); - if (simInfo.mSimSlotIndex != -1 && simInfo.mSimSlotIndex + 1 == simId) { - res = simInfo.mCarrierName + "_" + simInfo.mNumber; - break; - } - } - } catch (Exception e) { - Log.d(TAG, "getSimExtra Fail: " + e.getMessage()); - } - - return res.replace("null", "unknown"); - } -} diff --git a/app/src/main/java/com/idormy/sms/forwarder/utils/SmsUtils.java b/app/src/main/java/com/idormy/sms/forwarder/utils/SmsUtils.java deleted file mode 100644 index 3cacee1e..00000000 --- a/app/src/main/java/com/idormy/sms/forwarder/utils/SmsUtils.java +++ /dev/null @@ -1,66 +0,0 @@ -package com.idormy.sms.forwarder.utils; - -import android.annotation.SuppressLint; -import android.app.PendingIntent; -import android.content.Context; -import android.content.Intent; -import android.os.Build; -import android.telephony.SmsManager; -import android.util.Log; - -import androidx.annotation.RequiresApi; - -import java.util.ArrayList; -import java.util.Objects; - -@SuppressWarnings("SynchronizeOnNonFinalField") -public class SmsUtils { - static final String TAG = "SmsUtils"; - static Boolean hasInit = false; - @SuppressLint("StaticFieldLeak") - static Context context; - - public static void init(Context context1) { - synchronized (hasInit) { - if (hasInit) return; - hasInit = true; - context = context1; - } - } - - @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP_MR1) - public static String sendSms(int subId, String mobiles, String message) { - mobiles = mobiles.replace(";", ";").replace(",", ";").replace(",", ";"); - Log.d(TAG, "subId = " + subId + ", mobiles = " + mobiles + ", message = " + message); - - String[] mobileArray = mobiles.split(";"); - for (String mobile : mobileArray) { - try { - SmsManager smsManager = SmsManager.getSmsManagerForSubscriptionId(subId); - - int sendFlags = Build.VERSION.SDK_INT >= 30 ? PendingIntent.FLAG_IMMUTABLE : PendingIntent.FLAG_ONE_SHOT; - PendingIntent sendPI = PendingIntent.getBroadcast(context, 0, new Intent(Context.TELEPHONY_SUBSCRIPTION_SERVICE), sendFlags); - - int deliverFlags = Build.VERSION.SDK_INT >= 30 ? PendingIntent.FLAG_IMMUTABLE : 0; - PendingIntent deliverPI = PendingIntent.getBroadcast(context, 0, new Intent("DELIVERED_SMS_ACTION"), deliverFlags); - - ArrayList sentPendingIntents = new ArrayList<>(); - ArrayList deliveredPendingIntents = new ArrayList<>(); - ArrayList divideContents = smsManager.divideMessage(message); - - for (int i = 0; i < divideContents.size(); i++) { - sentPendingIntents.add(i, sendPI); - deliveredPendingIntents.add(i, deliverPI); - } - smsManager.sendMultipartTextMessage(mobile, null, divideContents, sentPendingIntents, deliveredPendingIntents); - - } catch (Exception e) { - Log.e(TAG, Objects.requireNonNull(e.getMessage())); - return e.getMessage(); - } - } - - return null; - } - -} diff --git a/app/src/main/java/com/idormy/sms/forwarder/utils/TimeUtils.java b/app/src/main/java/com/idormy/sms/forwarder/utils/TimeUtils.java deleted file mode 100644 index d0f01ac8..00000000 --- a/app/src/main/java/com/idormy/sms/forwarder/utils/TimeUtils.java +++ /dev/null @@ -1,100 +0,0 @@ -package com.idormy.sms.forwarder.utils; - -import android.annotation.SuppressLint; - -import java.text.ParseException; -import java.text.SimpleDateFormat; -import java.util.Date; -import java.util.Locale; -import java.util.TimeZone; - -public class TimeUtils { - - //友好时间显示 - public static String friendlyTime(String utcTime) { - @SuppressLint("SimpleDateFormat") SimpleDateFormat utcFormatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault()); - utcFormatter.setTimeZone(TimeZone.getTimeZone("UTC"));//时区定义并进行时间获取 - Date utcDate; - try { - utcDate = utcFormatter.parse(utcTime); - } catch (ParseException e) { - e.printStackTrace(); - return utcTime; - } - - //获取utcDate距离当前的秒数 - assert utcDate != null; - int ct = (int) ((System.currentTimeMillis() - utcDate.getTime()) / 1000); - - if (ct == 0) { - return "刚刚"; - } - - if (ct > 0 && ct < 60) { - return ct + "秒前"; - } - - if (ct >= 60 && ct < 3600) { - return Math.max(ct / 60, 1) + "分钟前"; - } - if (ct >= 3600 && ct < 86400) { - return ct / 3600 + "小时前"; - } - if (ct >= 86400 && ct < 2592000) { //86400 * 30 - int day = ct / 86400; - return day + "天前"; - } - if (ct >= 2592000 && ct < 31104000) { //86400 * 30 - return ct / 2592000 + "月前"; - } - - return ct / 31104000 + "年前"; - } - - /** - * 函数功能描述:UTC时间转本地时间格式 - * - * @param utcTime UTC时间 - * @return 本地时间格式的时间 - */ - public static Date utc2LocalDate(String utcTime) throws ParseException { - String utcTimePatten = "yyyy-MM-dd HH:mm:ss"; - @SuppressLint("SimpleDateFormat") SimpleDateFormat utcFormatter = new SimpleDateFormat(utcTimePatten); - utcFormatter.setTimeZone(TimeZone.getTimeZone("UTC"));//时区定义并进行时间获取 - - return utcFormatter.parse(utcTime); - } - - /** - * 函数功能描述:UTC时间转本地时间格式 - * - * @param utcTime UTC时间 - * @return 本地时间格式的时间 - */ - public static String utc2Local(String utcTime) { - String localTimePatten = "yyyy-MM-dd HH:mm:ss"; - - Date utcDate; - try { - utcDate = utc2LocalDate(utcTime); - } catch (ParseException e) { - e.printStackTrace(); - return utcTime; - } - - @SuppressLint("SimpleDateFormat") SimpleDateFormat localFormatter = new SimpleDateFormat(localTimePatten); - localFormatter.setTimeZone(TimeZone.getDefault()); - assert utcDate != null; - return localFormatter.format(utcDate.getTime()); - } - - public static String getTimeString(String pattern) { - return new SimpleDateFormat(pattern, Locale.CHINESE).format(new Date()); - } - - public static String getTimeString(long time, String pattern) { - SimpleDateFormat df = new SimpleDateFormat(pattern, Locale.CHINESE); - return df.format(new Date(time)); - } - -} diff --git a/app/src/main/java/com/idormy/sms/forwarder/utils/TokenUtils.kt b/app/src/main/java/com/idormy/sms/forwarder/utils/TokenUtils.kt new file mode 100644 index 00000000..e5258db6 --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/utils/TokenUtils.kt @@ -0,0 +1,85 @@ +package com.idormy.sms.forwarder.utils + +import android.content.Context +import com.idormy.sms.forwarder.R +import com.umeng.analytics.MobclickAgent +import com.xuexiang.xui.utils.ResUtils +import com.xuexiang.xutil.app.ActivityUtils +import com.xuexiang.xutil.common.StringUtils + +/** + * Token管理工具 + * + * @author xuexiang + * @since 2019-11-17 22:37 + */ +@Suppress("unused") +class TokenUtils private constructor() { + companion object { + private var sToken: String? = null + private const val KEY_TOKEN = "KEY_TOKEN" + private const val KEY_PROFILE_CHANNEL = "github" + + /** + * 初始化Token信息 + */ + @JvmStatic + fun init(context: Context) { + MMKVUtils.init(context) + sToken = MMKVUtils.getString(KEY_TOKEN, "") + } + + private fun clearToken() { + sToken = null + MMKVUtils.remove(KEY_TOKEN) + } + + var token: String? + get() = sToken + set(token) { + sToken = token + MMKVUtils.put(KEY_TOKEN, token) + } + + @JvmStatic + fun hasToken(): Boolean { + return MMKVUtils.containsKey(KEY_TOKEN) + } + + /** + * 处理登录成功的事件 + * + * @param token 账户信息 + */ + @JvmStatic + fun handleLoginSuccess(token: String?): Boolean { + return if (!StringUtils.isEmpty(token)) { + XToastUtils.success(ResUtils.getString(R.string.login_succeeded)) + MobclickAgent.onProfileSignIn(KEY_PROFILE_CHANNEL, token) + Companion.token = token + true + } else { + XToastUtils.error(ResUtils.getString(R.string.login_failed)) + false + } + } + + /** + * 处理登出的事件 + */ + @JvmStatic + fun handleLogoutSuccess() { + MobclickAgent.onProfileSignOff() + //登出时,清除账号信息 + clearToken() + XToastUtils.success(ResUtils.getString(R.string.logout_succeeded)) + SettingUtils.isAgreePrivacy = false + //跳转到登录页 + ActivityUtils.startActivity(com.idormy.sms.forwarder.activity.LoginActivity::class.java) + } + } + + init { + throw UnsupportedOperationException("u can't instantiate me...") + } +} \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/utils/UmInitConfig.java b/app/src/main/java/com/idormy/sms/forwarder/utils/UmInitConfig.java deleted file mode 100644 index ad071e4c..00000000 --- a/app/src/main/java/com/idormy/sms/forwarder/utils/UmInitConfig.java +++ /dev/null @@ -1,59 +0,0 @@ -package com.idormy.sms.forwarder.utils; - -import android.content.Context; - -import com.umeng.analytics.MobclickAgent; -import com.umeng.commonsdk.UMConfigure; - - -public class UmInitConfig { - - private static final String TAG = "MyApplication"; - - public void UMinit(Context context) { - - //初始化组件化基础库, 统计SDK/推送SDK/分享SDK都必须调用此初始化接口 - UMConfigure.init(context, "60254fc7425ec25f10f4293e", "Umeng", UMConfigure.DEVICE_TYPE_PHONE, ""); - - //集成umeng-crash-vx.x.x.aar,则需要关闭原有统计SDK异常捕获功能 - MobclickAgent.setCatchUncaughtExceptions(false); - - //统计SDK是否支持采集在子进程中打点的自定义事件,默认不支持 - UMConfigure.setProcessEvent(true);//支持多进程打点 - - // 页面数据采集模式 - // setPageCollectionMode接口参数说明: - // 1. MobclickAgent.PageMode.AUTO: 建议大多数用户使用本采集模式,SDK在此模式下自动采集Activity - // 页面访问路径,开发者不需要针对每一个Activity在onResume/onPause函数中进行手动埋点。在此模式下, - // 开发者如需针对Fragment、CustomView等自定义页面进行页面统计,直接调用MobclickAgent.onPageStart/ - // MobclickAgent.onPageEnd手动埋点即可。此采集模式简化埋点工作,唯一缺点是在Android 4.0以下设备中 - // 统计不到Activity页面数据和各类基础指标(提示:目前Android 4.0以下设备市场占比已经极小)。 - - // 2. MobclickAgent.PageMode.MANUAL:对于要求在Android 4.0以下设备中也能正常采集数据的App,可以使用 - // 本模式,开发者需要在每一个Activity的onResume函数中手动调用MobclickAgent.onResume接口,在Activity的 - // onPause函数中手动调用MobclickAgent.onPause接口。在此模式下,开发者如需针对Fragment、CustomView等 - // 自定义页面进行页面统计,直接调用MobclickAgent.onPageStart/MobclickAgent.onPageEnd手动埋点即可。 - - // 如下两种LEGACY模式不建议首次集成友盟统计SDK的新用户选用。 - // 如果您是友盟统计SDK的老用户,App需要从老版本统计SDK升级到8.0.0版本统计SDK, - // 并且:您的App之前MobclickAgent.onResume/onPause接口埋点分散在所有Activity - // 中,逐个删除修改工作量很大且易出错。 - // 若您的App符合以上特征,可以选用如下两种LEGACY模式,否则不建议继续使用LEGACY模式。 - // 简单来说,升级SDK的老用户,如果不需要手动统计页面路径,选用LEGACY_AUTO模式。 - // 如果需要手动统计页面路径,选用LEGACY_MANUAL模式。 - // 3. MobclickAgent.PageMode.LEGACY_AUTO: 本模式适合不需要对Fragment、CustomView - // 等自定义页面进行页面访问统计的开发者,SDK仅对App中所有Activity进行页面统计,开发者需要在 - // 每一个Activity的onResume函数中手动调用MobclickAgent.onResume接口,在Activity的 - // onPause函数中手动调用MobclickAgent.onPause接口。此模式下MobclickAgent.onPageStart - // ,MobclickAgent.onPageEnd这两个接口无效。 - - // 4. MobclickAgent.PageMode.LEGACY_MANUAL: 本模式适合需要对Fragment、CustomView - // 等自定义页面进行手动页面统计的开发者,开发者如需针对Fragment、CustomView等 - // 自定义页面进行页面统计,直接调用MobclickAgent.onPageStart/MobclickAgent.onPageEnd - // 手动埋点即可。开发者还需要在每一个Activity的onResume函数中手动调用MobclickAgent.onResume接口, - // 在Activity的onPause函数中手动调用MobclickAgent.onPause接口。 - MobclickAgent.setPageCollectionMode(MobclickAgent.PageMode.AUTO); - - } - -} diff --git a/app/src/main/java/com/idormy/sms/forwarder/utils/XToastUtils.kt b/app/src/main/java/com/idormy/sms/forwarder/utils/XToastUtils.kt new file mode 100644 index 00000000..e9bb080e --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/utils/XToastUtils.kt @@ -0,0 +1,139 @@ +package com.idormy.sms.forwarder.utils + +import android.annotation.SuppressLint +import androidx.annotation.MainThread +import androidx.annotation.StringRes +import com.xuexiang.xui.XUI +import com.xuexiang.xui.widget.toast.XToast + +/** + * xtoast 工具类 + * + * @author xuexiang + * @since 2019-06-30 19:04 + */ +@Suppress("unused") +class XToastUtils private constructor() { + @SuppressLint("CheckResult") + companion object { + //======普通土司=======// + @MainThread + fun toast(message: CharSequence) { + XToast.normal(XUI.getContext(), message).show() + } + + @MainThread + fun toast(@StringRes message: Int) { + XToast.normal(XUI.getContext(), message).show() + } + + @MainThread + fun toast(message: CharSequence, duration: Int) { + XToast.normal(XUI.getContext(), message, duration).show() + } + + @MainThread + fun toast(@StringRes message: Int, duration: Int) { + XToast.normal(XUI.getContext(), message, duration).show() + } + + //======错误【红色】=======// + @MainThread + fun error(throwable: Throwable) { + XToast.error(XUI.getContext(), throwable.message!!).show() + } + + @MainThread + fun error(message: CharSequence) { + XToast.error(XUI.getContext(), message).show() + } + + @MainThread + fun error(@StringRes message: Int) { + XToast.error(XUI.getContext(), message).show() + } + + @MainThread + fun error(message: CharSequence, duration: Int) { + XToast.error(XUI.getContext(), message, duration).show() + } + + @MainThread + fun error(@StringRes message: Int, duration: Int) { + XToast.error(XUI.getContext(), message, duration).show() + } + + //======成功【绿色】=======// + @MainThread + fun success(message: CharSequence) { + XToast.success(XUI.getContext(), message).show() + } + + @MainThread + fun success(@StringRes message: Int) { + XToast.success(XUI.getContext(), message).show() + } + + @MainThread + fun success(message: CharSequence, duration: Int) { + XToast.success(XUI.getContext(), message, duration).show() + } + + @MainThread + fun success(@StringRes message: Int, duration: Int) { + XToast.success(XUI.getContext(), message, duration).show() + } + + //======信息【蓝色】=======// + @MainThread + fun info(message: CharSequence) { + XToast.info(XUI.getContext(), message).show() + } + + @MainThread + fun info(@StringRes message: Int) { + XToast.info(XUI.getContext(), message).show() + } + + @MainThread + fun info(message: CharSequence, duration: Int) { + XToast.info(XUI.getContext(), message, duration).show() + } + + @MainThread + fun info(@StringRes message: Int, duration: Int) { + XToast.info(XUI.getContext(), message, duration).show() + } + + //=======警告【黄色】======// + @MainThread + fun warning(message: CharSequence) { + XToast.warning(XUI.getContext(), message).show() + } + + @MainThread + fun warning(@StringRes message: Int) { + XToast.warning(XUI.getContext(), message).show() + } + + @MainThread + fun warning(message: CharSequence, duration: Int) { + XToast.warning(XUI.getContext(), message, duration).show() + } + + @MainThread + fun warning(@StringRes message: Int, duration: Int) { + XToast.warning(XUI.getContext(), message, duration).show() + } + + init { + XToast.Config.get() + .setAlpha(200) + .allowQueue(false) + } + } + + init { + throw UnsupportedOperationException("u can't instantiate me...") + } +} \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/utils/ZipUtils.java b/app/src/main/java/com/idormy/sms/forwarder/utils/ZipUtils.java deleted file mode 100644 index f3d616f5..00000000 --- a/app/src/main/java/com/idormy/sms/forwarder/utils/ZipUtils.java +++ /dev/null @@ -1,210 +0,0 @@ -package com.idormy.sms.forwarder.utils; - -import android.util.Log; - -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.InputStream; -import java.util.ArrayList; -import java.util.List; -import java.util.Objects; -import java.util.zip.ZipEntry; -import java.util.zip.ZipFile; -import java.util.zip.ZipInputStream; -import java.util.zip.ZipOutputStream; - -/** - * Created by YuShuangPing on 2018/11/11. - */ -@SuppressWarnings("ResultOfMethodCallIgnored") -public class ZipUtils { - public static final String TAG = "ZIP"; - - public ZipUtils() { - - } - - /** - * 解压zip到指定的路径 - * - * @param zipFileString ZIP的名称 - * @param outPathString 要解压缩路径 - * @throws Exception 异常抛出 - */ - public static void UnZipFolder(String zipFileString, String outPathString) throws Exception { - ZipInputStream inZip = new ZipInputStream(new FileInputStream(zipFileString)); - ZipEntry zipEntry; - String szName; - while ((zipEntry = inZip.getNextEntry()) != null) { - szName = zipEntry.getName(); - if (zipEntry.isDirectory()) { - //获取部件的文件夹名 - szName = szName.substring(0, szName.length() - 1); - File folder = new File(outPathString + File.separator + szName); - folder.mkdirs(); - } else { - Log.d(TAG, outPathString + File.separator + szName); - File file = new File(outPathString + File.separator + szName); - if (!file.exists()) { - Log.d(TAG, "Create the file:" + outPathString + File.separator + szName); - Objects.requireNonNull(file.getParentFile()).mkdirs(); - file.createNewFile(); - } - // 获取文件的输出流 - FileOutputStream out = new FileOutputStream(file); - int len; - byte[] buffer = new byte[1024]; - // 读取(字节)字节到缓冲区 - while ((len = inZip.read(buffer)) != -1) { - // 从缓冲区(0)位置写入(字节)字节 - out.write(buffer, 0, len); - out.flush(); - } - out.close(); - } - } - inZip.close(); - } - - public static void UnZipFolder(String zipFileString, String outPathString, String szName) throws Exception { - ZipInputStream inZip = new ZipInputStream(new FileInputStream(zipFileString)); - ZipEntry zipEntry; - while ((zipEntry = inZip.getNextEntry()) != null) { - //szName = zipEntry.getName(); - if (zipEntry.isDirectory()) { - //获取部件的文件夹名 - szName = szName.substring(0, szName.length() - 1); - File folder = new File(outPathString + File.separator + szName); - folder.mkdirs(); - } else { - Log.d(TAG, outPathString + File.separator + szName); - File file = new File(outPathString + File.separator + szName); - if (!file.exists()) { - Log.d(TAG, "Create the file:" + outPathString + File.separator + szName); - Objects.requireNonNull(file.getParentFile()).mkdirs(); - file.createNewFile(); - } - // 获取文件的输出流 - FileOutputStream out = new FileOutputStream(file); - int len; - byte[] buffer = new byte[1024]; - // 读取(字节)字节到缓冲区 - while ((len = inZip.read(buffer)) != -1) { - // 从缓冲区(0)位置写入(字节)字节 - out.write(buffer, 0, len); - out.flush(); - } - out.close(); - } - } - inZip.close(); - } - - /** - * 压缩文件和文件夹 - * - * @param srcFileString 要压缩的文件或文件夹 - * @param zipFileString 解压完成的Zip路径 - * @throws Exception 异常抛出 - */ - public static void ZipFolder(String srcFileString, String zipFileString) throws Exception { - //创建ZIP - ZipOutputStream outZip = new ZipOutputStream(new FileOutputStream(zipFileString)); - //创建文件 - File file = new File(srcFileString); - //压缩 - Log.d(TAG, "---->" + file.getParent() + "===" + file.getAbsolutePath()); - ZipFiles(file.getParent() + File.separator, file.getName(), outZip); - //完成和关闭 - outZip.finish(); - outZip.close(); - } - - /** - * 压缩文件 - * - * @param folderString 文件夹 - * @param fileString 文件 - * @param zipOutputSteam zip输出流 - * @throws Exception 异常抛出 - */ - private static void ZipFiles(String folderString, String fileString, ZipOutputStream zipOutputSteam) throws Exception { - Log.d(TAG, "folderString:" + folderString + "\n" + - "fileString:" + fileString + "\n=========================="); - if (zipOutputSteam == null) - return; - File file = new File(folderString + fileString); - if (file.isFile()) { - ZipEntry zipEntry = new ZipEntry(fileString); - FileInputStream inputStream = new FileInputStream(file); - zipOutputSteam.putNextEntry(zipEntry); - int len; - byte[] buffer = new byte[4096]; - while ((len = inputStream.read(buffer)) != -1) { - zipOutputSteam.write(buffer, 0, len); - } - zipOutputSteam.closeEntry(); - } else { - //文件夹 - String[] fileList = file.list(); - //没有子文件和压缩 - if (Objects.requireNonNull(fileList).length <= 0) { - ZipEntry zipEntry = new ZipEntry(fileString + File.separator); - zipOutputSteam.putNextEntry(zipEntry); - zipOutputSteam.closeEntry(); - } - //子文件和递归 - for (String s : fileList) { - ZipFiles(folderString + fileString + "/", s, zipOutputSteam); - } - } - } - - /** - * 返回zip的文件输入流 - * - * @param zipFileString zip的名称 - * @param fileString ZIP的文件名 - * @return InputStream 输出流 - * @throws Exception 异常抛出 - */ - public static InputStream UpZip(String zipFileString, String fileString) throws Exception { - ZipFile zipFile = new ZipFile(zipFileString); - ZipEntry zipEntry = zipFile.getEntry(fileString); - return zipFile.getInputStream(zipEntry); - } - - /** - * 返回ZIP中的文件列表(文件和文件夹) - * - * @param zipFileString ZIP的名称 - * @param bContainFolder 是否包含文件夹 - * @param bContainFile 是否包含文件 - * @throws Exception 异常抛出 - */ - public static List GetFileList(String zipFileString, boolean bContainFolder, boolean bContainFile) throws Exception { - List fileList = new ArrayList<>(); - ZipInputStream inZip = new ZipInputStream(new FileInputStream(zipFileString)); - ZipEntry zipEntry; - String szName; - while ((zipEntry = inZip.getNextEntry()) != null) { - szName = zipEntry.getName(); - if (zipEntry.isDirectory()) { - // 获取部件的文件夹名 - szName = szName.substring(0, szName.length() - 1); - File folder = new File(szName); - if (bContainFolder) { - fileList.add(folder); - } - } else { - File file = new File(szName); - if (bContainFile) { - fileList.add(file); - } - } - } - inZip.close(); - return fileList; - } -} diff --git a/app/src/main/java/com/idormy/sms/forwarder/utils/mail/Mail.kt b/app/src/main/java/com/idormy/sms/forwarder/utils/mail/Mail.kt new file mode 100644 index 00000000..8ae7822b --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/utils/mail/Mail.kt @@ -0,0 +1,27 @@ +package com.idormy.sms.forwarder.utils.mail + +import java.io.File + +/** + * desc: 邮件实体类 + * time: 2019/8/1 + * @author teprinciple + */ +data class Mail( + var mailServerHost: String = "", // 发件箱邮箱服务器地址 + var mailServerPort: String = "", // 发件箱邮箱服务器端口 + var fromAddress: String = "", // 发件箱 + var password: String = "", // 发件箱授权码(密码) + + var toAddress: ArrayList = ArrayList(), // 直接收件人邮箱 + var ccAddress: ArrayList = ArrayList(), // 抄送者邮箱 + var bccAddress: ArrayList = ArrayList(), // 密送者邮箱 + + var subject: String = "", // 邮件主题 + var content: CharSequence = "", // 邮件内容 + var attachFiles: ArrayList = ArrayList(), // 附件 + + var openSSL: Boolean = false, //是否开启ssl验证 默认关闭 + var sslFactory: String = "javax.net.ssl.SSLSocketFactory", //SSL构建类名 + var startTls: Boolean = false, //是否开启starttls加密方式 默认关闭 +) diff --git a/app/src/main/java/com/idormy/sms/forwarder/utils/mail/MailSender.kt b/app/src/main/java/com/idormy/sms/forwarder/utils/mail/MailSender.kt new file mode 100644 index 00000000..6d1c475f --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/utils/mail/MailSender.kt @@ -0,0 +1,47 @@ +package com.idormy.sms.forwarder.utils.mail + +import android.util.Log +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.async +import kotlinx.coroutines.launch +import javax.mail.Transport + +/** + * 邮件发送器 + */ +object MailSender { + + /** + * 获取单例 + */ + @JvmStatic + fun getInstance() = this + + /** + * 发送邮件 + */ + fun sendMail(mail: Mail, onMailSendListener: OnMailSendListener? = null) { + val send = GlobalScope.async(Dispatchers.IO) { + Transport.send(MailUtil.createMailMessage(mail)) + } + + GlobalScope.launch(Dispatchers.Main) { + runCatching { + send.await() + onMailSendListener?.onSuccess() + }.onFailure { + Log.e("MailSender", it.message.toString()) + onMailSendListener?.onError(it) + } + } + } + + /** + * 发送回调 + */ + interface OnMailSendListener { + fun onSuccess() + fun onError(e: Throwable) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/utils/mail/MailUtil.kt b/app/src/main/java/com/idormy/sms/forwarder/utils/mail/MailUtil.kt new file mode 100644 index 00000000..fcdc7665 --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/utils/mail/MailUtil.kt @@ -0,0 +1,107 @@ +package com.idormy.sms.forwarder.utils.mail + +import android.text.Html +import android.text.Spanned +import android.util.Log +import java.util.* +import javax.activation.DataHandler +import javax.activation.FileDataSource +import javax.mail.Authenticator +import javax.mail.Message +import javax.mail.PasswordAuthentication +import javax.mail.Session +import javax.mail.internet.* + +/** + * desc: 邮件帮助类 + * time: 2019/8/1 + * @author teprinciple + */ +@Suppress("DEPRECATION") +object MailUtil { + + /** + * 创建邮件 + */ + fun createMailMessage(mail: Mail): MimeMessage { + Log.e("createMailMessage", mail.toString()) + val properties = Properties() + properties["mail.debug"] = "true" + properties["mail.smtp.host"] = mail.mailServerHost + properties["mail.smtp.port"] = mail.mailServerPort + properties["mail.smtp.auth"] = "true" + properties["mail.smtp.ssl.enable"] = mail.openSSL + if (mail.startTls) { + properties["mail.smtp.starttls.enable"] = mail.startTls + } + if (mail.openSSL) { + properties["mail.smtp.socketFactory.class"] = mail.sslFactory + } + val authenticator = MailAuthenticator(mail.fromAddress, mail.password) + val session = Session.getInstance(properties, authenticator) + session.debug = true + + Log.e("createMailMessage", session.toString()) + return MimeMessage(session).apply { + + // 设置发件箱 + setFrom(InternetAddress(mail.fromAddress)) + + // 设置直接接收者收件箱 + val toAddress = mail.toAddress.map { + InternetAddress(it) + }.toTypedArray() + setRecipients(Message.RecipientType.TO, toAddress) + + // 设置抄送者收件箱 + val ccAddress = mail.ccAddress.map { + InternetAddress(it) + }.toTypedArray() + setRecipients(Message.RecipientType.CC, ccAddress) + + // 设置密送者收件箱 + val bccAddress = mail.bccAddress.map { + InternetAddress(it) + }.toTypedArray() + setRecipients(Message.RecipientType.BCC, bccAddress) + + // 邮件主题 + subject = mail.subject + + // 邮件内容 + val contentPart = MimeMultipart() + + // 邮件正文 + val textBodyPart = MimeBodyPart() + if (mail.content is Spanned) { + textBodyPart.setContent(Html.toHtml(mail.content as Spanned), "text/html;charset=UTF-8") + } else { + textBodyPart.setContent(mail.content, "text/html;charset=UTF-8") + } + contentPart.addBodyPart(textBodyPart) + + // 邮件附件 + mail.attachFiles.forEach { + val fileBodyPart = MimeBodyPart() + val ds = FileDataSource(it) + val dh = DataHandler(ds) + fileBodyPart.dataHandler = dh + fileBodyPart.fileName = MimeUtility.encodeText(dh.name) + contentPart.addBodyPart(fileBodyPart) + } + contentPart.setSubType("mixed") + setContent(contentPart) + saveChanges() + } + } + + /** + * 发件箱auth校验 + */ + class MailAuthenticator(username: String?, private var password: String?) : Authenticator() { + private var userName: String? = username + override fun getPasswordAuthentication(): PasswordAuthentication { + return PasswordAuthentication(userName, password) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/utils/sdkinit/ANRWatchDogInit.kt b/app/src/main/java/com/idormy/sms/forwarder/utils/sdkinit/ANRWatchDogInit.kt new file mode 100644 index 00000000..2ac9acb6 --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/utils/sdkinit/ANRWatchDogInit.kt @@ -0,0 +1,64 @@ +@file:Suppress("MemberVisibilityCanBePrivate") + +package com.idormy.sms.forwarder.utils.sdkinit + +import com.github.anrwatchdog.ANRError +import com.github.anrwatchdog.ANRWatchDog +import com.github.anrwatchdog.ANRWatchDog.ANRListener +import com.xuexiang.xutil.common.logger.Logger + +/** + * ANR看门狗监听器初始化 + * + * @author xuexiang + * @since 2020-02-18 15:08 + */ +@Suppress("unused", "MemberVisibilityCanBePrivate") +class ANRWatchDogInit private constructor() { + companion object { + private const val TAG = "ANRWatchDog" + + /** + * ANR看门狗 + */ + var aNRWatchDog: ANRWatchDog? = null + private set + + /** + * ANR监听触发的时间 + */ + private const val ANR_DURATION = 4000 + + /** + * ANR静默处理【就是不处理,直接记录一下日志】 + */ + private val SILENT_LISTENER = ANRListener { error: ANRError? -> Logger.eTag(TAG, error) } + + /** + * ANR自定义处理【可以是记录日志用于上传】 + */ + private val CUSTOM_LISTENER = ANRListener { error: ANRError? -> + Logger.eTag(TAG, "Detected Application Not Responding!", error) + throw error!! + } + + fun init() { + //这里设置监听的间隔为2秒 + aNRWatchDog = ANRWatchDog(2000) + aNRWatchDog!!.setANRInterceptor { duration: Long -> + val ret = ANR_DURATION - duration + if (ret > 0) { + Logger.wTag( + TAG, + "Intercepted ANR that is too short ($duration ms), postponing for $ret ms." + ) + } + ret + }.setANRListener(SILENT_LISTENER).start() + } + } + + init { + throw UnsupportedOperationException("u can't instantiate me...") + } +} \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/utils/sdkinit/UMengInit.kt b/app/src/main/java/com/idormy/sms/forwarder/utils/sdkinit/UMengInit.kt new file mode 100644 index 00000000..d82a4b77 --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/utils/sdkinit/UMengInit.kt @@ -0,0 +1,89 @@ +package com.idormy.sms.forwarder.utils.sdkinit + +import android.app.Application +import android.content.Context +import com.idormy.sms.forwarder.BuildConfig +import com.idormy.sms.forwarder.utils.SettingUtils.Companion.isAgreePrivacy +import com.meituan.android.walle.WalleChannelReader +import com.umeng.analytics.MobclickAgent +import com.umeng.commonsdk.UMConfigure +import com.xuexiang.xui.XUI + +/** + * UMeng 统计 SDK初始化 + * + * @author xuexiang + * @since 2019-06-18 15:49 + */ +class UMengInit private constructor() { + companion object { + private const val DEFAULT_CHANNEL_ID = "github" + /** + * 初始化SDK,合规指南【先进行预初始化,如果用户隐私同意后可以初始化UmengSDK进行信息上报】 + */ + /** + * 初始化SDK,合规指南【先进行预初始化,如果用户隐私同意后可以初始化UmengSDK进行信息上报】 + */ + @JvmOverloads + fun init(context: Context = XUI.getContext()) { + val appContext = context.applicationContext + if (appContext is Application) { + initApplication(appContext) + } + } + + /** + * 初始化SDK,合规指南【先进行预初始化,如果用户隐私同意后可以初始化UmengSDK进行信息上报】 + */ + private fun initApplication(application: Application?) { + // 运营统计数据调试运行时不初始化 + if (com.idormy.sms.forwarder.App.isDebug) { + return + } + UMConfigure.setLogEnabled(false) + UMConfigure.preInit(application, BuildConfig.APP_ID_UMENG, getChannel(application)) + // 用户同意了隐私协议 + if (isAgreePrivacy) { + realInit(application) + } + } + + /** + * 真实的初始化UmengSDK【进行设备信息的统计上报,必须在获得用户隐私同意后方可调用】 + */ + private fun realInit(application: Application?) { + // 运营统计数据调试运行时不初始化 + if (com.idormy.sms.forwarder.App.isDebug) { + return + } + //初始化组件化基础库, 注意: 即使您已经在AndroidManifest.xml中配置过appkey和channel值,也需要在App代码中调用初始化接口(如需要使用AndroidManifest.xml中配置好的appkey和channel值,UMConfigure.init调用中appkey和channel参数请置为null)。 + //第二个参数是appkey,最后一个参数是pushSecret + //这里BuildConfig.APP_ID_UMENG是根据local.properties中定义的APP_ID_UMENG生成的,只是运行看效果的话,可以不初始化该SDK + UMConfigure.init( + application, + BuildConfig.APP_ID_UMENG, + getChannel(application), + UMConfigure.DEVICE_TYPE_PHONE, + "" + ) + //统计SDK是否支持采集在子进程中打点的自定义事件,默认不支持 + //支持多进程打点 + UMConfigure.setProcessEvent(true) + MobclickAgent.setPageCollectionMode(MobclickAgent.PageMode.AUTO) + } + + /** + * 获取渠道信息 + * + * @param context + * @return + */ + private fun getChannel(context: Context?): String? { + return WalleChannelReader.getChannel(context!!, DEFAULT_CHANNEL_ID) + } + } + + init { + throw UnsupportedOperationException("u can't instantiate me...") + } +} \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/utils/sdkinit/XBasicLibInit.kt b/app/src/main/java/com/idormy/sms/forwarder/utils/sdkinit/XBasicLibInit.kt new file mode 100644 index 00000000..e29796cc --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/utils/sdkinit/XBasicLibInit.kt @@ -0,0 +1,122 @@ +package com.idormy.sms.forwarder.utils.sdkinit + +import android.app.Application +import com.idormy.sms.forwarder.core.BaseActivity +import com.idormy.sms.forwarder.utils.TokenUtils +import com.idormy.sms.forwarder.utils.XToastUtils +import com.xuexiang.xaop.XAOP +import com.xuexiang.xhttp2.XHttpSDK +import com.xuexiang.xpage.PageConfig +import com.xuexiang.xrouter.launcher.XRouter +import com.xuexiang.xui.XUI +import com.xuexiang.xutil.XUtil +import com.xuexiang.xutil.common.StringUtils + +/** + * X系列基础库初始化 + * + * @author xuexiang + * @since 2019-06-30 23:54 + */ +class XBasicLibInit private constructor() { + companion object { + /** + * 初始化基础库SDK + */ + fun init(application: Application) { + //工具类 + initXUtil(application) + + //网络请求框架 + initXHttp2(application) + + //页面框架 + initXPage(application) + + //切片框架 + initXAOP(application) + + //UI框架 + initXUI(application) + + //路由框架 + initRouter(application) + } + + /** + * 初始化XUtil工具类 + */ + private fun initXUtil(application: Application) { + XUtil.init(application) + XUtil.debug(com.idormy.sms.forwarder.App.isDebug) + TokenUtils.init(application) + } + + /** + * 初始化XHttp2 + */ + private fun initXHttp2(application: Application) { + //初始化网络请求框架,必须首先执行 + XHttpSDK.init(application) + //需要调试的时候执行 + if (com.idormy.sms.forwarder.App.isDebug) { + XHttpSDK.debug() + } + // XHttpSDK.debug(new CustomLoggingInterceptor()); //设置自定义的日志打印拦截器 + //设置网络请求的全局基础地址 + XHttpSDK.setBaseUrl("https://gitee.com/") + // //设置动态参数添加拦截器 +// XHttpSDK.addInterceptor(new CustomDynamicInterceptor()); +// //请求失效校验拦截器 +// XHttpSDK.addInterceptor(new CustomExpiredInterceptor()); + } + + /** + * 初始化XPage页面框架 + */ + private fun initXPage(application: Application) { + PageConfig.getInstance() + .debug(com.idormy.sms.forwarder.App.isDebug) + .setContainActivityClazz(BaseActivity::class.java) + .init(application) + } + + /** + * 初始化XAOP + */ + private fun initXAOP(application: Application) { + XAOP.init(application) + XAOP.debug(com.idormy.sms.forwarder.App.isDebug) + //设置动态申请权限切片 申请权限被拒绝的事件响应监听 + XAOP.setOnPermissionDeniedListener { permissionsDenied: List? -> + XToastUtils.error( + "权限申请被拒绝:" + StringUtils.listToString(permissionsDenied, ",") + ) + } + } + + /** + * 初始化XUI框架 + */ + private fun initXUI(application: Application) { + XUI.init(application) + XUI.debug(com.idormy.sms.forwarder.App.isDebug) + } + + /** + * 初始化路由框架 + */ + private fun initRouter(application: Application) { + // 这两行必须写在init之前,否则这些配置在init过程中将无效 + if (com.idormy.sms.forwarder.App.isDebug) { + XRouter.openLog() // 打印日志 + XRouter.openDebug() // 开启调试模式(如果在InstantRun模式下运行,必须开启调试模式!线上版本需要关闭,否则有安全风险) + } + XRouter.init(application) + } + } + + init { + throw UnsupportedOperationException("u can't instantiate me...") + } +} \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/utils/sdkinit/XUpdateInit.kt b/app/src/main/java/com/idormy/sms/forwarder/utils/sdkinit/XUpdateInit.kt new file mode 100644 index 00000000..b376aa58 --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/utils/sdkinit/XUpdateInit.kt @@ -0,0 +1,70 @@ +package com.idormy.sms.forwarder.utils.sdkinit + +import android.app.Application +import android.content.Context +import com.idormy.sms.forwarder.utils.KEY_UPDATE_URL +import com.idormy.sms.forwarder.utils.update.CustomUpdateDownloader +import com.idormy.sms.forwarder.utils.update.CustomUpdateFailureListener +import com.idormy.sms.forwarder.utils.update.XHttpUpdateHttpServiceImpl +import com.xuexiang.xupdate.XUpdate +import com.xuexiang.xupdate.utils.UpdateUtils +import com.xuexiang.xutil.common.StringUtils + +/** + * XUpdate 版本更新 SDK 初始化 + * + * 详细使用参见:https://github.com/xuexiangjys/XUpdate/wiki + * + * @author xuexiang + * @since 2019-06-18 15:51 + */ +@Suppress("SameParameterValue") +class XUpdateInit private constructor() { + companion object { + /** + * 应用版本更新的检查地址 + */ + fun init(application: Application) { + XUpdate.get() + .debug(com.idormy.sms.forwarder.App.isDebug) + //默认设置只在wifi下检查版本更新 + .isWifiOnly(false) + //默认设置使用get请求检查版本 + .isGet(true) + //默认设置非自动模式,可根据具体使用配置 + .isAutoMode(false) + //设置默认公共请求参数 + .param("versionCode", UpdateUtils.getVersionCode(application)) + .param("appKey", application.packageName) //这个必须设置!实现网络请求功能。 + .setIUpdateHttpService(XHttpUpdateHttpServiceImpl()) + .setIUpdateDownLoader(CustomUpdateDownloader()) //这个必须初始化 + .init(application) + } + + /** + * 进行版本更新检查 + */ + fun checkUpdate(context: Context, needErrorTip: Boolean) { + checkUpdate(context, KEY_UPDATE_URL, needErrorTip) + } + + /** + * 进行版本更新检查 + * + * @param context 上下文 + * @param url 版本更新检查的地址 + * @param needErrorTip 是否需要错误的提示 + */ + private fun checkUpdate(context: Context, url: String, needErrorTip: Boolean) { + if (StringUtils.isEmpty(url)) { + return + } + XUpdate.newBuild(context).updateUrl(url).update() + XUpdate.get().setOnUpdateFailureListener(CustomUpdateFailureListener(needErrorTip)) + } + } + + init { + throw UnsupportedOperationException("u can't instantiate me...") + } +} \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/utils/sender/BarkUtils.kt b/app/src/main/java/com/idormy/sms/forwarder/utils/sender/BarkUtils.kt new file mode 100644 index 00000000..1106ce06 --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/utils/sender/BarkUtils.kt @@ -0,0 +1,107 @@ +package com.idormy.sms.forwarder.utils.sender + +import android.text.TextUtils +import android.util.Log +import com.google.gson.Gson +import com.idormy.sms.forwarder.R +import com.idormy.sms.forwarder.database.entity.Rule +import com.idormy.sms.forwarder.entity.MsgInfo +import com.idormy.sms.forwarder.entity.result.BarkResult +import com.idormy.sms.forwarder.entity.setting.BarkSetting +import com.idormy.sms.forwarder.utils.SendUtils +import com.idormy.sms.forwarder.utils.SettingUtils +import com.idormy.sms.forwarder.utils.XToastUtils +import com.xuexiang.xhttp2.XHttp +import com.xuexiang.xhttp2.cache.model.CacheMode +import com.xuexiang.xhttp2.callback.SimpleCallBack +import com.xuexiang.xhttp2.exception.ApiException +import com.xuexiang.xui.utils.ResUtils +import java.util.regex.Pattern + +@Suppress("PrivatePropertyName", "UNUSED_PARAMETER", "unused") +class BarkUtils { + companion object { + + private val TAG: String = BarkUtils::class.java.simpleName + + fun sendMsg( + setting: BarkSetting, + msgInfo: MsgInfo, + rule: Rule?, + logId: Long?, + ) { + val title: String = if (rule != null) { + msgInfo.getTitleForSend(setting.title.toString(), rule.regexReplace) + } else { + msgInfo.getTitleForSend(setting.title.toString()) + } + val content: String = if (rule != null) { + msgInfo.getContentForSend(rule.smsTemplate, rule.regexReplace) + } else { + msgInfo.getContentForSend(SettingUtils.smsTemplate.toString()) + } + + val requestUrl: String = setting.server //推送地址 + Log.i(TAG, "requestUrl:$requestUrl") + + val request = XHttp.post(requestUrl) + .params("title", title) + .params("body", content) + .params("isArchive", 1) + if (!TextUtils.isEmpty(setting.group)) request.params("group", setting.group) + if (!TextUtils.isEmpty(setting.icon)) request.params("icon", setting.icon) + if (!TextUtils.isEmpty(setting.level)) request.params("level", setting.level) + if (!TextUtils.isEmpty(setting.sound)) request.params("sound", setting.sound) + if (!TextUtils.isEmpty(setting.badge)) request.params("badge", setting.badge) + if (!TextUtils.isEmpty(setting.url)) request.params("url", setting.url) + + val isCode: Int = content.indexOf("验证码") + val isPassword: Int = content.indexOf("动态密码") + val isPassword2: Int = content.indexOf("短信密码") + if (isCode != -1 || isPassword != -1 || isPassword2 != -1) { + val p = Pattern.compile("(\\d{4,6})") + val m = p.matcher(content) + if (m.find()) { + println(m.group()) + request.params("automaticallyCopy", "1") + request.params("copy", m.group()) + } + } + + request.keepJson(true) + .timeOut((SettingUtils.requestTimeout * 1000).toLong()) //超时时间10s + .cacheMode(CacheMode.NO_CACHE) + .retryCount(SettingUtils.requestRetryTimes) //超时重试的次数 + .retryDelay(SettingUtils.requestDelayTime) //超时重试的延迟时间 + .retryIncreaseDelay(SettingUtils.requestDelayTime) //超时重试叠加延时 + .timeStamp(true) + .execute(object : SimpleCallBack() { + + override fun onError(e: ApiException) { + SendUtils.updateLogs(logId, 0, e.displayMessage) + Log.e(TAG, e.detailMessage) + XToastUtils.error(e.displayMessage) + } + + override fun onSuccess(response: String) { + Log.i(TAG, response) + + val resp = Gson().fromJson(response, BarkResult::class.java) + if (resp.code == 200L) { + SendUtils.updateLogs(logId, 2, response) + XToastUtils.success(ResUtils.getString(R.string.request_succeeded)) + } else { + SendUtils.updateLogs(logId, 0, response) + XToastUtils.error(ResUtils.getString(R.string.request_failed) + response) + } + } + + }) + + } + + fun sendMsg(setting: BarkSetting, msgInfo: MsgInfo) { + sendMsg(setting, msgInfo, null, null) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/utils/sender/DingtalkUtils.kt b/app/src/main/java/com/idormy/sms/forwarder/utils/sender/DingtalkUtils.kt new file mode 100644 index 00000000..266b36d0 --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/utils/sender/DingtalkUtils.kt @@ -0,0 +1,127 @@ +package com.idormy.sms.forwarder.utils.sender + +import android.text.TextUtils +import android.util.Base64 +import android.util.Log +import com.google.gson.Gson +import com.idormy.sms.forwarder.R +import com.idormy.sms.forwarder.database.entity.Rule +import com.idormy.sms.forwarder.entity.MsgInfo +import com.idormy.sms.forwarder.entity.result.DingtalkResult +import com.idormy.sms.forwarder.entity.setting.DingtalkSetting +import com.idormy.sms.forwarder.utils.SendUtils +import com.idormy.sms.forwarder.utils.SettingUtils +import com.idormy.sms.forwarder.utils.XToastUtils +import com.xuexiang.xhttp2.XHttp +import com.xuexiang.xhttp2.cache.model.CacheMode +import com.xuexiang.xhttp2.callback.SimpleCallBack +import com.xuexiang.xhttp2.exception.ApiException +import com.xuexiang.xui.utils.ResUtils +import java.net.URLEncoder +import java.nio.charset.StandardCharsets +import javax.crypto.Mac +import javax.crypto.spec.SecretKeySpec + +@Suppress("PrivatePropertyName", "UNUSED_PARAMETER") +class DingtalkUtils private constructor() { + companion object { + + private val TAG: String = DingtalkUtils::class.java.simpleName + + fun sendMsg( + setting: DingtalkSetting, + msgInfo: MsgInfo, + rule: Rule?, + logId: Long?, + ) { + val content: String = if (rule != null) { + msgInfo.getContentForSend(rule.smsTemplate, rule.regexReplace) + } else { + msgInfo.getContentForSend(SettingUtils.smsTemplate.toString()) + } + + var requestUrl = if (setting.token.startsWith("http")) setting.token else "https://oapi.dingtalk.com/robot/send?access_token=" + setting.token + + if (setting.secret != null) { + val timestamp = System.currentTimeMillis() + val stringToSign = "$timestamp\n" + setting.secret + val mac = Mac.getInstance("HmacSHA256") + mac.init(SecretKeySpec(setting.secret?.toByteArray(StandardCharsets.UTF_8), "HmacSHA256")) + val signData = mac.doFinal(stringToSign.toByteArray(StandardCharsets.UTF_8)) + val sign = URLEncoder.encode(String(Base64.encode(signData, Base64.NO_WRAP)), "UTF-8") + requestUrl += "×tamp=$timestamp&sign=$sign" + } + + Log.i(TAG, "requestUrl:$requestUrl") + + val msgMap: MutableMap = mutableMapOf() + msgMap["msgtype"] = "text" + + val textText: MutableMap = mutableMapOf() + textText["content"] = content + msgMap["text"] = textText + + val atMap: MutableMap = mutableMapOf() + msgMap["at"] = atMap + if (setting.atAll == true) { + atMap["isAtAll"] = true + } else { + atMap["isAtAll"] = false + if (!TextUtils.isEmpty(setting.atMobiles)) { + val atMobilesArray: Array? = setting.atMobiles?.split(",".toRegex())?.toTypedArray() + if (atMobilesArray != null) { + val atMobilesList: MutableList = ArrayList() + for (atMobile in atMobilesArray) { + if (TextUtils.isDigitsOnly(atMobile)) { + atMobilesList.add(atMobile) + } + } + if (atMobilesList.isNotEmpty()) { + atMap["atMobiles"] = atMobilesList + } + } + } + } + + val requestMsg: String = Gson().toJson(msgMap) + Log.i(TAG, "requestMsg:$requestMsg") + + XHttp.post(requestUrl) + .upJson(requestMsg) + .keepJson(true) + .timeOut((SettingUtils.requestTimeout * 1000).toLong()) //超时时间10s + .cacheMode(CacheMode.NO_CACHE) + .retryCount(SettingUtils.requestRetryTimes) //超时重试的次数 + .retryDelay(SettingUtils.requestDelayTime) //超时重试的延迟时间 + .retryIncreaseDelay(SettingUtils.requestDelayTime) //超时重试叠加延时 + .timeStamp(true) + .execute(object : SimpleCallBack() { + + override fun onError(e: ApiException) { + SendUtils.updateLogs(logId, 0, e.displayMessage) + Log.e(TAG, e.detailMessage) + XToastUtils.error(e.displayMessage) + } + + override fun onSuccess(response: String) { + Log.i(TAG, response) + + val resp = Gson().fromJson(response, DingtalkResult::class.java) + if (resp.errcode == 0L) { + SendUtils.updateLogs(logId, 2, response) + XToastUtils.success(ResUtils.getString(R.string.request_succeeded)) + } else { + SendUtils.updateLogs(logId, 0, response) + XToastUtils.error(ResUtils.getString(R.string.request_failed) + response) + } + } + + }) + + } + + fun sendMsg(setting: DingtalkSetting, msgInfo: MsgInfo) { + sendMsg(setting, msgInfo, null, null) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/utils/sender/EmailUtils.kt b/app/src/main/java/com/idormy/sms/forwarder/utils/sender/EmailUtils.kt new file mode 100644 index 00000000..ee9525bb --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/utils/sender/EmailUtils.kt @@ -0,0 +1,150 @@ +package com.idormy.sms.forwarder.utils.sender + +import android.util.Log +import com.idormy.sms.forwarder.R +import com.idormy.sms.forwarder.database.entity.Rule +import com.idormy.sms.forwarder.entity.MsgInfo +import com.idormy.sms.forwarder.entity.setting.EmailSetting +import com.idormy.sms.forwarder.utils.SendUtils +import com.idormy.sms.forwarder.utils.SettingUtils +import com.idormy.sms.forwarder.utils.XToastUtils +import com.idormy.sms.forwarder.utils.mail.Mail +import com.idormy.sms.forwarder.utils.mail.MailSender +import com.xuexiang.xui.utils.ResUtils + +@Suppress("PrivatePropertyName", "UNUSED_PARAMETER", "unused") +class EmailUtils { + companion object { + + private val TAG: String = EmailUtils::class.java.simpleName + + fun sendMsg( + setting: EmailSetting, + msgInfo: MsgInfo, + rule: Rule?, + logId: Long?, + ) { + val title: String = if (rule != null) { + msgInfo.getTitleForSend(setting.title.toString(), rule.regexReplace) + } else { + msgInfo.getTitleForSend(setting.title.toString()) + } + val message: String = if (rule != null) { + msgInfo.getContentForSend(rule.smsTemplate, rule.regexReplace) + } else { + msgInfo.getContentForSend(SettingUtils.smsTemplate.toString()) + } + + //常用邮箱类型的转换 + when (setting.mailType) { + "@qq.com", "@foxmail.com" -> { + setting.host = "smtp.qq.com" + setting.port = "465" + setting.ssl = true + setting.fromEmail += setting.mailType + } + "@exmail.qq.com" -> { + setting.host = "smtp.exmail.qq.com" + setting.port = "465" + setting.ssl = true + setting.fromEmail += setting.mailType + } + "@msn.com" -> { + setting.host = "smtp-mail.outlook.com" + setting.port = "587" + setting.ssl = false + setting.startTls = true + setting.fromEmail += setting.mailType + } + "@outlook.com", "@office365.com", "@live.com", "@hotmail.com" -> { + setting.host = "smtp.office365.com" + setting.port = "587" + setting.ssl = false + setting.startTls = true + setting.fromEmail += setting.mailType + } + "@gmail.com" -> { + setting.host = "smtp.gmail.com" + setting.port = "587" + setting.ssl = true + setting.startTls = true + setting.fromEmail += setting.mailType + } + "@yeah.net" -> { + setting.host = "smtp.yeah.net" + setting.port = "465" + setting.ssl = true + setting.fromEmail += setting.mailType + } + "@163.com" -> { + setting.host = "smtp.163.com" + setting.port = "465" + setting.ssl = true + setting.fromEmail += setting.mailType + } + "@126.com" -> { + setting.host = "smtp.126.com" + setting.port = "465" + setting.ssl = true + setting.fromEmail += setting.mailType + } + "@sina.com" -> { + setting.host = "smtp.sina.com" + setting.port = "465" + setting.ssl = true + setting.fromEmail += setting.mailType + } + "@sina.cn" -> { + setting.host = "smtp.sina.cn" + setting.port = "465" + setting.ssl = true + setting.fromEmail += setting.mailType + } + "@139.com" -> { + setting.host = "smtp.139.com" + setting.port = "465" + setting.ssl = true + setting.fromEmail += setting.mailType + } + "@189.cn" -> { + setting.host = "smtp.189.cn" + setting.port = "465" + setting.ssl = true + setting.fromEmail += setting.mailType + } + else -> {} + } + + // 创建邮箱 + val mail = Mail().apply { + mailServerHost = setting.host.toString() + mailServerPort = setting.port.toString() + fromAddress = setting.fromEmail.toString() + password = setting.pwd.toString() + toAddress = arrayListOf(setting.toEmail.toString()) + subject = title + content = message + openSSL = setting.ssl == true + startTls = setting.startTls == true + } + + MailSender.getInstance().sendMail(mail, object : MailSender.OnMailSendListener { + override fun onError(e: Throwable) { + SendUtils.updateLogs(logId, 0, e.message.toString()) + Log.e("MailSender", e.message.toString()) + XToastUtils.error(ResUtils.getString(R.string.request_failed) + e.message.toString()) + } + + override fun onSuccess() { + SendUtils.updateLogs(logId, 2, ResUtils.getString(R.string.request_succeeded)) + XToastUtils.success(ResUtils.getString(R.string.request_succeeded)) + } + }) + + } + + fun sendMsg(setting: EmailSetting, msgInfo: MsgInfo) { + sendMsg(setting, msgInfo, null, null) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/utils/sender/FeishuUtils.kt b/app/src/main/java/com/idormy/sms/forwarder/utils/sender/FeishuUtils.kt new file mode 100644 index 00000000..a731bf7e --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/utils/sender/FeishuUtils.kt @@ -0,0 +1,193 @@ +package com.idormy.sms.forwarder.utils.sender + +import android.util.Base64 +import android.util.Log +import com.google.gson.Gson +import com.idormy.sms.forwarder.R +import com.idormy.sms.forwarder.database.entity.Rule +import com.idormy.sms.forwarder.entity.MsgInfo +import com.idormy.sms.forwarder.entity.result.FeishuResult +import com.idormy.sms.forwarder.entity.setting.FeishuSetting +import com.idormy.sms.forwarder.utils.SendUtils +import com.idormy.sms.forwarder.utils.SettingUtils +import com.idormy.sms.forwarder.utils.XToastUtils +import com.xuexiang.xhttp2.XHttp +import com.xuexiang.xhttp2.cache.model.CacheMode +import com.xuexiang.xhttp2.callback.SimpleCallBack +import com.xuexiang.xhttp2.exception.ApiException +import com.xuexiang.xui.utils.ResUtils +import java.nio.charset.StandardCharsets +import java.text.SimpleDateFormat +import java.util.* +import javax.crypto.Mac +import javax.crypto.spec.SecretKeySpec + +@Suppress("PrivatePropertyName", "UNUSED_PARAMETER") +class FeishuUtils private constructor() { + companion object { + + private val TAG: String = FeishuUtils::class.java.simpleName + private val MSG_TEMPLATE = """ +{ + "config": { + "wide_screen_mode": true + }, + "elements": [ + { + "fields": [ + { + "is_short": true, + "text": { + "content": "**时间**\n{{MSG_TIME}}", + "tag": "lark_md" + } + }, + { + "is_short": true, + "text": { + "content": "**来源**\n{{MSG_FROM}}", + "tag": "lark_md" + } + } + ], + "tag": "div" + }, + { + "tag": "div", + "text": { + "content": "{{MSG_CONTENT}}", + "tag": "lark_md" + } + }, + { + "tag": "hr" + }, + { + "elements": [ + { + "content": "[SmsForwarder](https://github.com/pppscn/SmsForwarder)", + "tag": "lark_md" + } + ], + "tag": "note" + } + ], + "header": { + "template": "turquoise", + "title": { + "content": "{{MSG_TITLE}}", + "tag": "plain_text" + } + } +} + """.trimIndent() + + fun sendMsg( + setting: FeishuSetting, + msgInfo: MsgInfo, + rule: Rule?, + logId: Long?, + ) { + val from: String = msgInfo.from + val title: String = if (rule != null) { + msgInfo.getTitleForSend(setting.titleTemplate.toString(), rule.regexReplace) + } else { + msgInfo.getTitleForSend(setting.titleTemplate.toString()) + } + val content: String = if (rule != null) { + msgInfo.getContentForSend(rule.smsTemplate, rule.regexReplace) + } else { + msgInfo.getContentForSend(SettingUtils.smsTemplate.toString()) + } + + val requestUrl = setting.webhook + Log.i(TAG, "requestUrl:$requestUrl") + + val msgMap: MutableMap = mutableMapOf() + if (setting.secret != null) { + val timestamp = System.currentTimeMillis() / 1000 + val stringToSign = "$timestamp\n" + setting.secret + Log.i(TAG, "stringToSign = $stringToSign") + + //使用HmacSHA256算法计算签名 + val mac = Mac.getInstance("HmacSHA256") + mac.init(SecretKeySpec(stringToSign.toByteArray(StandardCharsets.UTF_8), "HmacSHA256")) + val signData = mac.doFinal(byteArrayOf()) + val sign = String(Base64.encode(signData, Base64.NO_WRAP)) + + msgMap["timestamp"] = timestamp + msgMap["sign"] = sign + } + + //组装报文 + val requestMsg: String + if (setting.msgType == null || setting.msgType == "interactive") { + msgMap["msg_type"] = "interactive" + msgMap["card"] = "{{CARD_BODY}}" + requestMsg = Gson().toJson(msgMap).replace("\"{{CARD_BODY}}\"", buildMsg(title, content, from, msgInfo.date)) + } else { + msgMap["msg_type"] = "text" + val contentMap: MutableMap = mutableMapOf() + contentMap["text"] = content + msgMap["content"] = contentMap + requestMsg = Gson().toJson(msgMap) + } + Log.i(TAG, "requestMsg:$requestMsg") + + XHttp.post(requestUrl) + .upJson(requestMsg) + .keepJson(true) + .timeOut((SettingUtils.requestTimeout * 1000).toLong()) //超时时间10s + .cacheMode(CacheMode.NO_CACHE) + .retryCount(SettingUtils.requestRetryTimes) //超时重试的次数 + .retryDelay(SettingUtils.requestDelayTime) //超时重试的延迟时间 + .retryIncreaseDelay(SettingUtils.requestDelayTime) //超时重试叠加延时 + .timeStamp(true) + .execute(object : SimpleCallBack() { + + override fun onError(e: ApiException) { + SendUtils.updateLogs(logId, 0, e.displayMessage) + Log.e(TAG, e.detailMessage) + XToastUtils.error(e.displayMessage) + } + + override fun onSuccess(response: String) { + Log.i(TAG, response) + + val resp = Gson().fromJson(response, FeishuResult::class.java) + if (resp.code == 0L) { + SendUtils.updateLogs(logId, 2, response) + XToastUtils.success(ResUtils.getString(R.string.request_succeeded)) + } else { + SendUtils.updateLogs(logId, 0, response) + XToastUtils.error(ResUtils.getString(R.string.request_failed) + response) + } + } + + }) + + } + + private fun buildMsg(title: String, content: String, from: String, date: Date): String { + val msgTitle = jsonInnerStr(title) + val msgContent = jsonInnerStr(content) + val msgFrom = jsonInnerStr(from) + val msgTime = jsonInnerStr(SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault()).format(date)) + return MSG_TEMPLATE.replace("{{MSG_TITLE}}", msgTitle) + .replace("{{MSG_TIME}}", msgTime) + .replace("{{MSG_FROM}}", msgFrom) + .replace("{{MSG_CONTENT}}", msgContent) + } + + private fun jsonInnerStr(string: String?): String { + if (string == null) return "null" + + val jsonStr: String = Gson().toJson(string) + return if (jsonStr.length >= 2) jsonStr.substring(1, jsonStr.length - 1) else jsonStr + } + + fun sendMsg(setting: FeishuSetting, msgInfo: MsgInfo) { + sendMsg(setting, msgInfo, null, null) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/utils/sender/GotifyUtils.kt b/app/src/main/java/com/idormy/sms/forwarder/utils/sender/GotifyUtils.kt new file mode 100644 index 00000000..f823b210 --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/utils/sender/GotifyUtils.kt @@ -0,0 +1,85 @@ +package com.idormy.sms.forwarder.utils.sender + +import android.util.Log +import com.google.gson.Gson +import com.idormy.sms.forwarder.R +import com.idormy.sms.forwarder.database.entity.Rule +import com.idormy.sms.forwarder.entity.MsgInfo +import com.idormy.sms.forwarder.entity.result.GotifyResult +import com.idormy.sms.forwarder.entity.setting.GotifySetting +import com.idormy.sms.forwarder.utils.SendUtils +import com.idormy.sms.forwarder.utils.SettingUtils +import com.idormy.sms.forwarder.utils.XToastUtils +import com.xuexiang.xhttp2.XHttp +import com.xuexiang.xhttp2.cache.model.CacheMode +import com.xuexiang.xhttp2.callback.SimpleCallBack +import com.xuexiang.xhttp2.exception.ApiException +import com.xuexiang.xui.utils.ResUtils + +@Suppress("PrivatePropertyName", "UNUSED_PARAMETER", "unused") +class GotifyUtils { + companion object { + + private val TAG: String = GotifyUtils::class.java.simpleName + + fun sendMsg( + setting: GotifySetting, + msgInfo: MsgInfo, + rule: Rule?, + logId: Long?, + ) { + val title: String = if (rule != null) { + msgInfo.getTitleForSend(setting.title.toString(), rule.regexReplace) + } else { + msgInfo.getTitleForSend(setting.title.toString()) + } + val content: String = if (rule != null) { + msgInfo.getContentForSend(rule.smsTemplate, rule.regexReplace) + } else { + msgInfo.getContentForSend(SettingUtils.smsTemplate.toString()) + } + + val requestUrl: String = setting.webServer //推送地址 + Log.i(TAG, "requestUrl:$requestUrl") + + XHttp.post(requestUrl) + .params("title", title) + .params("message", content) + .params("priority", setting.priority) + .keepJson(true) + .timeOut((SettingUtils.requestTimeout * 1000).toLong()) //超时时间10s + .cacheMode(CacheMode.NO_CACHE) + .retryCount(SettingUtils.requestRetryTimes) //超时重试的次数 + .retryDelay(SettingUtils.requestDelayTime) //超时重试的延迟时间 + .retryIncreaseDelay(SettingUtils.requestDelayTime) //超时重试叠加延时 + .timeStamp(true) + .execute(object : SimpleCallBack() { + + override fun onError(e: ApiException) { + SendUtils.updateLogs(logId, 0, e.displayMessage) + Log.e(TAG, e.detailMessage) + XToastUtils.error(e.displayMessage) + } + + override fun onSuccess(response: String) { + Log.i(TAG, response) + + val resp = Gson().fromJson(response, GotifyResult::class.java) + if (resp?.id != null) { + SendUtils.updateLogs(logId, 2, response) + XToastUtils.success(ResUtils.getString(R.string.request_succeeded)) + } else { + SendUtils.updateLogs(logId, 0, response) + XToastUtils.error(ResUtils.getString(R.string.request_failed) + response) + } + } + + }) + + } + + fun sendMsg(setting: GotifySetting, msgInfo: MsgInfo) { + sendMsg(setting, msgInfo, null, null) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/utils/sender/PushplusUtils.kt b/app/src/main/java/com/idormy/sms/forwarder/utils/sender/PushplusUtils.kt new file mode 100644 index 00000000..6d486bdd --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/utils/sender/PushplusUtils.kt @@ -0,0 +1,109 @@ +package com.idormy.sms.forwarder.utils.sender + +import android.text.TextUtils +import android.util.Log +import com.google.gson.Gson +import com.idormy.sms.forwarder.R +import com.idormy.sms.forwarder.database.entity.Rule +import com.idormy.sms.forwarder.entity.MsgInfo +import com.idormy.sms.forwarder.entity.result.PushplusResult +import com.idormy.sms.forwarder.entity.setting.PushplusSetting +import com.idormy.sms.forwarder.utils.SendUtils +import com.idormy.sms.forwarder.utils.SettingUtils +import com.idormy.sms.forwarder.utils.XToastUtils +import com.xuexiang.xhttp2.XHttp +import com.xuexiang.xhttp2.cache.model.CacheMode +import com.xuexiang.xhttp2.callback.SimpleCallBack +import com.xuexiang.xhttp2.exception.ApiException +import com.xuexiang.xui.utils.ResUtils + + +@Suppress("PrivatePropertyName", "UNUSED_PARAMETER") +class PushplusUtils private constructor() { + companion object { + + private val TAG: String = PushplusUtils::class.java.simpleName + + fun sendMsg( + setting: PushplusSetting, + msgInfo: MsgInfo, + rule: Rule?, + logId: Long?, + ) { + val title: String = if (rule != null) { + msgInfo.getTitleForSend(setting.titleTemplate.toString(), rule.regexReplace) + } else { + msgInfo.getTitleForSend(setting.titleTemplate.toString()) + } + val content: String = if (rule != null) { + msgInfo.getContentForSend(rule.smsTemplate, rule.regexReplace) + } else { + msgInfo.getContentForSend(SettingUtils.smsTemplate.toString()) + } + + val requestUrl = "https://" + setting.website + "/send" + Log.i(TAG, "requestUrl:$requestUrl") + + val msgMap: MutableMap = mutableMapOf() + msgMap["token"] = setting.token + msgMap["content"] = content + + if (!TextUtils.isEmpty(title)) msgMap["title"] = title + if (!TextUtils.isEmpty(setting.template)) msgMap["template"] = setting.template.toString() + if (!TextUtils.isEmpty(setting.topic)) msgMap["topic"] = setting.topic.toString() + + if (setting.website == ResUtils.getString(R.string.pushplus_plus)) { + if (!TextUtils.isEmpty(setting.channel)) msgMap["channel"] = setting.channel.toString() + if (!TextUtils.isEmpty(setting.webhook)) msgMap["webhook"] = setting.webhook.toString() + if (!TextUtils.isEmpty(setting.callbackUrl)) msgMap["callbackUrl"] = setting.callbackUrl.toString() + if (!TextUtils.isEmpty(setting.validTime)) { + val validTime = setting.validTime?.toInt() + if (validTime != null && validTime > 0) { + msgMap["timestamp"] = System.currentTimeMillis() + validTime * 1000L + } + } + } + + + val requestMsg: String = Gson().toJson(msgMap) + Log.i(TAG, "requestMsg:$requestMsg") + + XHttp.post(requestUrl) + .upJson(requestMsg) + .keepJson(true) + .timeOut((SettingUtils.requestTimeout * 1000).toLong()) //超时时间10s + .cacheMode(CacheMode.NO_CACHE) + .retryCount(SettingUtils.requestRetryTimes) //超时重试的次数 + .retryDelay(SettingUtils.requestDelayTime) //超时重试的延迟时间 + .retryIncreaseDelay(SettingUtils.requestDelayTime) //超时重试叠加延时 + .timeStamp(true) + .execute(object : SimpleCallBack() { + + override fun onError(e: ApiException) { + SendUtils.updateLogs(logId, 0, e.displayMessage) + Log.e(TAG, e.detailMessage) + XToastUtils.error(e.displayMessage) + } + + override fun onSuccess(response: String) { + Log.i(TAG, response) + + val resp = Gson().fromJson(response, PushplusResult::class.java) + if (resp.code == 200L) { + SendUtils.updateLogs(logId, 2, response) + XToastUtils.success(ResUtils.getString(R.string.request_succeeded)) + } else { + SendUtils.updateLogs(logId, 0, response) + XToastUtils.error(ResUtils.getString(R.string.request_failed) + response) + } + } + + }) + + } + + fun sendMsg(setting: PushplusSetting, msgInfo: MsgInfo) { + sendMsg(setting, msgInfo, null, null) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/utils/sender/RetryInterceptor.kt b/app/src/main/java/com/idormy/sms/forwarder/utils/sender/RetryInterceptor.kt new file mode 100644 index 00000000..7887f66a --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/utils/sender/RetryInterceptor.kt @@ -0,0 +1,88 @@ +package com.idormy.sms.forwarder.utils.sender + +import android.util.Log +import okhttp3.Interceptor +import okhttp3.Request +import okhttp3.Response +import java.io.IOException +import java.io.InterruptedIOException + +class RetryInterceptor internal constructor(builder: Builder) : Interceptor { + //重试的间隔 + private val retryInterval: Long + + //更新记录ID + private val logId: Long + + //最大重试次数 + private val executionCount: Int + + @Throws(IOException::class) + override fun intercept(chain: Interceptor.Chain): Response { + var retryTimes = 0 + val request = chain.request() + var response: Response + do { + if (retryTimes > 0 && retryInterval > 0) { + val delayTime = retryTimes * retryInterval + try { + Log.w(TAG, "第 $retryTimes 次重试,休眠 $delayTime 秒") + Thread.sleep(delayTime * 1000) + } catch (e: InterruptedException) { + Thread.currentThread().interrupt() + throw InterruptedIOException(e.message) + } + } + response = doRequest(chain, request, retryTimes)!! + retryTimes++ + } while ((!response.isSuccessful) && retryTimes <= executionCount) + + return response + } + + private fun doRequest(chain: Interceptor.Chain, request: Request, retryTimes: Int): Response? { + var response: Response? = null + try { + response = chain.proceed(request) + } catch (e: Exception) { + val resp = if (retryTimes > 0) "第" + retryTimes + "次重试:" + e.message else e.message!! + //LogUtils.updateLog(logId, 1, resp); + Log.w(TAG, resp) + } + return response + } + + class Builder { + var executionCount = 3 + var retryInterval: Long = 1000 + var logId: Long = 0 + fun executionCount(executionCount: Int): Builder { + this.executionCount = executionCount + return this + } + + fun retryInterval(retryInterval: Long): Builder { + this.retryInterval = retryInterval + return this + } + + fun logId(logId: Long): Builder { + this.logId = logId + return this + } + + fun build(): RetryInterceptor { + return RetryInterceptor(this) + } + } + + companion object { + const val TAG = "RetryInterceptor" + } + + init { + executionCount = builder.executionCount + retryInterval = builder.retryInterval + logId = builder.logId + } +} \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/utils/sender/ServerchanUtils.kt b/app/src/main/java/com/idormy/sms/forwarder/utils/sender/ServerchanUtils.kt new file mode 100644 index 00000000..b451f16c --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/utils/sender/ServerchanUtils.kt @@ -0,0 +1,89 @@ +package com.idormy.sms.forwarder.utils.sender + +import android.text.TextUtils +import android.util.Log +import com.google.gson.Gson +import com.idormy.sms.forwarder.R +import com.idormy.sms.forwarder.database.entity.Rule +import com.idormy.sms.forwarder.entity.MsgInfo +import com.idormy.sms.forwarder.entity.result.ServerchanResult +import com.idormy.sms.forwarder.entity.setting.ServerchanSetting +import com.idormy.sms.forwarder.utils.SendUtils +import com.idormy.sms.forwarder.utils.SettingUtils +import com.idormy.sms.forwarder.utils.XToastUtils +import com.xuexiang.xhttp2.XHttp +import com.xuexiang.xhttp2.cache.model.CacheMode +import com.xuexiang.xhttp2.callback.SimpleCallBack +import com.xuexiang.xhttp2.exception.ApiException +import com.xuexiang.xui.utils.ResUtils + +@Suppress("PrivatePropertyName", "UNUSED_PARAMETER", "unused") +class ServerchanUtils { + companion object { + + private val TAG: String = ServerchanUtils::class.java.simpleName + + fun sendMsg( + setting: ServerchanSetting, + msgInfo: MsgInfo, + rule: Rule?, + logId: Long?, + ) { + val title: String = if (rule != null) { + msgInfo.getTitleForSend(setting.titleTemplate.toString(), rule.regexReplace) + } else { + msgInfo.getTitleForSend(setting.titleTemplate.toString()) + } + val content: String = if (rule != null) { + msgInfo.getContentForSend(rule.smsTemplate, rule.regexReplace) + } else { + msgInfo.getContentForSend(SettingUtils.smsTemplate.toString()) + } + + val requestUrl: String = String.format("https://sctapi.ftqq.com/%s.send", setting.sendKey) //推送地址 + Log.i(TAG, "requestUrl:$requestUrl") + + val request = XHttp.post(requestUrl) + .params("title", title) + .params("desp", content) + + if (!TextUtils.isEmpty(setting.channel)) request.params("channel", setting.channel) + if (!TextUtils.isEmpty(setting.openid)) request.params("group", setting.openid) + + request.keepJson(true) + .timeOut((SettingUtils.requestTimeout * 1000).toLong()) //超时时间10s + .cacheMode(CacheMode.NO_CACHE) + .retryCount(SettingUtils.requestRetryTimes) //超时重试的次数 + .retryDelay(SettingUtils.requestDelayTime) //超时重试的延迟时间 + .retryIncreaseDelay(SettingUtils.requestDelayTime) //超时重试叠加延时 + .timeStamp(true) + .execute(object : SimpleCallBack() { + + override fun onError(e: ApiException) { + SendUtils.updateLogs(logId, 0, e.displayMessage) + Log.e(TAG, e.detailMessage) + XToastUtils.error(e.displayMessage) + } + + override fun onSuccess(response: String) { + Log.i(TAG, response) + + val resp = Gson().fromJson(response, ServerchanResult::class.java) + if (resp?.code == 0L) { + SendUtils.updateLogs(logId, 2, response) + XToastUtils.success(ResUtils.getString(R.string.request_succeeded)) + } else { + SendUtils.updateLogs(logId, 0, response) + XToastUtils.error(ResUtils.getString(R.string.request_failed) + response) + } + } + + }) + + } + + fun sendMsg(setting: ServerchanSetting, msgInfo: MsgInfo) { + sendMsg(setting, msgInfo, null, null) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/utils/sender/SmsUtils.kt b/app/src/main/java/com/idormy/sms/forwarder/utils/sender/SmsUtils.kt new file mode 100644 index 00000000..7639aaa1 --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/utils/sender/SmsUtils.kt @@ -0,0 +1,73 @@ +package com.idormy.sms.forwarder.utils.sender + +import android.Manifest +import android.content.pm.PackageManager +import android.util.Log +import androidx.core.app.ActivityCompat +import com.idormy.sms.forwarder.App +import com.idormy.sms.forwarder.R +import com.idormy.sms.forwarder.database.entity.Rule +import com.idormy.sms.forwarder.entity.MsgInfo +import com.idormy.sms.forwarder.entity.setting.SmsSetting +import com.idormy.sms.forwarder.utils.PhoneUtils +import com.idormy.sms.forwarder.utils.SendUtils +import com.idormy.sms.forwarder.utils.SettingUtils +import com.idormy.sms.forwarder.utils.XToastUtils +import com.xuexiang.xui.utils.ResUtils +import com.xuexiang.xutil.XUtil +import com.xuexiang.xutil.net.NetworkUtils + +@Suppress("PrivatePropertyName", "UNUSED_PARAMETER", "unused") +class SmsUtils { + companion object { + + private val TAG: String = SmsUtils::class.java.simpleName + + fun sendMsg( + setting: SmsSetting, + msgInfo: MsgInfo, + rule: Rule?, + logId: Long?, + ) { + //仅当无网络时启用 && 判断是否真实有网络 + if (setting.onlyNoNetwork == true && NetworkUtils.isHaveInternet() && NetworkUtils.isAvailableByPing()) { + return + } + + val content: String = if (rule != null) { + msgInfo.getContentForSend(rule.smsTemplate, rule.regexReplace) + } else { + msgInfo.getContentForSend(SettingUtils.smsTemplate.toString()) + } + + //【注意】判断卡槽配置:0=原进原出、1=卡槽1、2=卡槽2 + val simSlotIndex = if (setting.simSlot == 0) msgInfo.simSlot else setting.simSlot - 1 + + //获取卡槽信息 + if (App.SimInfoList.isEmpty()) { + App.SimInfoList = PhoneUtils.getSimMultiInfo() + } + Log.d(TAG, App.SimInfoList.toString()) + + //TODO:取不到卡槽信息时,采用默认卡槽发送 + val mSubscriptionId: Int = App.SimInfoList[simSlotIndex]?.mSubscriptionId ?: -1 + + if (ActivityCompat.checkSelfPermission(XUtil.getContext(), Manifest.permission.SEND_SMS) != PackageManager.PERMISSION_GRANTED) { + XToastUtils.error(ResUtils.getString(R.string.no_sms_sending_permission)) + return + } + val res: String? = PhoneUtils.sendSms(mSubscriptionId, setting.mobiles, content) + if (res == null) { + SendUtils.updateLogs(logId, 2, ResUtils.getString(R.string.request_succeeded)) + XToastUtils.success(ResUtils.getString(R.string.request_succeeded)) + } else { + SendUtils.updateLogs(logId, 0, res) + XToastUtils.error(ResUtils.getString(R.string.request_failed) + res) + } + } + + fun sendMsg(setting: SmsSetting, msgInfo: MsgInfo) { + sendMsg(setting, msgInfo, null, null) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/utils/sender/TelegramUtils.kt b/app/src/main/java/com/idormy/sms/forwarder/utils/sender/TelegramUtils.kt new file mode 100644 index 00000000..ff1688c1 --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/utils/sender/TelegramUtils.kt @@ -0,0 +1,132 @@ +package com.idormy.sms.forwarder.utils.sender + +import android.text.TextUtils +import android.util.Log +import com.google.gson.Gson +import com.idormy.sms.forwarder.R +import com.idormy.sms.forwarder.database.entity.Rule +import com.idormy.sms.forwarder.entity.MsgInfo +import com.idormy.sms.forwarder.entity.result.TelegramResult +import com.idormy.sms.forwarder.entity.setting.TelegramSetting +import com.idormy.sms.forwarder.utils.SendUtils +import com.idormy.sms.forwarder.utils.SettingUtils +import com.idormy.sms.forwarder.utils.XToastUtils +import com.xuexiang.xui.utils.ResUtils +import okhttp3.* +import java.io.IOException +import java.net.InetSocketAddress +import java.net.Proxy +import java.net.URLEncoder +import java.util.concurrent.TimeUnit + +//TODO:待迁移!!! +@Suppress("PrivatePropertyName", "UNUSED_PARAMETER", "unused") +class TelegramUtils { + companion object { + + private val TAG: String = TelegramUtils::class.java.simpleName + + fun sendMsg( + setting: TelegramSetting, + msgInfo: MsgInfo, + rule: Rule?, + logId: Long?, + ) { + val content: String = if (rule != null) { + msgInfo.getContentForSend(rule.smsTemplate, rule.regexReplace) + } else { + msgInfo.getContentForSend(SettingUtils.smsTemplate.toString()) + } + + val requestUrl = if (setting.apiToken.startsWith("http")) { + setting.apiToken + } else { + "https://api.telegram.org/bot" + setting.apiToken + "/sendMessage" + } + Log.i(TAG, "requestUrl:$requestUrl") + + val clientBuilder = OkHttpClient.Builder() + //设置代理 + if ((setting.proxyType == Proxy.Type.HTTP || setting.proxyType == Proxy.Type.SOCKS) && !TextUtils.isEmpty(setting.proxyHost) && !TextUtils.isEmpty(setting.proxyPort)) { + //代理服务器的IP和端口号 + clientBuilder.proxy(Proxy(setting.proxyType, setting.proxyPort?.let { InetSocketAddress(setting.proxyHost, it.toInt()) })) + + //代理的鉴权账号密码 + if (setting.proxyAuthenticator == true && (!TextUtils.isEmpty(setting.proxyUsername) || !TextUtils.isEmpty(setting.proxyPassword))) { + clientBuilder.proxyAuthenticator { _: Route?, response: Response -> + //设置代理服务器账号密码 + val credential = Credentials.basic(setting.proxyUsername.toString(), setting.proxyPassword.toString()) + response.request().newBuilder() + .header("Proxy-Authorization", credential) + .build() + } + } + } + + //设置重试拦截器 + val retryTimes: Int = SettingUtils.requestRetryTimes + if (retryTimes > 0) { + val delayTime: Long = SettingUtils.requestDelayTime.toLong() + val retryInterceptor: RetryInterceptor = RetryInterceptor.Builder().executionCount(retryTimes).retryInterval(delayTime).logId(0).build() + clientBuilder.addInterceptor(retryInterceptor) + } + + + //设置读取超时时间 + val client = clientBuilder + .readTimeout(SettingUtils.requestTimeout.toLong(), TimeUnit.SECONDS) + .writeTimeout(SettingUtils.requestTimeout.toLong(), TimeUnit.SECONDS) + .connectTimeout(SettingUtils.requestTimeout.toLong(), TimeUnit.SECONDS) + .build() + + val request: Request + if (setting.method != null && setting.method == "GET") { + request = Request.Builder() + .url(requestUrl + "?chat_id=" + setting.chatId + "&text=" + URLEncoder.encode(content, "UTF-8")) + .build() + } else { + val bodyMap: MutableMap = mutableMapOf() + bodyMap["chat_id"] = setting.chatId + bodyMap["text"] = content + bodyMap["parse_mode"] = "HTML" + val requestMsg: String = Gson().toJson(bodyMap) + Log.i(TAG, "requestMsg:$requestMsg") + val requestBody = RequestBody.create(MediaType.parse("application/json;charset=utf-8"), requestMsg) + request = Request.Builder() + .url(requestUrl) + .addHeader("Content-Type", "application/json; charset=utf-8") + .post(requestBody) + .build() + } + + client.newCall(request).enqueue(object : Callback { + + override fun onFailure(call: Call, e: IOException) { + SendUtils.updateLogs(logId, 0, e.message.toString()) + e.printStackTrace() + //XToastUtils.error("发送失败:" + e.message) + } + + @Throws(IOException::class) + override fun onResponse(call: Call, response: Response) { + val responseStr = response.body()?.string() + Log.d(TAG, "Response:" + response.code() + "," + responseStr) + + val resp = Gson().fromJson(responseStr, TelegramResult::class.java) + if (resp.ok == true) { + SendUtils.updateLogs(logId, 2, responseStr.toString()) + XToastUtils.success(ResUtils.getString(R.string.request_succeeded)) + } else { + SendUtils.updateLogs(logId, 0, responseStr.toString()) + XToastUtils.error(ResUtils.getString(R.string.request_failed) + response) + } + } + }) + + } + + fun sendMsg(setting: TelegramSetting, msgInfo: MsgInfo) { + sendMsg(setting, msgInfo, null, null) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/utils/sender/WebhookUtils.kt b/app/src/main/java/com/idormy/sms/forwarder/utils/sender/WebhookUtils.kt new file mode 100644 index 00000000..00fa5cba --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/utils/sender/WebhookUtils.kt @@ -0,0 +1,200 @@ +package com.idormy.sms.forwarder.utils.sender + +import android.annotation.SuppressLint +import android.text.TextUtils +import android.util.Base64 +import android.util.Log +import com.google.gson.Gson +import com.idormy.sms.forwarder.R +import com.idormy.sms.forwarder.database.entity.Rule +import com.idormy.sms.forwarder.entity.MsgInfo +import com.idormy.sms.forwarder.entity.setting.WebhookSetting +import com.idormy.sms.forwarder.utils.CertUtils +import com.idormy.sms.forwarder.utils.SendUtils +import com.idormy.sms.forwarder.utils.SettingUtils +import com.idormy.sms.forwarder.utils.XToastUtils +import com.xuexiang.xui.utils.ResUtils +import com.xuexiang.xutil.app.AppUtils +import okhttp3.* +import java.io.IOException +import java.net.URLEncoder +import java.nio.charset.StandardCharsets +import java.text.SimpleDateFormat +import java.util.* +import java.util.concurrent.TimeUnit +import javax.crypto.Mac +import javax.crypto.spec.SecretKeySpec + +@Suppress("PrivatePropertyName", "UNUSED_PARAMETER", "unused") +class WebhookUtils { + companion object { + + private val TAG: String = WebhookUtils::class.java.simpleName + + fun sendMsg( + setting: WebhookSetting, + msgInfo: MsgInfo, + rule: Rule?, + logId: Long?, + ) { + val from: String = msgInfo.from + val content: String = if (rule != null) { + msgInfo.getContentForSend(rule.smsTemplate, rule.regexReplace) + } else { + msgInfo.getContentForSend(SettingUtils.smsTemplate.toString()) + } + + var webServer: String = setting.webServer //推送地址 + Log.i(TAG, "requestUrl:$webServer") + + val timestamp = System.currentTimeMillis() + val orgContent: String = msgInfo.content + val deviceMark: String = SettingUtils.extraDeviceMark ?: "" + val appVersion: String = AppUtils.getAppVersionName() + val simInfo: String = msgInfo.simInfo + @SuppressLint("SimpleDateFormat") val receiveTime = SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(Date()) //smsVo.getDate() + + var sign = "" + if (!TextUtils.isEmpty(setting.secret)) { + val stringToSign = "$timestamp\n" + setting.secret + val mac = Mac.getInstance("HmacSHA256") + mac.init(SecretKeySpec(setting.secret?.toByteArray(StandardCharsets.UTF_8), "HmacSHA256")) + val signData = mac.doFinal(stringToSign.toByteArray(StandardCharsets.UTF_8)) + sign = URLEncoder.encode(String(Base64.encode(signData, Base64.NO_WRAP)), "UTF-8") + } + + var webParams = setting.webParams?.trim() + val requestBuilder: Request.Builder + if (setting.method == "GET" && TextUtils.isEmpty(webParams)) { + setting.webServer += (if (setting.webServer.contains("?")) "&" else "?") + "from=" + URLEncoder.encode(from, "UTF-8") + webServer += "&content=" + URLEncoder.encode(content, "UTF-8") + if (!TextUtils.isEmpty(setting.secret)) { + webServer += "×tamp=$timestamp" + webServer += "&sign=$sign" + } + Log.d(TAG, "method = GET, Url = $webServer") + requestBuilder = Request.Builder().url(webServer).get() + } else if (setting.method == "GET" && !TextUtils.isEmpty(setting.webParams)) { + webParams = webParams.toString().replace("[from]", URLEncoder.encode(from, "UTF-8")) + .replace("[content]", URLEncoder.encode(content, "UTF-8")) + .replace("[msg]", URLEncoder.encode(content, "UTF-8")) + .replace("[org_content]", URLEncoder.encode(orgContent, "UTF-8")) + .replace("[device_mark]", URLEncoder.encode(deviceMark, "UTF-8")) + .replace("[app_version]", URLEncoder.encode(appVersion, "UTF-8")) + .replace("[title]", URLEncoder.encode(simInfo, "UTF-8")) + .replace("[card_slot]", URLEncoder.encode(simInfo, "UTF-8")) + .replace("[receive_time]", URLEncoder.encode(receiveTime, "UTF-8")) + .replace("\n", "%0A") + if (!TextUtils.isEmpty(setting.secret)) { + webParams = webParams.replace("[timestamp]", timestamp.toString()) + .replace("[sign]", URLEncoder.encode(sign, "UTF-8")) + } + webServer += (if (webServer.contains("?")) "&" else "?") + webParams + Log.d(TAG, "method = GET, Url = $webServer") + requestBuilder = Request.Builder().url(webServer).get() + } else if (webParams != null && webParams.contains("[msg]")) { + val bodyMsg: String + var contentType = "application/x-www-form-urlencoded" + if (webParams.startsWith("{")) { + contentType = "application/json;charset=utf-8" + bodyMsg = webParams.replace("[from]", from) + .replace("[content]", escapeJson(content)) + .replace("[msg]", escapeJson(content)) + .replace("[org_content]", escapeJson(orgContent)) + .replace("[device_mark]", escapeJson(deviceMark)) + .replace("[app_version]", appVersion) + .replace("[title]", escapeJson(simInfo)) + .replace("[card_slot]", escapeJson(simInfo)) + .replace("[receive_time]", receiveTime) + } else { + bodyMsg = webParams.replace("[from]", URLEncoder.encode(from, "UTF-8")) + .replace("[content]", URLEncoder.encode(content, "UTF-8")) + .replace("[msg]", URLEncoder.encode(content, "UTF-8")) + .replace("[org_content]", URLEncoder.encode(orgContent, "UTF-8")) + .replace("[device_mark]", URLEncoder.encode(deviceMark, "UTF-8")) + .replace("[app_version]", URLEncoder.encode(appVersion, "UTF-8")) + .replace("[title]", URLEncoder.encode(simInfo, "UTF-8")) + .replace("[card_slot]", URLEncoder.encode(simInfo, "UTF-8")) + .replace("[receive_time]", URLEncoder.encode(receiveTime, "UTF-8")) + } + val body = RequestBody.create(MediaType.parse(contentType), bodyMsg) + requestBuilder = Request.Builder() + .url(webServer) + .addHeader("Content-Type", contentType) + .method("POST", body) + Log.d(TAG, "method = POST webParams, Body = $bodyMsg") + } else { + val builder = MultipartBody.Builder().setType(MultipartBody.FORM) + .addFormDataPart("from", from) + .addFormDataPart("content", content) + if (!TextUtils.isEmpty(setting.secret)) { + builder.addFormDataPart("timestamp", timestamp.toString()) + builder.addFormDataPart("sign", sign) + } + val body: RequestBody = builder.build() + Log.d(TAG, "method = POST, Body = $body") + requestBuilder = Request.Builder().url(webServer).method("POST", body) + } + + for ((key, value) in setting.headers?.entries!!) { + requestBuilder.addHeader(key, value) + } + + val clientBuilder = OkHttpClient.Builder() + + //设置重试拦截器 + val retryTimes: Int = SettingUtils.requestRetryTimes + if (retryTimes > 0) { + val delayTime: Long = SettingUtils.requestDelayTime.toLong() + val retryInterceptor: RetryInterceptor = RetryInterceptor.Builder().executionCount(retryTimes).retryInterval(delayTime).logId(0).build() + clientBuilder.addInterceptor(retryInterceptor) + } + + //忽略https证书 + CertUtils.x509TrustManager?.let { clientBuilder.sslSocketFactory(CertUtils.sSLSocketFactory, it).hostnameVerifier(CertUtils.hostnameVerifier) } + + //设置读取超时时间 + val client = clientBuilder + .readTimeout(SettingUtils.requestTimeout.toLong(), TimeUnit.SECONDS) + .writeTimeout(SettingUtils.requestTimeout.toLong(), TimeUnit.SECONDS) + .connectTimeout(SettingUtils.requestTimeout.toLong(), TimeUnit.SECONDS) + .build() + + client.newCall(requestBuilder.build()).enqueue(object : Callback { + override fun onFailure(call: Call, e: IOException) { + SendUtils.updateLogs(logId, 0, e.message.toString()) + //LogUtils.updateLog(logId, 0, e.message) + XToastUtils.error(ResUtils.getString(R.string.request_failed) + e.message) + } + + @Throws(IOException::class) + override fun onResponse(call: Call, response: Response) { + val responseStr = response.body().toString() + Log.d(TAG, "Response:" + response.code() + "," + responseStr) + + + //返回http状态200即为成功 + if (200 == response.code()) { + SendUtils.updateLogs(logId, 2, responseStr) + XToastUtils.success(ResUtils.getString(R.string.request_succeeded)) + } else { + SendUtils.updateLogs(logId, 0, responseStr) + XToastUtils.error(ResUtils.getString(R.string.request_failed) + response) + } + } + }) + + } + + //JSON需要转义的字符 + private fun escapeJson(str: String?): String { + if (str == null) return "null" + val jsonStr: String = Gson().toJson(str) + return if (jsonStr.length >= 2) jsonStr.substring(1, jsonStr.length - 1) else jsonStr + } + + fun sendMsg(setting: WebhookSetting, msgInfo: MsgInfo) { + sendMsg(setting, msgInfo, null, null) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/utils/sender/WeworkAgentUtils.kt b/app/src/main/java/com/idormy/sms/forwarder/utils/sender/WeworkAgentUtils.kt new file mode 100644 index 00000000..a89f5ced --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/utils/sender/WeworkAgentUtils.kt @@ -0,0 +1,139 @@ +package com.idormy.sms.forwarder.utils.sender + +import android.text.TextUtils +import android.util.Log +import com.google.gson.Gson +import com.idormy.sms.forwarder.R +import com.idormy.sms.forwarder.database.entity.Rule +import com.idormy.sms.forwarder.entity.MsgInfo +import com.idormy.sms.forwarder.entity.result.DingtalkResult +import com.idormy.sms.forwarder.entity.result.WeworkAgentResult +import com.idormy.sms.forwarder.entity.setting.WeworkAgentSetting +import com.idormy.sms.forwarder.utils.MMKVUtils +import com.idormy.sms.forwarder.utils.SendUtils +import com.idormy.sms.forwarder.utils.SettingUtils +import com.idormy.sms.forwarder.utils.XToastUtils +import com.xuexiang.xhttp2.XHttp +import com.xuexiang.xhttp2.cache.model.CacheMode +import com.xuexiang.xhttp2.callback.SimpleCallBack +import com.xuexiang.xhttp2.exception.ApiException +import com.xuexiang.xui.utils.ResUtils + +@Suppress("PrivatePropertyName", "UNUSED_PARAMETER") +class WeworkAgentUtils private constructor() { + companion object { + + private val TAG: String = WeworkAgentUtils::class.java.simpleName + + fun sendMsg( + setting: WeworkAgentSetting, + msgInfo: MsgInfo, + rule: Rule?, + logId: Long?, + ) { + + val accessToken: String? = MMKVUtils.getString("access_token_" + setting.agentID, "") + val expiresIn: Long = MMKVUtils.getLong("expires_in_" + setting.agentID, 0L) + if (!TextUtils.isEmpty(accessToken) && expiresIn > System.currentTimeMillis()) { + return sendTextMsg(setting, msgInfo, rule, logId) + } + + var getTokenUrl = "https://qyapi.weixin.qq.com/cgi-bin/gettoken?" + getTokenUrl += "corpid=" + setting.corpID + getTokenUrl += "&corpsecret=" + setting.secret + Log.d(TAG, "getTokenUrl:$getTokenUrl") + + XHttp.get(getTokenUrl) + .keepJson(true) + .timeOut((SettingUtils.requestTimeout * 1000).toLong()) //超时时间10s + .cacheMode(CacheMode.NO_CACHE) + .timeStamp(true) + .execute(object : SimpleCallBack() { + + override fun onError(e: ApiException) { + SendUtils.updateLogs(logId, 0, e.displayMessage) + Log.e(TAG, e.detailMessage) + XToastUtils.error(e.displayMessage) + } + + override fun onSuccess(response: String) { + Log.i(TAG, response) + + val resp = Gson().fromJson(response, WeworkAgentResult::class.java) + if (resp.errcode == 0L) { + MMKVUtils.put("access_token_" + setting.agentID, resp.access_token) + MMKVUtils.put("expires_in_" + setting.agentID, System.currentTimeMillis() + ((resp.expires_in ?: 7200) - 120) * 1000L) //提前2分钟过期 + sendTextMsg(setting, msgInfo, rule, logId) + } else { + XToastUtils.error("请求失败:$response") + } + } + + }) + + } + + //发送文本消息 + private fun sendTextMsg( + setting: WeworkAgentSetting, + msgInfo: MsgInfo, + rule: Rule?, + logId: Long?, + ) { + val content: String = if (rule != null) { + msgInfo.getContentForSend(rule.smsTemplate, rule.regexReplace) + } else { + msgInfo.getContentForSend(SettingUtils.smsTemplate.toString()) + } + + val textMsgMap: MutableMap = mutableMapOf() + textMsgMap["touser"] = setting.toUser.toString() + textMsgMap["msgtype"] = "text" + textMsgMap["agentid"] = setting.agentID + val textText: MutableMap = mutableMapOf() + textText["content"] = content + textMsgMap["text"] = textText + val requestUrl = "https://qyapi.weixin.qq.com/cgi-bin/message/send?access_token=" + MMKVUtils.getString("access_token_" + setting.agentID, "") + Log.i(TAG, "requestUrl:$requestUrl") + val requestMsg: String = Gson().toJson(textMsgMap) + Log.i(TAG, "requestMsg:$requestMsg") + + XHttp.post(requestUrl) + .upJson(requestMsg) + .keepJson(true) + .timeOut((SettingUtils.requestTimeout * 1000).toLong()) //超时时间10s + .cacheMode(CacheMode.NO_CACHE) + .retryCount(SettingUtils.requestRetryTimes) //超时重试的次数 + .retryDelay(SettingUtils.requestDelayTime) //超时重试的延迟时间 + .retryIncreaseDelay(SettingUtils.requestDelayTime) //超时重试叠加延时 + .timeStamp(true) + .execute(object : SimpleCallBack() { + + override fun onError(e: ApiException) { + SendUtils.updateLogs(logId, 0, e.displayMessage) + Log.e(TAG, e.detailMessage) + XToastUtils.error(e.displayMessage) + } + + override fun onSuccess(response: String) { + Log.i(TAG, response) + + val resp = Gson().fromJson(response, DingtalkResult::class.java) + if (resp.errcode == 0L) { + SendUtils.updateLogs(logId, 2, response) + XToastUtils.success(ResUtils.getString(R.string.request_succeeded)) + } else { + SendUtils.updateLogs(logId, 0, response) + XToastUtils.error(ResUtils.getString(R.string.request_failed) + response) + } + } + + }) + } + + fun sendMsg(setting: WeworkAgentSetting, msgInfo: MsgInfo) { + sendMsg(setting, msgInfo, null, null) + } + + } +} \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/utils/sender/WeworkRobotUtils.kt b/app/src/main/java/com/idormy/sms/forwarder/utils/sender/WeworkRobotUtils.kt new file mode 100644 index 00000000..72b1c18f --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/utils/sender/WeworkRobotUtils.kt @@ -0,0 +1,88 @@ +package com.idormy.sms.forwarder.utils.sender + +import android.util.Log +import com.google.gson.Gson +import com.idormy.sms.forwarder.R +import com.idormy.sms.forwarder.database.entity.Rule +import com.idormy.sms.forwarder.entity.MsgInfo +import com.idormy.sms.forwarder.entity.result.WeworkRobotResult +import com.idormy.sms.forwarder.entity.setting.WeworkRobotSetting +import com.idormy.sms.forwarder.utils.SendUtils +import com.idormy.sms.forwarder.utils.SettingUtils +import com.idormy.sms.forwarder.utils.XToastUtils +import com.xuexiang.xhttp2.XHttp +import com.xuexiang.xhttp2.cache.model.CacheMode +import com.xuexiang.xhttp2.callback.SimpleCallBack +import com.xuexiang.xhttp2.exception.ApiException +import com.xuexiang.xui.utils.ResUtils + +@Suppress("PrivatePropertyName", "UNUSED_PARAMETER") +class WeworkRobotUtils private constructor() { + companion object { + + private val TAG: String = WeworkRobotUtils::class.java.simpleName + + fun sendMsg( + setting: WeworkRobotSetting, + msgInfo: MsgInfo, + rule: Rule?, + logId: Long?, + ) { + val content: String = if (rule != null) { + msgInfo.getContentForSend(rule.smsTemplate, rule.regexReplace) + } else { + msgInfo.getContentForSend(SettingUtils.smsTemplate.toString()) + } + + val requestUrl = setting.webHook + Log.i(TAG, "requestUrl:$requestUrl") + + val msgMap: MutableMap = mutableMapOf() + msgMap["msgtype"] = "text" + + val textText: MutableMap = mutableMapOf() + textText["content"] = content + msgMap["text"] = textText + + val requestMsg: String = Gson().toJson(msgMap) + Log.i(TAG, "requestMsg:$requestMsg") + + XHttp.post(requestUrl) + .upJson(requestMsg) + .keepJson(true) + .timeOut((SettingUtils.requestTimeout * 1000).toLong()) //超时时间10s + .cacheMode(CacheMode.NO_CACHE) + .retryCount(SettingUtils.requestRetryTimes) //超时重试的次数 + .retryDelay(SettingUtils.requestDelayTime) //超时重试的延迟时间 + .retryIncreaseDelay(SettingUtils.requestDelayTime) //超时重试叠加延时 + .timeStamp(true) + .execute(object : SimpleCallBack() { + + override fun onError(e: ApiException) { + SendUtils.updateLogs(logId, 0, e.displayMessage) + Log.e(TAG, e.detailMessage) + XToastUtils.error(e.displayMessage) + } + + override fun onSuccess(response: String) { + Log.i(TAG, response) + + val resp = Gson().fromJson(response, WeworkRobotResult::class.java) + if (resp.errcode == 0L) { + SendUtils.updateLogs(logId, 2, response) + XToastUtils.success(ResUtils.getString(R.string.request_succeeded)) + } else { + SendUtils.updateLogs(logId, 0, response) + XToastUtils.error(ResUtils.getString(R.string.request_failed) + response) + } + } + + }) + + } + + fun sendMsg(setting: WeworkRobotSetting, msgInfo: MsgInfo) { + sendMsg(setting, msgInfo, null, null) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/utils/service/JsonSerializationService.kt b/app/src/main/java/com/idormy/sms/forwarder/utils/service/JsonSerializationService.kt new file mode 100644 index 00000000..42fabe2b --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/utils/service/JsonSerializationService.kt @@ -0,0 +1,42 @@ +package com.idormy.sms.forwarder.utils.service + +import android.content.Context +import com.xuexiang.xrouter.annotation.Router +import com.xuexiang.xrouter.facade.service.SerializationService +import com.xuexiang.xutil.net.JsonUtil +import java.lang.reflect.Type + +/** + * @author XUE + * @since 2019/3/27 16:39 + */ +@Router(path = "/service/json") +class JsonSerializationService : SerializationService { + /** + * 对象序列化为json + * + * @param instance obj + * @return json string + */ + override fun object2Json(instance: Any): String { + return JsonUtil.toJson(instance) + } + + /** + * json反序列化为对象 + * + * @param input json string + * @param clazz object type + * @return instance of object + */ + override fun parseObject(input: String, clazz: Type): T { + return JsonUtil.fromJson(input, clazz) + } + + /** + * 进程初始化的方法 + * + * @param context 上下文 + */ + override fun init(context: Context) {} +} \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/utils/tinker/ShareReflectUtil.kt b/app/src/main/java/com/idormy/sms/forwarder/utils/tinker/ShareReflectUtil.kt new file mode 100644 index 00000000..a1bc3e17 --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/utils/tinker/ShareReflectUtil.kt @@ -0,0 +1,240 @@ +package com.idormy.sms.forwarder.utils.tinker + +import android.annotation.SuppressLint +import android.content.Context +import java.lang.reflect.Constructor +import java.lang.reflect.Field +import java.lang.reflect.Method + +@Suppress("NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS", "UNCHECKED_CAST", "SENSELESS_COMPARISON", "unused") +object ShareReflectUtil { + /** + * Locates a given field anywhere in the class inheritance hierarchy. + * + * @param instance an object to search the field into. + * @param name field name + * @return a field object + * @throws NoSuchFieldException if the field cannot be located + */ + @Throws(NoSuchFieldException::class) + fun findField(instance: Any, name: String): Field { + var clazz: Class<*>? = instance.javaClass + while (clazz != null) { + try { + val field = clazz.getDeclaredField(name) + if (!field.isAccessible) { + field.isAccessible = true + } + return field + } catch (e: NoSuchFieldException) { + // ignore and search next + } + clazz = clazz.superclass + } + throw NoSuchFieldException("Field " + name + " not found in " + instance.javaClass) + } + + @Throws(NoSuchFieldException::class) + fun findField(originClazz: Class<*>, name: String): Field { + var clazz: Class<*>? = originClazz + while (clazz != null) { + try { + val field = clazz.getDeclaredField(name) + if (!field.isAccessible) { + field.isAccessible = true + } + return field + } catch (e: NoSuchFieldException) { + // ignore and search next + } + clazz = clazz.superclass + } + throw NoSuchFieldException("Field $name not found in $originClazz") + } + + /** + * Locates a given method anywhere in the class inheritance hierarchy. + * + * @param instance an object to search the method into. + * @param name method name + * @param parameterTypes method parameter types + * @return a method object + * @throws NoSuchMethodException if the method cannot be located + */ + @Throws(NoSuchMethodException::class) + fun findMethod(instance: Any, name: String, vararg parameterTypes: Class<*>?): Method { + var clazz: Class<*>? = instance.javaClass + while (clazz != null) { + try { + val method = clazz.getDeclaredMethod(name, *parameterTypes) + if (!method.isAccessible) { + method.isAccessible = true + } + return method + } catch (e: NoSuchMethodException) { + // ignore and search next + } + clazz = clazz.superclass + } + throw NoSuchMethodException( + "Method " + + name + + " with parameters " + + listOf(*parameterTypes) + + " not found in " + instance.javaClass + ) + } + + /** + * Locates a given method anywhere in the class inheritance hierarchy. + * + * @param clazz a class to search the method into. + * @param name method name + * @param parameterTypes method parameter types + * @return a method object + * @throws NoSuchMethodException if the method cannot be located + */ + @Throws(NoSuchMethodException::class) + fun findMethod(clazz: Class<*>?, name: String, vararg parameterTypes: Class<*>?): Method { + var tClazz = clazz + while (tClazz != null) { + try { + val method = tClazz.getDeclaredMethod(name, *parameterTypes) + if (!method.isAccessible) { + method.isAccessible = true + } + return method + } catch (e: NoSuchMethodException) { + // ignore and search next + } + tClazz = tClazz.superclass + } + throw NoSuchMethodException( + "Method " + + name + + " with parameters " + + listOf(*parameterTypes) + + " not found in " + tClazz + ) + } + + /** + * Locates a given constructor anywhere in the class inheritance hierarchy. + * + * @param instance an object to search the constructor into. + * @param parameterTypes constructor parameter types + * @return a constructor object + * @throws NoSuchMethodException if the constructor cannot be located + */ + @Throws(NoSuchMethodException::class) + fun findConstructor(instance: Any, vararg parameterTypes: Class<*>?): Constructor<*> { + var clazz: Class<*>? = instance.javaClass + while (clazz != null) { + try { + val constructor = clazz.getDeclaredConstructor(*parameterTypes) + if (!constructor.isAccessible) { + constructor.isAccessible = true + } + return constructor + } catch (e: NoSuchMethodException) { + // ignore and search next + } + clazz = clazz.superclass + } + throw NoSuchMethodException( + "Constructor" + + " with parameters " + + listOf(*parameterTypes) + + " not found in " + instance.javaClass + ) + } + + /** + * Replace the value of a field containing a non null array, by a new array containing the + * elements of the original array plus the elements of extraElements. + * + * @param instance the instance whose field is to be modified. + * @param fieldName the field to modify. + * @param extraElements elements to append at the end of the array. + */ + @Throws(NoSuchFieldException::class, IllegalArgumentException::class, IllegalAccessException::class) + fun expandFieldArray(instance: Any, fieldName: String, extraElements: Array) { + val jlrField = findField(instance, fieldName) + val original = jlrField[instance] as Array + val combined = java.lang.reflect.Array.newInstance(original.javaClass.componentType, original.size + extraElements.size) as Array + + // NOTE: changed to copy extraElements first, for patch load first + System.arraycopy(extraElements, 0, combined, 0, extraElements.size) + System.arraycopy(original, 0, combined, extraElements.size, original.size) + jlrField[instance] = combined + } + + /** + * Replace the value of a field containing a non null array, by a new array containing the + * elements of the original array plus the elements of extraElements. + * + * @param instance the instance whose field is to be modified. + * @param fieldName the field to modify. + */ + @Throws(NoSuchFieldException::class, IllegalArgumentException::class, IllegalAccessException::class) + fun reduceFieldArray(instance: Any, fieldName: String, reduceSize: Int) { + if (reduceSize <= 0) { + return + } + val jlrField = findField(instance, fieldName) + val original = jlrField[instance] as Array + val finalLength = original.size - reduceSize + if (finalLength <= 0) { + return + } + val combined = java.lang.reflect.Array.newInstance(original.javaClass.componentType, finalLength) as Array + System.arraycopy(original, reduceSize, combined, 0, finalLength) + jlrField[instance] = combined + } + + @SuppressLint("PrivateApi") + fun getActivityThread( + context: Context?, + activityThread: Class<*>?, + ): Any? { + var tActivityThread = activityThread + return try { + if (tActivityThread == null) { + tActivityThread = Class.forName("android.app.ActivityThread") + } + val m = tActivityThread!!.getMethod("currentActivityThread") + m.isAccessible = true + var currentActivityThread = m.invoke(null) + if (currentActivityThread == null && context != null) { + // In older versions of Android (prior to frameworks/base 66a017b63461a22842) + // the currentActivityThread was built on thread locals, so we'll need to try + // even harder + val mLoadedApk = context.javaClass.getField("mLoadedApk") + mLoadedApk.isAccessible = true + val apk = mLoadedApk[context] + val mActivityThreadField = apk.javaClass.getDeclaredField("mActivityThread") + mActivityThreadField.isAccessible = true + currentActivityThread = mActivityThreadField[apk] + } + currentActivityThread + } catch (ignore: Throwable) { + null + } + } + + /** + * Handy method for fetching hidden integer constant value in system classes. + * + * @param clazz + * @param fieldName + * @return + */ + fun getValueOfStaticIntField(clazz: Class<*>, fieldName: String, defVal: Int): Int { + return try { + val field = findField(clazz, fieldName) + field.getInt(null) + } catch (thr: Throwable) { + defVal + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/utils/tinker/TinkerLoadLibrary.kt b/app/src/main/java/com/idormy/sms/forwarder/utils/tinker/TinkerLoadLibrary.kt new file mode 100644 index 00000000..77db9157 --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/utils/tinker/TinkerLoadLibrary.kt @@ -0,0 +1,196 @@ +/* + * Tencent is pleased to support the open source community by making Tinker available. + * + * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.idormy.sms.forwarder.utils.tinker + +import android.annotation.SuppressLint +import android.os.Build +import android.util.Log +import java.io.File +import java.io.IOException + +/** + * Created by zhangshaowen on 17/1/5. + * Thanks for Android Fragmentation + */ +@Suppress("NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS", "UNCHECKED_CAST", "SENSELESS_COMPARISON") +object TinkerLoadLibrary { + private const val TAG = "Tinker.LoadLibrary" + + @SuppressLint("ObsoleteSdkInt") + @Throws(Throwable::class) + fun installNativeLibraryPath(classLoader: ClassLoader, folder: File?) { + if (folder == null || !folder.exists()) { + Log.e(TAG, String.format("installNativeLibraryPath, folder %s is illegal", folder)) + return + } + // android o sdk_int 26 + // for android o preview sdk_int 25 + if (Build.VERSION.SDK_INT == 25 && Build.VERSION.PREVIEW_SDK_INT != 0 + || Build.VERSION.SDK_INT > 25 + ) { + try { + V25.install(classLoader, folder) + } catch (throwable: Throwable) { + // install fail, try to treat it as v23 + // some preview N version may go here + Log.e( + TAG, String.format( + "installNativeLibraryPath, v25 fail, sdk: %d, error: %s, try to fallback to V23", + Build.VERSION.SDK_INT, throwable.message + ) + ) + V23.install(classLoader, folder) + } + } else if (Build.VERSION.SDK_INT >= 23) { + try { + V23.install(classLoader, folder) + } catch (throwable: Throwable) { + // install fail, try to treat it as v14 + Log.e( + TAG, String.format( + "installNativeLibraryPath, v23 fail, sdk: %d, error: %s, try to fallback to V14", + Build.VERSION.SDK_INT, throwable.message + ) + ) + V14.install(classLoader, folder) + } + } else if (Build.VERSION.SDK_INT >= 14) { + V14.install(classLoader, folder) + } else { + V4.install(classLoader, folder) + } + } + + object V4 { + @Throws(Throwable::class) + fun install(classLoader: ClassLoader, folder: File) { + val addPath = folder.path + val pathField = ShareReflectUtil.findField(classLoader, "libPath") + val origLibPaths = pathField[classLoader] as String + val origLibPathSplit = origLibPaths.split(":".toRegex()).toTypedArray() + val newLibPaths = StringBuilder(addPath) + for (origLibPath in origLibPathSplit) { + if (origLibPath == null || addPath == origLibPath) { + continue + } + newLibPaths.append(':').append(origLibPath) + } + pathField[classLoader] = newLibPaths.toString() + val libraryPathElementsFiled = ShareReflectUtil.findField(classLoader, "libraryPathElements") + val libraryPathElements = libraryPathElementsFiled[classLoader] as MutableList + val libPathElementIt = libraryPathElements.iterator() + while (libPathElementIt.hasNext()) { + val libPath = libPathElementIt.next() + if (addPath == libPath) { + libPathElementIt.remove() + break + } + } + libraryPathElements.add(0, addPath) + libraryPathElementsFiled[classLoader] = libraryPathElements + } + } + + object V14 { + @Throws(Throwable::class) + fun install(classLoader: ClassLoader, folder: File) { + val pathListField = ShareReflectUtil.findField(classLoader, "pathList") + val dexPathList = pathListField[classLoader] + val nativeLibDirField = ShareReflectUtil.findField(dexPathList, "nativeLibraryDirectories") + val origNativeLibDirs = nativeLibDirField[dexPathList] as Array + val newNativeLibDirList: MutableList = ArrayList(origNativeLibDirs.size + 1) + newNativeLibDirList.add(folder) + for (origNativeLibDir in origNativeLibDirs) { + if (folder != origNativeLibDir) { + newNativeLibDirList.add(origNativeLibDir) + } + } + nativeLibDirField[dexPathList] = newNativeLibDirList.toTypedArray() + } + } + + object V23 { + @Throws(Throwable::class) + fun install(classLoader: ClassLoader, folder: File) { + val pathListField = ShareReflectUtil.findField(classLoader, "pathList") + val dexPathList = pathListField[classLoader] + val nativeLibraryDirectories = ShareReflectUtil.findField(dexPathList, "nativeLibraryDirectories") + var origLibDirs = nativeLibraryDirectories[dexPathList] as MutableList + if (origLibDirs == null) { + origLibDirs = ArrayList(2) + } + val libDirIt = origLibDirs.iterator() + while (libDirIt.hasNext()) { + val libDir = libDirIt.next() + if (folder == libDir) { + libDirIt.remove() + break + } + } + origLibDirs.add(0, folder) + val systemNativeLibraryDirectories = ShareReflectUtil.findField(dexPathList, "systemNativeLibraryDirectories") + var origSystemLibDirs = systemNativeLibraryDirectories[dexPathList] as List + if (origSystemLibDirs == null) { + origSystemLibDirs = ArrayList(2) + } + val newLibDirs: MutableList = ArrayList(origLibDirs.size + origSystemLibDirs.size + 1) + newLibDirs.addAll(origLibDirs) + newLibDirs.addAll(origSystemLibDirs) + val makeElements = ShareReflectUtil.findMethod( + dexPathList, + "makePathElements", MutableList::class.java, File::class.java, MutableList::class.java + ) + val suppressedExceptions = ArrayList() + val elements = makeElements.invoke(dexPathList, newLibDirs, null, suppressedExceptions) as Array + val nativeLibraryPathElements = ShareReflectUtil.findField(dexPathList, "nativeLibraryPathElements") + nativeLibraryPathElements[dexPathList] = elements + } + } + + object V25 { + @Throws(Throwable::class) + fun install(classLoader: ClassLoader, folder: File) { + val pathListField = ShareReflectUtil.findField(classLoader, "pathList") + val dexPathList = pathListField[classLoader] + val nativeLibraryDirectories = ShareReflectUtil.findField(dexPathList, "nativeLibraryDirectories") + var origLibDirs = nativeLibraryDirectories[dexPathList] as MutableList + if (origLibDirs == null) { + origLibDirs = ArrayList(2) + } + val libDirIt = origLibDirs.iterator() + while (libDirIt.hasNext()) { + val libDir = libDirIt.next() + if (folder == libDir) { + libDirIt.remove() + break + } + } + origLibDirs.add(0, folder) + val systemNativeLibraryDirectories = ShareReflectUtil.findField(dexPathList, "systemNativeLibraryDirectories") + var origSystemLibDirs = systemNativeLibraryDirectories[dexPathList] as List + if (origSystemLibDirs == null) { + origSystemLibDirs = ArrayList(2) + } + val newLibDirs: MutableList = ArrayList(origLibDirs.size + origSystemLibDirs.size + 1) + newLibDirs.addAll(origLibDirs) + newLibDirs.addAll(origSystemLibDirs) + val makeElements = ShareReflectUtil.findMethod(dexPathList, "makePathElements", MutableList::class.java) + val elements = makeElements.invoke(dexPathList, newLibDirs) as Array + val nativeLibraryPathElements = ShareReflectUtil.findField(dexPathList, "nativeLibraryPathElements") + nativeLibraryPathElements[dexPathList] = elements + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/utils/update/CustomUpdateDownloader.kt b/app/src/main/java/com/idormy/sms/forwarder/utils/update/CustomUpdateDownloader.kt new file mode 100644 index 00000000..79c71ffe --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/utils/update/CustomUpdateDownloader.kt @@ -0,0 +1,31 @@ +package com.idormy.sms.forwarder.utils.update + +import com.xuexiang.xupdate.entity.UpdateEntity +import com.xuexiang.xupdate.proxy.impl.DefaultUpdateDownloader +import com.xuexiang.xupdate.service.OnFileDownloadListener +import com.xuexiang.xutil.app.ActivityUtils + +/** + * 重写DefaultUpdateDownloader,在取消下载时,弹出提示 + * + * @author xuexiang + * @since 2019-06-14 23:47 + */ +class CustomUpdateDownloader : DefaultUpdateDownloader() { + private var mIsStartDownload = false + override fun startDownload( + updateEntity: UpdateEntity, + downloadListener: OnFileDownloadListener?, + ) { + super.startDownload(updateEntity, downloadListener) + mIsStartDownload = true + } + + override fun cancelDownload() { + super.cancelDownload() + if (mIsStartDownload) { + mIsStartDownload = false + ActivityUtils.startActivity(UpdateTipDialog::class.java) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/utils/update/CustomUpdateFailureListener.kt b/app/src/main/java/com/idormy/sms/forwarder/utils/update/CustomUpdateFailureListener.kt new file mode 100644 index 00000000..69c0021e --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/utils/update/CustomUpdateFailureListener.kt @@ -0,0 +1,39 @@ +package com.idormy.sms.forwarder.utils.update + +import com.idormy.sms.forwarder.R +import com.idormy.sms.forwarder.utils.XToastUtils +import com.idormy.sms.forwarder.utils.update.UpdateTipDialog.Companion.show +import com.xuexiang.xui.utils.ResUtils +import com.xuexiang.xupdate.entity.UpdateError +import com.xuexiang.xupdate.listener.OnUpdateFailureListener + +/** + * 自定义版本更新提示 + * + * @author xuexiang + * @since 2019/4/15 上午12:01 + */ +class CustomUpdateFailureListener @JvmOverloads constructor( + /** + * 是否需要错误提示 + */ + private val mNeedErrorTip: Boolean = true, +) : OnUpdateFailureListener { + /** + * 更新失败 + * + * @param error 错误 + */ + override fun onFailure(error: UpdateError) { + if (mNeedErrorTip) { + if (error.detailMsg.contains("{\"code\":-1,\"msg\":null,\"data\":null}")) { + XToastUtils.success(ResUtils.getString(R.string.no_new_version)) + } else { + XToastUtils.error(error) + } + } + if (error.code == UpdateError.ERROR.DOWNLOAD_FAILED) { + show(String.format(ResUtils.getString(R.string.download_failed_switch_download_url), UpdateTipDialog.DOWNLOAD_TYPE_NAME)) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/utils/update/CustomUpdateParser.kt b/app/src/main/java/com/idormy/sms/forwarder/utils/update/CustomUpdateParser.kt new file mode 100644 index 00000000..f85ac970 --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/utils/update/CustomUpdateParser.kt @@ -0,0 +1,20 @@ +package com.idormy.sms.forwarder.utils.update + +import com.xuexiang.xupdate.entity.UpdateEntity +import com.xuexiang.xupdate.proxy.impl.AbstractUpdateParser + +/** + * 版本更新信息自定义json解析器 + * 具体使用参见: https://github.com/xuexiangjys/XUpdate/wiki/%E9%AB%98%E9%98%B6%E4%BD%BF%E7%94%A8#%E8%87%AA%E5%AE%9A%E4%B9%89%E7%89%88%E6%9C%AC%E6%9B%B4%E6%96%B0%E8%A7%A3%E6%9E%90%E5%99%A8 + * + * @author xuexiang + * @since 2020-02-18 13:01 + */ +@Suppress("unused") +class CustomUpdateParser : AbstractUpdateParser() { + @Throws(Exception::class) + override fun parseJson(json: String): UpdateEntity? { + // TODO: 2020-02-18 这里填写你需要自定义的json格式,如果使用默认的API就不需要设置 + return null + } +} \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/utils/update/UpdateTipDialog.kt b/app/src/main/java/com/idormy/sms/forwarder/utils/update/UpdateTipDialog.kt new file mode 100644 index 00000000..73c0d561 --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/utils/update/UpdateTipDialog.kt @@ -0,0 +1,61 @@ +package com.idormy.sms.forwarder.utils.update + +import android.content.DialogInterface +import android.content.Intent +import android.os.Bundle +import android.text.TextUtils +import androidx.appcompat.app.AppCompatActivity +import com.idormy.sms.forwarder.R +import com.idormy.sms.forwarder.utils.CommonUtils.Companion.goWeb +import com.xuexiang.xui.utils.ResUtils +import com.xuexiang.xui.widget.dialog.DialogLoader +import com.xuexiang.xupdate.XUpdate + +/** + * 版本更新提示弹窗 + * + * @author xuexiang + * @since 2019-06-15 00:06 + */ +class UpdateTipDialog : AppCompatActivity(), DialogInterface.OnDismissListener { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + var content = intent.getStringExtra(KEY_CONTENT) + if (TextUtils.isEmpty(content)) { + content = String.format(ResUtils.getString(R.string.download_slow_switch_download_url), DOWNLOAD_TYPE_NAME) + } + DialogLoader.getInstance() + .showConfirmDialog(this, content, ResUtils.getString(R.string.yes), { dialog: DialogInterface, _: Int -> + dialog.dismiss() + goWeb(this@UpdateTipDialog, DOWNLOAD_URL) + }, ResUtils.getString(R.string.no)) + .setOnDismissListener(this) + } + + override fun onDismiss(dialog: DialogInterface) { + finish() + } + + companion object { + const val KEY_CONTENT = "com.idormy.sms.forwarder.utils.update.KEY_CONTENT" + + // TODO: 2021/5/11 填写你应用下载类型名 + const val DOWNLOAD_TYPE_NAME = "酷安" + + // TODO: 2021/5/11 填写你应用下载页面的链接 + private const val DOWNLOAD_URL = "https://www.coolapk.com/apk/com.idormy.sms.forwarder" + + /** + * 显示版本更新重试提示弹窗 + * + * @param content + */ + @JvmStatic + fun show(content: String?) { + val intent = Intent(XUpdate.getContext(), UpdateTipDialog::class.java) + intent.putExtra(KEY_CONTENT, content) + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + XUpdate.getContext().startActivity(intent) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/utils/update/XHttpUpdateHttpServiceImpl.kt b/app/src/main/java/com/idormy/sms/forwarder/utils/update/XHttpUpdateHttpServiceImpl.kt new file mode 100644 index 00000000..34d597e1 --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/utils/update/XHttpUpdateHttpServiceImpl.kt @@ -0,0 +1,93 @@ +package com.idormy.sms.forwarder.utils.update + +import com.idormy.sms.forwarder.R +import com.idormy.sms.forwarder.utils.XToastUtils +import com.xuexiang.xhttp2.XHttp +import com.xuexiang.xhttp2.XHttpSDK +import com.xuexiang.xhttp2.callback.DownloadProgressCallBack +import com.xuexiang.xhttp2.callback.SimpleCallBack +import com.xuexiang.xhttp2.exception.ApiException +import com.xuexiang.xui.utils.ResUtils +import com.xuexiang.xupdate.proxy.IUpdateHttpService +import com.xuexiang.xupdate.proxy.IUpdateHttpService.DownloadCallback +import com.xuexiang.xutil.file.FileUtils +import com.xuexiang.xutil.net.JsonUtil + +/** + * XHttp2实现的请求更新 + * + * @author xuexiang + * @since 2018/8/12 上午11:46 + */ +class XHttpUpdateHttpServiceImpl : IUpdateHttpService { + override fun asyncGet( + url: String, + params: Map, + callBack: IUpdateHttpService.Callback, + ) { + XHttp.get(url) + .params(params) + .keepJson(true) + .execute(object : SimpleCallBack() { + @Throws(Throwable::class) + override fun onSuccess(response: String) { + callBack.onSuccess(response) + } + + override fun onError(e: ApiException) { + callBack.onError(e) + } + }) + } + + override fun asyncPost( + url: String, + params: Map, + callBack: IUpdateHttpService.Callback, + ) { + XHttp.post(url) + .upJson(JsonUtil.toJson(params)) + .keepJson(true) + .execute(object : SimpleCallBack() { + @Throws(Throwable::class) + override fun onSuccess(response: String) { + callBack.onSuccess(response) + } + + override fun onError(e: ApiException) { + callBack.onError(e) + } + }) + } + + override fun download(url: String, path: String, fileName: String, callback: DownloadCallback) { + XHttpSDK.addRequest( + url, XHttp.downLoad(url) + .savePath(path) + .saveName(fileName) + .isUseBaseUrl(false) + .execute(object : DownloadProgressCallBack() { + override fun onStart() { + callback.onStart() + } + + override fun onError(e: ApiException) { + callback.onError(e) + } + + override fun update(downLoadSize: Long, totalSize: Long, done: Boolean) { + callback.onProgress(downLoadSize / totalSize.toFloat(), totalSize) + } + + override fun onComplete(path: String) { + callback.onSuccess(FileUtils.getFileByPath(path)) + } + }) + ) + } + + override fun cancelDownload(url: String) { + XToastUtils.info(ResUtils.getString(R.string.update_cancelled)) + XHttpSDK.cancelRequest(url) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/view/ClearEditText.java b/app/src/main/java/com/idormy/sms/forwarder/view/ClearEditText.java deleted file mode 100644 index c3f71497..00000000 --- a/app/src/main/java/com/idormy/sms/forwarder/view/ClearEditText.java +++ /dev/null @@ -1,292 +0,0 @@ -package com.idormy.sms.forwarder.view; - -import android.animation.ObjectAnimator; -import android.content.Context; -import android.content.res.TypedArray; -import android.text.Editable; -import android.text.InputFilter; -import android.text.InputType; -import android.text.TextWatcher; -import android.text.method.HideReturnsTransformationMethod; -import android.text.method.PasswordTransformationMethod; -import android.util.AttributeSet; -import android.util.TypedValue; -import android.view.View; -import android.widget.EditText; -import android.widget.ImageView; -import android.widget.RelativeLayout; - -import androidx.annotation.NonNull; -import androidx.viewpager.widget.ViewPager; - -import com.idormy.sms.forwarder.R; -import com.idormy.sms.forwarder.utils.AnimationUtils; -import com.idormy.sms.forwarder.utils.CommonUtils; - -public class ClearEditText extends RelativeLayout { - private EditText myEdie; - private ImageView ivEditClean; - private ImageView ivEditEye; - private boolean isChecked = true; - private final Context mContext; - private TypedArray mTypedArray; - private boolean showClean = true;//清空图标是否显示,true:显示 - private boolean showEye = false;//密码可见图标是否显示,true:显示 - private int drawableLeft = -1;//是否显示输入框左侧图片 - private int drawableEyeOpen = R.drawable.clear_icon_eye_open;//可以看见密码小眼睛样式 - private int drawableEyeClose = R.drawable.clear_icon_eye_close;//不可见密码小眼睛样式 - private int drawableClean = R.drawable.clear_icon_close;//清除按钮图片 - private int cleanPadding = 0;//清除按钮padding边距 - private String hintStr; - private String textStr; - private int mTextColorHint = -1; //Color.LTGRAY - private int mTextColor = -1;//Color.BLACK - private int mTextSize = -1; - private int mMaxLength = 2000; - private int mMaxLines = 1; - private int mInputType = 0;//输入类型,就做了不限制、数字、文本密码三种 - private boolean isInput = false;//输入1个字符后更改状态为true,保证小眼睛移动一次 - private boolean isHideClean = false;//输入字符后,清除了需要小眼睛归为,清除按钮隐藏 - - public ClearEditText(Context context) { - super(context); - mContext = context; - initView(); - } - - public ClearEditText(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); - mContext = context; - mTypedArray = mContext.obtainStyledAttributes(attrs, R.styleable.ClearEditText); - initView(); - } - - public ClearEditText(Context context, AttributeSet attrs) { - super(context, attrs); - mContext = context; - mTypedArray = mContext.obtainStyledAttributes(attrs, R.styleable.ClearEditText); - showClean = mTypedArray.getBoolean(R.styleable.ClearEditText_showClean, showClean); - drawableClean = mTypedArray.getResourceId(R.styleable.ClearEditText_drawableClean, drawableClean); - cleanPadding = mTypedArray.getDimensionPixelSize(R.styleable.ClearEditText_cleanPadding, cleanPadding); - - showEye = mTypedArray.getBoolean(R.styleable.ClearEditText_showEye, showEye); - drawableLeft = mTypedArray.getResourceId(R.styleable.ClearEditText_drawableLeft, -1); - drawableEyeClose = mTypedArray.getResourceId(R.styleable.ClearEditText_drawableEyeClose, drawableEyeClose); - drawableEyeOpen = mTypedArray.getResourceId(R.styleable.ClearEditText_drawableEyeOpen, drawableEyeOpen); - - hintStr = mTypedArray.getString(R.styleable.ClearEditText_hint); - textStr = mTypedArray.getString(R.styleable.ClearEditText_text); - mTextColorHint = mTypedArray.getColor(R.styleable.ClearEditText_textColorHint, mTextColorHint); - mTextColor = mTypedArray.getColor(R.styleable.ClearEditText_textColor, mTextColor); - mTextSize = mTypedArray.getDimensionPixelSize(R.styleable.ClearEditText_textSize, mTextSize); - mMaxLength = mTypedArray.getInteger(R.styleable.ClearEditText_maxLength, mMaxLength); - mMaxLines = mTypedArray.getDimensionPixelSize(R.styleable.ClearEditText_maxLines, mMaxLines); - mInputType = mTypedArray.getInteger(R.styleable.ClearEditText_inputType, mInputType); - - mTypedArray.recycle(); - initView(); - } - - // 初始化视图 - private void initView() { - View view = View.inflate(getContext(), R.layout.edit_text_clear, null); - ImageView ivLeftIcon = view.findViewById(R.id.iv_edit_left_icon); - myEdie = view.findViewById(R.id.view_edit_show); - ivEditClean = view.findViewById(R.id.iv_edit_clean); - ivEditEye = view.findViewById(R.id.iv_edit_eye); - - myEdie.setHint(hintStr); - if (mTextColorHint != -1) myEdie.setHintTextColor(mTextColorHint); - myEdie.setText(textStr); - if (mTextColor != -1) myEdie.setTextColor(mTextColor); - myEdie.setMaxLines(mMaxLines); - myEdie.setFilters(new InputFilter[]{new InputFilter.LengthFilter(mMaxLength)}); - if (mTextSize != -1) { - myEdie.setTextSize(TypedValue.COMPLEX_UNIT_PX, mTextSize); - } else { - myEdie.setTextSize(15); - } - if (mInputType == 1) { - myEdie.setInputType(InputType.TYPE_CLASS_NUMBER); - } else if (mInputType == 2) { - myEdie.setInputType(InputType.TYPE_TEXT_VARIATION_PASSWORD | InputType.TYPE_CLASS_TEXT); - } else { - myEdie.setInputType(InputType.TYPE_NUMBER_VARIATION_NORMAL | InputType.TYPE_CLASS_TEXT); - } - if (showEye) { - myEdie.setTransformationMethod(new AsteriskPasswordTransformationMethod()); - } - if (showClean && showEye) { - int left = myEdie.getPaddingLeft(); - int top = myEdie.getPaddingTop(); - int bottom = myEdie.getPaddingBottom(); - myEdie.setPadding(left, top, CommonUtils.dp2px(mContext, 90), bottom); - } else if (!showClean && !showEye) { - int left = myEdie.getPaddingLeft(); - int top = myEdie.getPaddingTop(); - int right = myEdie.getPaddingRight(); - int bottom = myEdie.getPaddingBottom(); - myEdie.setPadding(left, top, right, bottom); - } else { - int left = myEdie.getPaddingLeft(); - int top = myEdie.getPaddingTop(); - int bottom = myEdie.getPaddingBottom(); - myEdie.setPadding(left, top, CommonUtils.dp2px(mContext, 45), bottom); - } - - myEdie.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) { - if (s.length() > 0) { - isHideClean = false; - } - } - - @Override - public void afterTextChanged(Editable s) { - if (s.length() > 0 && !isInput) {//输入字符大于0且只有一个字符时候显示清除按钮动画,小眼睛移动出位置给清除按钮使用 - showEditClean(); - moveEditEye(); - isInput = true; - } else if (s.length() == 0) {//无字符小眼睛归位 - UndoEditEye(); - } - if (s.length() == 0 & !isHideClean) { - hideEditClean(); - isHideClean = true; - isInput = false; - } - if (onEditInputListener != null) { - onEditInputListener.input(getText()); - } - } - }); - - setEditClean(showClean); - ivEditClean.setOnClickListener(v -> myEdie.setText("")); - ivEditClean.setImageResource(drawableClean); - ivEditClean.setPadding(cleanPadding, cleanPadding, cleanPadding, cleanPadding); - - setEditEye(showEye); - ivEditEye.setOnClickListener(v -> { - if (isChecked) { - // 输入一个对用户可见的密码 - myEdie.setTransformationMethod(HideReturnsTransformationMethod.getInstance()); - myEdie.setSelection(getText().length()); - ivEditEye.setImageResource(drawableEyeOpen); - isChecked = false; - } else { - // 输入一个对用户不可见的密码 - myEdie.setTransformationMethod(new AsteriskPasswordTransformationMethod()); - myEdie.setSelection(getText().length()); - ivEditEye.setImageResource(drawableEyeClose); - isChecked = true; - } - }); - if (drawableLeft != -1) { - ivLeftIcon.setVisibility(View.VISIBLE); - ivLeftIcon.setImageResource(drawableLeft); - } else { - ivLeftIcon.setVisibility(View.GONE); - } - view.setLayoutParams(new LayoutParams(ViewPager.LayoutParams.MATCH_PARENT, ViewPager.LayoutParams.WRAP_CONTENT)); - addView(view); - } - - //密码不可见时候,使用*替换密码 - public static class AsteriskPasswordTransformationMethod extends PasswordTransformationMethod { - @Override - public CharSequence getTransformation(CharSequence source, View view) { - return new PasswordCharSequence(source); - } - - private static class PasswordCharSequence implements CharSequence { - - private final CharSequence mSource; - - public PasswordCharSequence(CharSequence source) { - mSource = source; // Store char sequence - } - - public char charAt(int index) { - return '*'; // This is the important part - } - - public int length() { - return mSource.length(); // Return default - } - - @NonNull - public CharSequence subSequence(int start, int end) { - return mSource.subSequence(start, end); // Return default - } - } - - } - - public String getText() { - return myEdie.getText().toString().trim(); - } - - public void setText(String text) { - myEdie.setText(text); - } - - //代码设置是否显示清除按钮 - public void setEditClean(boolean isCanClose) { - showClean = isCanClose; - } - - //代码设置是否显示小眼睛 - public void setEditEye(boolean isCanSee) { - showEye = isCanSee; - if (showEye) { - ivEditEye.setVisibility(View.VISIBLE); - } else { - ivEditEye.setVisibility(View.GONE); - } - } - - private void showEditClean() { - if (showClean) { - AnimationUtils.showAndHiddenCenterAnimation(ivEditClean, AnimationUtils.AnimationState.STATE_SHOW, 500); - } - } - - private void hideEditClean() { - if (showClean) { - AnimationUtils.showAndHiddenCenterAnimation(ivEditClean, AnimationUtils.AnimationState.STATE_HIDDEN, 500); - } - } - - private void moveEditEye() { - if (showEye) { - //关闭按钮的宽度 - int ivWidth = 35; - ObjectAnimator.ofFloat(ivEditEye, "translationX", -CommonUtils.dp2px(mContext, ivWidth)).setDuration(500).start(); - } - } - - private void UndoEditEye() { - if (showEye) { - ObjectAnimator.ofFloat(ivEditEye, "translationX", 0).setDuration(500).start(); - } - } - - public OnEditInputListener onEditInputListener; - - public void setOnEditInputListener(OnEditInputListener listener) { - onEditInputListener = listener; - } - - //输入监听 - public interface OnEditInputListener { - void input(String content); - } - -} \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/view/IPEditText.java b/app/src/main/java/com/idormy/sms/forwarder/view/IPEditText.java deleted file mode 100644 index 5c5f2196..00000000 --- a/app/src/main/java/com/idormy/sms/forwarder/view/IPEditText.java +++ /dev/null @@ -1,254 +0,0 @@ -package com.idormy.sms.forwarder.view; - -import android.content.Context; -import android.text.Editable; -import android.text.TextUtils; -import android.text.TextWatcher; -import android.util.AttributeSet; -import android.view.KeyEvent; -import android.view.LayoutInflater; -import android.view.View; -import android.widget.EditText; -import android.widget.LinearLayout; - -import com.hjq.toast.ToastUtils; -import com.idormy.sms.forwarder.R; - -import java.util.regex.Pattern; - -public class IPEditText extends LinearLayout { - - //控件 - private final EditText Edit1; - private final EditText Edit2; - private final EditText Edit3; - private final EditText Edit4; - private String ip1; - private String ip2; - private String ip3; - private String ip4; - - public IPEditText(final Context context, AttributeSet attrs) { - super(context, attrs); - //初始化界面 - View view = LayoutInflater.from(context).inflate(R.layout.edit_text_ip, this); - //绑定 - Edit1 = findViewById(R.id.edit1); - Edit2 = findViewById(R.id.edit2); - Edit3 = findViewById(R.id.edit3); - Edit4 = findViewById(R.id.edit4); - //初始化函数 - init(context); - } - - private void init(final Context context) { - /* - 监听文本,得到ip段,自动进入下一个输入框 - */ - Edit1.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) { - ip1 = s.toString().trim(); - int lenIp1 = ip1.length(); - if (lenIp1 > 0 && !Pattern.matches("^([1-9]|[1-9]\\d|1\\d{2}|2[0-4]\\d|25[0-5])\\.?$", ip1)) { - ip1 = ip1.substring(0, lenIp1 - 1); - Edit1.setText(ip1); - Edit1.setSelection(ip1.length()); - ToastUtils.delayedShow(R.string.invalid_ip, 3000); - return; - } - //非空输入 . 跳到下一个输入框 - if (lenIp1 > 1 && ".".equals(ip1.substring(lenIp1 - 1))) { - ip1 = ip1.substring(0, lenIp1 - 1); - Edit1.setText(ip1); - Edit2.setFocusable(true); - Edit2.requestFocus(); - return; - } - //已输3位数字,跳到下一个输入框 - if (lenIp1 > 2) { - Edit2.setFocusable(true); - Edit2.requestFocus(); - } - } - - @Override - public void afterTextChanged(Editable s) { - } - }); - - Edit2.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) { - ip2 = s.toString().trim(); - int lenIp2 = ip2.length(); - if (lenIp2 > 0 && !Pattern.matches("^(\\d|[1-9]\\d|1\\d{2}|2[0-4]\\d|25[0-5])\\.?$", ip2)) { - ip2 = ip2.substring(0, lenIp2 - 1); - Edit2.setText(ip2); - Edit2.setSelection(ip2.length()); - ToastUtils.delayedShow(R.string.invalid_ip, 3000); - return; - } - //非空输入 . 跳到下一个输入框 - if (lenIp2 > 1 && ".".equals(ip2.substring(lenIp2 - 1))) { - ip2 = ip2.substring(0, lenIp2 - 1); - Edit2.setText(ip2); - Edit3.setFocusable(true); - Edit3.requestFocus(); - return; - } - //已输3位数字,跳到下一个输入框 - if (lenIp2 > 2) { - Edit3.setFocusable(true); - Edit3.requestFocus(); - } - } - - @Override - public void afterTextChanged(Editable s) { - - } - }); - - Edit3.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) { - ip3 = s.toString().trim(); - int lenIp3 = ip3.length(); - if (lenIp3 > 0 && !Pattern.matches("^(\\d|[1-9]\\d|1\\d{2}|2[0-4]\\d|25[0-5])\\.?$", ip3)) { - ip3 = ip3.substring(0, lenIp3 - 1); - Edit3.setText(ip3); - Edit3.setSelection(ip3.length()); - ToastUtils.delayedShow(R.string.invalid_ip, 3000); - return; - } - //非空输入 . 跳到下一个输入框 - if (lenIp3 > 1 && ".".equals(ip3.substring(lenIp3 - 1))) { - ip3 = ip3.substring(0, lenIp3 - 1); - Edit3.setText(ip3); - Edit4.setFocusable(true); - Edit4.requestFocus(); - return; - } - //已输3位数字,跳到下一个输入框 - if (lenIp3 > 2) { - Edit4.setFocusable(true); - Edit4.requestFocus(); - } - } - - @Override - public void afterTextChanged(Editable s) { - - } - }); - - Edit4.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) { - ip4 = s.toString().trim(); - int lenIp4 = ip4.length(); - if (lenIp4 > 0 && !Pattern.matches("^(\\d|[1-9]\\d|1\\d{2}|2[0-4]\\d|25[0-5])$", ip4)) { - ip4 = ip4.substring(0, lenIp4 - 1); - Edit4.setText(ip4); - Edit4.setSelection(ip4.length()); - ToastUtils.delayedShow(R.string.invalid_ip, 3000); - } - } - - @Override - public void afterTextChanged(Editable s) { - - } - }); - - /* - 监听控件,空值时del键返回上一输入框 - */ - Edit2.setOnKeyListener((v, keyCode, event) -> { - if (ip2 == null || ip2.isEmpty()) { - if (keyCode == KeyEvent.KEYCODE_DEL) { - Edit1.setFocusable(true); - Edit1.requestFocus(); - Edit1.setSelection(ip1.length()); - } - } - return false; - }); - Edit3.setOnKeyListener((v, keyCode, event) -> { - if (ip3 == null || ip3.isEmpty()) { - if (keyCode == KeyEvent.KEYCODE_DEL) { - Edit2.setFocusable(true); - Edit2.requestFocus(); - Edit2.setSelection(ip2.length()); - } - } - return false; - }); - Edit4.setOnKeyListener((v, keyCode, event) -> { - if (ip4 == null || ip4.isEmpty()) { - if (keyCode == KeyEvent.KEYCODE_DEL) { - Edit3.setFocusable(true); - Edit3.requestFocus(); - Edit3.setSelection(ip3.length()); - } - } - return false; - }); - } - - /** - * 成员函数,返回整个ip地址 - */ - public String getIP() { - //文本 - String text; - if (TextUtils.isEmpty(ip1) || TextUtils.isEmpty(ip2) - || TextUtils.isEmpty(ip3) || TextUtils.isEmpty(ip4)) { - text = null; - } else { - text = ip1 + "." + ip2 + "." + ip3 + "." + ip4; - } - return text; - } - - /** - * 成员函数,返回整个ip地址 - */ - public void setIP(String ip) { - if (ip == null || ip.isEmpty() - || !Pattern.matches("^([1-9]|[1-9]\\d|1\\d{2}|2[0-4]\\d|25[0-5])(\\.(\\d|[1-9]\\d|1\\d{2}|2[0-4]\\d|25[0-5])){3}$", ip)) { - ip1 = ""; - ip2 = ""; - ip3 = ""; - ip4 = ""; - } else { - String[] ips = ip.split("\\."); - ip1 = ips[0]; - ip2 = ips[1]; - ip3 = ips[2]; - ip4 = ips[3]; - } - - Edit1.setText(ip1); - Edit2.setText(ip2); - Edit3.setText(ip3); - Edit4.setText(ip4); - } -} diff --git a/app/src/main/java/com/idormy/sms/forwarder/view/RefreshListView.java b/app/src/main/java/com/idormy/sms/forwarder/view/RefreshListView.java deleted file mode 100644 index 78573537..00000000 --- a/app/src/main/java/com/idormy/sms/forwarder/view/RefreshListView.java +++ /dev/null @@ -1,269 +0,0 @@ -package com.idormy.sms.forwarder.view; - -import android.annotation.SuppressLint; -import android.content.Context; -import android.util.AttributeSet; -import android.util.Log; -import android.view.LayoutInflater; -import android.view.MotionEvent; -import android.view.View; -import android.view.ViewGroup; -import android.view.animation.RotateAnimation; -import android.widget.AbsListView; -import android.widget.ImageView; -import android.widget.ListView; -import android.widget.ProgressBar; -import android.widget.TextView; - -import com.idormy.sms.forwarder.R; - -import java.text.SimpleDateFormat; -import java.util.Locale; - -/** - * 自定义listview - */ -@SuppressWarnings({"CommentedOutCode", "unused"}) -public class RefreshListView extends ListView implements AbsListView.OnScrollListener { - //private static final String TAG = "RefreshListView"; - final int NONE = 0;// 正常状态; - final int PULL = 1;// 提示下拉状态; - final int RELEASE = 2;// 提示释放状态; - final int REFLASHING = 3;// 刷新状态; - View header;// 顶部布局文件; - int headerHeight;// 顶部布局文件的高度; - int firstVisibleItem;// 当前第一个可见的item的位置; - int scrollState;// listview 当前滚动状态; - boolean isRemark;// 标记,当前是在listview最顶端摁下的; - int startY;// 摁下时的Y值; - int state;// 当前的状态; - IRefreshListener iRefreshListener;//刷新数据的接口 - - public RefreshListView(Context context) { - super(context); - // TODO Auto-generated constructor stub - initView(context); - } - - public RefreshListView(Context context, AttributeSet attrs) { - super(context, attrs); - // TODO Auto-generated constructor stub - initView(context); - } - - public RefreshListView(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); - // TODO Auto-generated constructor stub - initView(context); - } - - /** - * 初始化界面,添加顶部布局文件到 listview - */ - @SuppressLint("InflateParams") - private void initView(Context context) { - LayoutInflater inflater = LayoutInflater.from(context); - header = inflater.inflate(R.layout.header, null); - measureView(header); - headerHeight = header.getMeasuredHeight(); - Log.i("tag", "headerHeight = " + headerHeight); - topPadding(-headerHeight); - this.addHeaderView(header); - this.setOnScrollListener(this); - } - - /** - * 通知父布局,占用的宽,高; - */ - private void measureView(View view) { - ViewGroup.LayoutParams p = view.getLayoutParams(); - if (p == null) { - p = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, - ViewGroup.LayoutParams.WRAP_CONTENT); - } - int width = ViewGroup.getChildMeasureSpec(0, 0, p.width); - int height; - int tempHeight = p.height; - if (tempHeight > 0) { - height = MeasureSpec.makeMeasureSpec(tempHeight, - MeasureSpec.EXACTLY); - } else { - height = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); - } - view.measure(width, height); - } - - /** - * 设置header 布局 上边距; - */ - private void topPadding(int topPadding) { - header.setPadding(header.getPaddingLeft(), topPadding, - header.getPaddingRight(), header.getPaddingBottom()); - header.invalidate(); - } - - @Override - public void onScroll(AbsListView view, int firstVisibleItem, - int visibleItemCount, int totalItemCount) { - // TODO Auto-generated method stub - this.firstVisibleItem = firstVisibleItem; - } - - @Override - public void onScrollStateChanged(AbsListView view, int scrollState) { - // TODO Auto-generated method stub - this.scrollState = scrollState; - } - - @SuppressLint("ClickableViewAccessibility") - @Override - public boolean onTouchEvent(MotionEvent ev) { - // TODO Auto-generated method stub - switch (ev.getAction()) { - case MotionEvent.ACTION_DOWN: - if (firstVisibleItem == 0) { - isRemark = true; - startY = (int) ev.getY(); - } - break; - - case MotionEvent.ACTION_MOVE: - onMove(ev); - break; - case MotionEvent.ACTION_UP: - if (state == RELEASE || state == PULL) { - state = REFLASHING; - // 加载最新数据; - refreshViewByState(); - iRefreshListener.onRefresh(); - } -// if (state == RELEASE) { -// Log.d(TAG, "onTouchEvent: up release"); -// state = REFLASHING; -// // 加载最新数据; -// refreshViewByState(); -// iRefreshListener.onRefresh(); -// } else if (state == PULL) { -// Log.d(TAG, "onTouchEvent: up pull"); -// state = NONE; -// isRemark = false; -// refreshViewByState(); -// } - break; - } - return super.onTouchEvent(ev); - } - - /** - * 判断移动过程操作; - */ - private void onMove(MotionEvent ev) { - if (!isRemark) { - return; - } - int tempY = (int) ev.getY(); - int space = tempY - startY; - int topPadding = space - headerHeight; - switch (state) { - case NONE: - if (space > 0) { - state = PULL; - refreshViewByState(); - } - break; - case PULL: - topPadding(topPadding); - if (space > headerHeight + 30 - && scrollState == SCROLL_STATE_TOUCH_SCROLL) { - state = RELEASE; - refreshViewByState(); - } - break; - case RELEASE: - topPadding(topPadding); - if (space < headerHeight + 30) { - state = PULL; - refreshViewByState(); - } else if (space <= 0) { - state = NONE; - isRemark = false; - refreshViewByState(); - } - break; - } - } - - /** - * 根据当前状态,改变界面显示; - */ - private void refreshViewByState() { - TextView tip = header.findViewById(R.id.tip); - ImageView arrow = header.findViewById(R.id.arrow); - ProgressBar progress = header.findViewById(R.id.progress); - RotateAnimation anim = new RotateAnimation(0, 180, - RotateAnimation.RELATIVE_TO_SELF, 0.5f, - RotateAnimation.RELATIVE_TO_SELF, 0.5f); - anim.setDuration(500); - anim.setFillAfter(true); - RotateAnimation anim1 = new RotateAnimation(180, 0, - RotateAnimation.RELATIVE_TO_SELF, 0.5f, - RotateAnimation.RELATIVE_TO_SELF, 0.5f); - anim1.setDuration(500); - anim1.setFillAfter(true); - switch (state) { - case NONE: - arrow.clearAnimation(); - topPadding(-headerHeight); - break; - - case PULL: - arrow.setVisibility(View.VISIBLE); - progress.setVisibility(View.GONE); - tip.setText(R.string.pull_tips); - arrow.clearAnimation(); - arrow.setAnimation(anim1); - break; - case RELEASE: - arrow.setVisibility(View.VISIBLE); - progress.setVisibility(View.GONE); - tip.setText(R.string.release_tips); - arrow.clearAnimation(); - arrow.setAnimation(anim); - break; - case REFLASHING: - topPadding(50); - arrow.setVisibility(View.GONE); - progress.setVisibility(View.VISIBLE); - tip.setText(R.string.reflashing_tips); - arrow.clearAnimation(); - break; - } - } - - /** - * 获取完数据; - */ - public void refreshComplete() { - state = NONE; - isRemark = false; - refreshViewByState(); - TextView lastUpdateTime = header - .findViewById(R.id.lastUpdateTime); - SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault()); - String time = sdf.format(new java.util.Date()); - lastUpdateTime.setText(time); - } - - public void setInterface(IRefreshListener iRefreshListener) { - this.iRefreshListener = iRefreshListener; - } - - /** - * 刷新数据接口 - * - * @author Administrator - */ - public interface IRefreshListener { - void onRefresh(); - } -} \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/view/StepBar.java b/app/src/main/java/com/idormy/sms/forwarder/view/StepBar.java deleted file mode 100644 index 45bce08f..00000000 --- a/app/src/main/java/com/idormy/sms/forwarder/view/StepBar.java +++ /dev/null @@ -1,210 +0,0 @@ -package com.idormy.sms.forwarder.view; - -import android.annotation.SuppressLint; -import android.content.Context; -import android.content.Intent; -import android.content.res.TypedArray; -import android.util.AttributeSet; -import android.view.LayoutInflater; -import android.view.View; -import android.widget.ImageView; -import android.widget.LinearLayout; -import android.widget.TextView; - -import com.idormy.sms.forwarder.MainActivity; -import com.idormy.sms.forwarder.MyApplication; -import com.idormy.sms.forwarder.R; -import com.idormy.sms.forwarder.RuleActivity; -import com.idormy.sms.forwarder.SenderActivity; -import com.idormy.sms.forwarder.SettingActivity; -import com.idormy.sms.forwarder.sender.SenderUtil; -import com.idormy.sms.forwarder.utils.LogUtils; -import com.idormy.sms.forwarder.utils.RuleUtils; -import com.idormy.sms.forwarder.utils.SettingUtils; - -@SuppressWarnings("FieldCanBeLocal") -public class StepBar extends LinearLayout { - private final Context mContext; - private TypedArray mTypedArray; - //自定义参数 - private String current_step; - private String help_tip; - //控件 - private TextView txHelpTip; - private TextView txStep1; - private TextView txStep2; - private TextView txStep3; - private TextView txStep4; - private TextView tvStep1; - private TextView tvStep2; - private TextView tvStep3; - private TextView tvStep4; - private ImageView ivStep_12_1; - private ImageView ivStep_12_2; - private ImageView ivStep_12_3; - private ImageView ivStep_23_1; - private ImageView ivStep_23_2; - private ImageView ivStep_23_3; - private ImageView ivStep_34_1; - private ImageView ivStep_34_2; - private ImageView ivStep_34_3; - - public StepBar(Context context) { - super(context); - mContext = context; - initView(); - } - - public StepBar(final Context context, AttributeSet attrs) { - super(context, attrs); - mContext = context; - initParams(context, attrs); - initView(); - } - - public StepBar(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); - mContext = context; - initParams(context, attrs); - initView(); - } - - private void initParams(Context context, AttributeSet attrs) { - mTypedArray = mContext.obtainStyledAttributes(attrs, R.styleable.StepBar); - if (mTypedArray != null) { - current_step = mTypedArray.getString(R.styleable.StepBar_current_step); - help_tip = mTypedArray.getString(R.styleable.StepBar_help_tip); - mTypedArray.recycle(); - } - } - - private void initView() { - //初始化界面 - View view = LayoutInflater.from(mContext).inflate(R.layout.step_bar, this); - - txHelpTip = findViewById(R.id.txHelpTip); - if (txHelpTip != null) { - txHelpTip.setText(help_tip); - txHelpTip.setVisibility(MyApplication.showHelpTip ? View.VISIBLE : View.GONE); - } - - //步骤1 - txStep1 = findViewById(R.id.txStep1); - tvStep1 = findViewById(R.id.tvStep1); - if (!current_step.equalsIgnoreCase("setting")) { - tvStep1.setOnClickListener(v -> { - Intent intent = new Intent(v.getContext(), SettingActivity.class); - v.getContext().startActivity(intent); - }); - txStep1.setOnClickListener(v -> { - Intent intent = new Intent(v.getContext(), SettingActivity.class); - v.getContext().startActivity(intent); - }); - } else { - tvStep1.setTextColor(mContext.getResources().getColor(R.color.colorPrimaryDark)); - } - - //步骤2 - txStep2 = findViewById(R.id.txStep2); - tvStep2 = findViewById(R.id.tvStep2); - if (!current_step.equalsIgnoreCase("sender")) { - tvStep2.setOnClickListener(v -> { - Intent intent = new Intent(v.getContext(), SenderActivity.class); - v.getContext().startActivity(intent); - }); - txStep2.setOnClickListener(v -> { - Intent intent = new Intent(v.getContext(), SenderActivity.class); - v.getContext().startActivity(intent); - }); - } else { - tvStep2.setTextColor(mContext.getResources().getColor(R.color.colorPrimaryDark)); - } - - //步骤3 - txStep3 = findViewById(R.id.txStep3); - tvStep3 = findViewById(R.id.tvStep3); - if (!current_step.equalsIgnoreCase("rule")) { - tvStep3.setOnClickListener(v -> { - Intent intent = new Intent(v.getContext(), RuleActivity.class); - v.getContext().startActivity(intent); - }); - txStep3.setOnClickListener(v -> { - Intent intent = new Intent(v.getContext(), RuleActivity.class); - v.getContext().startActivity(intent); - }); - } else { - tvStep3.setTextColor(mContext.getResources().getColor(R.color.colorPrimaryDark)); - } - - //步骤4 - txStep4 = findViewById(R.id.txStep4); - tvStep4 = findViewById(R.id.tvStep4); - if (!current_step.equalsIgnoreCase("main")) { - tvStep4.setOnClickListener(v -> { - Intent intent = new Intent(v.getContext(), MainActivity.class); - v.getContext().startActivity(intent); - }); - txStep4.setOnClickListener(v -> { - Intent intent = new Intent(v.getContext(), MainActivity.class); - v.getContext().startActivity(intent); - }); - } else { - tvStep4.setTextColor(mContext.getResources().getColor(R.color.colorPrimaryDark)); - } - - ivStep_12_1 = findViewById(R.id.ivStep_12_1); - ivStep_12_2 = findViewById(R.id.ivStep_12_2); - ivStep_12_3 = findViewById(R.id.ivStep_12_3); - ivStep_23_1 = findViewById(R.id.ivStep_23_1); - ivStep_23_2 = findViewById(R.id.ivStep_23_2); - ivStep_23_3 = findViewById(R.id.ivStep_23_3); - ivStep_34_1 = findViewById(R.id.ivStep_34_1); - ivStep_34_2 = findViewById(R.id.ivStep_34_2); - ivStep_34_3 = findViewById(R.id.ivStep_34_3); - } - - @SuppressLint("UseCompatLoadingForDrawables") - public void setHighlight() { - SettingUtils.init(mContext); - SenderUtil.init(mContext); - RuleUtils.init(mContext); - LogUtils.init(mContext); - - boolean Step1 = SettingUtils.getSwitchEnableSms() || SettingUtils.getSwitchEnablePhone() || SettingUtils.getSwitchEnableAppNotify(); - boolean Step2 = SenderUtil.countSender("1", null) > 0; - boolean Step3 = RuleUtils.countRule("1", null, null) > 0; - boolean Step4 = LogUtils.countLog("2", null, null) > 0; - - //页面提示文本 - if (txHelpTip != null) { - txHelpTip.setVisibility(MyApplication.showHelpTip ? View.VISIBLE : View.GONE); - if (MyApplication.showHelpTip && current_step.equals("main")) { - txHelpTip.setText(Step1 ? R.string.log_tips : R.string.setting_tips); - } - } - - if (Step1) txStep1.setBackground(mContext.getResources().getDrawable(R.drawable.step_circle_current)); - if (Step2) txStep2.setBackground(mContext.getResources().getDrawable(R.drawable.step_circle_current)); - if (Step3) txStep3.setBackground(mContext.getResources().getDrawable(R.drawable.step_circle_current)); - if (Step4) txStep4.setBackground(mContext.getResources().getDrawable(R.drawable.step_circle_current)); - - if (Step1 && Step2) { - ivStep_12_1.setImageResource(R.drawable.step_rectangle_current); - ivStep_12_2.setImageResource(R.drawable.step_rectangle_current); - ivStep_12_3.setImageResource(R.drawable.step_rectangle_current); - } - - if (Step2 && Step3) { - ivStep_23_1.setImageResource(R.drawable.step_rectangle_current); - ivStep_23_2.setImageResource(R.drawable.step_rectangle_current); - ivStep_23_3.setImageResource(R.drawable.step_rectangle_current); - } - - if (Step3 && Step4) { - ivStep_34_1.setImageResource(R.drawable.step_rectangle_current); - ivStep_34_2.setImageResource(R.drawable.step_rectangle_current); - ivStep_34_3.setImageResource(R.drawable.step_rectangle_current); - } - } - -} diff --git a/app/src/main/java/com/idormy/sms/forwarder/widget/GuideTipsDialog.kt b/app/src/main/java/com/idormy/sms/forwarder/widget/GuideTipsDialog.kt new file mode 100644 index 00000000..fd672083 --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/widget/GuideTipsDialog.kt @@ -0,0 +1,189 @@ +package com.idormy.sms.forwarder.widget + +import android.content.Context +import android.view.View +import android.widget.CompoundButton +import android.widget.ImageView +import android.widget.TextView +import androidx.appcompat.widget.AppCompatCheckBox +import com.idormy.sms.forwarder.R +import com.idormy.sms.forwarder.core.http.api.ApiService.IGetService +import com.idormy.sms.forwarder.core.http.callback.NoTipCallBack +import com.idormy.sms.forwarder.core.http.entity.TipInfo +import com.idormy.sms.forwarder.utils.MMKVUtils +import com.xuexiang.constant.TimeConstants +import com.xuexiang.xaop.annotation.SingleClick +import com.xuexiang.xhttp2.XHttp +import com.xuexiang.xhttp2.cache.model.CacheMode +import com.xuexiang.xui.widget.dialog.BaseDialog +import com.xuexiang.xutil.app.AppUtils +import com.zzhoujay.richtext.RichText + +/** + * 小贴士弹窗 + * + * @author xuexiang + * @since 2019-08-22 17:02 + */ +class GuideTipsDialog(context: Context?, tips: List) : + BaseDialog(context, R.layout.dialog_guide_tips), View.OnClickListener, + CompoundButton.OnCheckedChangeListener { + private var mTips: List? = null + private var mIndex = -1 + private var mTvPrevious: TextView? = null + private var mTvNext: TextView? = null + private var mTvTitle: TextView? = null + private var mTvContent: TextView? = null + + /** + * 初始化弹窗 + */ + private fun initViews() { + mTvTitle = findViewById(R.id.tv_title) + mTvContent = findViewById(R.id.tv_content) + val cbIgnore = findViewById(R.id.cb_ignore) + val ivClose = findViewById(R.id.iv_close) + mTvPrevious = findViewById(R.id.tv_previous) + mTvNext = findViewById(R.id.tv_next) + if (cbIgnore != null) { + cbIgnore.isChecked = isIgnoreTips + cbIgnore.setOnCheckedChangeListener(this) + } + ivClose?.setOnClickListener(this) + mTvPrevious!!.setOnClickListener(this) + mTvNext!!.setOnClickListener(this) + mTvPrevious!!.isEnabled = false + mTvNext!!.isEnabled = true + setCancelable(false) + setCanceledOnTouchOutside(true) + } + + /** + * 更新提示信息 + * + * @param tips 提示信息 + */ + private fun updateTips(tips: List) { + mTips = tips + if (mTips != null && mTips!!.isNotEmpty() && mTvContent != null) { + mIndex = 0 + showRichText(mTips!![mIndex]) + } + } + + /** + * 切换提示信息 + * + * @param index 索引 + */ + private fun switchTipInfo(index: Int) { + if (mTips != null && mTips!!.isNotEmpty() && mTvContent != null) { + if (index >= 0 && index <= mTips!!.size - 1) { + showRichText(mTips!![index]) + when (index) { + 0 -> { + mTvPrevious!!.isEnabled = false + mTvNext!!.isEnabled = true + } + mTips!!.size - 1 -> { + mTvPrevious!!.isEnabled = true + mTvNext!!.isEnabled = false + } + else -> { + mTvPrevious!!.isEnabled = true + mTvNext!!.isEnabled = true + } + } + } + } + } + + /** + * 显示富文本 + * + * @param tipInfo 提示信息 + */ + private fun showRichText(tipInfo: TipInfo) { + mTvTitle!!.text = tipInfo.title + RichText.fromHtml(tipInfo.content) + .bind(this) + .into(mTvContent) + } + + @SingleClick(300) + override fun onClick(view: View) { + val id = view.id + if (id == R.id.iv_close) { + dismiss() + } else if (id == R.id.tv_previous) { + if (mIndex > 0) { + mIndex-- + switchTipInfo(mIndex) + } + } else if (id == R.id.tv_next) { + if (mIndex < mTips!!.size - 1) { + mIndex++ + switchTipInfo(mIndex) + } + } + } + + override fun onCheckedChanged(buttonView: CompoundButton, isChecked: Boolean) { + setIsIgnoreTips(isChecked) + } + + override fun onDetachedFromWindow() { + RichText.clear(this) + super.onDetachedFromWindow() + } + + companion object { + private const val KEY_IS_IGNORE_TIPS = + "com.idormy.sms.forwarder.widget.key_is_ignore_tips_" + + /** + * 显示提示 + * + * @param context 上下文 + */ + @JvmStatic + fun showTips(context: Context?) { + if (!isIgnoreTips) { + showTipsForce(context) + } + } + + /** + * 强制显示提示 + * + * @param context 上下文 + */ + @JvmStatic + fun showTipsForce(context: Context?) { + val request = XHttp.custom().cacheMode(CacheMode.FIRST_CACHE) + .cacheTime(TimeConstants.DAY.toLong()).cacheKey("getTips") + request.apiCall(request.create( + IGetService::class.java + ).tips, object : NoTipCallBack>() { + @Throws(Throwable::class) + override fun onSuccess(response: List?) { + if (response != null && response.isNotEmpty()) { + GuideTipsDialog(context, response).show() + } + } + }) + } + + fun setIsIgnoreTips(isIgnore: Boolean): Boolean { + return MMKVUtils.put(KEY_IS_IGNORE_TIPS + AppUtils.getAppVersionCode(), isIgnore) + } + + val isIgnoreTips: Boolean + get() = MMKVUtils.getBoolean(KEY_IS_IGNORE_TIPS + AppUtils.getAppVersionCode(), false) + } + + init { + initViews() + updateTips(tips) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/widget/MaterialFooter.kt b/app/src/main/java/com/idormy/sms/forwarder/widget/MaterialFooter.kt new file mode 100644 index 00000000..674edeb2 --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/widget/MaterialFooter.kt @@ -0,0 +1,97 @@ +package com.idormy.sms.forwarder.widget + +import android.annotation.SuppressLint +import android.content.Context +import android.util.AttributeSet +import android.view.View +import android.widget.FrameLayout +import android.widget.ProgressBar +import com.scwang.smartrefresh.layout.api.RefreshFooter +import com.scwang.smartrefresh.layout.api.RefreshKernel +import com.scwang.smartrefresh.layout.api.RefreshLayout +import com.scwang.smartrefresh.layout.constant.RefreshState +import com.scwang.smartrefresh.layout.constant.SpinnerStyle +import com.scwang.smartrefresh.layout.util.DensityUtil + +/** + * Material风格的上拉加载 + * + * @author xuexiang + * @since 2019-08-03 11:14 + */ +class MaterialFooter @JvmOverloads constructor(context: Context?, attrs: AttributeSet? = null) : + ProgressBar(context, attrs), RefreshFooter { + private fun initView() { + visibility = GONE + val params = FrameLayout.LayoutParams( + FrameLayout.LayoutParams.MATCH_PARENT, + FrameLayout.LayoutParams.WRAP_CONTENT + ) + setPadding(0, DensityUtil.dp2px(10f), 0, DensityUtil.dp2px(10f)) + layoutParams = params + } + + override fun setNoMoreData(noMoreData: Boolean): Boolean { + return false + } + + override fun getView(): View { + return this + } + + override fun getSpinnerStyle(): SpinnerStyle { + //指定为平移,不能null + return SpinnerStyle.Translate + } + + @SuppressLint("RestrictedApi") + override fun onStartAnimator(refreshLayout: RefreshLayout, height: Int, maxDragHeight: Int) { + visibility = VISIBLE + } + + @SuppressLint("RestrictedApi") + override fun onFinish(refreshLayout: RefreshLayout, success: Boolean): Int { + visibility = GONE + return 100 + } + + @SuppressLint("RestrictedApi") + override fun onStateChanged( + refreshLayout: RefreshLayout, + oldState: RefreshState, + newState: RefreshState, + ) { + } + + override fun setPrimaryColors(vararg colors: Int) {} + + @SuppressLint("RestrictedApi") + override fun onInitialized(kernel: RefreshKernel, height: Int, maxDragHeight: Int) { + } + + @SuppressLint("RestrictedApi") + override fun onMoving( + isDragging: Boolean, + percent: Float, + offset: Int, + height: Int, + maxDragHeight: Int, + ) { + } + + @SuppressLint("RestrictedApi") + override fun onReleased(refreshLayout: RefreshLayout, height: Int, maxDragHeight: Int) { + } + + @SuppressLint("RestrictedApi") + override fun onHorizontalDrag(percentX: Float, offsetX: Int, offsetMax: Int) { + } + + override fun isSupportHorizontalDrag(): Boolean { + return false + } + + init { + initView() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/workers/SendWorker.kt b/app/src/main/java/com/idormy/sms/forwarder/workers/SendWorker.kt new file mode 100644 index 00000000..306e46b8 --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/workers/SendWorker.kt @@ -0,0 +1,77 @@ +package com.idormy.sms.forwarder.workers + +import android.content.Context +import android.util.Log +import androidx.work.CoroutineWorker +import androidx.work.WorkerParameters +import androidx.work.workDataOf +import com.google.gson.Gson +import com.idormy.sms.forwarder.core.Core +import com.idormy.sms.forwarder.database.entity.Logs +import com.idormy.sms.forwarder.database.entity.RuleAndSender +import com.idormy.sms.forwarder.entity.MsgInfo +import com.idormy.sms.forwarder.utils.HistoryUtils +import com.idormy.sms.forwarder.utils.SendUtils +import com.idormy.sms.forwarder.utils.SettingUtils +import com.idormy.sms.forwarder.utils.Worker +import com.xuexiang.xutil.security.CipherUtils +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext + +class SendWorker( + context: Context, + workerParams: WorkerParameters, +) : CoroutineWorker(context, workerParams) { + + override suspend fun doWork(): Result { + + return withContext(Dispatchers.IO) { + try { + val msgInfoJson = inputData.getString(Worker.sendMsgInfo) + val msgInfo = Gson().fromJson(msgInfoJson, MsgInfo::class.java) + + //TODO:过滤重复消息机制 + if (SettingUtils.duplicateMessagesLimits > 0) { + val key = CipherUtils.md5(msgInfo.type + msgInfo.from + msgInfo.content) + val timestamp: Long = System.currentTimeMillis() / 1000L + if (HistoryUtils.containsKey(key)) { + val timestampPrev = HistoryUtils.getLong(key, timestamp) + if (timestamp - timestampPrev <= SettingUtils.duplicateMessagesLimits) { + Log.e("SendWorker", "过滤重复消息机制") + return@withContext Result.failure(workDataOf("send" to "failed")) + } + } + HistoryUtils.put(key, timestamp) + } + + //val sendSbnId = inputData.getInt(Worker.sendSbnId, 0) + val simSlot = "SIM" + msgInfo.simSlot + val ruleList: List = Core.rule.getRuleAndSender(msgInfo.type, 1, simSlot) + if (ruleList.isEmpty()) { + return@withContext Result.failure(workDataOf("send" to "failed")) + } + + //var matchNum = 0 + for (rule in ruleList) { + if (!rule.rule.checkMsg(msgInfo)) continue + //matchNum++ + val log = Logs(0, msgInfo.type, msgInfo.from, msgInfo.content, rule.rule.id, msgInfo.simInfo) + val logId = Core.logs.insert(log) + SendUtils.sendMsgSender(msgInfo, rule.rule, rule.sender, logId) + } + + //TODO:自动消除通知 + /*if (matchNum > 0 && sendSbnId != 0 && SettingUtils.enableCancelAppNotify) { + Log.e("SendWorker", "自动消除通知") + return@withContext Result.success(workDataOf("matchNum" to matchNum)) + }*/ + + } catch (e: Exception) { + e.printStackTrace() + } + + return@withContext Result.success() + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/workers/UpdateLogsWorker.kt b/app/src/main/java/com/idormy/sms/forwarder/workers/UpdateLogsWorker.kt new file mode 100644 index 00000000..568a8296 --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/workers/UpdateLogsWorker.kt @@ -0,0 +1,27 @@ +package com.idormy.sms.forwarder.workers + +import android.content.Context +import androidx.work.CoroutineWorker +import androidx.work.WorkerParameters +import com.google.gson.Gson +import com.idormy.sms.forwarder.core.Core +import com.idormy.sms.forwarder.entity.result.SendResponse +import com.idormy.sms.forwarder.utils.Worker +import com.xuexiang.xutil.data.DateUtils +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import java.text.SimpleDateFormat +import java.util.* + +class UpdateLogsWorker( + context: Context, + workerParams: WorkerParameters, +) : CoroutineWorker(context, workerParams) { + override suspend fun doWork(): Result = withContext(Dispatchers.IO) { + val sendResponseJson = inputData.getString(Worker.updateLogs) + val sendResponse = Gson().fromJson(sendResponseJson, SendResponse::class.java) + Core.logs.updateStatus(sendResponse.logId, sendResponse.status, sendResponse.response + "\nAt " + DateUtils.getNowString(SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault()))) + return@withContext Result.success() + } + +} \ No newline at end of file diff --git a/app/src/main/res/color/selector_round_button_main_theme_color.xml b/app/src/main/res/color/selector_round_button_main_theme_color.xml new file mode 100644 index 00000000..3d91219e --- /dev/null +++ b/app/src/main/res/color/selector_round_button_main_theme_color.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable-anydpi-v24/ic_forwarder.xml b/app/src/main/res/drawable-anydpi-v24/ic_forwarder.xml index 737767c3..96eb261a 100644 --- a/app/src/main/res/drawable-anydpi-v24/ic_forwarder.xml +++ b/app/src/main/res/drawable-anydpi-v24/ic_forwarder.xml @@ -1,16 +1,17 @@ - - - + android:viewportWidth="24" + android:viewportHeight="24"> + + + diff --git a/app/src/main/res/drawable-hdpi/ic_menu_app.xml b/app/src/main/res/drawable-hdpi/ic_menu_app.xml new file mode 100644 index 00000000..dd304610 --- /dev/null +++ b/app/src/main/res/drawable-hdpi/ic_menu_app.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable-hdpi/ic_splash_app_logo.png b/app/src/main/res/drawable-hdpi/ic_splash_app_logo.png new file mode 100644 index 00000000..8ddaf5f8 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_splash_app_logo.png differ diff --git a/app/src/main/res/drawable-hdpi/ic_splash_company_logo.png b/app/src/main/res/drawable-hdpi/ic_splash_company_logo.png new file mode 100644 index 00000000..71efbb03 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_splash_company_logo.png differ diff --git a/app/src/main/res/drawable-hdpi/selector_custom_spinner_bg.xml b/app/src/main/res/drawable-hdpi/selector_custom_spinner_bg.xml new file mode 100644 index 00000000..e65c62a2 --- /dev/null +++ b/app/src/main/res/drawable-hdpi/selector_custom_spinner_bg.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable-v17/bg_bottom_sheet.xml b/app/src/main/res/drawable-v17/bg_bottom_sheet.xml new file mode 100644 index 00000000..d7a2ec0b --- /dev/null +++ b/app/src/main/res/drawable-v17/bg_bottom_sheet.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable-v17/xui_config_bg_splash.xml b/app/src/main/res/drawable-v17/xui_config_bg_splash.xml new file mode 100644 index 00000000..d29139b7 --- /dev/null +++ b/app/src/main/res/drawable-v17/xui_config_bg_splash.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable-v21/xui_config_bg_splash.xml b/app/src/main/res/drawable-v21/xui_config_bg_splash.xml new file mode 100644 index 00000000..3a142970 --- /dev/null +++ b/app/src/main/res/drawable-v21/xui_config_bg_splash.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml new file mode 100644 index 00000000..1f6bb290 --- /dev/null +++ b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + diff --git a/app/src/main/res/drawable-xxhdpi/clear_icon_close.webp b/app/src/main/res/drawable-xxhdpi/clear_icon_close.webp deleted file mode 100644 index 1f7f5d0e..00000000 Binary files a/app/src/main/res/drawable-xxhdpi/clear_icon_close.webp and /dev/null differ diff --git a/app/src/main/res/drawable-xxhdpi/clear_icon_eye_close.webp b/app/src/main/res/drawable-xxhdpi/clear_icon_eye_close.webp deleted file mode 100644 index c1465711..00000000 Binary files a/app/src/main/res/drawable-xxhdpi/clear_icon_eye_close.webp and /dev/null differ diff --git a/app/src/main/res/drawable-xxhdpi/clear_icon_eye_open.webp b/app/src/main/res/drawable-xxhdpi/clear_icon_eye_open.webp deleted file mode 100644 index d092e88d..00000000 Binary files a/app/src/main/res/drawable-xxhdpi/clear_icon_eye_open.webp and /dev/null differ diff --git a/app/src/main/res/drawable-xxhdpi/icon_login.webp b/app/src/main/res/drawable-xxhdpi/icon_login.webp deleted file mode 100644 index bace8136..00000000 Binary files a/app/src/main/res/drawable-xxhdpi/icon_login.webp and /dev/null differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_query.xml b/app/src/main/res/drawable-xxxhdpi/ic_query.xml new file mode 100644 index 00000000..9c9e50b3 --- /dev/null +++ b/app/src/main/res/drawable-xxxhdpi/ic_query.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable-xxxhdpi/ic_web_back.png b/app/src/main/res/drawable-xxxhdpi/ic_web_back.png new file mode 100644 index 00000000..f1474dc0 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_web_back.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_web_close.png b/app/src/main/res/drawable-xxxhdpi/ic_web_close.png new file mode 100644 index 00000000..39149184 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_web_close.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_web_more.png b/app/src/main/res/drawable-xxxhdpi/ic_web_more.png new file mode 100644 index 00000000..284e15d4 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_web_more.png differ diff --git a/app/src/main/res/drawable/bg_select_default.xml b/app/src/main/res/drawable/bg_dialog_common_tip_corner_white.xml similarity index 52% rename from app/src/main/res/drawable/bg_select_default.xml rename to app/src/main/res/drawable/bg_dialog_common_tip_corner_white.xml index 9f2f035f..dbcadadd 100644 --- a/app/src/main/res/drawable/bg_select_default.xml +++ b/app/src/main/res/drawable/bg_dialog_common_tip_corner_white.xml @@ -2,8 +2,8 @@ - + - + \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_select_pressed.xml b/app/src/main/res/drawable/bg_select_pressed.xml deleted file mode 100644 index fab22df1..00000000 --- a/app/src/main/res/drawable/bg_select_pressed.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_about.xml b/app/src/main/res/drawable/ic_about.xml deleted file mode 100644 index 53ae5eb0..00000000 --- a/app/src/main/res/drawable/ic_about.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/ic_action_close_white.xml b/app/src/main/res/drawable/ic_action_close_white.xml new file mode 100644 index 00000000..37f7b518 --- /dev/null +++ b/app/src/main/res/drawable/ic_action_close_white.xml @@ -0,0 +1,22 @@ + + + + diff --git a/app/src/main/res/drawable/ic_add.xml b/app/src/main/res/drawable/ic_add.xml index d9a4ad6a..9eb39c35 100644 --- a/app/src/main/res/drawable/ic_add.xml +++ b/app/src/main/res/drawable/ic_add.xml @@ -1,7 +1,7 @@ - - \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_app.xml b/app/src/main/res/drawable/ic_app.xml index 374e8f46..2d903a92 100644 --- a/app/src/main/res/drawable/ic_app.xml +++ b/app/src/main/res/drawable/ic_app.xml @@ -1,10 +1,10 @@ - + android:viewportHeight="24"> + diff --git a/app/src/main/res/drawable/ic_autorun.xml b/app/src/main/res/drawable/ic_autorun.xml new file mode 100644 index 00000000..a5923491 --- /dev/null +++ b/app/src/main/res/drawable/ic_autorun.xml @@ -0,0 +1,13 @@ + + + + diff --git a/app/src/main/res/drawable/ic_clone.xml b/app/src/main/res/drawable/ic_clone.xml deleted file mode 100644 index 5f9329b5..00000000 --- a/app/src/main/res/drawable/ic_clone.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/ic_copy.xml b/app/src/main/res/drawable/ic_copy.xml new file mode 100644 index 00000000..d0dff183 --- /dev/null +++ b/app/src/main/res/drawable/ic_copy.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_delete.xml b/app/src/main/res/drawable/ic_delete.xml index ce7932be..4c803eca 100644 --- a/app/src/main/res/drawable/ic_delete.xml +++ b/app/src/main/res/drawable/ic_delete.xml @@ -1,10 +1,9 @@ + android:viewportWidth="24.0" + android:viewportHeight="24.0"> diff --git a/app/src/main/res/drawable/ic_edit.xml b/app/src/main/res/drawable/ic_edit.xml new file mode 100644 index 00000000..4781397c --- /dev/null +++ b/app/src/main/res/drawable/ic_edit.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_help.xml b/app/src/main/res/drawable/ic_help.xml deleted file mode 100644 index 04fdd051..00000000 --- a/app/src/main/res/drawable/ic_help.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/ic_launcher_background.xml b/app/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 00000000..0d025f9b --- /dev/null +++ b/app/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_launcher_foreground.xml b/app/src/main/res/drawable/ic_launcher_foreground.xml new file mode 100644 index 00000000..2b027f9f --- /dev/null +++ b/app/src/main/res/drawable/ic_launcher_foreground.xml @@ -0,0 +1,13 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_login_close.xml b/app/src/main/res/drawable/ic_login_close.xml new file mode 100644 index 00000000..71703bf6 --- /dev/null +++ b/app/src/main/res/drawable/ic_login_close.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_manual.xml b/app/src/main/res/drawable/ic_manual.xml new file mode 100644 index 00000000..72970af4 --- /dev/null +++ b/app/src/main/res/drawable/ic_manual.xml @@ -0,0 +1,13 @@ + + + + diff --git a/app/src/main/res/drawable/ic_menu_about.xml b/app/src/main/res/drawable/ic_menu_about.xml new file mode 100644 index 00000000..128e602e --- /dev/null +++ b/app/src/main/res/drawable/ic_menu_about.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_menu_client.xml b/app/src/main/res/drawable/ic_menu_client.xml new file mode 100644 index 00000000..8762299a --- /dev/null +++ b/app/src/main/res/drawable/ic_menu_client.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_menu_frpc.xml b/app/src/main/res/drawable/ic_menu_frpc.xml new file mode 100644 index 00000000..24eed1e8 --- /dev/null +++ b/app/src/main/res/drawable/ic_menu_frpc.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_menu_help.xml b/app/src/main/res/drawable/ic_menu_help.xml new file mode 100644 index 00000000..0494f87e --- /dev/null +++ b/app/src/main/res/drawable/ic_menu_help.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_menu_logcat.xml b/app/src/main/res/drawable/ic_menu_logcat.xml new file mode 100644 index 00000000..827290d0 --- /dev/null +++ b/app/src/main/res/drawable/ic_menu_logcat.xml @@ -0,0 +1,12 @@ + + + + diff --git a/app/src/main/res/drawable/ic_menu_logs.xml b/app/src/main/res/drawable/ic_menu_logs.xml new file mode 100644 index 00000000..0f2cf989 --- /dev/null +++ b/app/src/main/res/drawable/ic_menu_logs.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_menu_notifications_white.xml b/app/src/main/res/drawable/ic_menu_notifications_white.xml new file mode 100644 index 00000000..839eb75e --- /dev/null +++ b/app/src/main/res/drawable/ic_menu_notifications_white.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_menu_rule.xml b/app/src/main/res/drawable/ic_menu_rule.xml new file mode 100644 index 00000000..8c55c6d7 --- /dev/null +++ b/app/src/main/res/drawable/ic_menu_rule.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_menu_send.xml b/app/src/main/res/drawable/ic_menu_send.xml new file mode 100644 index 00000000..760b7ba3 --- /dev/null +++ b/app/src/main/res/drawable/ic_menu_send.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_menu_server.xml b/app/src/main/res/drawable/ic_menu_server.xml new file mode 100644 index 00000000..31f64961 --- /dev/null +++ b/app/src/main/res/drawable/ic_menu_server.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_menu_settings.xml b/app/src/main/res/drawable/ic_menu_settings.xml new file mode 100644 index 00000000..c29dab58 --- /dev/null +++ b/app/src/main/res/drawable/ic_menu_settings.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_password.xml b/app/src/main/res/drawable/ic_password.xml new file mode 100644 index 00000000..5b4ce70d --- /dev/null +++ b/app/src/main/res/drawable/ic_password.xml @@ -0,0 +1,18 @@ + + + + + + diff --git a/app/src/main/res/drawable/ic_phone.xml b/app/src/main/res/drawable/ic_phone.xml new file mode 100644 index 00000000..62654143 --- /dev/null +++ b/app/src/main/res/drawable/ic_phone.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_phone_in.xml b/app/src/main/res/drawable/ic_phone_in.xml new file mode 100644 index 00000000..171d32c7 --- /dev/null +++ b/app/src/main/res/drawable/ic_phone_in.xml @@ -0,0 +1,11 @@ + + + diff --git a/app/src/main/res/drawable/ic_phone_missed.xml b/app/src/main/res/drawable/ic_phone_missed.xml new file mode 100644 index 00000000..3be5c41c --- /dev/null +++ b/app/src/main/res/drawable/ic_phone_missed.xml @@ -0,0 +1,11 @@ + + + diff --git a/app/src/main/res/drawable/ic_phone_out.xml b/app/src/main/res/drawable/ic_phone_out.xml new file mode 100644 index 00000000..4f509378 --- /dev/null +++ b/app/src/main/res/drawable/ic_phone_out.xml @@ -0,0 +1,11 @@ + + + diff --git a/app/src/main/res/drawable/ic_refresh.xml b/app/src/main/res/drawable/ic_refresh.xml new file mode 100644 index 00000000..89648911 --- /dev/null +++ b/app/src/main/res/drawable/ic_refresh.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_reply.xml b/app/src/main/res/drawable/ic_reply.xml new file mode 100644 index 00000000..be709aea --- /dev/null +++ b/app/src/main/res/drawable/ic_reply.xml @@ -0,0 +1,11 @@ + + + diff --git a/app/src/main/res/drawable/ic_restore.xml b/app/src/main/res/drawable/ic_restore.xml new file mode 100644 index 00000000..bb75fb8b --- /dev/null +++ b/app/src/main/res/drawable/ic_restore.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_round_cancel.xml b/app/src/main/res/drawable/ic_round_cancel.xml index 70213c3e..4df6c585 100644 --- a/app/src/main/res/drawable/ic_round_cancel.xml +++ b/app/src/main/res/drawable/ic_round_cancel.xml @@ -1,5 +1,10 @@ - - + + diff --git a/app/src/main/res/drawable/ic_round_check.xml b/app/src/main/res/drawable/ic_round_check.xml index 1175dac3..62b1fb96 100644 --- a/app/src/main/res/drawable/ic_round_check.xml +++ b/app/src/main/res/drawable/ic_round_check.xml @@ -1,5 +1,10 @@ - - + + diff --git a/app/src/main/res/drawable/ic_round_pause.xml b/app/src/main/res/drawable/ic_round_pause.xml deleted file mode 100644 index c243424d..00000000 --- a/app/src/main/res/drawable/ic_round_pause.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/ic_round_play.xml b/app/src/main/res/drawable/ic_round_play.xml deleted file mode 100644 index 4e33efa8..00000000 --- a/app/src/main/res/drawable/ic_round_play.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/ic_round_warning.xml b/app/src/main/res/drawable/ic_round_warning.xml index 09951ec9..b4de37f5 100644 --- a/app/src/main/res/drawable/ic_round_warning.xml +++ b/app/src/main/res/drawable/ic_round_warning.xml @@ -1,5 +1,10 @@ - - + + diff --git a/app/src/main/res/drawable/ic_save.xml b/app/src/main/res/drawable/ic_save.xml new file mode 100644 index 00000000..47a92519 --- /dev/null +++ b/app/src/main/res/drawable/ic_save.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_sim1.xml b/app/src/main/res/drawable/ic_sim1.xml new file mode 100644 index 00000000..5033c3e6 --- /dev/null +++ b/app/src/main/res/drawable/ic_sim1.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_sim2.xml b/app/src/main/res/drawable/ic_sim2.xml new file mode 100644 index 00000000..5f95d476 --- /dev/null +++ b/app/src/main/res/drawable/ic_sim2.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_sms.xml b/app/src/main/res/drawable/ic_sms.xml new file mode 100644 index 00000000..e58c33c4 --- /dev/null +++ b/app/src/main/res/drawable/ic_sms.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_start.xml b/app/src/main/res/drawable/ic_start.xml new file mode 100644 index 00000000..10870c15 --- /dev/null +++ b/app/src/main/res/drawable/ic_start.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_stop.xml b/app/src/main/res/drawable/ic_stop.xml new file mode 100644 index 00000000..1c953d67 --- /dev/null +++ b/app/src/main/res/drawable/ic_stop.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_swap_horiz.xml b/app/src/main/res/drawable/ic_swap_horiz.xml new file mode 100644 index 00000000..44de6e4c --- /dev/null +++ b/app/src/main/res/drawable/ic_swap_horiz.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/icon_add.png b/app/src/main/res/drawable/icon_add.png new file mode 100644 index 00000000..2f8f31ef Binary files /dev/null and b/app/src/main/res/drawable/icon_add.png differ diff --git a/app/src/main/res/drawable/icon_api_battery_query.webp b/app/src/main/res/drawable/icon_api_battery_query.webp new file mode 100644 index 00000000..9218017c Binary files /dev/null and b/app/src/main/res/drawable/icon_api_battery_query.webp differ diff --git a/app/src/main/res/drawable/icon_api_call_query.webp b/app/src/main/res/drawable/icon_api_call_query.webp new file mode 100644 index 00000000..d55f8183 Binary files /dev/null and b/app/src/main/res/drawable/icon_api_call_query.webp differ diff --git a/app/src/main/res/drawable/icon_api_clone.webp b/app/src/main/res/drawable/icon_api_clone.webp new file mode 100644 index 00000000..817bbf7f Binary files /dev/null and b/app/src/main/res/drawable/icon_api_clone.webp differ diff --git a/app/src/main/res/drawable/icon_api_contact_query.webp b/app/src/main/res/drawable/icon_api_contact_query.webp new file mode 100644 index 00000000..f40c4804 Binary files /dev/null and b/app/src/main/res/drawable/icon_api_contact_query.webp differ diff --git a/app/src/main/res/drawable/icon_api_sms_query.webp b/app/src/main/res/drawable/icon_api_sms_query.webp new file mode 100644 index 00000000..c76fd837 Binary files /dev/null and b/app/src/main/res/drawable/icon_api_sms_query.webp differ diff --git a/app/src/main/res/drawable/icon_api_sms_send.webp b/app/src/main/res/drawable/icon_api_sms_send.webp new file mode 100644 index 00000000..6e9d5469 Binary files /dev/null and b/app/src/main/res/drawable/icon_api_sms_send.webp differ diff --git a/app/src/main/res/drawable/icon_arrow_right_grey.xml b/app/src/main/res/drawable/icon_arrow_right_grey.xml new file mode 100644 index 00000000..104e1a0d --- /dev/null +++ b/app/src/main/res/drawable/icon_arrow_right_grey.xml @@ -0,0 +1,12 @@ + + + + diff --git a/app/src/main/res/mipmap-xxhdpi/bark.webp b/app/src/main/res/drawable/icon_bark.webp similarity index 100% rename from app/src/main/res/mipmap-xxhdpi/bark.webp rename to app/src/main/res/drawable/icon_bark.webp diff --git a/app/src/main/res/drawable/icon_delete.png b/app/src/main/res/drawable/icon_delete.png new file mode 100644 index 00000000..cefede92 Binary files /dev/null and b/app/src/main/res/drawable/icon_delete.png differ diff --git a/app/src/main/res/mipmap-xxhdpi/dingding.webp b/app/src/main/res/drawable/icon_dingtalk.webp similarity index 100% rename from app/src/main/res/mipmap-xxhdpi/dingding.webp rename to app/src/main/res/drawable/icon_dingtalk.webp diff --git a/app/src/main/res/mipmap-xxhdpi/email.webp b/app/src/main/res/drawable/icon_email.webp similarity index 100% rename from app/src/main/res/mipmap-xxhdpi/email.webp rename to app/src/main/res/drawable/icon_email.webp diff --git a/app/src/main/res/mipmap-xxhdpi/feishu.webp b/app/src/main/res/drawable/icon_feishu.webp similarity index 100% rename from app/src/main/res/mipmap-xxhdpi/feishu.webp rename to app/src/main/res/drawable/icon_feishu.webp diff --git a/app/src/main/res/mipmap-xxhdpi/gotify.webp b/app/src/main/res/drawable/icon_gotify.webp similarity index 100% rename from app/src/main/res/mipmap-xxhdpi/gotify.webp rename to app/src/main/res/drawable/icon_gotify.webp diff --git a/app/src/main/res/drawable/icon_off.png b/app/src/main/res/drawable/icon_off.png new file mode 100644 index 00000000..2992a1b8 Binary files /dev/null and b/app/src/main/res/drawable/icon_off.png differ diff --git a/app/src/main/res/drawable/icon_on.png b/app/src/main/res/drawable/icon_on.png new file mode 100644 index 00000000..31e7d910 Binary files /dev/null and b/app/src/main/res/drawable/icon_on.png differ diff --git a/app/src/main/res/mipmap-xxhdpi/pushplus.webp b/app/src/main/res/drawable/icon_pushplus.webp similarity index 100% rename from app/src/main/res/mipmap-xxhdpi/pushplus.webp rename to app/src/main/res/drawable/icon_pushplus.webp diff --git a/app/src/main/res/drawable/icon_save.png b/app/src/main/res/drawable/icon_save.png new file mode 100644 index 00000000..495035ac Binary files /dev/null and b/app/src/main/res/drawable/icon_save.png differ diff --git a/app/src/main/res/mipmap-xxhdpi/serverchan.webp b/app/src/main/res/drawable/icon_serverchan.webp similarity index 100% rename from app/src/main/res/mipmap-xxhdpi/serverchan.webp rename to app/src/main/res/drawable/icon_serverchan.webp diff --git a/app/src/main/res/drawable/icon_sms.webp b/app/src/main/res/drawable/icon_sms.webp new file mode 100644 index 00000000..7f4a68f2 Binary files /dev/null and b/app/src/main/res/drawable/icon_sms.webp differ diff --git a/app/src/main/res/mipmap-xxhdpi/telegram.webp b/app/src/main/res/drawable/icon_telegram.webp similarity index 100% rename from app/src/main/res/mipmap-xxhdpi/telegram.webp rename to app/src/main/res/drawable/icon_telegram.webp diff --git a/app/src/main/res/drawable/icon_test.png b/app/src/main/res/drawable/icon_test.png new file mode 100644 index 00000000..cfdaa719 Binary files /dev/null and b/app/src/main/res/drawable/icon_test.png differ diff --git a/app/src/main/res/mipmap-xxhdpi/webhook.webp b/app/src/main/res/drawable/icon_webhook.webp similarity index 100% rename from app/src/main/res/mipmap-xxhdpi/webhook.webp rename to app/src/main/res/drawable/icon_webhook.webp diff --git a/app/src/main/res/mipmap-xxhdpi/qywxapp.webp b/app/src/main/res/drawable/icon_wework_agent.webp similarity index 100% rename from app/src/main/res/mipmap-xxhdpi/qywxapp.webp rename to app/src/main/res/drawable/icon_wework_agent.webp diff --git a/app/src/main/res/mipmap-xxhdpi/qywx.webp b/app/src/main/res/drawable/icon_wework_robot.webp similarity index 100% rename from app/src/main/res/mipmap-xxhdpi/qywx.webp rename to app/src/main/res/drawable/icon_wework_robot.webp diff --git a/app/src/main/res/drawable/img_guide_tip_top.xml b/app/src/main/res/drawable/img_guide_tip_top.xml new file mode 100644 index 00000000..7ded5d6b --- /dev/null +++ b/app/src/main/res/drawable/img_guide_tip_top.xml @@ -0,0 +1,38 @@ + + + + + + + diff --git a/app/src/main/res/drawable/select_selector.xml b/app/src/main/res/drawable/select_selector.xml deleted file mode 100644 index ab3118f6..00000000 --- a/app/src/main/res/drawable/select_selector.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/sim1.xml b/app/src/main/res/drawable/sim1.xml deleted file mode 100644 index 2f8d582c..00000000 --- a/app/src/main/res/drawable/sim1.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/sim2.xml b/app/src/main/res/drawable/sim2.xml deleted file mode 100644 index 3a4ba85c..00000000 --- a/app/src/main/res/drawable/sim2.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/step_circle_current.xml b/app/src/main/res/drawable/step_circle_current.xml deleted file mode 100644 index 3d674eb0..00000000 --- a/app/src/main/res/drawable/step_circle_current.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/step_circle_normal.xml b/app/src/main/res/drawable/step_circle_normal.xml deleted file mode 100644 index 20c6a080..00000000 --- a/app/src/main/res/drawable/step_circle_normal.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/step_rectangle_current.xml b/app/src/main/res/drawable/step_rectangle_current.xml deleted file mode 100644 index 1d0bf523..00000000 --- a/app/src/main/res/drawable/step_rectangle_current.xml +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/step_rectangle_normal.xml b/app/src/main/res/drawable/step_rectangle_normal.xml deleted file mode 100644 index ff11851f..00000000 --- a/app/src/main/res/drawable/step_rectangle_normal.xml +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/txt_select_selector.xml b/app/src/main/res/drawable/txt_select_selector.xml deleted file mode 100644 index a7c25e4a..00000000 --- a/app/src/main/res/drawable/txt_select_selector.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/xui_config_bg_splash.xml b/app/src/main/res/drawable/xui_config_bg_splash.xml new file mode 100644 index 00000000..cc156618 --- /dev/null +++ b/app/src/main/res/drawable/xui_config_bg_splash.xml @@ -0,0 +1,22 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_about.xml b/app/src/main/res/layout/activity_about.xml deleted file mode 100644 index 6bc15e0c..00000000 --- a/app/src/main/res/layout/activity_about.xml +++ /dev/null @@ -1,199 +0,0 @@ - - - - - - - - - - - - - - -