SmsForwarder V3.0.0
This commit is contained in:
parent
4c1497490e
commit
0aa1abd826
|
@ -0,0 +1,3 @@
|
|||
# These are supported funding model platforms
|
||||
|
||||
custom: https://gitee.com/pp/SmsForwarder/wikis/pages?sort_id=4912193&doc_id=1821427
|
|
@ -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! 🙂
|
|
@ -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
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
[submodule "keystore"]
|
||||
path = keystore
|
||||
url = https://github.com/pppscn/keystore.git
|
|
@ -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.
|
110
README.md
110
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
|
||||
监控Android手机短信、来电、APP通知,并根据指定规则转发到其他手机:钉钉机器人、企业微信群机器人、飞书机器人、企业微信应用消息、邮箱、bark、webhook、Telegram机器人、Server酱、PushPlus、手机短信等。
|
||||
|
||||
> ⚠ 国内镜像:https://gitee.com/pp/SmsForwarder/releases
|
||||
包括主动控制服务端与客户端,让你轻松远程发短信、查短信、查通话、查话簿、查电量等。(V3.0 新增)
|
||||
|
||||
> ⚠ 网盘下载:https://wws.lanzoui.com/b025yl86h 访问密码:`pppscn`
|
||||
> 注意:从`2022-06-06`开始,原`Java版`的代码归档到`v2.x`分支,不再更新!
|
||||
|
||||
> ⚠ 酷安应用市场:https://www.coolapk.com/apk/com.idormy.sms.forwarder
|
||||
> 1、从`v2.x`到`v3.x`不是简单的功能迭代,采用`kotlin`全新重构了(不是单纯的迁移代码,起初我也是这么认为的),由于我是第一次使用`kotlin`开发(Java版也是第一次),到处踩坑,每一行代码都是度娘手把手教会我的,所以`v3.x`版本可能一开始并不稳定。另外,眼睛葡萄膜炎还没好,晚上不敢肝,中间停摆了个把月,进度缓慢,历时2个月终于让`V3.x`顺产了!
|
||||
|
||||
### 使用文档
|
||||
|
||||
> ⚠ 首发地址:https://github.com/pppscn/SmsForwarder/wiki
|
||||
|
||||
> ⚠ 国内镜像: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 (内网穿透)
|
||||
+ [<img src="https://resources.jetbrains.com/storage/products/company/brand/logos/jb_beam.svg?_ga=2.126618957.1361252949.1638261367-1417196221.1635638144&_gl=1*1pfl3dq*_ga*MTQxNzE5NjIyMS4xNjM1NjM4MTQ0*_ga_V0XZL7QHEB*MTYzODMzMjA4OC43LjAuMTYzODMzMjA5Ny4w" alt="GitHub license" style="zoom:50%;" />](https://jb.gg/OpenSourceSupport) (License Certificate for JetBrains All Products Pack)
|
||||
|
||||
|
|
105
README_en.md
105
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
|
||||
SmsForwarder - Not only forwarding text messages, but also a must-have for backup devices!
|
||||
|
||||
> ⚠ Repo address: https://github.com/pppscn/SmsForwarder/releases
|
||||
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.
|
||||
|
||||
> ⚠ 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
|
||||
|
||||
> ⚠ GitHub: https://github.com/pppscn/SmsForwarder/wiki
|
||||
|
||||
> ⚠ 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
|
||||
|
||||
**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:
|
||||
|
||||
* **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)
|
||||
|
||||
> + 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.
|
||||
|
||||
* **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:
|
||||
## Workflow:
|
||||
|
||||
![Workflow](pic/working_principle_en.png "Workflow")
|
||||
|
||||
### 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] 适配暗夜模式
|
||||
## Screenshots :
|
||||
|
||||
![界面预览](https://images.gitee.com/uploads/images/2022/0606/133422_808b4589_16273.png "界面预览.png")
|
||||
|
||||
See more screenshots:https://github.com/pppscn/SmsForwarder/wiki
|
||||
|
||||
--------
|
||||
|
||||
### Screenshots :
|
||||
## Download
|
||||
|
||||
| 前台服务常驻状态栏 | 应用主界面 | 发送通道 | 转发规则 |
|
||||
| :--: | :--: | :--: | :--: |
|
||||
| ![前台服务常驻状态栏](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 "配置导出导入功能(一键克隆)") |
|
||||
> ⚠ Repo address: https://github.com/pppscn/SmsForwarder/releases
|
||||
|
||||
更多截图参见 https://github.com/pppscn/SmsForwarder/wiki
|
||||
> ⚠ 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
|
||||
|
||||
> ⚠ GitHub: 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 (内网穿透)
|
||||
+ [<img src="https://resources.jetbrains.com/storage/products/company/brand/logos/jb_beam.svg?_ga=2.126618957.1361252949.1638261367-1417196221.1635638144&_gl=1*1pfl3dq*_ga*MTQxNzE5NjIyMS4xNjM1NjM4MTQ0*_ga_V0XZL7QHEB*MTYzODMzMjA4OC43LjAuMTYzODMzMjA5Ny4w" alt="GitHub license" style="zoom:50%;" />](https://jb.gg/OpenSourceSupport) (License Certificate for JetBrains All Products Pack)
|
||||
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
theme: jekyll-theme-cayman
|
|
@ -0,0 +1 @@
|
|||
/build
|
312
app/build.gradle
312
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 {
|
|||
|
||||
}
|
||||
|
||||
|
||||
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")
|
||||
}
|
||||
}
|
||||
|
||||
void cmdExecute(String cmd) {
|
||||
println "\n执行$cmd"
|
||||
println cmd.execute().text
|
||||
}
|
||||
|
||||
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'
|
||||
|
||||
//okhttp
|
||||
//noinspection GradleDependency
|
||||
implementation 'com.squareup.okhttp3:okhttp:4.9.3'
|
||||
implementation 'com.squareup.okio:okio:3.0.0'
|
||||
|
||||
//fastjson
|
||||
implementation "com.alibaba:fastjson:1.2.80"
|
||||
|
||||
//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'
|
||||
|
||||
//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'
|
||||
|
||||
//Lombok
|
||||
//noinspection AnnotationProcessorOnCompilePath
|
||||
compileOnly 'org.projectlombok:lombok:1.18.22'
|
||||
annotationProcessor 'org.projectlombok:lombok:1.18.22'
|
||||
|
||||
//RxJava
|
||||
//implementation 'io.reactivex.rxjava3:rxjava:3.1.3'
|
||||
|
||||
//AndroidAsync
|
||||
implementation 'com.koushikdutta.async:androidasync:3.1.0'
|
||||
|
||||
//吐司框架:https://github.com/getActivity/ToastUtils
|
||||
implementation 'com.github.getActivity:ToastUtils:10.3'
|
||||
//权限请求框架:https://github.com/getActivity/XXPermissions
|
||||
implementation 'com.github.getActivity:XXPermissions:13.2'
|
||||
|
||||
//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"
|
||||
|
||||
//友盟统计SDK
|
||||
implementation 'com.umeng.umsdk:common:9.4.7'// (必选)
|
||||
implementation 'com.umeng.umsdk:asms:1.6.0'// 必选
|
||||
|
||||
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
||||
//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"
|
||||
testImplementation deps.junit
|
||||
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
|
||||
androidTestImplementation deps.espresso.core
|
||||
|
||||
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'
|
||||
|
||||
//分包
|
||||
implementation deps.androidx.multidex
|
||||
|
||||
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'
|
||||
|
||||
//ANR异常捕获
|
||||
implementation 'com.github.anrwatchdog:anrwatchdog:1.4.0'
|
||||
|
||||
//美团多渠道打包
|
||||
implementation 'com.meituan.android.walle:library:1.1.6'
|
||||
|
||||
api("androidx.work:work-multiprocess:2.7.1")
|
||||
api("androidx.work:work-runtime-ktx:2.7.1")
|
||||
|
||||
//Android Room
|
||||
def room_version = '2.4.2'
|
||||
implementation "androidx.room:room-ktx:$room_version"
|
||||
implementation "androidx.room:room-runtime:$room_version"
|
||||
annotationProcessor "androidx.room:room-compiler:$room_version"
|
||||
implementation "androidx.room:room-paging:$room_version"
|
||||
implementation "androidx.room:room-rxjava2:$room_version"
|
||||
}
|
||||
kapt "androidx.room:room-compiler:$room_version"
|
||||
|
||||
implementation 'com.github.AmrDeveloper:CodeView:1.3.4'
|
||||
implementation 'io.github.jeremyliao:live-event-bus-x:1.8.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/XXPermissions
|
||||
implementation 'com.github.getActivity:XXPermissions:13.6'
|
||||
|
||||
def mail_version = '1.6.7'
|
||||
implementation "com.sun.mail:android-mail:$mail_version"
|
||||
implementation "com.sun.mail:android-activation:$mail_version"
|
||||
|
||||
//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'
|
||||
|
||||
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
# 美团
|
||||
meituan
|
||||
# 三星
|
||||
samsungapps
|
||||
# 小米
|
||||
xiaomi
|
||||
# 91助手
|
||||
91com
|
||||
# 魅族
|
||||
meizu
|
||||
# 豌豆荚
|
||||
wandou
|
||||
# Google Play
|
||||
googleplay
|
||||
# 百度
|
||||
baidu
|
||||
# 360
|
||||
360cn
|
||||
# 应用宝
|
||||
myapp
|
||||
# 华为
|
||||
huawei
|
||||
# 蒲公英
|
||||
pgyer
|
||||
github
|
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,9 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<lint>
|
||||
<issue id="IconDipSize">
|
||||
<ignore path="src/main/res/mipmap-xhdpi/nav_banner.png" />
|
||||
</issue>
|
||||
<issue id="IconLauncherShape">
|
||||
<ignore path="src/main/res/mipmap-hdpi/ic_launcher.png" />
|
||||
</issue>
|
||||
</lint>
|
|
@ -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")
|
||||
}
|
|
@ -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
|
||||
#=========================================基础不变的混淆配置=========================================##
|
||||
#指定代码的压缩级别
|
||||
-optimizationpasses 5
|
||||
#包名不混合大小写
|
||||
-dontusemixedcaseclassnames
|
||||
#不去忽略非公共的库类
|
||||
-dontskipnonpubliclibraryclasses
|
||||
# 指定不去忽略非公共的库的类的成员
|
||||
-dontskipnonpubliclibraryclassmembers
|
||||
#优化 不优化输入的类文件
|
||||
-dontoptimize
|
||||
#预校验
|
||||
-dontpreverify
|
||||
#混淆时是否记录日志
|
||||
-verbose
|
||||
# 混淆时所采用的算法
|
||||
-optimizations !code/simplification/arithmetic,!field/*,!class/merging/*
|
||||
#保护注解
|
||||
-keepattributes *Annotation*
|
||||
#忽略警告
|
||||
-ignorewarnings
|
||||
|
||||
# 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 *;
|
||||
##记录生成的日志数据,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 <init>(android.content.Context);
|
||||
public <init>(android.content.Context, android.util.AttributeSet);
|
||||
public <init>(android.content.Context, android.util.AttributeSet, int);
|
||||
public void set*(...);
|
||||
}
|
||||
|
||||
#保持 native 方法不被混淆
|
||||
-keepclasseswithmembernames class * {
|
||||
native <methods>;
|
||||
}
|
||||
|
||||
#保持自定义控件类不被混淆
|
||||
-keepclasseswithmembers class * {
|
||||
public <init>(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 <fields>;
|
||||
!private <fields>;
|
||||
!private <methods>;
|
||||
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.**
|
||||
|
||||
# If you keep the line number information, uncomment this to
|
||||
# hide the original source file name.
|
||||
#-renamesourcefileattribute SourceFile
|
||||
# fastjson
|
||||
-dontwarn com.alibaba.fastjson.**
|
||||
-keep class com.alibaba.fastjson.** { *; }
|
||||
-keepattributes Signature
|
||||
|
||||
-keep class com.idormy.**{*;}
|
||||
# xpage
|
||||
-keep class com.xuexiang.xpage.annotation.** { *; }
|
||||
-keep class com.xuexiang.xpage.config.** { *; }
|
||||
|
||||
#emailkit
|
||||
# xaop
|
||||
-keep @com.xuexiang.xaop.annotation.* class * {*;}
|
||||
-keep @org.aspectj.lang.annotation.* class * {*;}
|
||||
-keep class * {
|
||||
@com.xuexiang.xaop.annotation.* <fields>;
|
||||
@org.aspectj.lang.annotation.* <fields>;
|
||||
}
|
||||
-keepclassmembers class * {
|
||||
@com.xuexiang.xaop.annotation.* <methods>;
|
||||
@org.aspectj.lang.annotation.* <methods>;
|
||||
}
|
||||
|
||||
# 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.* <fields>;
|
||||
}
|
||||
-keepclassmembers class * {
|
||||
@com.xuexiang.xipc.annotation.* <methods>;
|
||||
}
|
||||
|
||||
# umeng统计
|
||||
-keep class com.umeng.** {*;}
|
||||
-keepclassmembers class * {
|
||||
public <init> (org.json.JSONObject);
|
||||
}
|
||||
-keepclassmembers enum * {
|
||||
public static **[] values();
|
||||
public static ** valueOf(java.lang.String);
|
||||
}
|
||||
|
||||
-keep class com.xuexiang.xui.widget.edittext.materialedittext.** { *; }
|
||||
|
||||
# Android Keep Alive(安卓保活),Cactus 集成双进程前台服务,JobScheduler,onePix(一像素),WorkManager,无声音乐
|
||||
-keep class com.gyf.cactus.entity.* {*;}
|
||||
|
||||
# 排除实体类
|
||||
-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.** {*;}
|
||||
|
||||
# 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.* <methods>;
|
||||
@Upload.* <methods>;
|
||||
@DownloadGroup.* <methods>;
|
||||
}
|
||||
|
||||
#友盟统计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 *;
|
||||
}
|
||||
-keep class com.idormy.sms.forwarder.utils.mail.** {*;}
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -7,6 +7,9 @@
|
|||
<uses-permission
|
||||
android:name="android.permission.QUERY_ALL_PACKAGES"
|
||||
tools:ignore="QueryAllPackagesPermission" />
|
||||
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
|
||||
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
|
||||
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
|
@ -16,9 +19,8 @@
|
|||
tools:ignore="ProtectedPermissions" />
|
||||
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
||||
<uses-permission
|
||||
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
|
||||
tools:ignore="ScopedStorage" />
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
|
||||
|
||||
<uses-permission android:name="android.permission.RECEIVE_SMS" />
|
||||
<uses-permission android:name="android.permission.READ_SMS" />
|
||||
|
@ -28,6 +30,8 @@
|
|||
<uses-permission android:name="android.permission.READ_PHONE_NUMBERS" />
|
||||
<uses-permission android:name="android.permission.READ_CALL_LOG" />
|
||||
<uses-permission android:name="android.permission.READ_CONTACTS" />
|
||||
<uses-permission android:name="android.permission.WRITE_CONTACTS" />
|
||||
<uses-permission android:name="android.permission.GET_ACCOUNTS" />
|
||||
<!--Android 9(API 级别 28)或更高版本并使用前台服务,则其必须请求 FOREGROUND_SERVICE 权限-->
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />
|
||||
|
@ -44,10 +48,16 @@
|
|||
<uses-permission
|
||||
android:name="android.permission.WRITE_SETTINGS"
|
||||
tools:ignore="ProtectedPermissions" />
|
||||
<!--进程杀死-->
|
||||
<uses-permission android:name="android.permission.KILL_BACKGROUND_PROCESSES" />
|
||||
<uses-permission
|
||||
android:name="android.permission.READ_LOGS"
|
||||
tools:ignore="ProtectedPermissions" />
|
||||
|
||||
<application
|
||||
android:name=".MyApplication"
|
||||
android:name=".App"
|
||||
android:allowBackup="true"
|
||||
android:configChanges="screenSize|keyboardHidden|orientation|keyboard"
|
||||
android:fullBackupContent="@xml/backup_descriptor"
|
||||
android:hardwareAccelerated="true"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
|
@ -55,95 +65,169 @@
|
|||
android:largeHeap="true"
|
||||
android:networkSecurityConfig="@xml/network_security_config"
|
||||
android:requestLegacyExternalStorage="true"
|
||||
android:roundIcon="@mipmap/ic_launcher"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/Theme.App"
|
||||
android:theme="@style/AppTheme"
|
||||
android:usesCleartextTraffic="true"
|
||||
tools:ignore="DataExtractionRules,UnusedAttribute">
|
||||
android:windowSoftInputMode="adjustPan|stateHidden"
|
||||
tools:ignore="DataExtractionRules,LockedOrientationActivity,UnusedAttribute"
|
||||
tools:replace="android:allowBackup">
|
||||
|
||||
<meta-data
|
||||
android:name="ScopedStorage"
|
||||
android:value="true" />
|
||||
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:name=".activity.SplashActivity"
|
||||
android:configChanges="screenSize|keyboardHidden|orientation|keyboard"
|
||||
android:excludeFromRecents="true"
|
||||
android:exported="true"
|
||||
tools:ignore="IntentFilterExportedReceiver">
|
||||
android:screenOrientation="portrait"
|
||||
android:taskAffinity=":splash"
|
||||
android:theme="@style/AppTheme.Launch.App"
|
||||
android:windowSoftInputMode="adjustPan|stateHidden"
|
||||
tools:ignore="TranslucentOrientation">
|
||||
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
|
||||
<data android:scheme="um.60254fc7425ec25f10f4293e" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<activity
|
||||
android:name=".AboutActivity"
|
||||
android:name=".activity.MainActivity"
|
||||
android:configChanges="screenSize|keyboardHidden|orientation|keyboard"
|
||||
android:exported="true"
|
||||
android:label="@string/about"
|
||||
tools:ignore="IntentFilterExportedReceiver">
|
||||
<intent-filter>
|
||||
<!--协议部分,随便设置-->
|
||||
<data
|
||||
android:host="main"
|
||||
android:scheme="forwarder" />
|
||||
<!--下面这几行也必须得设置-->
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
android:screenOrientation="portrait"
|
||||
android:windowSoftInputMode="adjustPan|stateHidden" />
|
||||
<activity
|
||||
android:name=".SettingActivity"
|
||||
android:name=".activity.LoginActivity"
|
||||
android:configChanges="screenSize|keyboardHidden|orientation|keyboard"
|
||||
android:exported="true"
|
||||
android:label="@string/setting" />
|
||||
<activity
|
||||
android:name=".CloneActivity"
|
||||
android:exported="true"
|
||||
android:label="@string/clone" />
|
||||
<activity
|
||||
android:name=".RuleActivity"
|
||||
android:exported="true"
|
||||
android:label="@string/rule_setting" />
|
||||
<activity
|
||||
android:name=".SenderActivity"
|
||||
android:exported="true"
|
||||
android:label="@string/sender_setting" />
|
||||
<activity
|
||||
android:name=".AppListActivity"
|
||||
android:exported="true"
|
||||
android:label="@string/app_list" />
|
||||
<activity
|
||||
android:name=".HelpActivity"
|
||||
android:exported="true"
|
||||
android:label="@string/help" />
|
||||
<activity
|
||||
android:name=".OnePixelActivity"
|
||||
android:configChanges="keyboardHidden|orientation|screenSize|navigation|keyboard"
|
||||
android:excludeFromRecents="true"
|
||||
android:exported="false"
|
||||
android:finishOnTaskLaunch="false"
|
||||
android:label="@string/one_pixel"
|
||||
android:launchMode="singleInstance"
|
||||
android:process=":live"
|
||||
android:theme="@style/OnePixelActivity" />
|
||||
android:screenOrientation="portrait"
|
||||
android:windowSoftInputMode="adjustPan|stateHidden" />
|
||||
<!--通用浏览器-->
|
||||
<activity
|
||||
android:name=".core.webview.AgentWebActivity"
|
||||
android:configChanges="screenSize|keyboardHidden|orientation|keyboard"
|
||||
android:exported="true"
|
||||
android:hardwareAccelerated="true"
|
||||
android:label="@string/app_browser_name"
|
||||
android:theme="@style/AppTheme">
|
||||
<!-- Scheme -->
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<action android:name="com.xuexiang.xui.applink" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
|
||||
<data android:scheme="http" />
|
||||
<data android:scheme="https" />
|
||||
<data android:scheme="about" />
|
||||
<data android:scheme="javascript" />
|
||||
<!-- 设置自己的deeplink -->
|
||||
<!-- <data-->
|
||||
<!-- android:host="xxx.com"-->
|
||||
<!-- android:scheme="xui"/>-->
|
||||
</intent-filter>
|
||||
<!-- AppLink -->
|
||||
<intent-filter
|
||||
android:autoVerify="true"
|
||||
tools:targetApi="m">
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
|
||||
<data android:scheme="http" />
|
||||
<data android:scheme="https" />
|
||||
<data android:scheme="inline" />
|
||||
<data android:mimeType="text/html" />
|
||||
<data android:mimeType="text/plain" />
|
||||
<data android:mimeType="application/xhtml+xml" />
|
||||
<data android:mimeType="application/vnd.wap.xhtml+xml" />
|
||||
<!-- 设置自己的applink -->
|
||||
<!-- <data-->
|
||||
<!-- android:host="xxx.com"-->
|
||||
<!-- android:scheme="http"/>-->
|
||||
<!-- <data-->
|
||||
<!-- android:host="xxx.com"-->
|
||||
<!-- android:scheme="https"/>-->
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<!--fragment的页面容器-->
|
||||
<activity
|
||||
android:name=".core.BaseActivity"
|
||||
android:configChanges="screenSize|keyboardHidden|orientation|keyboard"
|
||||
android:exported="true"
|
||||
android:screenOrientation="portrait"
|
||||
android:windowSoftInputMode="adjustPan|stateHidden" />
|
||||
<!-- 版本更新提示-->
|
||||
<activity
|
||||
android:name=".utils.update.UpdateTipDialog"
|
||||
android:exported="true"
|
||||
android:screenOrientation="portrait"
|
||||
android:theme="@style/DialogTheme" />
|
||||
<!-- Webview拦截提示弹窗-->
|
||||
<activity
|
||||
android:name=".core.webview.WebViewInterceptDialog"
|
||||
android:exported="true"
|
||||
android:screenOrientation="portrait"
|
||||
android:theme="@style/DialogTheme" />
|
||||
<!-- applink的中转页面 -->
|
||||
<activity
|
||||
android:name=".core.XPageTransferActivity"
|
||||
android:configChanges="screenSize|keyboardHidden|orientation|keyboard"
|
||||
android:exported="true"
|
||||
android:screenOrientation="portrait"
|
||||
android:windowSoftInputMode="adjustPan|stateHidden" />
|
||||
|
||||
<!--屏幕自适应设计图-->
|
||||
<meta-data
|
||||
android:name="design_width_in_dp"
|
||||
android:exported="true"
|
||||
android:value="360" />
|
||||
<meta-data
|
||||
android:name="design_height_in_dp"
|
||||
android:exported="true"
|
||||
android:value="640" />
|
||||
|
||||
<service
|
||||
android:name=".service.HttpService"
|
||||
android:enabled="true" />
|
||||
<service
|
||||
android:name=".service.BatteryService"
|
||||
android:enabled="true" />
|
||||
<service
|
||||
android:name=".service.ForegroundService"
|
||||
android:enabled="true" />
|
||||
<service
|
||||
android:name=".service.NotifyService"
|
||||
android:enabled="true"
|
||||
android:exported="false"
|
||||
android:label="@string/app_name"
|
||||
android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE">
|
||||
<intent-filter>
|
||||
<action android:name="android.service.notification.NotificationListenerService" />
|
||||
</intent-filter>
|
||||
</service>
|
||||
|
||||
<receiver
|
||||
android:name=".receiver.RebootBroadcastReceiver"
|
||||
android:name=".receiver.BootReceiver"
|
||||
android:directBootAware="true"
|
||||
android:exported="true"
|
||||
tools:ignore="IntentFilterExportedReceiver">
|
||||
<intent-filter android:priority="2147483647">
|
||||
<action android:name="android.intent.action.BOOT_COMPLETED" />
|
||||
<action android:name="android.intent.action.USER_PRESENT" />
|
||||
<action android:name="android.intent.action.PACKAGE_RESTARTED" />
|
||||
<action android:name="android.intent.action.LOCKED_BOOT_COMPLETED" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
<receiver
|
||||
android:name=".receiver.SmsBroadcastReceiver"
|
||||
android:name=".receiver.SmsReceiver"
|
||||
android:exported="true"
|
||||
android:permission="android.permission.BROADCAST_SMS"
|
||||
tools:ignore="IntentFilterExportedReceiver">
|
||||
|
@ -163,30 +247,6 @@
|
|||
<action android:name="android.intent.action.PHONE_STATE" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
<receiver android:name=".receiver.ScreenBroadcastReceiver" />
|
||||
|
||||
<service
|
||||
android:name=".service.FrontService"
|
||||
android:enabled="true" />
|
||||
<service
|
||||
android:name=".service.BatteryService"
|
||||
android:enabled="true" />
|
||||
<service
|
||||
android:name=".service.MusicService"
|
||||
android:enabled="true"
|
||||
android:exported="true"
|
||||
tools:ignore="ExportedService" />
|
||||
<service
|
||||
android:name=".service.NotifyService"
|
||||
android:enabled="true"
|
||||
android:exported="false"
|
||||
android:label="@string/app_name"
|
||||
android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE">
|
||||
<intent-filter>
|
||||
<action android:name="android.service.notification.NotificationListenerService" />
|
||||
</intent-filter>
|
||||
</service>
|
||||
|
||||
</application>
|
||||
|
||||
</manifest>
|
|
@ -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. 多米科技和合作公司有权按需要修改或变更所提供的收费服务、收费标准、收费方式、服务费及服务条款。多米科技在提供服务时,可能现在或日后对部分服务的用户开始收取一定的费用如用户拒绝支付该等费用,则不能在收费开始后继续使用相关的服务。多米科技和合作公司将尽最大努力通过电邮或其他方式通知用户有关的修改或变更。
|
|
@ -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 信息)以提供统计分析服务,并通过地理位置校准报表数据准确性,提供基础反作弊能力。
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"Code": 0,
|
||||
"Data": [
|
||||
{
|
||||
"title": "新用户必读",
|
||||
"content": "开始设置之前,请您认真地看一遍 <a href=\"https://gitee.com/pp/SmsForwarder/wikis/pages\"><font color=\"#800080\">Wiki</font></a> !<br />\n遇到问题,请按照 <a href=\"https://gitee.com/pp/SmsForwarder/wikis/pages?sort_id=4877445&doc_id=1821427\"><font color=\"#0000FF\">常见问题</font></a> 章节进行排查!<br />\n没找到答案的,再加入QQ互助交流群里提问,请清楚地描述问题,并给出对应的配置截图与相关日志,方便大家直观的判断问题! "
|
||||
},
|
||||
{
|
||||
"title": "QQ互助交流群",
|
||||
"content": "<a href=\"http://qm.qq.com/cgi-bin/qm/qr?k=Mj5m39bqy6eodOImrFLI19Tdeqvv-9zf\">QQ互助交流①群</a><br /><a href=\"http://qm.qq.com/cgi-bin/qm/qr?k=jPXy4YaUzA7Uo0yPPbZXdkb66NS1smU_\">QQ互助交流②群</a><br /><a href=\"https://qm.qq.com/cgi-bin/qm/qr?k=itGVH4lB-HLGyJGTfP_5rjyCQj6kgIBt\">QQ互助交流③群</a><br /><a href=\"https://qm.qq.com/cgi-bin/qm/qr?k=83fYtikg2ARpUECsgJv9CcWTKQB74REK\">QQ互助交流④群</a><br /><a href=\"https://qm.qq.com/cgi-bin/qm/qr?k=CcamLcA-QVN-KqCDjeMZqdTx8IGlJrVx\">QQ互助交流⑤群</a>"
|
||||
},
|
||||
{
|
||||
"title": "打赏名单",
|
||||
"content": "感谢热心网友们对开源项目的喜爱和支持!<a href=\"https://gitee.com/pp/SmsForwarder/wikis/pages?sort_id=4912193&doc_id=1821427\"><font color=\"#800080\">查看赞助名单!</font></a>"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
<html>
|
||||
Hello SmsForwarder!!!
|
||||
</html>
|
|
@ -0,0 +1,3 @@
|
|||
<html>
|
||||
Welcome to Main Page!!!
|
||||
</html>
|
Binary file not shown.
Before Width: | Height: | Size: 31 KiB |
|
@ -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<String> 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<String> 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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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<Int, SimInfo> = mutableMapOf()
|
||||
|
||||
//已安装App信息
|
||||
var AppInfoList: List<AppUtils.AppInfo> = arrayListOf()
|
||||
|
||||
/**
|
||||
* @return 当前app是否是调试开发模式
|
||||
*/
|
||||
val isDebug: Boolean
|
||||
get() = BuildConfig.DEBUG
|
||||
|
||||
//Cactus结束时间
|
||||
val mEndDate = MutableLiveData<String>()
|
||||
|
||||
//Cactus上次存活时间
|
||||
val mLastTimer = MutableLiveData<String>()
|
||||
|
||||
//Cactus存活时间
|
||||
val mTimer = MutableLiveData<String>()
|
||||
|
||||
//Cactus运行状态
|
||||
val mStatus = MutableLiveData<Boolean>().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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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<AppInfo> 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<PackageInfo> 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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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<String> permissions, boolean all) {
|
||||
backupPath = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).getPath();
|
||||
backupPathTxt.setText(backupPath + File.separator + backupFile);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDenied(List<String> 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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
|
@ -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<LogVo> 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<String> 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<String> 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<LogVo> 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();
|
||||
}
|
||||
}
|
|
@ -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<PhoneUtils.SimInfo> 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<ActivityManager.AppTask> 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;
|
||||
}
|
||||
}
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -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<RuleModel> 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<SenderModel> 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<SenderModel> 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;
|
||||
}
|
||||
|
||||
}
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -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<ViewBinding?>() {
|
||||
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)
|
||||
}
|
||||
}
|
|
@ -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<ActivityMainBinding?>(),
|
||||
View.OnClickListener,
|
||||
BottomNavigationView.OnNavigationItemSelectedListener,
|
||||
Toolbar.OnMenuItemClickListener,
|
||||
RecyclerViewHolder.OnItemClickListener<PageInfo> {
|
||||
|
||||
private val TAG: String = MainActivity::class.java.simpleName
|
||||
private lateinit var mTitles: Array<String>
|
||||
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<TextView>(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<XPageFragment>) //跳转的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!!)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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<AppInfo> {
|
||||
private final int resourceId;
|
||||
private List<AppInfo> list;
|
||||
|
||||
// 适配器的构造函数,把要适配的数据传入这里
|
||||
public AppAdapter(Context context, int textViewResourceId, List<AppInfo> 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<AppInfo> appModels) {
|
||||
if (list != null) {
|
||||
list = appModels;
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
}
|
||||
|
||||
public void del(List<AppInfo> appModels) {
|
||||
if (list != null) {
|
||||
list = appModels;
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
}
|
||||
|
||||
public void update(List<AppInfo> appModels) {
|
||||
if (list != null) {
|
||||
list = appModels;
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
}
|
||||
|
||||
// 定义一个内部类,用于对控件的实例进行缓存
|
||||
static class ViewHolder {
|
||||
TextView appName;
|
||||
TextView pkgName;
|
||||
ImageView appIcon;
|
||||
TextView verName;
|
||||
TextView verCode;
|
||||
}
|
||||
}
|
|
@ -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<AppInfo?>(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<ImageView>(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)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<Frpc, MyViewHolder>(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<Frpc> = object : DiffUtil.ItemCallback<Frpc>() {
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<LogVo> {
|
||||
private final int resourceId;
|
||||
private List<LogVo> list;
|
||||
|
||||
// 适配器的构造函数,把要适配的数据传入这里
|
||||
public LogAdapter(Context context, int textViewResourceId, List<LogVo> 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<LogVo> logVos) {
|
||||
if (list != null) {
|
||||
list = logVos;
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
}
|
||||
|
||||
public void del(List<LogVo> logVos) {
|
||||
if (list != null) {
|
||||
list = logVos;
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
}
|
||||
|
||||
public void update(List<LogVo> logVos) {
|
||||
if (list != null) {
|
||||
list = logVos;
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
}
|
||||
|
||||
public void onDateChange(List<LogVo> logVos) {
|
||||
list = logVos;
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
// 定义一个内部类,用于对控件的实例进行缓存
|
||||
static class ViewHolder {
|
||||
TextView tLogFrom;
|
||||
TextView tLogContent;
|
||||
TextView tLogRule;
|
||||
TextView tLogTime;
|
||||
ImageView senderImage;
|
||||
ImageView simImage;
|
||||
ImageView statusImage;
|
||||
}
|
||||
}
|
|
@ -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<LogsAndRuleAndSender, MyViewHolder>(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<LogsAndRuleAndSender> = object : DiffUtil.ItemCallback<LogsAndRuleAndSender>() {
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<RuleModel> {
|
||||
private final int resourceId;
|
||||
private List<RuleModel> list;
|
||||
|
||||
// 适配器的构造函数,把要适配的数据传入这里
|
||||
public RuleAdapter(Context context, int textViewResourceId, List<RuleModel> 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> 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<RuleModel> ruleModels) {
|
||||
if (list != null) {
|
||||
list = ruleModels;
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
}
|
||||
|
||||
public void del(List<RuleModel> ruleModels) {
|
||||
if (list != null) {
|
||||
list = ruleModels;
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
}
|
||||
|
||||
public void update(List<RuleModel> ruleModels) {
|
||||
if (list != null) {
|
||||
list = ruleModels;
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
}
|
||||
|
||||
// 定义一个内部类,用于对控件的实例进行缓存
|
||||
static class ViewHolder {
|
||||
TextView ruleMatch;
|
||||
TextView ruleSender;
|
||||
ImageView ruleImage;
|
||||
ImageView ruleStatus;
|
||||
ImageView ruleSenderImage;
|
||||
ImageView ruleSenderStatus;
|
||||
}
|
||||
|
||||
}
|
|
@ -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<RuleAndSender, MyViewHolder>(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<RuleAndSender> = object : DiffUtil.ItemCallback<RuleAndSender>() {
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<SenderModel> {
|
||||
private final int resourceId;
|
||||
private List<SenderModel> list;
|
||||
|
||||
// 适配器的构造函数,把要适配的数据传入这里
|
||||
public SenderAdapter(Context context, int textViewResourceId, List<SenderModel> 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<SenderModel> senderModels) {
|
||||
if (list != null) {
|
||||
list = senderModels;
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
}
|
||||
|
||||
public void del(List<SenderModel> senderModels) {
|
||||
if (list != null) {
|
||||
list = senderModels;
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
}
|
||||
|
||||
public void update(List<SenderModel> senderModels) {
|
||||
if (list != null) {
|
||||
list = senderModels;
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
}
|
||||
|
||||
// 定义一个内部类,用于对控件的实例进行缓存
|
||||
static class ViewHolder {
|
||||
ImageView senderImage;
|
||||
ImageView senderStatus;
|
||||
TextView senderName;
|
||||
}
|
||||
|
||||
}
|
|
@ -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<Sender, MyViewHolder>(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<Sender> = object : DiffUtil.ItemCallback<Sender>() {
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<PageInfo>) : BaseRecyclerAdapter<PageInfo>(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)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<T>(collection: Collection<T>?) :
|
||||
BaseRecyclerAdapter<T>(collection) {
|
||||
/**
|
||||
* 是否已经加载成功
|
||||
*/
|
||||
private var mHasLoad = false
|
||||
private val mBroccoliMap: MutableMap<View, Broccoli> = 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<T>): XRecyclerAdapter<*, *> {
|
||||
mHasLoad = true
|
||||
return super.refresh(collection)
|
||||
}
|
||||
|
||||
/**
|
||||
* 资源释放,防止内存泄漏
|
||||
*/
|
||||
fun recycle() {
|
||||
for (broccoli in mBroccoliMap.values) {
|
||||
broccoli.removeAllPlaceholders()
|
||||
}
|
||||
mBroccoliMap.clear()
|
||||
clear()
|
||||
}
|
||||
}
|
|
@ -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<T> : SimpleDelegateAdapter<T> {
|
||||
/**
|
||||
* 是否已经加载成功
|
||||
*/
|
||||
private var mHasLoad = false
|
||||
private val mBroccoliMap: MutableMap<View, Broccoli> = HashMap()
|
||||
|
||||
constructor(layoutId: Int, layoutHelper: LayoutHelper) : super(layoutId, layoutHelper)
|
||||
constructor(layoutId: Int, layoutHelper: LayoutHelper, list: Collection<T>?) : super(
|
||||
layoutId,
|
||||
layoutHelper,
|
||||
list
|
||||
)
|
||||
|
||||
constructor(layoutId: Int, layoutHelper: LayoutHelper?, data: Array<T>?) : 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<T>?): XDelegateAdapter<*, *> {
|
||||
mHasLoad = true
|
||||
return super.refresh(collection)
|
||||
}
|
||||
|
||||
/**
|
||||
* 资源释放,防止内存泄漏
|
||||
*/
|
||||
fun recycle() {
|
||||
for (broccoli in mBroccoliMap.values) {
|
||||
broccoli.removeAllPlaceholders()
|
||||
}
|
||||
mBroccoliMap.clear()
|
||||
clear()
|
||||
}
|
||||
}
|
|
@ -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<T> : XDelegateAdapter<T, RecyclerViewHolder> {
|
||||
constructor() : super()
|
||||
constructor(list: Collection<T>?) : super(list)
|
||||
constructor(data: Array<T>?) : 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)))
|
||||
}
|
||||
}
|
|
@ -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<T> : BaseDelegateAdapter<T> {
|
||||
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<T>?) : super(list) {
|
||||
mLayoutId = layoutId
|
||||
mLayoutHelper = layoutHelper
|
||||
}
|
||||
|
||||
constructor(layoutId: Int, layoutHelper: LayoutHelper, data: Array<T>?) : super(data) {
|
||||
mLayoutId = layoutId
|
||||
mLayoutHelper = layoutHelper
|
||||
}
|
||||
|
||||
override fun getItemLayoutId(viewType: Int): Int {
|
||||
return mLayoutId
|
||||
}
|
||||
|
||||
override fun onCreateLayoutHelper(): LayoutHelper {
|
||||
return mLayoutHelper
|
||||
}
|
||||
}
|
|
@ -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<T, V : RecyclerView.ViewHolder?> : DelegateAdapter.Adapter<V> {
|
||||
/**
|
||||
* 数据源
|
||||
*/
|
||||
private val mData: MutableList<T> = ArrayList()
|
||||
/**
|
||||
* @return 当前列表的选中项
|
||||
*/
|
||||
/**
|
||||
* 当前点击的条目
|
||||
*/
|
||||
private var selectPosition = -1
|
||||
|
||||
constructor()
|
||||
constructor(list: Collection<T>?) {
|
||||
if (list != null) {
|
||||
mData.addAll(list)
|
||||
}
|
||||
}
|
||||
|
||||
constructor(data: Array<T>?) {
|
||||
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<T>
|
||||
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<T>?): XDelegateAdapter<*, *> {
|
||||
if (collection != null) {
|
||||
mData.clear()
|
||||
mData.addAll(collection)
|
||||
selectPosition = -1
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* 刷新列表数据
|
||||
*
|
||||
* @param array
|
||||
* @return
|
||||
*/
|
||||
@SuppressLint("NotifyDataSetChanged")
|
||||
fun refresh(array: Array<T>?): 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<T>?): XDelegateAdapter<*, *> {
|
||||
if (collection != null) {
|
||||
mData.addAll(collection)
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载更多
|
||||
*
|
||||
* @param array
|
||||
* @return
|
||||
*/
|
||||
@SuppressLint("NotifyDataSetChanged")
|
||||
fun loadMore(array: Array<T>?): 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()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<String>): Array<AppListAdapterItem?> {
|
||||
val array = arrayOfNulls<AppListAdapterItem>(title.size)
|
||||
for (i in array.indices) {
|
||||
array[i] = AppListAdapterItem(title[i])
|
||||
}
|
||||
return array
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<T> : BaseEditSpinnerAdapter<T>, 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<T>?) : super(data)
|
||||
|
||||
/**
|
||||
* 构造方法
|
||||
*
|
||||
* @param data 选项数据
|
||||
*/
|
||||
constructor(data: Array<T>?) : 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(), "<font color=\"$mFilterColor\">$keyword</font>"))
|
||||
} 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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<CharSequence>): Array<SenderAdapterItem?> {
|
||||
val array = arrayOfNulls<SenderAdapterItem>(title.size)
|
||||
for (i in array.indices) {
|
||||
array[i] = SenderAdapterItem(title[i])
|
||||
}
|
||||
return array
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<T> : BaseEditSpinnerAdapter<T>, 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<T>?) : super(data)
|
||||
|
||||
/**
|
||||
* 构造方法
|
||||
*
|
||||
* @param data 选项数据
|
||||
*/
|
||||
constructor(data: Array<T>?) : 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(), "<font color=\"$mFilterColor\">$keyword</font>"))
|
||||
} 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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<Binding : ViewBinding?> : 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 <T : XPageFragment?> openPage(clazz: Class<T>?, addToBackStack: Boolean): T {
|
||||
val page = CoreSwitchBean(clazz)
|
||||
.setAddToBackStack(addToBackStack)
|
||||
return openPage(page) as T
|
||||
}
|
||||
|
||||
/**
|
||||
* 打开fragment
|
||||
*
|
||||
* @return 打开的fragment对象
|
||||
*/
|
||||
fun <T : XPageFragment?> openNewPage(clazz: Class<T>?): T {
|
||||
val page = CoreSwitchBean(clazz)
|
||||
.setNewActivity(true)
|
||||
return openPage(page) as T
|
||||
}
|
||||
|
||||
/**
|
||||
* 切换fragment
|
||||
*
|
||||
* @param clazz 页面类
|
||||
* @return 打开的fragment对象
|
||||
*/
|
||||
fun <T : XPageFragment?> switchPage(clazz: Class<T>?): 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"
|
||||
}
|
||||
}
|
|
@ -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<Map<String?, String?>?> = ArrayList()
|
||||
for (content in mSimpleData) {
|
||||
val item: MutableMap<String?, String?> = 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)
|
||||
}
|
||||
}
|
|
@ -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<Binding : ViewBinding?> : 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 <T>
|
||||
* @return
|
||||
</T> */
|
||||
fun <T : XPageFragment> openNewPage(clazz: Class<T>): Fragment? {
|
||||
return PageOption.to(clazz)
|
||||
.setNewActivity(true)
|
||||
.open(this)
|
||||
}
|
||||
|
||||
/**
|
||||
* 打开一个新的页面【建议只在主tab页使用】
|
||||
*
|
||||
* @param pageName 页面名
|
||||
* @param <T>
|
||||
* @return
|
||||
</T> */
|
||||
fun <T : XPageFragment> openNewPage(pageName: String): Fragment? {
|
||||
return PageOption.to(pageName)
|
||||
.setAnim(CoreAnim.slide)
|
||||
.setNewActivity(true)
|
||||
.open(this)
|
||||
}
|
||||
|
||||
/**
|
||||
* 打开一个新的页面【建议只在主tab页使用】
|
||||
*
|
||||
* @param clazz 页面的类
|
||||
* @param containActivityClazz 页面容器
|
||||
* @param <T>
|
||||
* @return
|
||||
</T> */
|
||||
fun <T : XPageFragment?> openNewPage(
|
||||
clazz: Class<T>,
|
||||
containActivityClazz: Class<out XPageActivity>,
|
||||
): Fragment? {
|
||||
return PageOption.to(clazz)
|
||||
.setNewActivity(true)
|
||||
.setContainActivityClazz(containActivityClazz)
|
||||
.open(this)
|
||||
}
|
||||
|
||||
/**
|
||||
* 打开一个新的页面【建议只在主tab页使用】
|
||||
*
|
||||
* @param clazz 页面的类
|
||||
* @param key 入参的键
|
||||
* @param value 入参的值
|
||||
* @param <T>
|
||||
* @return
|
||||
</T> */
|
||||
fun <T : XPageFragment?> openNewPage(clazz: Class<T>, 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 <T>
|
||||
* @return
|
||||
</T> */
|
||||
fun <T : XPageFragment?> openPage(
|
||||
clazz: Class<T>?,
|
||||
addToBackStack: Boolean,
|
||||
key: String?,
|
||||
value: String?,
|
||||
): Fragment? {
|
||||
return PageOption(clazz)
|
||||
.setAddToBackStack(addToBackStack)
|
||||
.putString(key, value)
|
||||
.open(this)
|
||||
}
|
||||
|
||||
/**
|
||||
* 打开页面
|
||||
*
|
||||
* @param clazz 页面的类
|
||||
* @param key 入参的键
|
||||
* @param value 入参的值
|
||||
* @param <T>
|
||||
* @return
|
||||
</T> */
|
||||
fun <T : XPageFragment?> openPage(clazz: Class<T>?, key: String?, value: Any?): Fragment? {
|
||||
return openPage(clazz, true, key, value)
|
||||
}
|
||||
|
||||
/**
|
||||
* 打开页面
|
||||
*
|
||||
* @param clazz 页面的类
|
||||
* @param addToBackStack 是否加入回退栈
|
||||
* @param key 入参的键
|
||||
* @param value 入参的值
|
||||
* @param <T>
|
||||
* @return
|
||||
</T> */
|
||||
fun <T : XPageFragment?> openPage(
|
||||
clazz: Class<T>?,
|
||||
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 <T>
|
||||
* @return
|
||||
</T> */
|
||||
fun <T : XPageFragment?> openPage(clazz: Class<T>?, key: String?, value: String?): Fragment? {
|
||||
return PageOption(clazz)
|
||||
.putString(key, value)
|
||||
.open(this)
|
||||
}
|
||||
|
||||
/**
|
||||
* 打开页面,需要结果返回
|
||||
*
|
||||
* @param clazz 页面的类
|
||||
* @param key 入参的键
|
||||
* @param value 入参的值
|
||||
* @param requestCode 请求码
|
||||
* @param <T>
|
||||
* @return
|
||||
</T> */
|
||||
fun <T : XPageFragment?> openPageForResult(
|
||||
clazz: Class<T>?,
|
||||
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 <T>
|
||||
* @return
|
||||
</T> */
|
||||
fun <T : XPageFragment?> openPageForResult(
|
||||
clazz: Class<T>?,
|
||||
key: String?,
|
||||
value: String?,
|
||||
requestCode: Int,
|
||||
): Fragment? {
|
||||
return PageOption(clazz)
|
||||
.setRequestCode(requestCode)
|
||||
.putString(key, value)
|
||||
.open(this)
|
||||
}
|
||||
|
||||
/**
|
||||
* 打开页面,需要结果返回
|
||||
*
|
||||
* @param clazz 页面的类
|
||||
* @param requestCode 请求码
|
||||
* @param <T>
|
||||
* @return
|
||||
</T> */
|
||||
fun <T : XPageFragment?> openPageForResult(clazz: Class<T>?, 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 <T> 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)
|
||||
}
|
||||
}
|
|
@ -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 <T>
|
||||
* @return
|
||||
</T> */
|
||||
fun <T : XPageFragment?> openNewPage(clazz: Class<T>?): Fragment? {
|
||||
return PageOption(clazz)
|
||||
.setNewActivity(true)
|
||||
.open(this)
|
||||
}
|
||||
|
||||
/**
|
||||
* 打开一个新的页面【建议只在主tab页使用】
|
||||
*
|
||||
* @param pageName 页面名
|
||||
* @param <T>
|
||||
* @return
|
||||
</T> */
|
||||
fun <T : XPageFragment?> openNewPage(pageName: String?): Fragment? {
|
||||
return PageOption(pageName)
|
||||
.setAnim(CoreAnim.slide)
|
||||
.setNewActivity(true)
|
||||
.open(this)
|
||||
}
|
||||
|
||||
/**
|
||||
* 打开一个新的页面【建议只在主tab页使用】
|
||||
*
|
||||
* @param clazz 页面的类
|
||||
* @param containActivityClazz 页面容器
|
||||
* @param <T>
|
||||
* @return
|
||||
</T> */
|
||||
fun <T : XPageFragment?> openNewPage(
|
||||
clazz: Class<T>?,
|
||||
containActivityClazz: Class<out XPageActivity?>,
|
||||
): Fragment? {
|
||||
return PageOption(clazz)
|
||||
.setNewActivity(true)
|
||||
.setContainActivityClazz(containActivityClazz)
|
||||
.open(this)
|
||||
}
|
||||
|
||||
/**
|
||||
* 打开一个新的页面【建议只在主tab页使用】
|
||||
*
|
||||
* @param clazz 页面的类
|
||||
* @param key 入参的键
|
||||
* @param value 入参的值
|
||||
* @param <T>
|
||||
* @return
|
||||
</T> */
|
||||
fun <T : XPageFragment?> openNewPage(clazz: Class<T>?, 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 <T>
|
||||
* @return
|
||||
</T> */
|
||||
fun <T : XPageFragment?> openPage(
|
||||
clazz: Class<T>?,
|
||||
addToBackStack: Boolean,
|
||||
key: String?,
|
||||
value: String?,
|
||||
): Fragment? {
|
||||
return PageOption(clazz)
|
||||
.setAddToBackStack(addToBackStack)
|
||||
.putString(key, value)
|
||||
.open(this)
|
||||
}
|
||||
|
||||
/**
|
||||
* 打开页面
|
||||
*
|
||||
* @param clazz 页面的类
|
||||
* @param key 入参的键
|
||||
* @param value 入参的值
|
||||
* @param <T>
|
||||
* @return
|
||||
</T> */
|
||||
fun <T : XPageFragment?> openPage(clazz: Class<T>?, key: String?, value: Any?): Fragment? {
|
||||
return openPage(clazz, true, key, value)
|
||||
}
|
||||
|
||||
/**
|
||||
* 打开页面
|
||||
*
|
||||
* @param clazz 页面的类
|
||||
* @param addToBackStack 是否加入回退栈
|
||||
* @param key 入参的键
|
||||
* @param value 入参的值
|
||||
* @param <T>
|
||||
* @return
|
||||
</T> */
|
||||
fun <T : XPageFragment?> openPage(
|
||||
clazz: Class<T>?,
|
||||
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 <T>
|
||||
* @return
|
||||
</T> */
|
||||
fun <T : XPageFragment?> openPage(clazz: Class<T>?, key: String?, value: String?): Fragment? {
|
||||
return PageOption(clazz)
|
||||
.putString(key, value)
|
||||
.open(this)
|
||||
}
|
||||
|
||||
/**
|
||||
* 打开页面,需要结果返回
|
||||
*
|
||||
* @param clazz 页面的类
|
||||
* @param key 入参的键
|
||||
* @param value 入参的值
|
||||
* @param requestCode 请求码
|
||||
* @param <T>
|
||||
* @return
|
||||
</T> */
|
||||
fun <T : XPageFragment?> openPageForResult(
|
||||
clazz: Class<T>?,
|
||||
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 <T>
|
||||
* @return
|
||||
</T> */
|
||||
fun <T : XPageFragment?> openPageForResult(
|
||||
clazz: Class<T>?,
|
||||
key: String?,
|
||||
value: String?,
|
||||
requestCode: Int,
|
||||
): Fragment? {
|
||||
return PageOption(clazz)
|
||||
.setRequestCode(requestCode)
|
||||
.putString(key, value)
|
||||
.open(this)
|
||||
}
|
||||
|
||||
/**
|
||||
* 打开页面,需要结果返回
|
||||
*
|
||||
* @param clazz 页面的类
|
||||
* @param requestCode 请求码
|
||||
* @param <T>
|
||||
* @return
|
||||
</T> */
|
||||
fun <T : XPageFragment?> openPageForResult(clazz: Class<T>?, 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`)
|
||||
}
|
||||
}
|
|
@ -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<UserManager>()!! }*/
|
||||
|
||||
|
||||
/*val directBootAware: Boolean get() = directBootSupported && dataStore.canToggleLocked
|
||||
val directBootSupported by lazy {
|
||||
Build.VERSION.SDK_INT >= 24 && try {
|
||||
app.getSystemService<DevicePolicyManager>()?.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))
|
||||
}
|
|
@ -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<Map<String?, String?>?>?) :
|
||||
BaseListAdapter<Map<String?, String?>, 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<String?, String?>, 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"
|
||||
}
|
||||
}
|
|
@ -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<ViewBinding?>() {
|
||||
|
||||
@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()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<ApiResult<List<TipInfo?>?>>
|
||||
}
|
||||
}
|
|
@ -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<T> : SimpleCallBack<T> {
|
||||
/**
|
||||
* 记录一下请求的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)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<T> : SimpleCallBack<T> {
|
||||
/**
|
||||
* 记录一下请求的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)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<T> : ProgressLoadingCallBack<T> {
|
||||
/**
|
||||
* 记录一下请求的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)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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开源之旅。
|
||||
*
|
||||
*<br></br>
|
||||
*/
|
||||
var title: String? = null
|
||||
var content: String? = null
|
||||
override fun toString(): String {
|
||||
return "TipInfo{" +
|
||||
"title='" + title + '\'' +
|
||||
", content='" + content + '\'' +
|
||||
'}'
|
||||
}
|
||||
}
|
|
@ -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?
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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...")
|
||||
}
|
||||
}
|
|
@ -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<T> : BaseSubscriber<T> {
|
||||
/**
|
||||
* 记录一下请求的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)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<T> : ProgressLoadingSubscriber<T> {
|
||||
/**
|
||||
* 记录一下请求的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)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<T> : BaseSubscriber<T> {
|
||||
/**
|
||||
* 记录一下请求的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)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<String, Long?>()
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<ViewBinding?>() {
|
||||
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()
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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:")
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<Activity> = 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<String>,
|
||||
callback: Handler.Callback,
|
||||
) {
|
||||
// 使用默认的UI
|
||||
super.onSelectItemsPrompt(view, url, items, callback)
|
||||
}
|
||||
|
||||
}
|
|
@ -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<WebView?, ViewGroup?> {
|
||||
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
|
||||
}
|
||||
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<FragmentAgentwebBinding?>(), 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<String, Long?>()
|
||||
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!!)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -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 + '\'' +
|
||||
'}';
|
||||
}
|
||||
}
|
|
@ -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<List<Config>> getAll();
|
||||
|
||||
@Query("SELECT * FROM config where uid=:uid")
|
||||
Single<Config> getConfigByUid(String uid);
|
||||
|
||||
@Update
|
||||
Completable update(Config config);
|
||||
|
||||
@Insert
|
||||
Completable insert(Config config);
|
||||
|
||||
@Delete
|
||||
Completable delete(Config config);
|
||||
}
|
|
@ -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<Frpc>
|
||||
|
||||
//TODO:允许主线程访问,后面再优化
|
||||
@Query("SELECT * FROM Frpc where autorun=1")
|
||||
fun getAutorun(): List<Frpc>
|
||||
|
||||
@Query("SELECT * FROM Frpc ORDER BY time DESC")
|
||||
fun pagingSource(): PagingSource<Int, Frpc>
|
||||
|
||||
}
|
|
@ -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<Logs>
|
||||
|
||||
@Query("SELECT count(*) FROM Logs where type=:type and forward_status=:forwardStatus")
|
||||
fun count(type: String, forwardStatus: Int): Single<Int>
|
||||
|
||||
@Transaction
|
||||
@Query("SELECT * FROM Logs WHERE type = :type ORDER BY id DESC")
|
||||
fun pagingSource(type: String): PagingSource<Int, LogsAndRuleAndSender>
|
||||
|
||||
@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
|
||||
}
|
|
@ -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<Rule>
|
||||
|
||||
@Query("SELECT count(*) FROM Rule where type=:type and status=:status")
|
||||
fun count(type: String, status: Int): Single<Int>
|
||||
|
||||
/*@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<Int, Rule>*/
|
||||
|
||||
@Transaction
|
||||
@Query("SELECT * FROM Rule where type=:type ORDER BY id DESC")
|
||||
fun pagingSource(type: String): PagingSource<Int, RuleAndSender>
|
||||
|
||||
@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<RuleAndSender>
|
||||
|
||||
//TODO:允许主线程访问,后面再优化
|
||||
@Query("SELECT * FROM rule ORDER BY id ASC")
|
||||
fun getAll(): List<Rule>
|
||||
}
|
|
@ -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<Sender>
|
||||
|
||||
@Query("SELECT count(*) FROM Sender where type=:type and status=:status")
|
||||
fun count(type: String, status: Int): Single<Int>
|
||||
|
||||
@Query("SELECT * FROM Sender where status=:status ORDER BY id DESC")
|
||||
fun pagingSource(status: Int): PagingSource<Int, Sender>
|
||||
|
||||
@Query("SELECT * FROM sender ORDER BY id DESC")
|
||||
fun getAll(): Single<List<Sender>>
|
||||
|
||||
@Query("SELECT COUNT(id) FROM sender WHERE status = 1")
|
||||
fun getOnCount(): Flow<Long>
|
||||
|
||||
//TODO:允许主线程访问,后面再优化
|
||||
@Query("SELECT * FROM sender ORDER BY id ASC")
|
||||
fun getAll2(): List<Sender>
|
||||
|
||||
@Query("DELETE FROM Sender")
|
||||
fun deleteAll()
|
||||
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
||||
}
|
|
@ -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
|
|
@ -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
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue