修复:重启手机自动启动APP时加载配置失败 #233 #245

pull/286/head
pppscn 1 year ago
parent 4d7146bd7b
commit 5f55d20c83

@ -181,11 +181,11 @@ dependencies {
implementation files('libs/frpclib.aar') implementation files('libs/frpclib.aar')
testImplementation deps.junit testImplementation deps.junit
androidTestImplementation 'androidx.test.ext:junit:1.1.4' androidTestImplementation 'androidx.test.ext:junit:1.1.5'
androidTestImplementation deps.espresso.core androidTestImplementation deps.espresso.core
implementation 'androidx.core:core-ktx:1.9.0' implementation 'androidx.core:core-ktx:1.8.0'
implementation "androidx.activity:activity-ktx:1.6.1" implementation "androidx.activity:activity-ktx:1.5.1"
implementation "androidx.fragment:fragment-ktx:1.5.4" implementation "androidx.fragment:fragment-ktx:1.5.4"
implementation "androidx.cardview:cardview:1.0.0" implementation "androidx.cardview:cardview:1.0.0"
implementation 'androidx.appcompat:appcompat:1.5.1' implementation 'androidx.appcompat:appcompat:1.5.1'
@ -202,8 +202,6 @@ dependencies {
//WebView //WebView
implementation 'com.github.xuexiangjys.AgentWeb:agentweb-core:1.0.0' implementation 'com.github.xuexiangjys.AgentWeb:agentweb-core:1.0.0'
implementation 'com.github.xuexiangjys.AgentWeb:agentweb-download:1.0.0'// implementation 'com.github.xuexiangjys.AgentWeb:agentweb-download:1.0.0'//
//mmkvhttps://github.com/Tencent/MMKV
implementation 'com.tencent:mmkv:1.2.15'
//AutoSizehttps://github.com/JessYanCoding/AndroidAutoSize //AutoSizehttps://github.com/JessYanCoding/AndroidAutoSize
implementation 'me.jessyan:autosize:1.2.1' implementation 'me.jessyan:autosize:1.2.1'
//umeng //umeng

@ -58,6 +58,8 @@
<application <application
android:name=".App" android:name=".App"
android:configChanges="screenSize|keyboardHidden|orientation|keyboard" android:configChanges="screenSize|keyboardHidden|orientation|keyboard"
android:defaultToDeviceProtectedStorage="true"
android:directBootAware="true"
android:fullBackupContent="@xml/backup_descriptor" android:fullBackupContent="@xml/backup_descriptor"
android:hardwareAccelerated="true" android:hardwareAccelerated="true"
android:icon="@mipmap/ic_launcher" android:icon="@mipmap/ic_launcher"
@ -215,6 +217,7 @@
<receiver <receiver
android:name=".receiver.BootReceiver" android:name=".receiver.BootReceiver"
android:defaultToDeviceProtectedStorage="true"
android:directBootAware="true" android:directBootAware="true"
android:exported="true" android:exported="true"
tools:ignore="IntentFilterExportedReceiver"> tools:ignore="IntentFilterExportedReceiver">

@ -173,7 +173,7 @@ class App : Application(), CactusCallback, Configuration.Provider by Core {
setChannelId(FRONT_CHANNEL_ID) //渠道Id setChannelId(FRONT_CHANNEL_ID) //渠道Id
setChannelName(FRONT_CHANNEL_NAME) //渠道名 setChannelName(FRONT_CHANNEL_NAME) //渠道名
setTitle(getString(R.string.app_name)) setTitle(getString(R.string.app_name))
setContent(SettingUtils.notifyContent.toString()) setContent(SettingUtils.notifyContent)
setSmallIcon(R.drawable.ic_forwarder) setSmallIcon(R.drawable.ic_forwarder)
setLargeIcon(R.mipmap.ic_launcher) setLargeIcon(R.mipmap.ic_launcher)
setPendingIntent(pendingIntent) setPendingIntent(pendingIntent)
@ -214,8 +214,10 @@ class App : Application(), CactusCallback, Configuration.Provider by Core {
*/ */
private fun initLibs() { private fun initLibs() {
Core.init(this) Core.init(this)
// 配置文件初始化
SharedPreference.init(applicationContext)
// 转发历史工具类初始化 // 转发历史工具类初始化
HistoryUtils.init(this) HistoryUtils.init(applicationContext)
// X系列基础库初始化 // X系列基础库初始化
XBasicLibInit.init(this) XBasicLibInit.init(this)
// 版本更新初始化 // 版本更新初始化

@ -1,14 +1,11 @@
package com.idormy.sms.forwarder.activity package com.idormy.sms.forwarder.activity
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.util.Log
import android.view.KeyEvent import android.view.KeyEvent
import com.idormy.sms.forwarder.R import com.idormy.sms.forwarder.R
import com.idormy.sms.forwarder.utils.CommonUtils.Companion.showPrivacyDialog import com.idormy.sms.forwarder.utils.CommonUtils.Companion.showPrivacyDialog
import com.idormy.sms.forwarder.utils.MMKVUtils
import com.idormy.sms.forwarder.utils.SettingUtils import com.idormy.sms.forwarder.utils.SettingUtils
import com.idormy.sms.forwarder.utils.SettingUtils.Companion.isAgreePrivacy 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.utils.KeyboardUtils
import com.xuexiang.xui.widget.activity.BaseSplashActivity import com.xuexiang.xui.widget.activity.BaseSplashActivity
import com.xuexiang.xui.widget.dialog.materialdialog.DialogAction import com.xuexiang.xui.widget.dialog.materialdialog.DialogAction
@ -38,12 +35,6 @@ class SplashActivity : BaseSplashActivity(), CancelAdapt {
* 启动页结束后的动作 * 启动页结束后的动作
*/ */
override fun onSplashFinished() { override fun onSplashFinished() {
if (isFirstOpen) {
isFirstOpen = false
Log.d(TAG, "从SP迁移数据")
MMKVUtils.importSharedPreferences(this)
}
if (isAgreePrivacy) { if (isAgreePrivacy) {
whereToJump() whereToJump()
} else { } else {

@ -1,149 +1,149 @@
package com.idormy.sms.forwarder.fragment package com.idormy.sms.forwarder.fragment
import android.content.Intent import android.content.Intent
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import com.idormy.sms.forwarder.R import com.idormy.sms.forwarder.R
import com.idormy.sms.forwarder.core.BaseFragment import com.idormy.sms.forwarder.core.BaseFragment
import com.idormy.sms.forwarder.core.webview.AgentWebActivity import com.idormy.sms.forwarder.core.webview.AgentWebActivity
import com.idormy.sms.forwarder.databinding.FragmentAboutBinding import com.idormy.sms.forwarder.databinding.FragmentAboutBinding
import com.idormy.sms.forwarder.utils.CacheUtils import com.idormy.sms.forwarder.utils.CacheUtils
import com.idormy.sms.forwarder.utils.CommonUtils.Companion.gotoProtocol import com.idormy.sms.forwarder.utils.CommonUtils.Companion.gotoProtocol
import com.idormy.sms.forwarder.utils.CommonUtils.Companion.previewMarkdown import com.idormy.sms.forwarder.utils.CommonUtils.Companion.previewMarkdown
import com.idormy.sms.forwarder.utils.CommonUtils.Companion.previewPicture import com.idormy.sms.forwarder.utils.CommonUtils.Companion.previewPicture
import com.idormy.sms.forwarder.utils.HistoryUtils import com.idormy.sms.forwarder.utils.HistoryUtils
import com.idormy.sms.forwarder.utils.HttpServerUtils import com.idormy.sms.forwarder.utils.HttpServerUtils
import com.idormy.sms.forwarder.utils.XToastUtils import com.idormy.sms.forwarder.utils.XToastUtils
import com.idormy.sms.forwarder.utils.sdkinit.XUpdateInit import com.idormy.sms.forwarder.utils.sdkinit.XUpdateInit
import com.xuexiang.xaop.annotation.SingleClick import com.xuexiang.xaop.annotation.SingleClick
import com.xuexiang.xpage.annotation.Page import com.xuexiang.xpage.annotation.Page
import com.xuexiang.xui.widget.actionbar.TitleBar import com.xuexiang.xui.widget.actionbar.TitleBar
import com.xuexiang.xui.widget.textview.supertextview.SuperTextView import com.xuexiang.xui.widget.textview.supertextview.SuperTextView
import com.xuexiang.xutil.app.AppUtils import com.xuexiang.xutil.app.AppUtils
import com.xuexiang.xutil.file.FileUtils import com.xuexiang.xutil.file.FileUtils
import frpclib.Frpclib import frpclib.Frpclib
import java.io.File import java.io.File
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.* import java.util.*
@Page(name = "关于软件") @Page(name = "关于软件")
class AboutFragment : BaseFragment<FragmentAboutBinding?>(), SuperTextView.OnSuperTextViewClickListener { class AboutFragment : BaseFragment<FragmentAboutBinding?>(), SuperTextView.OnSuperTextViewClickListener {
override fun viewBindingInflate( override fun viewBindingInflate(
inflater: LayoutInflater, inflater: LayoutInflater,
container: ViewGroup, container: ViewGroup,
): FragmentAboutBinding { ): FragmentAboutBinding {
return FragmentAboutBinding.inflate(inflater, container, false) return FragmentAboutBinding.inflate(inflater, container, false)
} }
override fun initTitle(): TitleBar? { override fun initTitle(): TitleBar? {
val titleBar = super.initTitle()!!.setImmersive(false) val titleBar = super.initTitle()!!.setImmersive(false)
titleBar.setTitle(R.string.menu_about) titleBar.setTitle(R.string.menu_about)
return titleBar return titleBar
} }
/** /**
* 初始化控件 * 初始化控件
*/ */
override fun initViews() { override fun initViews() {
binding!!.menuVersion.setLeftString(String.format(resources.getString(R.string.about_app_version), AppUtils.getAppVersionName())) binding!!.menuVersion.setLeftString(String.format(resources.getString(R.string.about_app_version), AppUtils.getAppVersionName()))
binding!!.menuCache.setLeftString(String.format(resources.getString(R.string.about_cache_size), CacheUtils.getTotalCacheSize(requireContext()))) binding!!.menuCache.setLeftString(String.format(resources.getString(R.string.about_cache_size), CacheUtils.getTotalCacheSize(requireContext())))
if (FileUtils.isFileExists(context?.filesDir?.absolutePath + "/libs/libgojni.so")) { if (FileUtils.isFileExists(context?.filesDir?.absolutePath + "/libs/libgojni.so")) {
binding!!.menuFrpc.setLeftString(String.format(resources.getString(R.string.about_frpc_version), Frpclib.getVersion())) binding!!.menuFrpc.setLeftString(String.format(resources.getString(R.string.about_frpc_version), Frpclib.getVersion()))
binding!!.menuFrpc.visibility = View.VISIBLE binding!!.menuFrpc.visibility = View.VISIBLE
} }
val dateFormat = SimpleDateFormat("yyyy", Locale.CHINA) val dateFormat = SimpleDateFormat("yyyy", Locale.CHINA)
val currentYear = dateFormat.format(Date()) val currentYear = dateFormat.format(Date())
binding!!.copyright.text = java.lang.String.format(resources.getString(R.string.about_copyright), currentYear) binding!!.copyright.text = java.lang.String.format(resources.getString(R.string.about_copyright), currentYear)
} }
override fun initListeners() { override fun initListeners() {
binding!!.btnUpdate.setOnClickListener { binding!!.btnUpdate.setOnClickListener {
XUpdateInit.checkUpdate(requireContext(), true) XUpdateInit.checkUpdate(requireContext(), true)
} }
binding!!.btnCache.setOnClickListener { binding!!.btnCache.setOnClickListener {
HistoryUtils.clear() HistoryUtils.clearPreference()
CacheUtils.clearAllCache(requireContext()) CacheUtils.clearAllCache(requireContext())
XToastUtils.success(R.string.about_cache_purged) XToastUtils.success(R.string.about_cache_purged)
binding!!.menuCache.setLeftString(String.format(resources.getString(R.string.about_cache_size), CacheUtils.getTotalCacheSize(requireContext()))) binding!!.menuCache.setLeftString(String.format(resources.getString(R.string.about_cache_size), CacheUtils.getTotalCacheSize(requireContext())))
} }
binding!!.btnFrpc.setOnClickListener { binding!!.btnFrpc.setOnClickListener {
try { try {
val soFile = File(context?.filesDir?.absolutePath + "/libs/libgojni.so") val soFile = File(context?.filesDir?.absolutePath + "/libs/libgojni.so")
if (soFile.exists()) soFile.delete() if (soFile.exists()) soFile.delete()
XToastUtils.success(R.string.about_frpc_deleted) XToastUtils.success(R.string.about_frpc_deleted)
val intent: Intent? = context?.packageManager?.getLaunchIntentForPackage(context?.packageName.toString()) val intent: Intent? = context?.packageManager?.getLaunchIntentForPackage(context?.packageName.toString())
intent?.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP) intent?.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
startActivity(intent) startActivity(intent)
android.os.Process.killProcess(android.os.Process.myPid()) //杀掉以前进程 android.os.Process.killProcess(android.os.Process.myPid()) //杀掉以前进程
} catch (e: Exception) { } catch (e: Exception) {
e.printStackTrace() e.printStackTrace()
XToastUtils.error(e.message.toString()) XToastUtils.error(e.message.toString())
} }
} }
binding!!.btnGithub.setOnClickListener { binding!!.btnGithub.setOnClickListener {
AgentWebActivity.goWeb(context, getString(R.string.url_project_github)) AgentWebActivity.goWeb(context, getString(R.string.url_project_github))
} }
binding!!.btnGitee.setOnClickListener { binding!!.btnGitee.setOnClickListener {
AgentWebActivity.goWeb(context, getString(R.string.url_project_gitee)) AgentWebActivity.goWeb(context, getString(R.string.url_project_gitee))
} }
binding!!.btnAddQqGroup1.setOnClickListener { binding!!.btnAddQqGroup1.setOnClickListener {
AgentWebActivity.goWeb(context, getString(R.string.url_add_qq_group_1)) AgentWebActivity.goWeb(context, getString(R.string.url_add_qq_group_1))
} }
binding!!.btnAddQqGroup2.setOnClickListener { binding!!.btnAddQqGroup2.setOnClickListener {
AgentWebActivity.goWeb(context, getString(R.string.url_add_qq_group_2)) AgentWebActivity.goWeb(context, getString(R.string.url_add_qq_group_2))
} }
binding!!.btnAddQqGroup3.setOnClickListener { binding!!.btnAddQqGroup3.setOnClickListener {
AgentWebActivity.goWeb(context, getString(R.string.url_add_qq_group_3)) AgentWebActivity.goWeb(context, getString(R.string.url_add_qq_group_3))
} }
binding!!.btnAddQqGroup4.setOnClickListener { binding!!.btnAddQqGroup4.setOnClickListener {
AgentWebActivity.goWeb(context, getString(R.string.url_add_qq_group_4)) AgentWebActivity.goWeb(context, getString(R.string.url_add_qq_group_4))
} }
binding!!.btnAddQqGroup5.setOnClickListener { binding!!.btnAddQqGroup5.setOnClickListener {
AgentWebActivity.goWeb(context, getString(R.string.url_add_qq_group_5)) AgentWebActivity.goWeb(context, getString(R.string.url_add_qq_group_5))
} }
binding!!.menuWechatMiniprogram.setOnSuperTextViewClickListener(this) binding!!.menuWechatMiniprogram.setOnSuperTextViewClickListener(this)
binding!!.menuDonation.setOnSuperTextViewClickListener(this) binding!!.menuDonation.setOnSuperTextViewClickListener(this)
binding!!.menuWecomGroup.setOnSuperTextViewClickListener(this) binding!!.menuWecomGroup.setOnSuperTextViewClickListener(this)
binding!!.menuDingtalkGroup.setOnSuperTextViewClickListener(this) binding!!.menuDingtalkGroup.setOnSuperTextViewClickListener(this)
binding!!.menuQqChannel.setOnSuperTextViewClickListener(this) binding!!.menuQqChannel.setOnSuperTextViewClickListener(this)
binding!!.menuUserProtocol.setOnSuperTextViewClickListener(this) binding!!.menuUserProtocol.setOnSuperTextViewClickListener(this)
binding!!.menuPrivacyProtocol.setOnSuperTextViewClickListener(this) binding!!.menuPrivacyProtocol.setOnSuperTextViewClickListener(this)
} }
@SingleClick @SingleClick
override fun onClick(v: SuperTextView) { override fun onClick(v: SuperTextView) {
when (v.id) { when (v.id) {
R.id.menu_donation -> { R.id.menu_donation -> {
previewMarkdown(this, getString(R.string.about_item_donation_link), getString(R.string.url_donation_link), false) previewMarkdown(this, getString(R.string.about_item_donation_link), getString(R.string.url_donation_link), false)
} }
R.id.menu_wechat_miniprogram -> { R.id.menu_wechat_miniprogram -> {
if (HttpServerUtils.safetyMeasures != 3) { if (HttpServerUtils.safetyMeasures != 3) {
XToastUtils.error("微信小程序只支持SM4加密传输请前往主动控制·服务端修改安全措施") XToastUtils.error("微信小程序只支持SM4加密传输请前往主动控制·服务端修改安全措施")
//return //return
} }
previewPicture(this, getString(R.string.url_wechat_miniprogram), null) previewPicture(this, getString(R.string.url_wechat_miniprogram), null)
} }
R.id.menu_wecom_group -> { R.id.menu_wecom_group -> {
previewPicture(this, getString(R.string.url_wework_group), null) previewPicture(this, getString(R.string.url_wework_group), null)
} }
R.id.menu_dingtalk_group -> { R.id.menu_dingtalk_group -> {
previewPicture(this, getString(R.string.url_dingtalk_group), null) previewPicture(this, getString(R.string.url_dingtalk_group), null)
} }
R.id.menu_qq_channel -> { R.id.menu_qq_channel -> {
AgentWebActivity.goWeb(context, getString(R.string.url_qq_channel)) AgentWebActivity.goWeb(context, getString(R.string.url_qq_channel))
} }
R.id.menu_user_protocol -> { R.id.menu_user_protocol -> {
gotoProtocol(this, isPrivacy = false, isImmersive = false) gotoProtocol(this, isPrivacy = false, isImmersive = false)
} }
R.id.menu_privacy_protocol -> { R.id.menu_privacy_protocol -> {
gotoProtocol(this, isPrivacy = true, isImmersive = false) gotoProtocol(this, isPrivacy = true, isImmersive = false)
} }
} }
} }
} }

@ -1,387 +1,387 @@
package com.idormy.sms.forwarder.fragment package com.idormy.sms.forwarder.fragment
import android.text.Editable import android.text.Editable
import android.text.TextWatcher import android.text.TextWatcher
import android.util.Log import android.util.Log
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.RadioGroup import android.widget.RadioGroup
import com.google.gson.Gson import com.google.gson.Gson
import com.google.gson.reflect.TypeToken import com.google.gson.reflect.TypeToken
import com.idormy.sms.forwarder.App import com.idormy.sms.forwarder.App
import com.idormy.sms.forwarder.R import com.idormy.sms.forwarder.R
import com.idormy.sms.forwarder.adapter.WidgetItemAdapter import com.idormy.sms.forwarder.adapter.WidgetItemAdapter
import com.idormy.sms.forwarder.core.BaseFragment import com.idormy.sms.forwarder.core.BaseFragment
import com.idormy.sms.forwarder.databinding.FragmentClientBinding import com.idormy.sms.forwarder.databinding.FragmentClientBinding
import com.idormy.sms.forwarder.server.model.BaseResponse import com.idormy.sms.forwarder.server.model.BaseResponse
import com.idormy.sms.forwarder.server.model.ConfigData import com.idormy.sms.forwarder.server.model.ConfigData
import com.idormy.sms.forwarder.utils.* import com.idormy.sms.forwarder.utils.*
import com.xuexiang.xaop.annotation.SingleClick import com.xuexiang.xaop.annotation.SingleClick
import com.xuexiang.xhttp2.XHttp import com.xuexiang.xhttp2.XHttp
import com.xuexiang.xhttp2.cache.model.CacheMode import com.xuexiang.xhttp2.cache.model.CacheMode
import com.xuexiang.xhttp2.callback.SimpleCallBack import com.xuexiang.xhttp2.callback.SimpleCallBack
import com.xuexiang.xhttp2.exception.ApiException import com.xuexiang.xhttp2.exception.ApiException
import com.xuexiang.xpage.annotation.Page import com.xuexiang.xpage.annotation.Page
import com.xuexiang.xpage.base.XPageFragment import com.xuexiang.xpage.base.XPageFragment
import com.xuexiang.xpage.core.PageOption import com.xuexiang.xpage.core.PageOption
import com.xuexiang.xpage.model.PageInfo import com.xuexiang.xpage.model.PageInfo
import com.xuexiang.xrouter.utils.TextUtils import com.xuexiang.xrouter.utils.TextUtils
import com.xuexiang.xui.adapter.recyclerview.RecyclerViewHolder import com.xuexiang.xui.adapter.recyclerview.RecyclerViewHolder
import com.xuexiang.xui.utils.CountDownButtonHelper import com.xuexiang.xui.utils.CountDownButtonHelper
import com.xuexiang.xui.utils.DensityUtils import com.xuexiang.xui.utils.DensityUtils
import com.xuexiang.xui.utils.ResUtils import com.xuexiang.xui.utils.ResUtils
import com.xuexiang.xui.utils.WidgetUtils import com.xuexiang.xui.utils.WidgetUtils
import com.xuexiang.xui.widget.actionbar.TitleBar import com.xuexiang.xui.widget.actionbar.TitleBar
import com.xuexiang.xui.widget.dialog.materialdialog.DialogAction import com.xuexiang.xui.widget.dialog.materialdialog.DialogAction
import com.xuexiang.xui.widget.dialog.materialdialog.MaterialDialog import com.xuexiang.xui.widget.dialog.materialdialog.MaterialDialog
import com.xuexiang.xutil.XUtil import com.xuexiang.xutil.XUtil
import com.xuexiang.xutil.data.ConvertTools import com.xuexiang.xutil.data.ConvertTools
@Suppress("PrivatePropertyName", "PropertyName") @Suppress("PrivatePropertyName", "PropertyName")
@Page(name = "主动控制·客户端") @Page(name = "主动控制·客户端")
class ClientFragment : BaseFragment<FragmentClientBinding?>(), View.OnClickListener, RecyclerViewHolder.OnItemClickListener<PageInfo> { class ClientFragment : BaseFragment<FragmentClientBinding?>(), View.OnClickListener, RecyclerViewHolder.OnItemClickListener<PageInfo> {
val TAG: String = ClientFragment::class.java.simpleName val TAG: String = ClientFragment::class.java.simpleName
private var appContext: App? = null private var appContext: App? = null
private var serverConfig: ConfigData? = null private var serverConfig: ConfigData? = null
private var serverHistory: MutableMap<String, String> = mutableMapOf() private var serverHistory: MutableMap<String, String> = mutableMapOf()
private var mCountDownHelper: CountDownButtonHelper? = null private var mCountDownHelper: CountDownButtonHelper? = null
override fun initViews() { override fun initViews() {
appContext = requireActivity().application as App appContext = requireActivity().application as App
//测试按钮增加倒计时,避免重复点击 //测试按钮增加倒计时,避免重复点击
mCountDownHelper = CountDownButtonHelper(binding!!.btnServerTest, 3) mCountDownHelper = CountDownButtonHelper(binding!!.btnServerTest, 3)
mCountDownHelper!!.setOnCountDownListener(object : CountDownButtonHelper.OnCountDownListener { mCountDownHelper!!.setOnCountDownListener(object : CountDownButtonHelper.OnCountDownListener {
override fun onCountDown(time: Int) { override fun onCountDown(time: Int) {
binding!!.btnServerTest.text = String.format(getString(R.string.seconds_n), time) binding!!.btnServerTest.text = String.format(getString(R.string.seconds_n), time)
} }
override fun onFinished() { override fun onFinished() {
binding!!.btnServerTest.text = getString(R.string.server_test) binding!!.btnServerTest.text = getString(R.string.server_test)
} }
}) })
WidgetUtils.initGridRecyclerView(binding!!.recyclerView, 3, DensityUtils.dp2px(1f)) WidgetUtils.initGridRecyclerView(binding!!.recyclerView, 3, DensityUtils.dp2px(1f))
val widgetItemAdapter = WidgetItemAdapter(CLIENT_FRAGMENT_LIST) val widgetItemAdapter = WidgetItemAdapter(CLIENT_FRAGMENT_LIST)
widgetItemAdapter.setOnItemClickListener(this) widgetItemAdapter.setOnItemClickListener(this)
binding!!.recyclerView.adapter = widgetItemAdapter binding!!.recyclerView.adapter = widgetItemAdapter
//取出历史记录 //取出历史记录
val history = HttpServerUtils.serverHistory val history = HttpServerUtils.serverHistory
if (!TextUtils.isEmpty(history)) { if (!TextUtils.isEmpty(history)) {
serverHistory = Gson().fromJson(history, object : TypeToken<MutableMap<String, String>>() {}.type) serverHistory = Gson().fromJson(history, object : TypeToken<MutableMap<String, String>>() {}.type)
} }
if (CommonUtils.checkUrl(HttpServerUtils.serverAddress)) queryConfig(false) if (CommonUtils.checkUrl(HttpServerUtils.serverAddress)) queryConfig(false)
} }
override fun initTitle(): TitleBar? { override fun initTitle(): TitleBar? {
val titleBar = super.initTitle()!!.setImmersive(false) val titleBar = super.initTitle()!!.setImmersive(false)
//纯客户端模式 //纯客户端模式
if (SettingUtils.enablePureClientMode) { if (SettingUtils.enablePureClientMode) {
titleBar.setTitle(R.string.app_name).setSubTitle(getString(R.string.menu_client)).disableLeftView() titleBar.setTitle(R.string.app_name).setSubTitle(getString(R.string.menu_client)).disableLeftView()
titleBar.addAction(object : TitleBar.ImageAction(R.drawable.ic_logout) { titleBar.addAction(object : TitleBar.ImageAction(R.drawable.ic_logout) {
@SingleClick @SingleClick
override fun performAction(view: View) { override fun performAction(view: View) {
XToastUtils.success(getString(R.string.exit_pure_client_mode)) XToastUtils.success(getString(R.string.exit_pure_client_mode))
SettingUtils.enablePureClientMode = false SettingUtils.enablePureClientMode = false
XUtil.exitApp() XUtil.exitApp()
} }
}) })
} else { } else {
titleBar.setTitle(R.string.menu_client) titleBar.setTitle(R.string.menu_client)
} }
return titleBar return titleBar
} }
override fun viewBindingInflate( override fun viewBindingInflate(
inflater: LayoutInflater, inflater: LayoutInflater,
container: ViewGroup, container: ViewGroup,
): FragmentClientBinding { ): FragmentClientBinding {
return FragmentClientBinding.inflate(inflater, container, false) return FragmentClientBinding.inflate(inflater, container, false)
} }
override fun initListeners() { override fun initListeners() {
binding!!.etServerAddress.setText(HttpServerUtils.serverAddress) binding!!.etServerAddress.setText(HttpServerUtils.serverAddress)
binding!!.etServerAddress.addTextChangedListener(object : TextWatcher { binding!!.etServerAddress.addTextChangedListener(object : TextWatcher {
override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {} override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {}
override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {} override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {}
override fun afterTextChanged(s: Editable) { override fun afterTextChanged(s: Editable) {
HttpServerUtils.serverAddress = binding!!.etServerAddress.text.toString().trim().trimEnd('/') HttpServerUtils.serverAddress = binding!!.etServerAddress.text.toString().trim().trimEnd('/')
} }
}) })
//安全措施 //安全措施
var safetyMeasuresId = R.id.rb_safety_measures_none var safetyMeasuresId = R.id.rb_safety_measures_none
when (HttpServerUtils.clientSafetyMeasures) { when (HttpServerUtils.clientSafetyMeasures) {
1 -> { 1 -> {
safetyMeasuresId = R.id.rb_safety_measures_sign safetyMeasuresId = R.id.rb_safety_measures_sign
binding!!.tvSignKey.text = getString(R.string.sign_key) binding!!.tvSignKey.text = getString(R.string.sign_key)
} }
2 -> { 2 -> {
safetyMeasuresId = R.id.rb_safety_measures_rsa safetyMeasuresId = R.id.rb_safety_measures_rsa
binding!!.tvSignKey.text = getString(R.string.public_key) binding!!.tvSignKey.text = getString(R.string.public_key)
} }
3 -> { 3 -> {
safetyMeasuresId = R.id.rb_safety_measures_sm4 safetyMeasuresId = R.id.rb_safety_measures_sm4
binding!!.tvSignKey.text = getString(R.string.sm4_key) binding!!.tvSignKey.text = getString(R.string.sm4_key)
} }
else -> { else -> {
binding!!.layoutSignKey.visibility = View.GONE binding!!.layoutSignKey.visibility = View.GONE
} }
} }
binding!!.rgSafetyMeasures.check(safetyMeasuresId) binding!!.rgSafetyMeasures.check(safetyMeasuresId)
binding!!.rgSafetyMeasures.setOnCheckedChangeListener { _: RadioGroup?, checkedId: Int -> binding!!.rgSafetyMeasures.setOnCheckedChangeListener { _: RadioGroup?, checkedId: Int ->
var safetyMeasures = 0 var safetyMeasures = 0
binding!!.layoutSignKey.visibility = View.VISIBLE binding!!.layoutSignKey.visibility = View.VISIBLE
when (checkedId) { when (checkedId) {
R.id.rb_safety_measures_sign -> { R.id.rb_safety_measures_sign -> {
safetyMeasures = 1 safetyMeasures = 1
binding!!.tvSignKey.text = getString(R.string.sign_key) binding!!.tvSignKey.text = getString(R.string.sign_key)
} }
R.id.rb_safety_measures_rsa -> { R.id.rb_safety_measures_rsa -> {
safetyMeasures = 2 safetyMeasures = 2
binding!!.tvSignKey.text = getString(R.string.public_key) binding!!.tvSignKey.text = getString(R.string.public_key)
} }
R.id.rb_safety_measures_sm4 -> { R.id.rb_safety_measures_sm4 -> {
safetyMeasures = 3 safetyMeasures = 3
binding!!.tvSignKey.text = getString(R.string.sm4_key) binding!!.tvSignKey.text = getString(R.string.sm4_key)
} }
else -> { else -> {
binding!!.layoutSignKey.visibility = View.GONE binding!!.layoutSignKey.visibility = View.GONE
} }
} }
HttpServerUtils.clientSafetyMeasures = safetyMeasures HttpServerUtils.clientSafetyMeasures = safetyMeasures
} }
binding!!.etSignKey.setText(HttpServerUtils.clientSignKey) binding!!.etSignKey.setText(HttpServerUtils.clientSignKey)
binding!!.etSignKey.addTextChangedListener(object : TextWatcher { binding!!.etSignKey.addTextChangedListener(object : TextWatcher {
override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {} override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {}
override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {} override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {}
override fun afterTextChanged(s: Editable) { override fun afterTextChanged(s: Editable) {
HttpServerUtils.clientSignKey = binding!!.etSignKey.text.toString().trim() HttpServerUtils.clientSignKey = binding!!.etSignKey.text.toString().trim()
} }
}) })
binding!!.btnWechatMiniprogram.setOnClickListener(this) binding!!.btnWechatMiniprogram.setOnClickListener(this)
binding!!.btnServerHistory.setOnClickListener(this) binding!!.btnServerHistory.setOnClickListener(this)
binding!!.btnServerTest.setOnClickListener(this) binding!!.btnServerTest.setOnClickListener(this)
} }
@SingleClick @SingleClick
override fun onClick(v: View) { override fun onClick(v: View) {
when (v.id) { when (v.id) {
R.id.btn_wechat_miniprogram -> { R.id.btn_wechat_miniprogram -> {
if (HttpServerUtils.safetyMeasures != 3) { if (HttpServerUtils.safetyMeasures != 3) {
XToastUtils.error("微信小程序只支持SM4加密传输请前往主动控制·服务端修改安全措施") XToastUtils.error("微信小程序只支持SM4加密传输请前往主动控制·服务端修改安全措施")
return return
} }
CommonUtils.previewPicture(this, getString(R.string.url_wechat_miniprogram), null) CommonUtils.previewPicture(this, getString(R.string.url_wechat_miniprogram), null)
} }
R.id.btn_server_history -> { R.id.btn_server_history -> {
if (serverHistory.isEmpty()) { if (serverHistory.isEmpty()) {
XToastUtils.warning(getString(R.string.no_server_history)) XToastUtils.warning(getString(R.string.no_server_history))
return return
} }
Log.d(TAG, "serverHistory = $serverHistory") Log.d(TAG, "serverHistory = $serverHistory")
MaterialDialog.Builder(requireContext()).title(R.string.server_history).items(serverHistory.keys).itemsCallbackSingleChoice(0) { _: MaterialDialog?, _: View?, _: Int, text: CharSequence -> MaterialDialog.Builder(requireContext()).title(R.string.server_history).items(serverHistory.keys).itemsCallbackSingleChoice(0) { _: MaterialDialog?, _: View?, _: Int, text: CharSequence ->
//XToastUtils.info("$which: $text") //XToastUtils.info("$which: $text")
val matches = Regex("【(.*)】(.*)", RegexOption.IGNORE_CASE).findAll(text).toList().flatMap(MatchResult::groupValues) val matches = Regex("【(.*)】(.*)", RegexOption.IGNORE_CASE).findAll(text).toList().flatMap(MatchResult::groupValues)
Log.i(TAG, "matches = $matches") Log.i(TAG, "matches = $matches")
if (matches.isNotEmpty()) { if (matches.isNotEmpty()) {
binding!!.etServerAddress.setText(matches[2]) binding!!.etServerAddress.setText(matches[2])
} else { } else {
binding!!.etServerAddress.setText(text) binding!!.etServerAddress.setText(text)
} }
val signKey = serverHistory[text].toString() val signKey = serverHistory[text].toString()
if (!TextUtils.isEmpty(signKey)) { if (!TextUtils.isEmpty(signKey)) {
val keyMatches = Regex("(.*)##(.*)", RegexOption.IGNORE_CASE).findAll(signKey).toList().flatMap(MatchResult::groupValues) val keyMatches = Regex("(.*)##(.*)", RegexOption.IGNORE_CASE).findAll(signKey).toList().flatMap(MatchResult::groupValues)
Log.i(TAG, "keyMatches = $keyMatches") Log.i(TAG, "keyMatches = $keyMatches")
if (keyMatches.isNotEmpty()) { if (keyMatches.isNotEmpty()) {
binding!!.etSignKey.setText(keyMatches[1]) binding!!.etSignKey.setText(keyMatches[1])
var safetyMeasuresId = R.id.rb_safety_measures_none var safetyMeasuresId = R.id.rb_safety_measures_none
when (keyMatches[2]) { when (keyMatches[2]) {
"1" -> { "1" -> {
safetyMeasuresId = R.id.rb_safety_measures_sign safetyMeasuresId = R.id.rb_safety_measures_sign
binding!!.tvSignKey.text = getString(R.string.sign_key) binding!!.tvSignKey.text = getString(R.string.sign_key)
} }
"2" -> { "2" -> {
safetyMeasuresId = R.id.rb_safety_measures_rsa safetyMeasuresId = R.id.rb_safety_measures_rsa
binding!!.tvSignKey.text = getString(R.string.public_key) binding!!.tvSignKey.text = getString(R.string.public_key)
} }
"3" -> { "3" -> {
safetyMeasuresId = R.id.rb_safety_measures_sm4 safetyMeasuresId = R.id.rb_safety_measures_sm4
binding!!.tvSignKey.text = getString(R.string.sm4_key) binding!!.tvSignKey.text = getString(R.string.sm4_key)
} }
else -> { else -> {
binding!!.tvSignKey.visibility = View.GONE binding!!.tvSignKey.visibility = View.GONE
binding!!.etSignKey.visibility = View.GONE binding!!.etSignKey.visibility = View.GONE
} }
} }
binding!!.rgSafetyMeasures.check(safetyMeasuresId) binding!!.rgSafetyMeasures.check(safetyMeasuresId)
} else { } else {
binding!!.etSignKey.setText(serverHistory[text]) binding!!.etSignKey.setText(serverHistory[text])
} }
} }
true // allow selection true // allow selection
}.positiveText(R.string.select).negativeText(R.string.cancel).neutralText(R.string.clear_history).neutralColor(ResUtils.getColors(R.color.red)).onNeutral { _: MaterialDialog?, _: DialogAction? -> }.positiveText(R.string.select).negativeText(R.string.cancel).neutralText(R.string.clear_history).neutralColor(ResUtils.getColors(R.color.red)).onNeutral { _: MaterialDialog?, _: DialogAction? ->
serverHistory.clear() serverHistory.clear()
HttpServerUtils.serverHistory = "" HttpServerUtils.serverHistory = ""
}.show() }.show()
} }
R.id.btn_server_test -> { R.id.btn_server_test -> {
if (!CommonUtils.checkUrl(HttpServerUtils.serverAddress)) { if (!CommonUtils.checkUrl(HttpServerUtils.serverAddress)) {
XToastUtils.error(getString(R.string.invalid_service_address)) XToastUtils.error(getString(R.string.invalid_service_address))
return return
} }
queryConfig(true) queryConfig(true)
} }
else -> {} else -> {}
} }
} }
override fun onItemClick(itemView: View, item: PageInfo, position: Int) { override fun onItemClick(itemView: View, item: PageInfo, position: Int) {
try { try {
if (item.name != ResUtils.getString(R.string.api_clone) && !CommonUtils.checkUrl(HttpServerUtils.serverAddress)) { if (item.name != ResUtils.getString(R.string.api_clone) && !CommonUtils.checkUrl(HttpServerUtils.serverAddress)) {
XToastUtils.error(getString(R.string.invalid_service_address)) XToastUtils.error(getString(R.string.invalid_service_address))
serverConfig = null serverConfig = null
return return
} }
if (serverConfig == null && item.name != ResUtils.getString(R.string.api_clone)) { if (serverConfig == null && item.name != ResUtils.getString(R.string.api_clone)) {
XToastUtils.error(getString(R.string.click_test_button_first)) XToastUtils.error(getString(R.string.click_test_button_first))
return return
} }
if (serverConfig != null && ((item.name == ResUtils.getString(R.string.api_sms_send) && !serverConfig!!.enableApiSmsSend) || (item.name == ResUtils.getString(R.string.api_sms_query) && !serverConfig!!.enableApiSmsQuery) || (item.name == ResUtils.getString(R.string.api_call_query) && !serverConfig!!.enableApiCallQuery) || (item.name == ResUtils.getString(R.string.api_contact_query) && !serverConfig!!.enableApiContactQuery) || (item.name == ResUtils.getString(R.string.api_battery_query) && !serverConfig!!.enableApiBatteryQuery) || (item.name == ResUtils.getString(R.string.api_wol) && !serverConfig!!.enableApiWol))) { if (serverConfig != null && ((item.name == ResUtils.getString(R.string.api_sms_send) && !serverConfig!!.enableApiSmsSend) || (item.name == ResUtils.getString(R.string.api_sms_query) && !serverConfig!!.enableApiSmsQuery) || (item.name == ResUtils.getString(R.string.api_call_query) && !serverConfig!!.enableApiCallQuery) || (item.name == ResUtils.getString(R.string.api_contact_query) && !serverConfig!!.enableApiContactQuery) || (item.name == ResUtils.getString(R.string.api_battery_query) && !serverConfig!!.enableApiBatteryQuery) || (item.name == ResUtils.getString(R.string.api_wol) && !serverConfig!!.enableApiWol))) {
XToastUtils.error(getString(R.string.disabled_on_the_server)) XToastUtils.error(getString(R.string.disabled_on_the_server))
return return
} }
@Suppress("UNCHECKED_CAST") PageOption.to(Class.forName(item.classPath) as Class<XPageFragment>) //跳转的fragment @Suppress("UNCHECKED_CAST") PageOption.to(Class.forName(item.classPath) as Class<XPageFragment>) //跳转的fragment
.setNewActivity(true).open(this) .setNewActivity(true).open(this)
} catch (e: Exception) { } catch (e: Exception) {
e.printStackTrace() e.printStackTrace()
XToastUtils.error(e.message.toString()) XToastUtils.error(e.message.toString())
} }
} }
private fun queryConfig(needToast: Boolean) { private fun queryConfig(needToast: Boolean) {
val requestUrl: String = HttpServerUtils.serverAddress + "/config/query" val requestUrl: String = HttpServerUtils.serverAddress + "/config/query"
Log.i(TAG, "requestUrl:$requestUrl") Log.i(TAG, "requestUrl:$requestUrl")
val msgMap: MutableMap<String, Any> = mutableMapOf() val msgMap: MutableMap<String, Any> = mutableMapOf()
val timestamp = System.currentTimeMillis() val timestamp = System.currentTimeMillis()
msgMap["timestamp"] = timestamp msgMap["timestamp"] = timestamp
val clientSignKey = HttpServerUtils.clientSignKey.toString() val clientSignKey = HttpServerUtils.clientSignKey
if (HttpServerUtils.clientSafetyMeasures != 0 && TextUtils.isEmpty(clientSignKey)) { if (HttpServerUtils.clientSafetyMeasures != 0 && TextUtils.isEmpty(clientSignKey)) {
if (needToast) XToastUtils.error("请输入签名密钥/RSA公钥/SM4密钥") if (needToast) XToastUtils.error("请输入签名密钥/RSA公钥/SM4密钥")
return return
} }
if (HttpServerUtils.clientSafetyMeasures == 1 && !TextUtils.isEmpty(clientSignKey)) { if (HttpServerUtils.clientSafetyMeasures == 1 && !TextUtils.isEmpty(clientSignKey)) {
msgMap["sign"] = HttpServerUtils.calcSign(timestamp.toString(), clientSignKey) msgMap["sign"] = HttpServerUtils.calcSign(timestamp.toString(), clientSignKey)
} }
val dataMap: MutableMap<String, Any> = mutableMapOf() val dataMap: MutableMap<String, Any> = mutableMapOf()
msgMap["data"] = dataMap msgMap["data"] = dataMap
var requestMsg: String = Gson().toJson(msgMap) var requestMsg: String = Gson().toJson(msgMap)
Log.i(TAG, "requestMsg:$requestMsg") Log.i(TAG, "requestMsg:$requestMsg")
val postRequest = XHttp.post(requestUrl) val postRequest = XHttp.post(requestUrl)
.keepJson(true) .keepJson(true)
.timeOut((SettingUtils.requestTimeout * 1000).toLong()) //超时时间10s .timeOut((SettingUtils.requestTimeout * 1000).toLong()) //超时时间10s
.cacheMode(CacheMode.NO_CACHE).timeStamp(true) .cacheMode(CacheMode.NO_CACHE).timeStamp(true)
when (HttpServerUtils.clientSafetyMeasures) { when (HttpServerUtils.clientSafetyMeasures) {
2 -> { 2 -> {
try { try {
val publicKey = RSACrypt.getPublicKey(HttpServerUtils.clientSignKey.toString()) val publicKey = RSACrypt.getPublicKey(HttpServerUtils.clientSignKey)
requestMsg = Base64.encode(requestMsg.toByteArray()) requestMsg = Base64.encode(requestMsg.toByteArray())
requestMsg = RSACrypt.encryptByPublicKey(requestMsg, publicKey) requestMsg = RSACrypt.encryptByPublicKey(requestMsg, publicKey)
Log.i(TAG, "requestMsg: $requestMsg") Log.i(TAG, "requestMsg: $requestMsg")
} catch (e: Exception) { } catch (e: Exception) {
if (needToast) XToastUtils.error(ResUtils.getString(R.string.request_failed) + e.message) if (needToast) XToastUtils.error(ResUtils.getString(R.string.request_failed) + e.message)
e.printStackTrace() e.printStackTrace()
return return
} }
postRequest.upString(requestMsg) postRequest.upString(requestMsg)
} }
3 -> { 3 -> {
try { try {
val sm4Key = ConvertTools.hexStringToByteArray(HttpServerUtils.clientSignKey.toString()) val sm4Key = ConvertTools.hexStringToByteArray(HttpServerUtils.clientSignKey)
//requestMsg = Base64.encode(requestMsg.toByteArray()) //requestMsg = Base64.encode(requestMsg.toByteArray())
val encryptCBC = SM4Crypt.encrypt(requestMsg.toByteArray(), sm4Key) val encryptCBC = SM4Crypt.encrypt(requestMsg.toByteArray(), sm4Key)
requestMsg = ConvertTools.bytes2HexString(encryptCBC) requestMsg = ConvertTools.bytes2HexString(encryptCBC)
Log.i(TAG, "requestMsg: $requestMsg") Log.i(TAG, "requestMsg: $requestMsg")
} catch (e: Exception) { } catch (e: Exception) {
if (needToast) XToastUtils.error(ResUtils.getString(R.string.request_failed) + e.message) if (needToast) XToastUtils.error(ResUtils.getString(R.string.request_failed) + e.message)
e.printStackTrace() e.printStackTrace()
return return
} }
postRequest.upString(requestMsg) postRequest.upString(requestMsg)
} }
else -> { else -> {
postRequest.upJson(requestMsg) postRequest.upJson(requestMsg)
} }
} }
if (needToast) mCountDownHelper?.start() if (needToast) mCountDownHelper?.start()
postRequest.execute(object : SimpleCallBack<String>() { postRequest.execute(object : SimpleCallBack<String>() {
override fun onError(e: ApiException) { override fun onError(e: ApiException) {
XToastUtils.error(e.displayMessage) XToastUtils.error(e.displayMessage)
if (needToast) mCountDownHelper?.finish() if (needToast) mCountDownHelper?.finish()
} }
override fun onSuccess(response: String) { override fun onSuccess(response: String) {
Log.i(TAG, response) Log.i(TAG, response)
try { try {
var json = response var json = response
if (HttpServerUtils.clientSafetyMeasures == 2) { if (HttpServerUtils.clientSafetyMeasures == 2) {
val publicKey = RSACrypt.getPublicKey(HttpServerUtils.clientSignKey.toString()) val publicKey = RSACrypt.getPublicKey(HttpServerUtils.clientSignKey)
json = RSACrypt.decryptByPublicKey(json, publicKey) json = RSACrypt.decryptByPublicKey(json, publicKey)
json = String(Base64.decode(json)) json = String(Base64.decode(json))
} else if (HttpServerUtils.clientSafetyMeasures == 3) { } else if (HttpServerUtils.clientSafetyMeasures == 3) {
val sm4Key = ConvertTools.hexStringToByteArray(HttpServerUtils.clientSignKey.toString()) val sm4Key = ConvertTools.hexStringToByteArray(HttpServerUtils.clientSignKey)
val encryptCBC = ConvertTools.hexStringToByteArray(json) val encryptCBC = ConvertTools.hexStringToByteArray(json)
val decryptCBC = SM4Crypt.decrypt(encryptCBC, sm4Key) val decryptCBC = SM4Crypt.decrypt(encryptCBC, sm4Key)
json = String(decryptCBC) json = String(decryptCBC)
} }
val resp: BaseResponse<ConfigData> = Gson().fromJson(json, object : TypeToken<BaseResponse<ConfigData>>() {}.type) val resp: BaseResponse<ConfigData> = Gson().fromJson(json, object : TypeToken<BaseResponse<ConfigData>>() {}.type)
if (resp.code == 200) { if (resp.code == 200) {
serverConfig = resp.data!! serverConfig = resp.data!!
if (needToast) XToastUtils.success(ResUtils.getString(R.string.request_succeeded)) if (needToast) XToastUtils.success(ResUtils.getString(R.string.request_succeeded))
//删除3.0.8之前保存的记录 //删除3.0.8之前保存的记录
serverHistory.remove(HttpServerUtils.serverAddress.toString()) serverHistory.remove(HttpServerUtils.serverAddress)
//添加到历史记录 //添加到历史记录
val key = "${serverConfig?.extraDeviceMark}${HttpServerUtils.serverAddress.toString()}" val key = "${serverConfig?.extraDeviceMark}${HttpServerUtils.serverAddress}"
if (TextUtils.isEmpty(HttpServerUtils.clientSignKey)) { if (TextUtils.isEmpty(HttpServerUtils.clientSignKey)) {
serverHistory[key] = "SMSFORWARDER##" + HttpServerUtils.clientSafetyMeasures.toString() serverHistory[key] = "SMSFORWARDER##" + HttpServerUtils.clientSafetyMeasures.toString()
} else { } else {
serverHistory[key] = HttpServerUtils.clientSignKey + "##" + HttpServerUtils.clientSafetyMeasures.toString() serverHistory[key] = HttpServerUtils.clientSignKey + "##" + HttpServerUtils.clientSafetyMeasures.toString()
} }
HttpServerUtils.serverHistory = Gson().toJson(serverHistory) HttpServerUtils.serverHistory = Gson().toJson(serverHistory)
HttpServerUtils.serverConfig = Gson().toJson(serverConfig) HttpServerUtils.serverConfig = Gson().toJson(serverConfig)
} else { } else {
if (needToast) XToastUtils.error(ResUtils.getString(R.string.request_failed) + resp.msg) if (needToast) XToastUtils.error(ResUtils.getString(R.string.request_failed) + resp.msg)
} }
if (needToast) mCountDownHelper?.finish() if (needToast) mCountDownHelper?.finish()
} catch (e: Exception) { } catch (e: Exception) {
e.printStackTrace() e.printStackTrace()
if (needToast) { if (needToast) {
XToastUtils.error(ResUtils.getString(R.string.request_failed) + response) XToastUtils.error(ResUtils.getString(R.string.request_failed) + response)
mCountDownHelper?.finish() mCountDownHelper?.finish()
} }
} }
} }
}) })
} }
override fun onDestroyView() { override fun onDestroyView() {
if (mCountDownHelper != null) mCountDownHelper!!.recycle() if (mCountDownHelper != null) mCountDownHelper!!.recycle()
super.onDestroyView() super.onDestroyView()
} }
} }

@ -79,23 +79,15 @@ class SettingsFragment : BaseFragment<FragmentSettingsBinding?>(), View.OnClickL
switchEnableSms(binding!!.sbEnableSms) switchEnableSms(binding!!.sbEnableSms)
//转发通话记录 //转发通话记录
switchEnablePhone( switchEnablePhone(
binding!!.sbEnablePhone, binding!!.sbEnablePhone, binding!!.scbCallType1, binding!!.scbCallType2, binding!!.scbCallType3, binding!!.scbCallType4
binding!!.scbCallType1,
binding!!.scbCallType2,
binding!!.scbCallType3,
binding!!.scbCallType4
) )
//转发应用通知 //转发应用通知
switchEnableAppNotify( switchEnableAppNotify(
binding!!.sbEnableAppNotify, binding!!.sbEnableAppNotify, binding!!.scbCancelAppNotify, binding!!.scbNotUserPresent
binding!!.scbCancelAppNotify,
binding!!.scbNotUserPresent
) )
//启动时异步获取已安装App信息 //启动时异步获取已安装App信息
switchEnableLoadAppList( switchEnableLoadAppList(
binding!!.sbEnableLoadAppList, binding!!.sbEnableLoadAppList, binding!!.scbLoadUserApp, binding!!.scbLoadSystemApp
binding!!.scbLoadUserApp,
binding!!.scbLoadSystemApp
) )
//过滤多久内重复消息 //过滤多久内重复消息
binding!!.xsbDuplicateMessagesLimits.setDefaultValue(SettingUtils.duplicateMessagesLimits) binding!!.xsbDuplicateMessagesLimits.setDefaultValue(SettingUtils.duplicateMessagesLimits)
@ -103,8 +95,7 @@ class SettingsFragment : BaseFragment<FragmentSettingsBinding?>(), View.OnClickL
SettingUtils.duplicateMessagesLimits = newValue SettingUtils.duplicateMessagesLimits = newValue
} }
//免打扰(禁用转发)时间段 //免打扰(禁用转发)时间段
binding!!.tvSilentPeriod.text = binding!!.tvSilentPeriod.text = mTimeOption[SettingUtils.silentPeriodStart] + " ~ " + mTimeOption[SettingUtils.silentPeriodEnd]
mTimeOption[SettingUtils.silentPeriodStart] + " ~ " + mTimeOption[SettingUtils.silentPeriodEnd]
//自动删除N天前的转发记录 //自动删除N天前的转发记录
binding!!.xsbAutoCleanLogs.setDefaultValue(SettingUtils.autoCleanLogsDays) binding!!.xsbAutoCleanLogs.setDefaultValue(SettingUtils.autoCleanLogsDays)
binding!!.xsbAutoCleanLogs.setOnSeekBarListener { _: XSeekBar?, newValue: Int -> binding!!.xsbAutoCleanLogs.setOnSeekBarListener { _: XSeekBar?, newValue: Int ->
@ -129,9 +120,7 @@ class SettingsFragment : BaseFragment<FragmentSettingsBinding?>(), View.OnClickL
//Cactus增强保活措施 //Cactus增强保活措施
switchEnableCactus( switchEnableCactus(
binding!!.sbEnableCactus, binding!!.sbEnableCactus, binding!!.scbPlaySilenceMusic, binding!!.scbOnePixelActivity
binding!!.scbPlaySilenceMusic,
binding!!.scbOnePixelActivity
) )
//接口请求失败重试时间间隔 //接口请求失败重试时间间隔
@ -176,21 +165,17 @@ class SettingsFragment : BaseFragment<FragmentSettingsBinding?>(), View.OnClickL
val etSmsTemplate: EditText = binding!!.etSmsTemplate val etSmsTemplate: EditText = binding!!.etSmsTemplate
when (v.id) { when (v.id) {
R.id.btn_silent_period -> { R.id.btn_silent_period -> {
OptionsPickerBuilder( OptionsPickerBuilder(context, OnOptionsSelectListener { _: View?, options1: Int, options2: Int, _: Int ->
context, SettingUtils.silentPeriodStart = options1
OnOptionsSelectListener { _: View?, options1: Int, options2: Int, _: Int -> SettingUtils.silentPeriodEnd = options2
SettingUtils.silentPeriodStart = options1 val txt = mTimeOption[options1] + " ~ " + mTimeOption[options2]
SettingUtils.silentPeriodEnd = options2 binding!!.tvSilentPeriod.text = txt
val txt = mTimeOption[options1] + " ~ " + mTimeOption[options2] XToastUtils.toast(txt)
binding!!.tvSilentPeriod.text = txt return@OnOptionsSelectListener false
XToastUtils.toast(txt) }).setTitleText(getString(R.string.select_time_period)).setSelectOptions(SettingUtils.silentPeriodStart, SettingUtils.silentPeriodEnd).build<Any>().also {
return@OnOptionsSelectListener false it.setNPicker(mTimeOption, mTimeOption)
}).setTitleText(getString(R.string.select_time_period)) it.show()
.setSelectOptions(SettingUtils.silentPeriodStart, SettingUtils.silentPeriodEnd) }
.build<Any>().also {
it.setNPicker(mTimeOption, mTimeOption)
it.show()
}
} }
R.id.btn_extra_device_mark -> { R.id.btn_extra_device_mark -> {
binding!!.etExtraDeviceMark.setText(PhoneUtils.getDeviceName()) binding!!.etExtraDeviceMark.setText(PhoneUtils.getDeviceName())
@ -201,8 +186,7 @@ class SettingsFragment : BaseFragment<FragmentSettingsBinding?>(), View.OnClickL
if (App.SimInfoList.isEmpty()) { if (App.SimInfoList.isEmpty()) {
XToastUtils.error(R.string.tip_can_not_get_sim_infos) XToastUtils.error(R.string.tip_can_not_get_sim_infos)
XXPermissions.startPermissionActivity( XXPermissions.startPermissionActivity(
requireContext(), requireContext(), "android.permission.READ_PHONE_STATE"
"android.permission.READ_PHONE_STATE"
) )
return return
} }
@ -210,8 +194,7 @@ class SettingsFragment : BaseFragment<FragmentSettingsBinding?>(), View.OnClickL
if (!App.SimInfoList.containsKey(0)) { if (!App.SimInfoList.containsKey(0)) {
XToastUtils.error( XToastUtils.error(
String.format( String.format(
getString(R.string.tip_can_not_get_sim_info), getString(R.string.tip_can_not_get_sim_info), 1
1
) )
) )
return return
@ -225,8 +208,7 @@ class SettingsFragment : BaseFragment<FragmentSettingsBinding?>(), View.OnClickL
if (App.SimInfoList.isEmpty()) { if (App.SimInfoList.isEmpty()) {
XToastUtils.error(R.string.tip_can_not_get_sim_infos) XToastUtils.error(R.string.tip_can_not_get_sim_infos)
XXPermissions.startPermissionActivity( XXPermissions.startPermissionActivity(
requireContext(), requireContext(), "android.permission.READ_PHONE_STATE"
"android.permission.READ_PHONE_STATE"
) )
return return
} }
@ -234,8 +216,7 @@ class SettingsFragment : BaseFragment<FragmentSettingsBinding?>(), View.OnClickL
if (!App.SimInfoList.containsKey(1)) { if (!App.SimInfoList.containsKey(1)) {
XToastUtils.error( XToastUtils.error(
String.format( String.format(
getString(R.string.tip_can_not_get_sim_info), getString(R.string.tip_can_not_get_sim_info), 2
2
) )
) )
return return
@ -254,22 +235,19 @@ class SettingsFragment : BaseFragment<FragmentSettingsBinding?>(), View.OnClickL
} }
R.id.bt_insert_extra -> { R.id.bt_insert_extra -> {
CommonUtils.insertOrReplaceText2Cursor( CommonUtils.insertOrReplaceText2Cursor(
etSmsTemplate, etSmsTemplate, getString(R.string.tag_card_slot)
getString(R.string.tag_card_slot)
) )
return return
} }
R.id.bt_insert_time -> { R.id.bt_insert_time -> {
CommonUtils.insertOrReplaceText2Cursor( CommonUtils.insertOrReplaceText2Cursor(
etSmsTemplate, etSmsTemplate, getString(R.string.tag_receive_time)
getString(R.string.tag_receive_time)
) )
return return
} }
R.id.bt_insert_device_name -> { R.id.bt_insert_device_name -> {
CommonUtils.insertOrReplaceText2Cursor( CommonUtils.insertOrReplaceText2Cursor(
etSmsTemplate, etSmsTemplate, getString(R.string.tag_device_name)
getString(R.string.tag_device_name)
) )
return return
} }
@ -291,8 +269,7 @@ class SettingsFragment : BaseFragment<FragmentSettingsBinding?>(), View.OnClickL
// 发送短信 // 发送短信
//.permission(Permission.SEND_SMS) //.permission(Permission.SEND_SMS)
// 读取短信 // 读取短信
.permission(Permission.READ_SMS) .permission(Permission.READ_SMS).request(object : OnPermissionCallback {
.request(object : OnPermissionCallback {
override fun onGranted(permissions: List<String>, all: Boolean) { override fun onGranted(permissions: List<String>, all: Boolean) {
if (all) { if (all) {
XToastUtils.info(R.string.toast_granted_all) XToastUtils.info(R.string.toast_granted_all)
@ -320,11 +297,7 @@ class SettingsFragment : BaseFragment<FragmentSettingsBinding?>(), View.OnClickL
//转发通话 //转发通话
@SuppressLint("UseSwitchCompatOrMaterialCode") @SuppressLint("UseSwitchCompatOrMaterialCode")
fun switchEnablePhone( fun switchEnablePhone(
sbEnablePhone: SwitchButton, sbEnablePhone: SwitchButton, scbCallType1: SmoothCheckBox, scbCallType2: SmoothCheckBox, scbCallType3: SmoothCheckBox, scbCallType4: SmoothCheckBox
scbCallType1: SmoothCheckBox,
scbCallType2: SmoothCheckBox,
scbCallType3: SmoothCheckBox,
scbCallType4: SmoothCheckBox
) { ) {
sbEnablePhone.isChecked = SettingUtils.enablePhone sbEnablePhone.isChecked = SettingUtils.enablePhone
scbCallType1.isChecked = SettingUtils.enableCallType1 scbCallType1.isChecked = SettingUtils.enableCallType1
@ -349,8 +322,7 @@ class SettingsFragment : BaseFragment<FragmentSettingsBinding?>(), View.OnClickL
// 读取通话记录 // 读取通话记录
.permission(Permission.READ_CALL_LOG) .permission(Permission.READ_CALL_LOG)
// 读取联系人 // 读取联系人
.permission(Permission.READ_CONTACTS) .permission(Permission.READ_CONTACTS).request(object : OnPermissionCallback {
.request(object : OnPermissionCallback {
override fun onGranted(permissions: List<String>, all: Boolean) { override fun onGranted(permissions: List<String>, all: Boolean) {
if (all) { if (all) {
XToastUtils.info(R.string.toast_granted_all) XToastUtils.info(R.string.toast_granted_all)
@ -410,9 +382,7 @@ class SettingsFragment : BaseFragment<FragmentSettingsBinding?>(), View.OnClickL
//转发应用通知 //转发应用通知
@SuppressLint("UseSwitchCompatOrMaterialCode") @SuppressLint("UseSwitchCompatOrMaterialCode")
fun switchEnableAppNotify( fun switchEnableAppNotify(
sbEnableAppNotify: SwitchButton, sbEnableAppNotify: SwitchButton, scbCancelAppNotify: SmoothCheckBox, scbNotUserPresent: SmoothCheckBox
scbCancelAppNotify: SmoothCheckBox,
scbNotUserPresent: SmoothCheckBox
) { ) {
val layoutOptionalAction: LinearLayout = binding!!.layoutOptionalAction val layoutOptionalAction: LinearLayout = binding!!.layoutOptionalAction
val isEnable: Boolean = SettingUtils.enableAppNotify val isEnable: Boolean = SettingUtils.enableAppNotify
@ -428,8 +398,7 @@ class SettingsFragment : BaseFragment<FragmentSettingsBinding?>(), View.OnClickL
// 通知栏监听权限 // 通知栏监听权限
.permission(Permission.BIND_NOTIFICATION_LISTENER_SERVICE) .permission(Permission.BIND_NOTIFICATION_LISTENER_SERVICE)
// 通知栏权限 // 通知栏权限
.permission(Permission.NOTIFICATION_SERVICE) .permission(Permission.NOTIFICATION_SERVICE).request(object : OnPermissionCallback {
.request(object : OnPermissionCallback {
override fun onGranted(permissions: List<String>, all: Boolean) { override fun onGranted(permissions: List<String>, all: Boolean) {
SettingUtils.enableAppNotify = true SettingUtils.enableAppNotify = true
sbEnableAppNotify.isChecked = true sbEnableAppNotify.isChecked = true
@ -441,17 +410,11 @@ class SettingsFragment : BaseFragment<FragmentSettingsBinding?>(), View.OnClickL
sbEnableAppNotify.isChecked = false sbEnableAppNotify.isChecked = false
XToastUtils.error(R.string.tips_notification_listener) XToastUtils.error(R.string.tips_notification_listener)
// 如果是被永久拒绝就跳转到应用权限系统设置页面 // 如果是被永久拒绝就跳转到应用权限系统设置页面
MaterialDialog.Builder(context!!) MaterialDialog.Builder(context!!).content(R.string.toast_denied_never).positiveText(R.string.lab_yes).negativeText(R.string.lab_no).onPositive { _: MaterialDialog?, _: DialogAction? ->
.content(R.string.toast_denied_never) XXPermissions.startPermissionActivity(
.positiveText(R.string.lab_yes) requireContext(), permissions
.negativeText(R.string.lab_no) )
.onPositive { _: MaterialDialog?, _: DialogAction? -> }.show()
XXPermissions.startPermissionActivity(
requireContext(),
permissions
)
}
.show()
} }
}) })
} }
@ -469,9 +432,7 @@ class SettingsFragment : BaseFragment<FragmentSettingsBinding?>(), View.OnClickL
//启动时异步获取已安装App信息 (binding!!.sbEnableLoadAppList, binding!!.scbLoadUserApp, binding!!.scbLoadSystemApp) //启动时异步获取已安装App信息 (binding!!.sbEnableLoadAppList, binding!!.scbLoadUserApp, binding!!.scbLoadSystemApp)
@SuppressLint("UseSwitchCompatOrMaterialCode") @SuppressLint("UseSwitchCompatOrMaterialCode")
fun switchEnableLoadAppList( fun switchEnableLoadAppList(
sbEnableLoadAppList: SwitchButton, sbEnableLoadAppList: SwitchButton, scbLoadUserApp: SmoothCheckBox, scbLoadSystemApp: SmoothCheckBox
scbLoadUserApp: SmoothCheckBox,
scbLoadSystemApp: SmoothCheckBox
) { ) {
val isEnable: Boolean = SettingUtils.enableLoadAppList val isEnable: Boolean = SettingUtils.enableLoadAppList
sbEnableLoadAppList.isChecked = isEnable sbEnableLoadAppList.isChecked = isEnable
@ -516,12 +477,10 @@ class SettingsFragment : BaseFragment<FragmentSettingsBinding?>(), View.OnClickL
//设置低电量报警 //设置低电量报警
private fun editBatteryLevelAlarm( private fun editBatteryLevelAlarm(
xrsBatteryLevelAlarm: XRangeSlider, xrsBatteryLevelAlarm: XRangeSlider, scbBatteryLevelAlarmOnce: SmoothCheckBox
scbBatteryLevelAlarmOnce: SmoothCheckBox
) { ) {
xrsBatteryLevelAlarm.setStartingMinMax( xrsBatteryLevelAlarm.setStartingMinMax(
SettingUtils.batteryLevelMin, SettingUtils.batteryLevelMin, SettingUtils.batteryLevelMax
SettingUtils.batteryLevelMax
) )
xrsBatteryLevelAlarm.setOnRangeSliderListener(object : OnRangeSliderListener { xrsBatteryLevelAlarm.setOnRangeSliderListener(object : OnRangeSliderListener {
override fun onMaxChanged(slider: XRangeSlider, maxValue: Int) { override fun onMaxChanged(slider: XRangeSlider, maxValue: Int) {
@ -548,8 +507,7 @@ class SettingsFragment : BaseFragment<FragmentSettingsBinding?>(), View.OnClickL
@SuppressLint("UseSwitchCompatOrMaterialCode") @SuppressLint("UseSwitchCompatOrMaterialCode")
fun switchBatteryCron(sbBatteryCron: SwitchButton) { fun switchBatteryCron(sbBatteryCron: SwitchButton) {
sbBatteryCron.isChecked = SettingUtils.enableBatteryCron sbBatteryCron.isChecked = SettingUtils.enableBatteryCron
binding!!.layoutBatteryCron.visibility = binding!!.layoutBatteryCron.visibility = if (SettingUtils.enableBatteryCron) View.VISIBLE else View.GONE
if (SettingUtils.enableBatteryCron) View.VISIBLE else View.GONE
sbBatteryCron.setOnCheckedChangeListener { _: CompoundButton?, isChecked: Boolean -> sbBatteryCron.setOnCheckedChangeListener { _: CompoundButton?, isChecked: Boolean ->
binding!!.layoutBatteryCron.visibility = if (isChecked) View.VISIBLE else View.GONE binding!!.layoutBatteryCron.visibility = if (isChecked) View.VISIBLE else View.GONE
SettingUtils.enableBatteryCron = isChecked SettingUtils.enableBatteryCron = isChecked
@ -560,8 +518,7 @@ class SettingsFragment : BaseFragment<FragmentSettingsBinding?>(), View.OnClickL
//设置推送电池状态时机 //设置推送电池状态时机
private fun editBatteryCronTiming( private fun editBatteryCronTiming(
etBatteryCronStartTime: EditText, etBatteryCronStartTime: EditText, etBatteryCronInterval: EditText
etBatteryCronInterval: EditText
) { ) {
etBatteryCronStartTime.setText(SettingUtils.batteryCronStartTime) etBatteryCronStartTime.setText(SettingUtils.batteryCronStartTime)
etBatteryCronStartTime.setOnClickListener { etBatteryCronStartTime.setOnClickListener {
@ -573,12 +530,7 @@ class SettingsFragment : BaseFragment<FragmentSettingsBinding?>(), View.OnClickL
//BatteryReportCronTask.getSingleton().updateTimer() //BatteryReportCronTask.getSingleton().updateTimer()
} }
//.setTimeSelectChangeListener { date: Date? -> etBatteryCronStartTime.setText(DateUtils.date2String(date, DateUtils.HHmm.get())) } //.setTimeSelectChangeListener { date: Date? -> etBatteryCronStartTime.setText(DateUtils.date2String(date, DateUtils.HHmm.get())) }
.setType(false, false, false, true, true, false) .setType(false, false, false, true, true, false).setTitleText(getString(R.string.time_picker)).setSubmitText(getString(R.string.ok)).setCancelText(getString(R.string.cancel)).setDate(calendar).build()
.setTitleText(getString(R.string.time_picker))
.setSubmitText(getString(R.string.ok))
.setCancelText(getString(R.string.cancel))
.setDate(calendar)
.build()
mTimePicker.show() mTimePicker.show()
} }
@ -601,8 +553,7 @@ class SettingsFragment : BaseFragment<FragmentSettingsBinding?>(), View.OnClickL
//开机启动 //开机启动
private fun checkWithReboot( private fun checkWithReboot(
@SuppressLint("UseSwitchCompatOrMaterialCode") sbWithReboot: SwitchButton, @SuppressLint("UseSwitchCompatOrMaterialCode") sbWithReboot: SwitchButton, tvAutoStartup: TextView
tvAutoStartup: TextView
) { ) {
tvAutoStartup.text = getAutoStartTips() tvAutoStartup.text = getAutoStartTips()
@ -610,12 +561,10 @@ class SettingsFragment : BaseFragment<FragmentSettingsBinding?>(), View.OnClickL
val cm = ComponentName(getAppPackageName(), BootReceiver::class.java.name) val cm = ComponentName(getAppPackageName(), BootReceiver::class.java.name)
val pm: PackageManager = getPackageManager() val pm: PackageManager = getPackageManager()
val state = pm.getComponentEnabledSetting(cm) val state = pm.getComponentEnabledSetting(cm)
sbWithReboot.isChecked = sbWithReboot.isChecked = !(state == PackageManager.COMPONENT_ENABLED_STATE_DISABLED || state == PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER)
(state != PackageManager.COMPONENT_ENABLED_STATE_DISABLED && state != PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER)
sbWithReboot.setOnCheckedChangeListener { _: CompoundButton?, isChecked: Boolean -> sbWithReboot.setOnCheckedChangeListener { _: CompoundButton?, isChecked: Boolean ->
try { try {
val newState = val newState = if (isChecked) PackageManager.COMPONENT_ENABLED_STATE_ENABLED else PackageManager.COMPONENT_ENABLED_STATE_DISABLED
if (isChecked) PackageManager.COMPONENT_ENABLED_STATE_ENABLED else PackageManager.COMPONENT_ENABLED_STATE_DISABLED
pm.setComponentEnabledSetting(cm, newState, PackageManager.DONT_KILL_APP) pm.setComponentEnabledSetting(cm, newState, PackageManager.DONT_KILL_APP)
if (isChecked) startToAutoStartSetting(requireContext()) if (isChecked) startToAutoStartSetting(requireContext())
} catch (e: Exception) { } catch (e: Exception) {
@ -635,8 +584,7 @@ class SettingsFragment : BaseFragment<FragmentSettingsBinding?>(), View.OnClickL
} }
try { try {
val isIgnoreBatteryOptimization: Boolean = val isIgnoreBatteryOptimization: Boolean = KeepAliveUtils.isIgnoreBatteryOptimization(requireActivity())
KeepAliveUtils.isIgnoreBatteryOptimization(requireActivity())
sbBatterySetting.isChecked = isIgnoreBatteryOptimization sbBatterySetting.isChecked = isIgnoreBatteryOptimization
sbBatterySetting.setOnCheckedChangeListener { _: CompoundButton?, isChecked: Boolean -> sbBatterySetting.setOnCheckedChangeListener { _: CompoundButton?, isChecked: Boolean ->
if (isChecked && !isIgnoreBatteryOptimization) { if (isChecked && !isIgnoreBatteryOptimization) {
@ -657,8 +605,7 @@ class SettingsFragment : BaseFragment<FragmentSettingsBinding?>(), View.OnClickL
//不在最近任务列表中显示 //不在最近任务列表中显示
@SuppressLint("ObsoleteSdkInt,UseSwitchCompatOrMaterialCode") @SuppressLint("ObsoleteSdkInt,UseSwitchCompatOrMaterialCode")
fun switchExcludeFromRecents( fun switchExcludeFromRecents(
layoutExcludeFromRecents: LinearLayout, layoutExcludeFromRecents: LinearLayout, sbExcludeFromRecents: SwitchButton
sbExcludeFromRecents: SwitchButton
) { ) {
//安卓6.0以下没有不在最近任务列表中显示 //安卓6.0以下没有不在最近任务列表中显示
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
@ -683,9 +630,7 @@ class SettingsFragment : BaseFragment<FragmentSettingsBinding?>(), View.OnClickL
//转发应用通知 //转发应用通知
@SuppressLint("UseSwitchCompatOrMaterialCode") @SuppressLint("UseSwitchCompatOrMaterialCode")
fun switchEnableCactus( fun switchEnableCactus(
sbEnableCactus: SwitchButton, sbEnableCactus: SwitchButton, scbPlaySilenceMusic: SmoothCheckBox, scbOnePixelActivity: SmoothCheckBox
scbPlaySilenceMusic: SmoothCheckBox,
scbOnePixelActivity: SmoothCheckBox
) { ) {
val layoutCactusOptional: LinearLayout = binding!!.layoutCactusOptional val layoutCactusOptional: LinearLayout = binding!!.layoutCactusOptional
val isEnable: Boolean = SettingUtils.enableCactus val isEnable: Boolean = SettingUtils.enableCactus
@ -716,9 +661,7 @@ class SettingsFragment : BaseFragment<FragmentSettingsBinding?>(), View.OnClickL
//接口请求失败重试时间间隔 //接口请求失败重试时间间隔
private fun editRetryDelayTime( private fun editRetryDelayTime(
etRetryTimes: EditText, etRetryTimes: EditText, etDelayTime: EditText, etTimeout: EditText
etDelayTime: EditText,
etTimeout: EditText
) { ) {
etRetryTimes.setText(java.lang.String.valueOf(SettingUtils.requestRetryTimes)) etRetryTimes.setText(java.lang.String.valueOf(SettingUtils.requestRetryTimes))
etRetryTimes.addTextChangedListener(object : TextWatcher { etRetryTimes.addTextChangedListener(object : TextWatcher {
@ -818,8 +761,7 @@ class SettingsFragment : BaseFragment<FragmentSettingsBinding?>(), View.OnClickL
override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {} override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {}
override fun afterTextChanged(s: Editable) { override fun afterTextChanged(s: Editable) {
SettingUtils.notifyContent = etNotifyContent.text.toString().trim() SettingUtils.notifyContent = etNotifyContent.text.toString().trim()
LiveEventBus.get(EVENT_UPDATE_NOTIFY, String::class.java) LiveEventBus.get(EVENT_UPDATE_NOTIFY, String::class.java).post(SettingUtils.notifyContent)
.post(SettingUtils.notifyContent.toString())
} }
}) })
} }
@ -875,14 +817,9 @@ class SettingsFragment : BaseFragment<FragmentSettingsBinding?>(), View.OnClickL
switchDirectlyToClient.setOnCheckedChangeListener { _: CompoundButton?, isChecked: Boolean -> switchDirectlyToClient.setOnCheckedChangeListener { _: CompoundButton?, isChecked: Boolean ->
SettingUtils.enablePureClientMode = isChecked SettingUtils.enablePureClientMode = isChecked
if (isChecked) { if (isChecked) {
MaterialDialog.Builder(requireContext()) MaterialDialog.Builder(requireContext()).content(getString(R.string.enabling_pure_client_mode)).positiveText(R.string.lab_yes).onPositive { _: MaterialDialog?, _: DialogAction? ->
.content(getString(R.string.enabling_pure_client_mode)) XUtil.exitApp()
.positiveText(R.string.lab_yes) }.negativeText(R.string.lab_no).show()
.onPositive { _: MaterialDialog?, _: DialogAction? ->
XUtil.exitApp()
}
.negativeText(R.string.lab_no)
.show()
} }
} }
} }
@ -914,37 +851,19 @@ class SettingsFragment : BaseFragment<FragmentSettingsBinding?>(), View.OnClickL
) )
put( put(
"samsung", listOf( "samsung", listOf(
"com.samsung.android.sm_cn/com.samsung.android.sm.ui.ram.AutoRunActivity", "com.samsung.android.sm_cn/com.samsung.android.sm.ui.ram.AutoRunActivity", "com.samsung.android.sm_cn/com.samsung.android.sm.ui.appmanagement.AppManagementActivity", "com.samsung.android.sm_cn/com.samsung.android.sm.ui.cstyleboard.SmartManagerDashBoardActivity", "com.samsung.android.sm_cn/.ui.ram.RamActivity", "com.samsung.android.sm_cn/.app.dashboard.SmartManagerDashBoardActivity", "com.samsung.android.sm/com.samsung.android.sm.ui.ram.AutoRunActivity", "com.samsung.android.sm/com.samsung.android.sm.ui.appmanagement.AppManagementActivity", "com.samsung.android.sm/com.samsung.android.sm.ui.cstyleboard.SmartManagerDashBoardActivity", "com.samsung.android.sm/.ui.ram.RamActivity", "com.samsung.android.sm/.app.dashboard.SmartManagerDashBoardActivity", "com.samsung.android.lool/com.samsung.android.sm.ui.battery.BatteryActivity", "com.samsung.android.sm_cn", "com.samsung.android.sm"
"com.samsung.android.sm_cn/com.samsung.android.sm.ui.appmanagement.AppManagementActivity",
"com.samsung.android.sm_cn/com.samsung.android.sm.ui.cstyleboard.SmartManagerDashBoardActivity",
"com.samsung.android.sm_cn/.ui.ram.RamActivity",
"com.samsung.android.sm_cn/.app.dashboard.SmartManagerDashBoardActivity",
"com.samsung.android.sm/com.samsung.android.sm.ui.ram.AutoRunActivity",
"com.samsung.android.sm/com.samsung.android.sm.ui.appmanagement.AppManagementActivity",
"com.samsung.android.sm/com.samsung.android.sm.ui.cstyleboard.SmartManagerDashBoardActivity",
"com.samsung.android.sm/.ui.ram.RamActivity",
"com.samsung.android.sm/.app.dashboard.SmartManagerDashBoardActivity",
"com.samsung.android.lool/com.samsung.android.sm.ui.battery.BatteryActivity",
"com.samsung.android.sm_cn",
"com.samsung.android.sm"
) )
) )
put( put(
"HUAWEI", listOf( "HUAWEI", listOf(
"com.huawei.systemmanager/.startupmgr.ui.StartupNormalAppListActivity", //EMUI9.1.0(方舟,9.0) "com.huawei.systemmanager/.startupmgr.ui.StartupNormalAppListActivity", //EMUI9.1.0(方舟,9.0)
"com.huawei.systemmanager/.appcontrol.activity.StartupAppControlActivity", "com.huawei.systemmanager/.appcontrol.activity.StartupAppControlActivity", "com.huawei.systemmanager/.optimize.process.ProtectActivity", "com.huawei.systemmanager/.optimize.bootstart.BootStartActivity", "com.huawei.systemmanager" //最后一行可以写包名, 这样如果签名的类路径在某些新版本的ROM中没找到 就直接跳转到对应的安全中心/手机管家 首页.
"com.huawei.systemmanager/.optimize.process.ProtectActivity",
"com.huawei.systemmanager/.optimize.bootstart.BootStartActivity",
"com.huawei.systemmanager" //最后一行可以写包名, 这样如果签名的类路径在某些新版本的ROM中没找到 就直接跳转到对应的安全中心/手机管家 首页.
) )
) )
put( put(
"vivo", listOf( "vivo", listOf(
"com.iqoo.secure/.ui.phoneoptimize.BgStartUpManager", "com.iqoo.secure/.ui.phoneoptimize.BgStartUpManager", "com.iqoo.secure/.safeguard.PurviewTabActivity", "com.vivo.permissionmanager/.activity.BgStartUpManagerActivity", //"com.iqoo.secure/.ui.phoneoptimize.AddWhiteListActivity", //这是白名单, 不是自启动
"com.iqoo.secure/.safeguard.PurviewTabActivity", "com.iqoo.secure", "com.vivo.permissionmanager"
"com.vivo.permissionmanager/.activity.BgStartUpManagerActivity", //"com.iqoo.secure/.ui.phoneoptimize.AddWhiteListActivity", //这是白名单, 不是自启动
"com.iqoo.secure",
"com.vivo.permissionmanager"
) )
) )
put( put(
@ -956,95 +875,77 @@ class SettingsFragment : BaseFragment<FragmentSettingsBinding?>(), View.OnClickL
) )
put( put(
"OPPO", listOf( "OPPO", listOf(
"com.coloros.safecenter/.startupapp.StartupAppListActivity", "com.coloros.safecenter/.startupapp.StartupAppListActivity", "com.coloros.safecenter/.permission.startup.StartupAppListActivity", "com.oppo.safe/.permission.startup.StartupAppListActivity", "com.coloros.oppoguardelf/com.coloros.powermanager.fuelgaue.PowerUsageModelActivity", "com.coloros.safecenter/com.coloros.privacypermissionsentry.PermissionTopActivity", "com.coloros.safecenter", "com.oppo.safe", "com.coloros.oppoguardelf"
"com.coloros.safecenter/.permission.startup.StartupAppListActivity",
"com.oppo.safe/.permission.startup.StartupAppListActivity",
"com.coloros.oppoguardelf/com.coloros.powermanager.fuelgaue.PowerUsageModelActivity",
"com.coloros.safecenter/com.coloros.privacypermissionsentry.PermissionTopActivity",
"com.coloros.safecenter",
"com.oppo.safe",
"com.coloros.oppoguardelf"
) )
) )
put( put(
"oneplus", listOf( "oneplus", listOf(
"com.oneplus.security/.chainlaunch.view.ChainLaunchAppListActivity", "com.oneplus.security/.chainlaunch.view.ChainLaunchAppListActivity", "com.oneplus.security"
"com.oneplus.security"
) )
) )
put( put(
"letv", listOf( "letv", listOf(
"com.letv.android.letvsafe/.AutobootManageActivity", "com.letv.android.letvsafe/.AutobootManageActivity", "com.letv.android.letvsafe/.BackgroundAppManageActivity", //应用保护
"com.letv.android.letvsafe/.BackgroundAppManageActivity", //应用保护
"com.letv.android.letvsafe" "com.letv.android.letvsafe"
) )
) )
put( put(
"zte", listOf( "zte", listOf(
"com.zte.heartyservice/.autorun.AppAutoRunManager", "com.zte.heartyservice/.autorun.AppAutoRunManager", "com.zte.heartyservice"
"com.zte.heartyservice"
) )
) )
//金立 //金立
put( put(
"F", listOf( "F", listOf(
"com.gionee.softmanager/.MainActivity", "com.gionee.softmanager/.MainActivity", "com.gionee.softmanager"
"com.gionee.softmanager"
) )
) )
//以下为未确定(厂商名也不确定) //以下为未确定(厂商名也不确定)
put( put(
"smartisanos", listOf( "smartisanos", listOf(
"com.smartisanos.security/.invokeHistory.InvokeHistoryActivity", "com.smartisanos.security/.invokeHistory.InvokeHistoryActivity", "com.smartisanos.security"
"com.smartisanos.security"
) )
) )
//360 //360
put( put(
"360", listOf( "360", listOf(
"com.yulong.android.coolsafe/.ui.activity.autorun.AutoRunListActivity", "com.yulong.android.coolsafe/.ui.activity.autorun.AutoRunListActivity", "com.yulong.android.coolsafe"
"com.yulong.android.coolsafe"
) )
) )
//360 //360
put( put(
"ulong", listOf( "ulong", listOf(
"com.yulong.android.coolsafe/.ui.activity.autorun.AutoRunListActivity", "com.yulong.android.coolsafe/.ui.activity.autorun.AutoRunListActivity", "com.yulong.android.coolsafe"
"com.yulong.android.coolsafe"
) )
) )
//酷派 //酷派
put( put(
"coolpad" /*厂商名称不确定是否正确*/, listOf( "coolpad" /*厂商名称不确定是否正确*/, listOf(
"com.yulong.android.security/com.yulong.android.seccenter.tabbarmain", "com.yulong.android.security/com.yulong.android.seccenter.tabbarmain", "com.yulong.android.security"
"com.yulong.android.security"
) )
) )
//联想 //联想
put( put(
"lenovo" /*厂商名称不确定是否正确*/, listOf( "lenovo" /*厂商名称不确定是否正确*/, listOf(
"com.lenovo.security/.purebackground.PureBackgroundActivity", "com.lenovo.security/.purebackground.PureBackgroundActivity", "com.lenovo.security"
"com.lenovo.security"
) )
) )
put( put(
"htc" /*厂商名称不确定是否正确*/, listOf( "htc" /*厂商名称不确定是否正确*/, listOf(
"com.htc.pitroad/.landingpage.activity.LandingPageActivity", "com.htc.pitroad/.landingpage.activity.LandingPageActivity", "com.htc.pitroad"
"com.htc.pitroad"
) )
) )
//华硕 //华硕
put( put(
"asus" /*厂商名称不确定是否正确*/, listOf( "asus" /*厂商名称不确定是否正确*/, listOf(
"com.asus.mobilemanager/.MainActivity", "com.asus.mobilemanager/.MainActivity", "com.asus.mobilemanager"
"com.asus.mobilemanager"
) )
) )
} }
@ -1069,8 +970,7 @@ class SettingsFragment : BaseFragment<FragmentSettingsBinding?>(), View.OnClickL
} else { } else {
//找不到? 网上的做法都是跳转到设置... 这基本上是没意义的 基本上自启动这个功能是第三方厂商自己写的安全管家类app //找不到? 网上的做法都是跳转到设置... 这基本上是没意义的 基本上自启动这个功能是第三方厂商自己写的安全管家类app
//所以我是直接跳转到对应的安全管家/安全中心 //所以我是直接跳转到对应的安全管家/安全中心
intent = intent = act?.let { context.packageManager.getLaunchIntentForPackage(it) }
act?.let { context.packageManager.getLaunchIntentForPackage(it) }
} }
context.startActivity(intent) context.startActivity(intent)
has = true has = true

@ -1,151 +1,151 @@
package com.idormy.sms.forwarder.fragment.client package com.idormy.sms.forwarder.fragment.client
import android.util.Log import android.util.Log
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.ViewGroup import android.view.ViewGroup
import com.google.gson.Gson import com.google.gson.Gson
import com.google.gson.reflect.TypeToken import com.google.gson.reflect.TypeToken
import com.idormy.sms.forwarder.R import com.idormy.sms.forwarder.R
import com.idormy.sms.forwarder.core.BaseFragment import com.idormy.sms.forwarder.core.BaseFragment
import com.idormy.sms.forwarder.databinding.FragmentClientBatteryQueryBinding import com.idormy.sms.forwarder.databinding.FragmentClientBatteryQueryBinding
import com.idormy.sms.forwarder.entity.BatteryInfo import com.idormy.sms.forwarder.entity.BatteryInfo
import com.idormy.sms.forwarder.server.model.BaseResponse import com.idormy.sms.forwarder.server.model.BaseResponse
import com.idormy.sms.forwarder.utils.* import com.idormy.sms.forwarder.utils.*
import com.xuexiang.xhttp2.XHttp import com.xuexiang.xhttp2.XHttp
import com.xuexiang.xhttp2.cache.model.CacheMode import com.xuexiang.xhttp2.cache.model.CacheMode
import com.xuexiang.xhttp2.callback.SimpleCallBack import com.xuexiang.xhttp2.callback.SimpleCallBack
import com.xuexiang.xhttp2.exception.ApiException import com.xuexiang.xhttp2.exception.ApiException
import com.xuexiang.xpage.annotation.Page import com.xuexiang.xpage.annotation.Page
import com.xuexiang.xrouter.utils.TextUtils import com.xuexiang.xrouter.utils.TextUtils
import com.xuexiang.xui.utils.ResUtils import com.xuexiang.xui.utils.ResUtils
import com.xuexiang.xui.widget.actionbar.TitleBar import com.xuexiang.xui.widget.actionbar.TitleBar
import com.xuexiang.xui.widget.grouplist.XUIGroupListView import com.xuexiang.xui.widget.grouplist.XUIGroupListView
import com.xuexiang.xutil.data.ConvertTools import com.xuexiang.xutil.data.ConvertTools
@Suppress("PropertyName") @Suppress("PropertyName")
@Page(name = "远程查电量") @Page(name = "远程查电量")
class BatteryQueryFragment : BaseFragment<FragmentClientBatteryQueryBinding?>() { class BatteryQueryFragment : BaseFragment<FragmentClientBatteryQueryBinding?>() {
val TAG: String = BatteryQueryFragment::class.java.simpleName val TAG: String = BatteryQueryFragment::class.java.simpleName
override fun viewBindingInflate( override fun viewBindingInflate(
inflater: LayoutInflater, inflater: LayoutInflater,
container: ViewGroup, container: ViewGroup,
): FragmentClientBatteryQueryBinding { ): FragmentClientBatteryQueryBinding {
return FragmentClientBatteryQueryBinding.inflate(inflater, container, false) return FragmentClientBatteryQueryBinding.inflate(inflater, container, false)
} }
override fun initTitle(): TitleBar? { override fun initTitle(): TitleBar? {
val titleBar = super.initTitle()!!.setImmersive(false) val titleBar = super.initTitle()!!.setImmersive(false)
titleBar.setTitle(R.string.api_battery_query) titleBar.setTitle(R.string.api_battery_query)
return titleBar return titleBar
} }
/** /**
* 初始化控件 * 初始化控件
*/ */
override fun initViews() { override fun initViews() {
val requestUrl: String = HttpServerUtils.serverAddress + "/battery/query" val requestUrl: String = HttpServerUtils.serverAddress + "/battery/query"
Log.i(TAG, "requestUrl:$requestUrl") Log.i(TAG, "requestUrl:$requestUrl")
val msgMap: MutableMap<String, Any> = mutableMapOf() val msgMap: MutableMap<String, Any> = mutableMapOf()
val timestamp = System.currentTimeMillis() val timestamp = System.currentTimeMillis()
msgMap["timestamp"] = timestamp msgMap["timestamp"] = timestamp
val clientSignKey = HttpServerUtils.clientSignKey val clientSignKey = HttpServerUtils.clientSignKey
if (!TextUtils.isEmpty(clientSignKey)) { if (!TextUtils.isEmpty(clientSignKey)) {
msgMap["sign"] = HttpServerUtils.calcSign(timestamp.toString(), clientSignKey.toString()) msgMap["sign"] = HttpServerUtils.calcSign(timestamp.toString(), clientSignKey)
} }
val dataMap: MutableMap<String, Any> = mutableMapOf() val dataMap: MutableMap<String, Any> = mutableMapOf()
msgMap["data"] = dataMap msgMap["data"] = dataMap
var requestMsg: String = Gson().toJson(msgMap) var requestMsg: String = Gson().toJson(msgMap)
Log.i(TAG, "requestMsg:$requestMsg") Log.i(TAG, "requestMsg:$requestMsg")
val postRequest = XHttp.post(requestUrl) val postRequest = XHttp.post(requestUrl)
.keepJson(true) .keepJson(true)
.timeOut((SettingUtils.requestTimeout * 1000).toLong()) //超时时间10s .timeOut((SettingUtils.requestTimeout * 1000).toLong()) //超时时间10s
.cacheMode(CacheMode.NO_CACHE) .cacheMode(CacheMode.NO_CACHE)
.timeStamp(true) .timeStamp(true)
when (HttpServerUtils.clientSafetyMeasures) { when (HttpServerUtils.clientSafetyMeasures) {
2 -> { 2 -> {
try { try {
val publicKey = RSACrypt.getPublicKey(HttpServerUtils.clientSignKey.toString()) val publicKey = RSACrypt.getPublicKey(HttpServerUtils.clientSignKey)
requestMsg = Base64.encode(requestMsg.toByteArray()) requestMsg = Base64.encode(requestMsg.toByteArray())
requestMsg = RSACrypt.encryptByPublicKey(requestMsg, publicKey) requestMsg = RSACrypt.encryptByPublicKey(requestMsg, publicKey)
Log.i(TAG, "requestMsg: $requestMsg") Log.i(TAG, "requestMsg: $requestMsg")
} catch (e: Exception) { } catch (e: Exception) {
XToastUtils.error(ResUtils.getString(R.string.request_failed) + e.message) XToastUtils.error(ResUtils.getString(R.string.request_failed) + e.message)
e.printStackTrace() e.printStackTrace()
return return
} }
postRequest.upString(requestMsg) postRequest.upString(requestMsg)
} }
3 -> { 3 -> {
try { try {
val sm4Key = ConvertTools.hexStringToByteArray(HttpServerUtils.clientSignKey.toString()) val sm4Key = ConvertTools.hexStringToByteArray(HttpServerUtils.clientSignKey)
//requestMsg = Base64.encode(requestMsg.toByteArray()) //requestMsg = Base64.encode(requestMsg.toByteArray())
val encryptCBC = SM4Crypt.encrypt(requestMsg.toByteArray(), sm4Key) val encryptCBC = SM4Crypt.encrypt(requestMsg.toByteArray(), sm4Key)
requestMsg = ConvertTools.bytes2HexString(encryptCBC) requestMsg = ConvertTools.bytes2HexString(encryptCBC)
Log.i(TAG, "requestMsg: $requestMsg") Log.i(TAG, "requestMsg: $requestMsg")
} catch (e: Exception) { } catch (e: Exception) {
XToastUtils.error(ResUtils.getString(R.string.request_failed) + e.message) XToastUtils.error(ResUtils.getString(R.string.request_failed) + e.message)
e.printStackTrace() e.printStackTrace()
return return
} }
postRequest.upString(requestMsg) postRequest.upString(requestMsg)
} }
else -> { else -> {
postRequest.upJson(requestMsg) postRequest.upJson(requestMsg)
} }
} }
postRequest.execute(object : SimpleCallBack<String>() { postRequest.execute(object : SimpleCallBack<String>() {
override fun onError(e: ApiException) { override fun onError(e: ApiException) {
XToastUtils.error(e.displayMessage) XToastUtils.error(e.displayMessage)
} }
override fun onSuccess(response: String) { override fun onSuccess(response: String) {
Log.i(TAG, response) Log.i(TAG, response)
try { try {
var json = response var json = response
if (HttpServerUtils.clientSafetyMeasures == 2) { if (HttpServerUtils.clientSafetyMeasures == 2) {
val publicKey = RSACrypt.getPublicKey(HttpServerUtils.clientSignKey.toString()) val publicKey = RSACrypt.getPublicKey(HttpServerUtils.clientSignKey)
json = RSACrypt.decryptByPublicKey(json, publicKey) json = RSACrypt.decryptByPublicKey(json, publicKey)
json = String(Base64.decode(json)) json = String(Base64.decode(json))
} else if (HttpServerUtils.clientSafetyMeasures == 3) { } else if (HttpServerUtils.clientSafetyMeasures == 3) {
val sm4Key = ConvertTools.hexStringToByteArray(HttpServerUtils.clientSignKey.toString()) val sm4Key = ConvertTools.hexStringToByteArray(HttpServerUtils.clientSignKey)
val encryptCBC = ConvertTools.hexStringToByteArray(json) val encryptCBC = ConvertTools.hexStringToByteArray(json)
val decryptCBC = SM4Crypt.decrypt(encryptCBC, sm4Key) val decryptCBC = SM4Crypt.decrypt(encryptCBC, sm4Key)
json = String(decryptCBC) json = String(decryptCBC)
} }
val resp: BaseResponse<BatteryInfo> = Gson().fromJson(json, object : TypeToken<BaseResponse<BatteryInfo>>() {}.type) val resp: BaseResponse<BatteryInfo> = Gson().fromJson(json, object : TypeToken<BaseResponse<BatteryInfo>>() {}.type)
if (resp.code == 200) { if (resp.code == 200) {
XToastUtils.success(ResUtils.getString(R.string.request_succeeded)) XToastUtils.success(ResUtils.getString(R.string.request_succeeded))
val batteryInfo = resp.data ?: return val batteryInfo = resp.data ?: return
val groupListView = binding!!.infoList val groupListView = binding!!.infoList
val section = XUIGroupListView.newSection(context) val section = XUIGroupListView.newSection(context)
section.addItemView(groupListView.createItemView(String.format(ResUtils.getString(R.string.battery_level), batteryInfo.level))) {} section.addItemView(groupListView.createItemView(String.format(ResUtils.getString(R.string.battery_level), batteryInfo.level))) {}
if (batteryInfo.scale != "") section.addItemView(groupListView.createItemView(String.format(ResUtils.getString(R.string.battery_scale), batteryInfo.scale))) {} if (batteryInfo.scale != "") section.addItemView(groupListView.createItemView(String.format(ResUtils.getString(R.string.battery_scale), batteryInfo.scale))) {}
if (batteryInfo.voltage != "") section.addItemView(groupListView.createItemView(String.format(ResUtils.getString(R.string.battery_voltage), batteryInfo.voltage))) {} if (batteryInfo.voltage != "") section.addItemView(groupListView.createItemView(String.format(ResUtils.getString(R.string.battery_voltage), batteryInfo.voltage))) {}
if (batteryInfo.temperature != "") section.addItemView(groupListView.createItemView(String.format(ResUtils.getString(R.string.battery_temperature), batteryInfo.temperature))) {} if (batteryInfo.temperature != "") section.addItemView(groupListView.createItemView(String.format(ResUtils.getString(R.string.battery_temperature), batteryInfo.temperature))) {}
section.addItemView(groupListView.createItemView(String.format(ResUtils.getString(R.string.battery_status), batteryInfo.status))) {} section.addItemView(groupListView.createItemView(String.format(ResUtils.getString(R.string.battery_status), batteryInfo.status))) {}
section.addItemView(groupListView.createItemView(String.format(ResUtils.getString(R.string.battery_health), batteryInfo.health))) {} section.addItemView(groupListView.createItemView(String.format(ResUtils.getString(R.string.battery_health), batteryInfo.health))) {}
section.addItemView(groupListView.createItemView(String.format(ResUtils.getString(R.string.battery_plugged), batteryInfo.plugged))) {} section.addItemView(groupListView.createItemView(String.format(ResUtils.getString(R.string.battery_plugged), batteryInfo.plugged))) {}
section.addTo(groupListView) section.addTo(groupListView)
} else { } else {
XToastUtils.error(ResUtils.getString(R.string.request_failed) + resp.msg) XToastUtils.error(ResUtils.getString(R.string.request_failed) + resp.msg)
} }
} catch (e: Exception) { } catch (e: Exception) {
e.printStackTrace() e.printStackTrace()
XToastUtils.error(ResUtils.getString(R.string.request_failed) + response) XToastUtils.error(ResUtils.getString(R.string.request_failed) + response)
} }
} }
}) })
} }
} }

@ -1,295 +1,295 @@
package com.idormy.sms.forwarder.fragment.client package com.idormy.sms.forwarder.fragment.client
import android.util.Log import android.util.Log
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.alibaba.android.vlayout.DelegateAdapter import com.alibaba.android.vlayout.DelegateAdapter
import com.alibaba.android.vlayout.VirtualLayoutManager import com.alibaba.android.vlayout.VirtualLayoutManager
import com.alibaba.android.vlayout.layout.LinearLayoutHelper import com.alibaba.android.vlayout.layout.LinearLayoutHelper
import com.google.gson.Gson import com.google.gson.Gson
import com.google.gson.reflect.TypeToken import com.google.gson.reflect.TypeToken
import com.idormy.sms.forwarder.R import com.idormy.sms.forwarder.R
import com.idormy.sms.forwarder.adapter.base.broccoli.BroccoliSimpleDelegateAdapter import com.idormy.sms.forwarder.adapter.base.broccoli.BroccoliSimpleDelegateAdapter
import com.idormy.sms.forwarder.adapter.base.delegate.SimpleDelegateAdapter import com.idormy.sms.forwarder.adapter.base.delegate.SimpleDelegateAdapter
import com.idormy.sms.forwarder.core.BaseFragment import com.idormy.sms.forwarder.core.BaseFragment
import com.idormy.sms.forwarder.databinding.FragmentClientCallQueryBinding import com.idormy.sms.forwarder.databinding.FragmentClientCallQueryBinding
import com.idormy.sms.forwarder.entity.CallInfo import com.idormy.sms.forwarder.entity.CallInfo
import com.idormy.sms.forwarder.server.model.BaseResponse import com.idormy.sms.forwarder.server.model.BaseResponse
import com.idormy.sms.forwarder.server.model.CallQueryData import com.idormy.sms.forwarder.server.model.CallQueryData
import com.idormy.sms.forwarder.utils.* import com.idormy.sms.forwarder.utils.*
import com.jeremyliao.liveeventbus.LiveEventBus import com.jeremyliao.liveeventbus.LiveEventBus
import com.scwang.smartrefresh.layout.api.RefreshLayout import com.scwang.smartrefresh.layout.api.RefreshLayout
import com.xuexiang.xaop.annotation.SingleClick import com.xuexiang.xaop.annotation.SingleClick
import com.xuexiang.xhttp2.XHttp import com.xuexiang.xhttp2.XHttp
import com.xuexiang.xhttp2.cache.model.CacheMode import com.xuexiang.xhttp2.cache.model.CacheMode
import com.xuexiang.xhttp2.callback.SimpleCallBack import com.xuexiang.xhttp2.callback.SimpleCallBack
import com.xuexiang.xhttp2.exception.ApiException import com.xuexiang.xhttp2.exception.ApiException
import com.xuexiang.xpage.annotation.Page import com.xuexiang.xpage.annotation.Page
import com.xuexiang.xpage.base.XPageActivity import com.xuexiang.xpage.base.XPageActivity
import com.xuexiang.xpage.core.PageOption import com.xuexiang.xpage.core.PageOption
import com.xuexiang.xrouter.utils.TextUtils import com.xuexiang.xrouter.utils.TextUtils
import com.xuexiang.xui.adapter.recyclerview.RecyclerViewHolder import com.xuexiang.xui.adapter.recyclerview.RecyclerViewHolder
import com.xuexiang.xui.utils.ResUtils import com.xuexiang.xui.utils.ResUtils
import com.xuexiang.xui.utils.SnackbarUtils import com.xuexiang.xui.utils.SnackbarUtils
import com.xuexiang.xui.widget.actionbar.TitleBar import com.xuexiang.xui.widget.actionbar.TitleBar
import com.xuexiang.xui.widget.searchview.MaterialSearchView import com.xuexiang.xui.widget.searchview.MaterialSearchView
import com.xuexiang.xutil.data.ConvertTools import com.xuexiang.xutil.data.ConvertTools
import com.xuexiang.xutil.data.DateUtils import com.xuexiang.xutil.data.DateUtils
import com.xuexiang.xutil.system.ClipboardUtils import com.xuexiang.xutil.system.ClipboardUtils
import me.samlss.broccoli.Broccoli import me.samlss.broccoli.Broccoli
@Suppress("PropertyName") @Suppress("PropertyName")
@Page(name = "远程查通话") @Page(name = "远程查通话")
class CallQueryFragment : BaseFragment<FragmentClientCallQueryBinding?>() { class CallQueryFragment : BaseFragment<FragmentClientCallQueryBinding?>() {
val TAG: String = CallQueryFragment::class.java.simpleName val TAG: String = CallQueryFragment::class.java.simpleName
private var mAdapter: SimpleDelegateAdapter<CallInfo>? = null private var mAdapter: SimpleDelegateAdapter<CallInfo>? = null
private var callType: Int = 3 private var callType: Int = 3
private var pageNum: Int = 1 private var pageNum: Int = 1
private val pageSize: Int = 20 private val pageSize: Int = 20
private var keyword: String = "" private var keyword: String = ""
override fun viewBindingInflate( override fun viewBindingInflate(
inflater: LayoutInflater, inflater: LayoutInflater,
container: ViewGroup, container: ViewGroup,
): FragmentClientCallQueryBinding { ): FragmentClientCallQueryBinding {
return FragmentClientCallQueryBinding.inflate(inflater, container, false) return FragmentClientCallQueryBinding.inflate(inflater, container, false)
} }
override fun initTitle(): TitleBar { override fun initTitle(): TitleBar {
val titleBar = super.initTitle()!!.setImmersive(false) val titleBar = super.initTitle()!!.setImmersive(false)
titleBar.setTitle(R.string.api_call_query) titleBar.setTitle(R.string.api_call_query)
titleBar!!.addAction(object : TitleBar.ImageAction(R.drawable.ic_query) { titleBar!!.addAction(object : TitleBar.ImageAction(R.drawable.ic_query) {
@SingleClick @SingleClick
override fun performAction(view: View) { override fun performAction(view: View) {
binding!!.searchView.showSearch() binding!!.searchView.showSearch()
} }
}) })
return titleBar return titleBar
} }
/** /**
* 初始化控件 * 初始化控件
*/ */
override fun initViews() { override fun initViews() {
val virtualLayoutManager = VirtualLayoutManager(requireContext()) val virtualLayoutManager = VirtualLayoutManager(requireContext())
binding!!.recyclerView.layoutManager = virtualLayoutManager binding!!.recyclerView.layoutManager = virtualLayoutManager
val viewPool = RecyclerView.RecycledViewPool() val viewPool = RecyclerView.RecycledViewPool()
binding!!.recyclerView.setRecycledViewPool(viewPool) binding!!.recyclerView.setRecycledViewPool(viewPool)
viewPool.setMaxRecycledViews(0, 10) viewPool.setMaxRecycledViews(0, 10)
mAdapter = object : BroccoliSimpleDelegateAdapter<CallInfo>( mAdapter = object : BroccoliSimpleDelegateAdapter<CallInfo>(
R.layout.adapter_call_card_view_list_item, R.layout.adapter_call_card_view_list_item,
LinearLayoutHelper(), LinearLayoutHelper(),
DataProvider.emptyCallInfo DataProvider.emptyCallInfo
) { ) {
override fun onBindData( override fun onBindData(
holder: RecyclerViewHolder, holder: RecyclerViewHolder,
model: CallInfo, model: CallInfo,
position: Int, position: Int,
) { ) {
val from = if (TextUtils.isEmpty(model.name)) model.number else model.number + " | " + model.name val from = if (TextUtils.isEmpty(model.name)) model.number else model.number + " | " + model.name
holder.text(R.id.tv_from, from) holder.text(R.id.tv_from, from)
holder.text(R.id.tv_time, DateUtils.getFriendlyTimeSpanByNow(model.dateLong)) holder.text(R.id.tv_time, DateUtils.getFriendlyTimeSpanByNow(model.dateLong))
holder.image(R.id.iv_image, model.typeImageId) holder.image(R.id.iv_image, model.typeImageId)
holder.image(R.id.iv_sim_image, model.simImageId) holder.image(R.id.iv_sim_image, model.simImageId)
holder.text(R.id.tv_duration, ResUtils.getString(R.string.call_duration) + model.duration + ResUtils.getString(R.string.seconds)) holder.text(R.id.tv_duration, ResUtils.getString(R.string.call_duration) + model.duration + ResUtils.getString(R.string.seconds))
holder.image(R.id.iv_copy, R.drawable.ic_copy) holder.image(R.id.iv_copy, R.drawable.ic_copy)
holder.image(R.id.iv_call, R.drawable.ic_phone_out) holder.image(R.id.iv_call, R.drawable.ic_phone_out)
holder.image(R.id.iv_reply, R.drawable.ic_reply) holder.image(R.id.iv_reply, R.drawable.ic_reply)
holder.click(R.id.iv_copy) { holder.click(R.id.iv_copy) {
XToastUtils.info(String.format(getString(R.string.copied_to_clipboard), from)) XToastUtils.info(String.format(getString(R.string.copied_to_clipboard), from))
ClipboardUtils.copyText(from) ClipboardUtils.copyText(from)
} }
holder.click(R.id.iv_call) { holder.click(R.id.iv_call) {
XToastUtils.info(getString(R.string.local_call) + model.number) XToastUtils.info(getString(R.string.local_call) + model.number)
PhoneUtils.dial(model.number) PhoneUtils.dial(model.number)
} }
holder.click(R.id.iv_reply) { holder.click(R.id.iv_reply) {
XToastUtils.info(getString(R.string.remote_sms) + model.number) XToastUtils.info(getString(R.string.remote_sms) + model.number)
LiveEventBus.get<Int>(EVENT_KEY_SIM_SLOT).post(model.simId) LiveEventBus.get<Int>(EVENT_KEY_SIM_SLOT).post(model.simId)
LiveEventBus.get<String>(EVENT_KEY_PHONE_NUMBERS).post(model.number) LiveEventBus.get<String>(EVENT_KEY_PHONE_NUMBERS).post(model.number)
PageOption.to(SmsSendFragment::class.java).setNewActivity(true).open((context as XPageActivity?)!!) PageOption.to(SmsSendFragment::class.java).setNewActivity(true).open((context as XPageActivity?)!!)
} }
} }
override fun onBindBroccoli(holder: RecyclerViewHolder, broccoli: Broccoli) { override fun onBindBroccoli(holder: RecyclerViewHolder, broccoli: Broccoli) {
broccoli.addPlaceholder(PlaceholderHelper.getParameter(holder.findView(R.id.tv_from))) broccoli.addPlaceholder(PlaceholderHelper.getParameter(holder.findView(R.id.tv_from)))
.addPlaceholder(PlaceholderHelper.getParameter(holder.findView(R.id.tv_time))) .addPlaceholder(PlaceholderHelper.getParameter(holder.findView(R.id.tv_time)))
.addPlaceholder(PlaceholderHelper.getParameter(holder.findView(R.id.iv_sim_image))) .addPlaceholder(PlaceholderHelper.getParameter(holder.findView(R.id.iv_sim_image)))
.addPlaceholder(PlaceholderHelper.getParameter(holder.findView(R.id.tv_duration))) .addPlaceholder(PlaceholderHelper.getParameter(holder.findView(R.id.tv_duration)))
.addPlaceholder(PlaceholderHelper.getParameter(holder.findView(R.id.iv_image))) .addPlaceholder(PlaceholderHelper.getParameter(holder.findView(R.id.iv_image)))
.addPlaceholder(PlaceholderHelper.getParameter(holder.findView(R.id.iv_copy))) .addPlaceholder(PlaceholderHelper.getParameter(holder.findView(R.id.iv_copy)))
.addPlaceholder(PlaceholderHelper.getParameter(holder.findView(R.id.iv_call))) .addPlaceholder(PlaceholderHelper.getParameter(holder.findView(R.id.iv_call)))
.addPlaceholder(PlaceholderHelper.getParameter(holder.findView(R.id.iv_reply))) .addPlaceholder(PlaceholderHelper.getParameter(holder.findView(R.id.iv_reply)))
} }
} }
val delegateAdapter = DelegateAdapter(virtualLayoutManager) val delegateAdapter = DelegateAdapter(virtualLayoutManager)
delegateAdapter.addAdapter(mAdapter) delegateAdapter.addAdapter(mAdapter)
binding!!.recyclerView.adapter = delegateAdapter binding!!.recyclerView.adapter = delegateAdapter
binding!!.tabBar.setTabTitles(ResUtils.getStringArray(R.array.call_type_option)) binding!!.tabBar.setTabTitles(ResUtils.getStringArray(R.array.call_type_option))
binding!!.tabBar.setOnTabClickListener { _, position -> binding!!.tabBar.setOnTabClickListener { _, position ->
//XToastUtils.toast("点击了$title--$position") //XToastUtils.toast("点击了$title--$position")
callType = 3 - position callType = 3 - position
loadRemoteData(true) loadRemoteData(true)
binding!!.recyclerView.scrollToPosition(0) binding!!.recyclerView.scrollToPosition(0)
} }
//搜索框 //搜索框
binding!!.searchView.findViewById<View>(com.xuexiang.xui.R.id.search_layout).visibility = View.GONE binding!!.searchView.findViewById<View>(com.xuexiang.xui.R.id.search_layout).visibility = View.GONE
binding!!.searchView.setVoiceSearch(true) binding!!.searchView.setVoiceSearch(true)
binding!!.searchView.setEllipsize(true) binding!!.searchView.setEllipsize(true)
binding!!.searchView.setSuggestions(resources.getStringArray(R.array.query_suggestions)) binding!!.searchView.setSuggestions(resources.getStringArray(R.array.query_suggestions))
binding!!.searchView.setOnQueryTextListener(object : MaterialSearchView.OnQueryTextListener { binding!!.searchView.setOnQueryTextListener(object : MaterialSearchView.OnQueryTextListener {
override fun onQueryTextSubmit(query: String): Boolean { override fun onQueryTextSubmit(query: String): Boolean {
SnackbarUtils.Indefinite(view, String.format(getString(R.string.search_keyword), query)).info() SnackbarUtils.Indefinite(view, String.format(getString(R.string.search_keyword), query)).info()
.actionColor(ResUtils.getColor(R.color.xui_config_color_white)) .actionColor(ResUtils.getColor(R.color.xui_config_color_white))
.setAction(getString(R.string.clear)) { .setAction(getString(R.string.clear)) {
keyword = "" keyword = ""
loadRemoteData(true) loadRemoteData(true)
}.show() }.show()
if (keyword != query) { if (keyword != query) {
keyword = query keyword = query
loadRemoteData(true) loadRemoteData(true)
} }
return false return false
} }
override fun onQueryTextChange(newText: String): Boolean { override fun onQueryTextChange(newText: String): Boolean {
//Do some magic //Do some magic
return false return false
} }
}) })
binding!!.searchView.setOnSearchViewListener(object : MaterialSearchView.SearchViewListener { binding!!.searchView.setOnSearchViewListener(object : MaterialSearchView.SearchViewListener {
override fun onSearchViewShown() { override fun onSearchViewShown() {
//Do some magic //Do some magic
} }
override fun onSearchViewClosed() { override fun onSearchViewClosed() {
//Do some magic //Do some magic
} }
}) })
binding!!.searchView.setSubmitOnClick(true) binding!!.searchView.setSubmitOnClick(true)
} }
override fun initListeners() { override fun initListeners() {
//下拉刷新 //下拉刷新
binding!!.refreshLayout.setOnRefreshListener { refreshLayout: RefreshLayout -> binding!!.refreshLayout.setOnRefreshListener { refreshLayout: RefreshLayout ->
refreshLayout.layout.postDelayed({ refreshLayout.layout.postDelayed({
loadRemoteData(true) loadRemoteData(true)
}, 1000) }, 1000)
} }
//上拉加载 //上拉加载
binding!!.refreshLayout.setOnLoadMoreListener { refreshLayout: RefreshLayout -> binding!!.refreshLayout.setOnLoadMoreListener { refreshLayout: RefreshLayout ->
refreshLayout.layout.postDelayed({ refreshLayout.layout.postDelayed({
loadRemoteData(false) loadRemoteData(false)
}, 1000) }, 1000)
} }
binding!!.refreshLayout.autoRefresh() //第一次进入触发自动刷新,演示效果 binding!!.refreshLayout.autoRefresh() //第一次进入触发自动刷新,演示效果
} }
private fun loadRemoteData(refresh: Boolean) { private fun loadRemoteData(refresh: Boolean) {
val requestUrl: String = HttpServerUtils.serverAddress + "/call/query" val requestUrl: String = HttpServerUtils.serverAddress + "/call/query"
Log.i(TAG, "requestUrl:$requestUrl") Log.i(TAG, "requestUrl:$requestUrl")
val msgMap: MutableMap<String, Any> = mutableMapOf() val msgMap: MutableMap<String, Any> = mutableMapOf()
val timestamp = System.currentTimeMillis() val timestamp = System.currentTimeMillis()
msgMap["timestamp"] = timestamp msgMap["timestamp"] = timestamp
val clientSignKey = HttpServerUtils.clientSignKey val clientSignKey = HttpServerUtils.clientSignKey
if (!TextUtils.isEmpty(clientSignKey)) { if (!TextUtils.isEmpty(clientSignKey)) {
msgMap["sign"] = HttpServerUtils.calcSign(timestamp.toString(), clientSignKey.toString()) msgMap["sign"] = HttpServerUtils.calcSign(timestamp.toString(), clientSignKey)
} }
if (refresh) pageNum = 1 if (refresh) pageNum = 1
msgMap["data"] = CallQueryData(callType, pageNum, pageSize, keyword) msgMap["data"] = CallQueryData(callType, pageNum, pageSize, keyword)
var requestMsg: String = Gson().toJson(msgMap) var requestMsg: String = Gson().toJson(msgMap)
Log.i(TAG, "requestMsg:$requestMsg") Log.i(TAG, "requestMsg:$requestMsg")
val postRequest = XHttp.post(requestUrl) val postRequest = XHttp.post(requestUrl)
.keepJson(true) .keepJson(true)
.timeOut((SettingUtils.requestTimeout * 1000).toLong()) //超时时间10s .timeOut((SettingUtils.requestTimeout * 1000).toLong()) //超时时间10s
.cacheMode(CacheMode.NO_CACHE) .cacheMode(CacheMode.NO_CACHE)
.timeStamp(true) .timeStamp(true)
when (HttpServerUtils.clientSafetyMeasures) { when (HttpServerUtils.clientSafetyMeasures) {
2 -> { 2 -> {
val publicKey = RSACrypt.getPublicKey(HttpServerUtils.clientSignKey.toString()) val publicKey = RSACrypt.getPublicKey(HttpServerUtils.clientSignKey)
try { try {
requestMsg = Base64.encode(requestMsg.toByteArray()) requestMsg = Base64.encode(requestMsg.toByteArray())
requestMsg = RSACrypt.encryptByPublicKey(requestMsg, publicKey) requestMsg = RSACrypt.encryptByPublicKey(requestMsg, publicKey)
Log.i(TAG, "requestMsg: $requestMsg") Log.i(TAG, "requestMsg: $requestMsg")
} catch (e: Exception) { } catch (e: Exception) {
XToastUtils.error(ResUtils.getString(R.string.request_failed) + e.message) XToastUtils.error(ResUtils.getString(R.string.request_failed) + e.message)
e.printStackTrace() e.printStackTrace()
return return
} }
postRequest.upString(requestMsg) postRequest.upString(requestMsg)
} }
3 -> { 3 -> {
try { try {
val sm4Key = ConvertTools.hexStringToByteArray(HttpServerUtils.clientSignKey.toString()) val sm4Key = ConvertTools.hexStringToByteArray(HttpServerUtils.clientSignKey)
//requestMsg = Base64.encode(requestMsg.toByteArray()) //requestMsg = Base64.encode(requestMsg.toByteArray())
val encryptCBC = SM4Crypt.encrypt(requestMsg.toByteArray(), sm4Key) val encryptCBC = SM4Crypt.encrypt(requestMsg.toByteArray(), sm4Key)
requestMsg = ConvertTools.bytes2HexString(encryptCBC) requestMsg = ConvertTools.bytes2HexString(encryptCBC)
Log.i(TAG, "requestMsg: $requestMsg") Log.i(TAG, "requestMsg: $requestMsg")
} catch (e: Exception) { } catch (e: Exception) {
XToastUtils.error(ResUtils.getString(R.string.request_failed) + e.message) XToastUtils.error(ResUtils.getString(R.string.request_failed) + e.message)
e.printStackTrace() e.printStackTrace()
return return
} }
postRequest.upString(requestMsg) postRequest.upString(requestMsg)
} }
else -> { else -> {
postRequest.upJson(requestMsg) postRequest.upJson(requestMsg)
} }
} }
postRequest.execute(object : SimpleCallBack<String>() { postRequest.execute(object : SimpleCallBack<String>() {
override fun onError(e: ApiException) { override fun onError(e: ApiException) {
XToastUtils.error(e.displayMessage) XToastUtils.error(e.displayMessage)
} }
override fun onSuccess(response: String) { override fun onSuccess(response: String) {
Log.i(TAG, response) Log.i(TAG, response)
try { try {
var json = response var json = response
if (HttpServerUtils.clientSafetyMeasures == 2) { if (HttpServerUtils.clientSafetyMeasures == 2) {
val publicKey = RSACrypt.getPublicKey(HttpServerUtils.clientSignKey.toString()) val publicKey = RSACrypt.getPublicKey(HttpServerUtils.clientSignKey)
json = RSACrypt.decryptByPublicKey(json, publicKey) json = RSACrypt.decryptByPublicKey(json, publicKey)
json = String(Base64.decode(json)) json = String(Base64.decode(json))
} else if (HttpServerUtils.clientSafetyMeasures == 3) { } else if (HttpServerUtils.clientSafetyMeasures == 3) {
val sm4Key = ConvertTools.hexStringToByteArray(HttpServerUtils.clientSignKey.toString()) val sm4Key = ConvertTools.hexStringToByteArray(HttpServerUtils.clientSignKey)
val encryptCBC = ConvertTools.hexStringToByteArray(json) val encryptCBC = ConvertTools.hexStringToByteArray(json)
val decryptCBC = SM4Crypt.decrypt(encryptCBC, sm4Key) val decryptCBC = SM4Crypt.decrypt(encryptCBC, sm4Key)
json = String(decryptCBC) json = String(decryptCBC)
} }
val resp: BaseResponse<List<CallInfo>?> = Gson().fromJson(json, object : TypeToken<BaseResponse<List<CallInfo>?>>() {}.type) val resp: BaseResponse<List<CallInfo>?> = Gson().fromJson(json, object : TypeToken<BaseResponse<List<CallInfo>?>>() {}.type)
if (resp.code == 200) { if (resp.code == 200) {
pageNum++ pageNum++
if (refresh) { if (refresh) {
mAdapter!!.refresh(resp.data) mAdapter!!.refresh(resp.data)
binding!!.refreshLayout.finishRefresh() binding!!.refreshLayout.finishRefresh()
binding!!.recyclerView.scrollToPosition(0) binding!!.recyclerView.scrollToPosition(0)
} else { } else {
mAdapter!!.loadMore(resp.data) mAdapter!!.loadMore(resp.data)
binding!!.refreshLayout.finishLoadMore() binding!!.refreshLayout.finishLoadMore()
} }
} else { } else {
XToastUtils.error(ResUtils.getString(R.string.request_failed) + resp.msg) XToastUtils.error(ResUtils.getString(R.string.request_failed) + resp.msg)
} }
} catch (e: Exception) { } catch (e: Exception) {
e.printStackTrace() e.printStackTrace()
XToastUtils.error(ResUtils.getString(R.string.request_failed) + response) XToastUtils.error(ResUtils.getString(R.string.request_failed) + response)
} }
} }
}) })
} }
} }

@ -1,459 +1,459 @@
package com.idormy.sms.forwarder.fragment.client package com.idormy.sms.forwarder.fragment.client
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.os.Environment import android.os.Environment
import android.util.Log import android.util.Log
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import com.google.gson.Gson import com.google.gson.Gson
import com.google.gson.GsonBuilder import com.google.gson.GsonBuilder
import com.google.gson.JsonDeserializer import com.google.gson.JsonDeserializer
import com.google.gson.reflect.TypeToken import com.google.gson.reflect.TypeToken
import com.hjq.permissions.OnPermissionCallback import com.hjq.permissions.OnPermissionCallback
import com.hjq.permissions.Permission import com.hjq.permissions.Permission
import com.hjq.permissions.XXPermissions import com.hjq.permissions.XXPermissions
import com.idormy.sms.forwarder.R import com.idormy.sms.forwarder.R
import com.idormy.sms.forwarder.core.BaseFragment import com.idormy.sms.forwarder.core.BaseFragment
import com.idormy.sms.forwarder.databinding.FragmentClientCloneBinding import com.idormy.sms.forwarder.databinding.FragmentClientCloneBinding
import com.idormy.sms.forwarder.entity.CloneInfo import com.idormy.sms.forwarder.entity.CloneInfo
import com.idormy.sms.forwarder.server.model.BaseResponse import com.idormy.sms.forwarder.server.model.BaseResponse
import com.idormy.sms.forwarder.utils.* import com.idormy.sms.forwarder.utils.*
import com.idormy.sms.forwarder.utils.Base64 import com.idormy.sms.forwarder.utils.Base64
import com.xuexiang.xaop.annotation.SingleClick import com.xuexiang.xaop.annotation.SingleClick
import com.xuexiang.xhttp2.XHttp import com.xuexiang.xhttp2.XHttp
import com.xuexiang.xhttp2.cache.model.CacheMode import com.xuexiang.xhttp2.cache.model.CacheMode
import com.xuexiang.xhttp2.callback.SimpleCallBack import com.xuexiang.xhttp2.callback.SimpleCallBack
import com.xuexiang.xhttp2.exception.ApiException import com.xuexiang.xhttp2.exception.ApiException
import com.xuexiang.xpage.annotation.Page import com.xuexiang.xpage.annotation.Page
import com.xuexiang.xrouter.utils.TextUtils import com.xuexiang.xrouter.utils.TextUtils
import com.xuexiang.xui.utils.CountDownButtonHelper import com.xuexiang.xui.utils.CountDownButtonHelper
import com.xuexiang.xui.utils.ResUtils import com.xuexiang.xui.utils.ResUtils
import com.xuexiang.xui.widget.actionbar.TitleBar import com.xuexiang.xui.widget.actionbar.TitleBar
import com.xuexiang.xutil.app.AppUtils import com.xuexiang.xutil.app.AppUtils
import com.xuexiang.xutil.data.ConvertTools import com.xuexiang.xutil.data.ConvertTools
import com.xuexiang.xutil.file.FileIOUtils import com.xuexiang.xutil.file.FileIOUtils
import com.xuexiang.xutil.file.FileUtils import com.xuexiang.xutil.file.FileUtils
import java.io.File import java.io.File
import java.util.* import java.util.*
@Suppress("PropertyName") @Suppress("PropertyName")
@Page(name = "一键换新机") @Page(name = "一键换新机")
class CloneFragment : BaseFragment<FragmentClientCloneBinding?>(), View.OnClickListener { class CloneFragment : BaseFragment<FragmentClientCloneBinding?>(), View.OnClickListener {
val TAG: String = SmsQueryFragment::class.java.simpleName val TAG: String = SmsQueryFragment::class.java.simpleName
private var backupPath: String? = null private var backupPath: String? = null
private val backupFile = "SmsForwarder.json" private val backupFile = "SmsForwarder.json"
private var pushCountDownHelper: CountDownButtonHelper? = null private var pushCountDownHelper: CountDownButtonHelper? = null
private var pullCountDownHelper: CountDownButtonHelper? = null private var pullCountDownHelper: CountDownButtonHelper? = null
private var exportCountDownHelper: CountDownButtonHelper? = null private var exportCountDownHelper: CountDownButtonHelper? = null
private var importCountDownHelper: CountDownButtonHelper? = null private var importCountDownHelper: CountDownButtonHelper? = null
override fun viewBindingInflate( override fun viewBindingInflate(
inflater: LayoutInflater, inflater: LayoutInflater,
container: ViewGroup, container: ViewGroup,
): FragmentClientCloneBinding { ): FragmentClientCloneBinding {
return FragmentClientCloneBinding.inflate(inflater, container, false) return FragmentClientCloneBinding.inflate(inflater, container, false)
} }
override fun initTitle(): TitleBar? { override fun initTitle(): TitleBar? {
val titleBar = super.initTitle()!!.setImmersive(false) val titleBar = super.initTitle()!!.setImmersive(false)
titleBar.setTitle(R.string.api_clone) titleBar.setTitle(R.string.api_clone)
return titleBar return titleBar
} }
/** /**
* 初始化控件 * 初始化控件
*/ */
override fun initViews() { override fun initViews() {
// 申请储存权限 // 申请储存权限
XXPermissions.with(this) XXPermissions.with(this)
//.permission(*Permission.Group.STORAGE) //.permission(*Permission.Group.STORAGE)
.permission(Permission.MANAGE_EXTERNAL_STORAGE) .permission(Permission.MANAGE_EXTERNAL_STORAGE)
.request(object : OnPermissionCallback { .request(object : OnPermissionCallback {
@SuppressLint("SetTextI18n") @SuppressLint("SetTextI18n")
override fun onGranted(permissions: List<String>, all: Boolean) { override fun onGranted(permissions: List<String>, all: Boolean) {
backupPath = backupPath =
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).path Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).path
binding!!.tvBackupPath.text = backupPath + File.separator + backupFile binding!!.tvBackupPath.text = backupPath + File.separator + backupFile
} }
override fun onDenied(permissions: List<String>, never: Boolean) { override fun onDenied(permissions: List<String>, never: Boolean) {
if (never) { if (never) {
XToastUtils.error(R.string.toast_denied_never) XToastUtils.error(R.string.toast_denied_never)
// 如果是被永久拒绝就跳转到应用权限系统设置页面 // 如果是被永久拒绝就跳转到应用权限系统设置页面
XXPermissions.startPermissionActivity(requireContext(), permissions) XXPermissions.startPermissionActivity(requireContext(), permissions)
} else { } else {
XToastUtils.error(R.string.toast_denied) XToastUtils.error(R.string.toast_denied)
} }
binding!!.tvBackupPath.text = getString(R.string.storage_permission_tips) binding!!.tvBackupPath.text = getString(R.string.storage_permission_tips)
} }
}) })
binding!!.tabBar.setTabTitles(ResUtils.getStringArray(R.array.clone_type_option)) binding!!.tabBar.setTabTitles(ResUtils.getStringArray(R.array.clone_type_option))
binding!!.tabBar.setOnTabClickListener { _, position -> binding!!.tabBar.setOnTabClickListener { _, position ->
//XToastUtils.toast("点击了$title--$position") //XToastUtils.toast("点击了$title--$position")
if (position == 1) { if (position == 1) {
binding!!.layoutNetwork.visibility = View.GONE binding!!.layoutNetwork.visibility = View.GONE
binding!!.layoutOffline.visibility = View.VISIBLE binding!!.layoutOffline.visibility = View.VISIBLE
} else { } else {
binding!!.layoutNetwork.visibility = View.VISIBLE binding!!.layoutNetwork.visibility = View.VISIBLE
binding!!.layoutOffline.visibility = View.GONE binding!!.layoutOffline.visibility = View.GONE
} }
} }
//按钮增加倒计时,避免重复点击 //按钮增加倒计时,避免重复点击
pushCountDownHelper = CountDownButtonHelper(binding!!.btnPush, SettingUtils.requestTimeout) pushCountDownHelper = CountDownButtonHelper(binding!!.btnPush, SettingUtils.requestTimeout)
pushCountDownHelper!!.setOnCountDownListener(object : pushCountDownHelper!!.setOnCountDownListener(object :
CountDownButtonHelper.OnCountDownListener { CountDownButtonHelper.OnCountDownListener {
override fun onCountDown(time: Int) { override fun onCountDown(time: Int) {
binding!!.btnPush.text = String.format(getString(R.string.seconds_n), time) binding!!.btnPush.text = String.format(getString(R.string.seconds_n), time)
} }
override fun onFinished() { override fun onFinished() {
binding!!.btnPush.text = getString(R.string.push) binding!!.btnPush.text = getString(R.string.push)
} }
}) })
pullCountDownHelper = CountDownButtonHelper(binding!!.btnPull, SettingUtils.requestTimeout) pullCountDownHelper = CountDownButtonHelper(binding!!.btnPull, SettingUtils.requestTimeout)
pullCountDownHelper!!.setOnCountDownListener(object : pullCountDownHelper!!.setOnCountDownListener(object :
CountDownButtonHelper.OnCountDownListener { CountDownButtonHelper.OnCountDownListener {
override fun onCountDown(time: Int) { override fun onCountDown(time: Int) {
binding!!.btnPull.text = String.format(getString(R.string.seconds_n), time) binding!!.btnPull.text = String.format(getString(R.string.seconds_n), time)
} }
override fun onFinished() { override fun onFinished() {
binding!!.btnPull.text = getString(R.string.pull) binding!!.btnPull.text = getString(R.string.pull)
} }
}) })
exportCountDownHelper = CountDownButtonHelper(binding!!.btnExport, 3) exportCountDownHelper = CountDownButtonHelper(binding!!.btnExport, 3)
exportCountDownHelper!!.setOnCountDownListener(object : exportCountDownHelper!!.setOnCountDownListener(object :
CountDownButtonHelper.OnCountDownListener { CountDownButtonHelper.OnCountDownListener {
override fun onCountDown(time: Int) { override fun onCountDown(time: Int) {
binding!!.btnExport.text = String.format(getString(R.string.seconds_n), time) binding!!.btnExport.text = String.format(getString(R.string.seconds_n), time)
} }
override fun onFinished() { override fun onFinished() {
binding!!.btnExport.text = getString(R.string.export) binding!!.btnExport.text = getString(R.string.export)
} }
}) })
importCountDownHelper = CountDownButtonHelper(binding!!.btnImport, 3) importCountDownHelper = CountDownButtonHelper(binding!!.btnImport, 3)
importCountDownHelper!!.setOnCountDownListener(object : importCountDownHelper!!.setOnCountDownListener(object :
CountDownButtonHelper.OnCountDownListener { CountDownButtonHelper.OnCountDownListener {
override fun onCountDown(time: Int) { override fun onCountDown(time: Int) {
binding!!.btnImport.text = String.format(getString(R.string.seconds_n), time) binding!!.btnImport.text = String.format(getString(R.string.seconds_n), time)
} }
override fun onFinished() { override fun onFinished() {
binding!!.btnImport.text = getString(R.string.imports) binding!!.btnImport.text = getString(R.string.imports)
} }
}) })
} }
override fun initListeners() { override fun initListeners() {
binding!!.btnPush.setOnClickListener(this) binding!!.btnPush.setOnClickListener(this)
binding!!.btnPull.setOnClickListener(this) binding!!.btnPull.setOnClickListener(this)
binding!!.btnExport.setOnClickListener(this) binding!!.btnExport.setOnClickListener(this)
binding!!.btnImport.setOnClickListener(this) binding!!.btnImport.setOnClickListener(this)
} }
@SingleClick @SingleClick
override fun onClick(v: View) { override fun onClick(v: View) {
when (v.id) { when (v.id) {
//推送配置 //推送配置
R.id.btn_push -> pushData() R.id.btn_push -> pushData()
//拉取配置 //拉取配置
R.id.btn_pull -> pullData() R.id.btn_pull -> pullData()
//导出配置 //导出配置
R.id.btn_export -> { R.id.btn_export -> {
try { try {
exportCountDownHelper?.start() exportCountDownHelper?.start()
val file = File(backupPath + File.separator + backupFile) val file = File(backupPath + File.separator + backupFile)
//判断文件是否存在,存在则在创建之前删除 //判断文件是否存在,存在则在创建之前删除
FileUtils.createFileByDeleteOldFile(file) FileUtils.createFileByDeleteOldFile(file)
val cloneInfo = HttpServerUtils.exportSettings() val cloneInfo = HttpServerUtils.exportSettings()
val jsonStr = Gson().toJson(cloneInfo) val jsonStr = Gson().toJson(cloneInfo)
if (FileIOUtils.writeFileFromString(file, jsonStr)) { if (FileIOUtils.writeFileFromString(file, jsonStr)) {
XToastUtils.success(getString(R.string.export_succeeded)) XToastUtils.success(getString(R.string.export_succeeded))
} else { } else {
binding!!.tvExport.text = getString(R.string.export_failed) binding!!.tvExport.text = getString(R.string.export_failed)
XToastUtils.error(getString(R.string.export_failed)) XToastUtils.error(getString(R.string.export_failed))
} }
} catch (e: Exception) { } catch (e: Exception) {
XToastUtils.error(String.format(getString(R.string.export_failed_tips), e.message)) XToastUtils.error(String.format(getString(R.string.export_failed_tips), e.message))
} }
} }
//导入配置 //导入配置
R.id.btn_import -> { R.id.btn_import -> {
try { try {
importCountDownHelper?.start() importCountDownHelper?.start()
val file = File(backupPath + File.separator + backupFile) val file = File(backupPath + File.separator + backupFile)
//判断文件是否存在 //判断文件是否存在
if (!FileUtils.isFileExists(file)) { if (!FileUtils.isFileExists(file)) {
XToastUtils.error(getString(R.string.import_failed_file_not_exist)) XToastUtils.error(getString(R.string.import_failed_file_not_exist))
return return
} }
val jsonStr = FileIOUtils.readFile2String(file) val jsonStr = FileIOUtils.readFile2String(file)
Log.d(TAG, "jsonStr = $jsonStr") Log.d(TAG, "jsonStr = $jsonStr")
if (TextUtils.isEmpty(jsonStr)) { if (TextUtils.isEmpty(jsonStr)) {
XToastUtils.error(getString(R.string.import_failed)) XToastUtils.error(getString(R.string.import_failed))
return return
} }
//替换Date字段为当前时间 //替换Date字段为当前时间
val builder = GsonBuilder() val builder = GsonBuilder()
builder.registerTypeAdapter( builder.registerTypeAdapter(
Date::class.java, Date::class.java,
JsonDeserializer<Any?> { _, _, _ -> Date() }) JsonDeserializer<Any?> { _, _, _ -> Date() })
val gson = builder.create() val gson = builder.create()
val cloneInfo = gson.fromJson(jsonStr, CloneInfo::class.java) val cloneInfo = gson.fromJson(jsonStr, CloneInfo::class.java)
Log.d(TAG, "cloneInfo = $cloneInfo") Log.d(TAG, "cloneInfo = $cloneInfo")
//判断版本是否一致 //判断版本是否一致
HttpServerUtils.compareVersion(cloneInfo) HttpServerUtils.compareVersion(cloneInfo)
if (HttpServerUtils.restoreSettings(cloneInfo)) { if (HttpServerUtils.restoreSettings(cloneInfo)) {
XToastUtils.success(getString(R.string.import_succeeded)) XToastUtils.success(getString(R.string.import_succeeded))
} else { } else {
XToastUtils.error(getString(R.string.import_failed)) XToastUtils.error(getString(R.string.import_failed))
} }
} catch (e: Exception) { } catch (e: Exception) {
XToastUtils.error(String.format(getString(R.string.import_failed_tips), e.message)) XToastUtils.error(String.format(getString(R.string.import_failed_tips), e.message))
} }
} }
} }
} }
//推送配置 //推送配置
private fun pushData() { private fun pushData() {
if (!CommonUtils.checkUrl(HttpServerUtils.serverAddress)) { if (!CommonUtils.checkUrl(HttpServerUtils.serverAddress)) {
XToastUtils.error(getString(R.string.invalid_service_address)) XToastUtils.error(getString(R.string.invalid_service_address))
return return
} }
pushCountDownHelper?.start() pushCountDownHelper?.start()
val requestUrl: String = HttpServerUtils.serverAddress + "/clone/push" val requestUrl: String = HttpServerUtils.serverAddress + "/clone/push"
Log.i(TAG, "requestUrl:$requestUrl") Log.i(TAG, "requestUrl:$requestUrl")
val msgMap: MutableMap<String, Any> = mutableMapOf() val msgMap: MutableMap<String, Any> = mutableMapOf()
val timestamp = System.currentTimeMillis() val timestamp = System.currentTimeMillis()
msgMap["timestamp"] = timestamp msgMap["timestamp"] = timestamp
val clientSignKey = HttpServerUtils.clientSignKey val clientSignKey = HttpServerUtils.clientSignKey
if (!TextUtils.isEmpty(clientSignKey)) { if (!TextUtils.isEmpty(clientSignKey)) {
msgMap["sign"] = msgMap["sign"] =
HttpServerUtils.calcSign(timestamp.toString(), clientSignKey.toString()) HttpServerUtils.calcSign(timestamp.toString(), clientSignKey)
} }
msgMap["data"] = HttpServerUtils.exportSettings() msgMap["data"] = HttpServerUtils.exportSettings()
var requestMsg: String = Gson().toJson(msgMap) var requestMsg: String = Gson().toJson(msgMap)
Log.i(TAG, "requestMsg:$requestMsg") Log.i(TAG, "requestMsg:$requestMsg")
val postRequest = XHttp.post(requestUrl) val postRequest = XHttp.post(requestUrl)
.keepJson(true) .keepJson(true)
.timeOut((SettingUtils.requestTimeout * 1000).toLong()) //超时时间10s .timeOut((SettingUtils.requestTimeout * 1000).toLong()) //超时时间10s
.cacheMode(CacheMode.NO_CACHE) .cacheMode(CacheMode.NO_CACHE)
.timeStamp(true) .timeStamp(true)
when (HttpServerUtils.clientSafetyMeasures) { when (HttpServerUtils.clientSafetyMeasures) {
2 -> { 2 -> {
val publicKey = RSACrypt.getPublicKey(HttpServerUtils.clientSignKey.toString()) val publicKey = RSACrypt.getPublicKey(HttpServerUtils.clientSignKey)
try { try {
requestMsg = Base64.encode(requestMsg.toByteArray()) requestMsg = Base64.encode(requestMsg.toByteArray())
requestMsg = RSACrypt.encryptByPublicKey(requestMsg, publicKey) requestMsg = RSACrypt.encryptByPublicKey(requestMsg, publicKey)
Log.i(TAG, "requestMsg: $requestMsg") Log.i(TAG, "requestMsg: $requestMsg")
} catch (e: Exception) { } catch (e: Exception) {
XToastUtils.error(ResUtils.getString(R.string.request_failed) + e.message) XToastUtils.error(ResUtils.getString(R.string.request_failed) + e.message)
e.printStackTrace() e.printStackTrace()
return return
} }
postRequest.upString(requestMsg) postRequest.upString(requestMsg)
} }
3 -> { 3 -> {
try { try {
val sm4Key = ConvertTools.hexStringToByteArray(HttpServerUtils.clientSignKey.toString()) val sm4Key = ConvertTools.hexStringToByteArray(HttpServerUtils.clientSignKey)
//requestMsg = Base64.encode(requestMsg.toByteArray()) //requestMsg = Base64.encode(requestMsg.toByteArray())
val encryptCBC = SM4Crypt.encrypt(requestMsg.toByteArray(), sm4Key) val encryptCBC = SM4Crypt.encrypt(requestMsg.toByteArray(), sm4Key)
requestMsg = ConvertTools.bytes2HexString(encryptCBC) requestMsg = ConvertTools.bytes2HexString(encryptCBC)
Log.i(TAG, "requestMsg: $requestMsg") Log.i(TAG, "requestMsg: $requestMsg")
} catch (e: Exception) { } catch (e: Exception) {
XToastUtils.error(ResUtils.getString(R.string.request_failed) + e.message) XToastUtils.error(ResUtils.getString(R.string.request_failed) + e.message)
e.printStackTrace() e.printStackTrace()
return return
} }
postRequest.upString(requestMsg) postRequest.upString(requestMsg)
} }
else -> { else -> {
postRequest.upJson(requestMsg) postRequest.upJson(requestMsg)
} }
} }
postRequest.execute(object : SimpleCallBack<String>() { postRequest.execute(object : SimpleCallBack<String>() {
override fun onError(e: ApiException) { override fun onError(e: ApiException) {
XToastUtils.error(e.displayMessage) XToastUtils.error(e.displayMessage)
pushCountDownHelper?.finish() pushCountDownHelper?.finish()
} }
override fun onSuccess(response: String) { override fun onSuccess(response: String) {
Log.i(TAG, response) Log.i(TAG, response)
try { try {
var json = response var json = response
if (HttpServerUtils.clientSafetyMeasures == 2) { if (HttpServerUtils.clientSafetyMeasures == 2) {
val publicKey = RSACrypt.getPublicKey(HttpServerUtils.clientSignKey.toString()) val publicKey = RSACrypt.getPublicKey(HttpServerUtils.clientSignKey)
json = RSACrypt.decryptByPublicKey(json, publicKey) json = RSACrypt.decryptByPublicKey(json, publicKey)
json = String(Base64.decode(json)) json = String(Base64.decode(json))
} else if (HttpServerUtils.clientSafetyMeasures == 3) { } else if (HttpServerUtils.clientSafetyMeasures == 3) {
val sm4Key = ConvertTools.hexStringToByteArray(HttpServerUtils.clientSignKey.toString()) val sm4Key = ConvertTools.hexStringToByteArray(HttpServerUtils.clientSignKey)
val encryptCBC = ConvertTools.hexStringToByteArray(json) val encryptCBC = ConvertTools.hexStringToByteArray(json)
val decryptCBC = SM4Crypt.decrypt(encryptCBC, sm4Key) val decryptCBC = SM4Crypt.decrypt(encryptCBC, sm4Key)
json = String(decryptCBC) json = String(decryptCBC)
} }
val resp: BaseResponse<String> = Gson().fromJson(json, object : TypeToken<BaseResponse<String>>() {}.type) val resp: BaseResponse<String> = Gson().fromJson(json, object : TypeToken<BaseResponse<String>>() {}.type)
if (resp.code == 200) { if (resp.code == 200) {
XToastUtils.success(ResUtils.getString(R.string.request_succeeded)) XToastUtils.success(ResUtils.getString(R.string.request_succeeded))
} else { } else {
XToastUtils.error(ResUtils.getString(R.string.request_failed) + resp.msg) XToastUtils.error(ResUtils.getString(R.string.request_failed) + resp.msg)
} }
} catch (e: Exception) { } catch (e: Exception) {
e.printStackTrace() e.printStackTrace()
XToastUtils.error(ResUtils.getString(R.string.request_failed) + response) XToastUtils.error(ResUtils.getString(R.string.request_failed) + response)
} }
pushCountDownHelper?.finish() pushCountDownHelper?.finish()
} }
}) })
} }
//拉取配置 //拉取配置
private fun pullData() { private fun pullData() {
if (!CommonUtils.checkUrl(HttpServerUtils.serverAddress)) { if (!CommonUtils.checkUrl(HttpServerUtils.serverAddress)) {
XToastUtils.error(getString(R.string.invalid_service_address)) XToastUtils.error(getString(R.string.invalid_service_address))
return return
} }
exportCountDownHelper?.start() exportCountDownHelper?.start()
val requestUrl: String = HttpServerUtils.serverAddress + "/clone/pull" val requestUrl: String = HttpServerUtils.serverAddress + "/clone/pull"
Log.i(TAG, "requestUrl:$requestUrl") Log.i(TAG, "requestUrl:$requestUrl")
val msgMap: MutableMap<String, Any> = mutableMapOf() val msgMap: MutableMap<String, Any> = mutableMapOf()
val timestamp = System.currentTimeMillis() val timestamp = System.currentTimeMillis()
msgMap["timestamp"] = timestamp msgMap["timestamp"] = timestamp
val clientSignKey = HttpServerUtils.clientSignKey val clientSignKey = HttpServerUtils.clientSignKey
if (!TextUtils.isEmpty(clientSignKey)) { if (!TextUtils.isEmpty(clientSignKey)) {
msgMap["sign"] = msgMap["sign"] =
HttpServerUtils.calcSign(timestamp.toString(), clientSignKey.toString()) HttpServerUtils.calcSign(timestamp.toString(), clientSignKey)
} }
val dataMap: MutableMap<String, Any> = mutableMapOf() val dataMap: MutableMap<String, Any> = mutableMapOf()
dataMap["version_code"] = AppUtils.getAppVersionCode() dataMap["version_code"] = AppUtils.getAppVersionCode()
msgMap["data"] = dataMap msgMap["data"] = dataMap
var requestMsg: String = Gson().toJson(msgMap) var requestMsg: String = Gson().toJson(msgMap)
Log.i(TAG, "requestMsg:$requestMsg") Log.i(TAG, "requestMsg:$requestMsg")
val postRequest = XHttp.post(requestUrl) val postRequest = XHttp.post(requestUrl)
.keepJson(true) .keepJson(true)
.timeOut((SettingUtils.requestTimeout * 1000).toLong()) //超时时间10s .timeOut((SettingUtils.requestTimeout * 1000).toLong()) //超时时间10s
.cacheMode(CacheMode.NO_CACHE) .cacheMode(CacheMode.NO_CACHE)
.timeStamp(true) .timeStamp(true)
when (HttpServerUtils.clientSafetyMeasures) { when (HttpServerUtils.clientSafetyMeasures) {
2 -> { 2 -> {
val publicKey = RSACrypt.getPublicKey(HttpServerUtils.clientSignKey.toString()) val publicKey = RSACrypt.getPublicKey(HttpServerUtils.clientSignKey)
try { try {
requestMsg = Base64.encode(requestMsg.toByteArray()) requestMsg = Base64.encode(requestMsg.toByteArray())
requestMsg = RSACrypt.encryptByPublicKey(requestMsg, publicKey) requestMsg = RSACrypt.encryptByPublicKey(requestMsg, publicKey)
Log.i(TAG, "requestMsg: $requestMsg") Log.i(TAG, "requestMsg: $requestMsg")
} catch (e: Exception) { } catch (e: Exception) {
XToastUtils.error(ResUtils.getString(R.string.request_failed) + e.message) XToastUtils.error(ResUtils.getString(R.string.request_failed) + e.message)
e.printStackTrace() e.printStackTrace()
return return
} }
postRequest.upString(requestMsg) postRequest.upString(requestMsg)
} }
3 -> { 3 -> {
try { try {
val sm4Key = ConvertTools.hexStringToByteArray(HttpServerUtils.clientSignKey.toString()) val sm4Key = ConvertTools.hexStringToByteArray(HttpServerUtils.clientSignKey)
//requestMsg = Base64.encode(requestMsg.toByteArray()) //requestMsg = Base64.encode(requestMsg.toByteArray())
val encryptCBC = SM4Crypt.encrypt(requestMsg.toByteArray(), sm4Key) val encryptCBC = SM4Crypt.encrypt(requestMsg.toByteArray(), sm4Key)
requestMsg = ConvertTools.bytes2HexString(encryptCBC) requestMsg = ConvertTools.bytes2HexString(encryptCBC)
Log.i(TAG, "requestMsg: $requestMsg") Log.i(TAG, "requestMsg: $requestMsg")
} catch (e: Exception) { } catch (e: Exception) {
XToastUtils.error(ResUtils.getString(R.string.request_failed) + e.message) XToastUtils.error(ResUtils.getString(R.string.request_failed) + e.message)
e.printStackTrace() e.printStackTrace()
return return
} }
postRequest.upString(requestMsg) postRequest.upString(requestMsg)
} }
else -> { else -> {
postRequest.upJson(requestMsg) postRequest.upJson(requestMsg)
} }
} }
postRequest.execute(object : SimpleCallBack<String>() { postRequest.execute(object : SimpleCallBack<String>() {
override fun onError(e: ApiException) { override fun onError(e: ApiException) {
XToastUtils.error(e.displayMessage) XToastUtils.error(e.displayMessage)
exportCountDownHelper?.finish() exportCountDownHelper?.finish()
} }
override fun onSuccess(response: String) { override fun onSuccess(response: String) {
Log.i(TAG, response) Log.i(TAG, response)
try { try {
var json = response var json = response
if (HttpServerUtils.clientSafetyMeasures == 2) { if (HttpServerUtils.clientSafetyMeasures == 2) {
val publicKey = RSACrypt.getPublicKey(HttpServerUtils.clientSignKey.toString()) val publicKey = RSACrypt.getPublicKey(HttpServerUtils.clientSignKey)
json = RSACrypt.decryptByPublicKey(json, publicKey) json = RSACrypt.decryptByPublicKey(json, publicKey)
json = String(Base64.decode(json)) json = String(Base64.decode(json))
} else if (HttpServerUtils.clientSafetyMeasures == 3) { } else if (HttpServerUtils.clientSafetyMeasures == 3) {
val sm4Key = ConvertTools.hexStringToByteArray(HttpServerUtils.clientSignKey.toString()) val sm4Key = ConvertTools.hexStringToByteArray(HttpServerUtils.clientSignKey)
val encryptCBC = ConvertTools.hexStringToByteArray(json) val encryptCBC = ConvertTools.hexStringToByteArray(json)
val decryptCBC = SM4Crypt.decrypt(encryptCBC, sm4Key) val decryptCBC = SM4Crypt.decrypt(encryptCBC, sm4Key)
json = String(decryptCBC) json = String(decryptCBC)
} }
//替换Date字段为当前时间 //替换Date字段为当前时间
val builder = GsonBuilder() val builder = GsonBuilder()
builder.registerTypeAdapter( builder.registerTypeAdapter(
Date::class.java, Date::class.java,
JsonDeserializer<Any?> { _, _, _ -> Date() }) JsonDeserializer<Any?> { _, _, _ -> Date() })
val gson = builder.create() val gson = builder.create()
val resp: BaseResponse<CloneInfo> = gson.fromJson(json, object : TypeToken<BaseResponse<CloneInfo>>() {}.type) val resp: BaseResponse<CloneInfo> = gson.fromJson(json, object : TypeToken<BaseResponse<CloneInfo>>() {}.type)
if (resp.code == 200) { if (resp.code == 200) {
val cloneInfo = resp.data val cloneInfo = resp.data
Log.d(TAG, "cloneInfo = $cloneInfo") Log.d(TAG, "cloneInfo = $cloneInfo")
if (cloneInfo == null) { if (cloneInfo == null) {
XToastUtils.error(ResUtils.getString(R.string.request_failed)) XToastUtils.error(ResUtils.getString(R.string.request_failed))
return return
} }
//判断版本是否一致 //判断版本是否一致
HttpServerUtils.compareVersion(cloneInfo) HttpServerUtils.compareVersion(cloneInfo)
if (HttpServerUtils.restoreSettings(cloneInfo)) { if (HttpServerUtils.restoreSettings(cloneInfo)) {
XToastUtils.success(getString(R.string.import_succeeded)) XToastUtils.success(getString(R.string.import_succeeded))
} }
} else { } else {
XToastUtils.error(ResUtils.getString(R.string.request_failed) + resp.msg) XToastUtils.error(ResUtils.getString(R.string.request_failed) + resp.msg)
} }
} catch (e: Exception) { } catch (e: Exception) {
e.printStackTrace() e.printStackTrace()
XToastUtils.error(ResUtils.getString(R.string.request_failed) + response) XToastUtils.error(ResUtils.getString(R.string.request_failed) + response)
} }
exportCountDownHelper?.finish() exportCountDownHelper?.finish()
} }
}) })
} }
override fun onDestroyView() { override fun onDestroyView() {
if (pushCountDownHelper != null) pushCountDownHelper!!.recycle() if (pushCountDownHelper != null) pushCountDownHelper!!.recycle()
if (pullCountDownHelper != null) pullCountDownHelper!!.recycle() if (pullCountDownHelper != null) pullCountDownHelper!!.recycle()
if (exportCountDownHelper != null) exportCountDownHelper!!.recycle() if (exportCountDownHelper != null) exportCountDownHelper!!.recycle()
if (importCountDownHelper != null) importCountDownHelper!!.recycle() if (importCountDownHelper != null) importCountDownHelper!!.recycle()
super.onDestroyView() super.onDestroyView()
} }
} }

@ -1,273 +1,273 @@
package com.idormy.sms.forwarder.fragment.client package com.idormy.sms.forwarder.fragment.client
import android.util.Log import android.util.Log
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.core.text.isDigitsOnly import androidx.core.text.isDigitsOnly
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.alibaba.android.vlayout.DelegateAdapter import com.alibaba.android.vlayout.DelegateAdapter
import com.alibaba.android.vlayout.VirtualLayoutManager import com.alibaba.android.vlayout.VirtualLayoutManager
import com.alibaba.android.vlayout.layout.LinearLayoutHelper import com.alibaba.android.vlayout.layout.LinearLayoutHelper
import com.google.gson.Gson import com.google.gson.Gson
import com.google.gson.reflect.TypeToken import com.google.gson.reflect.TypeToken
import com.idormy.sms.forwarder.R import com.idormy.sms.forwarder.R
import com.idormy.sms.forwarder.adapter.base.broccoli.BroccoliSimpleDelegateAdapter import com.idormy.sms.forwarder.adapter.base.broccoli.BroccoliSimpleDelegateAdapter
import com.idormy.sms.forwarder.adapter.base.delegate.SimpleDelegateAdapter import com.idormy.sms.forwarder.adapter.base.delegate.SimpleDelegateAdapter
import com.idormy.sms.forwarder.core.BaseFragment import com.idormy.sms.forwarder.core.BaseFragment
import com.idormy.sms.forwarder.databinding.FragmentClientContactQueryBinding import com.idormy.sms.forwarder.databinding.FragmentClientContactQueryBinding
import com.idormy.sms.forwarder.entity.ContactInfo import com.idormy.sms.forwarder.entity.ContactInfo
import com.idormy.sms.forwarder.server.model.BaseResponse import com.idormy.sms.forwarder.server.model.BaseResponse
import com.idormy.sms.forwarder.server.model.ContactQueryData import com.idormy.sms.forwarder.server.model.ContactQueryData
import com.idormy.sms.forwarder.utils.* import com.idormy.sms.forwarder.utils.*
import com.jeremyliao.liveeventbus.LiveEventBus import com.jeremyliao.liveeventbus.LiveEventBus
import com.scwang.smartrefresh.layout.api.RefreshLayout import com.scwang.smartrefresh.layout.api.RefreshLayout
import com.xuexiang.xaop.annotation.SingleClick import com.xuexiang.xaop.annotation.SingleClick
import com.xuexiang.xhttp2.XHttp import com.xuexiang.xhttp2.XHttp
import com.xuexiang.xhttp2.cache.model.CacheMode import com.xuexiang.xhttp2.cache.model.CacheMode
import com.xuexiang.xhttp2.callback.SimpleCallBack import com.xuexiang.xhttp2.callback.SimpleCallBack
import com.xuexiang.xhttp2.exception.ApiException import com.xuexiang.xhttp2.exception.ApiException
import com.xuexiang.xpage.annotation.Page import com.xuexiang.xpage.annotation.Page
import com.xuexiang.xpage.base.XPageActivity import com.xuexiang.xpage.base.XPageActivity
import com.xuexiang.xpage.core.PageOption import com.xuexiang.xpage.core.PageOption
import com.xuexiang.xrouter.utils.TextUtils import com.xuexiang.xrouter.utils.TextUtils
import com.xuexiang.xui.adapter.recyclerview.RecyclerViewHolder import com.xuexiang.xui.adapter.recyclerview.RecyclerViewHolder
import com.xuexiang.xui.utils.ResUtils import com.xuexiang.xui.utils.ResUtils
import com.xuexiang.xui.utils.SnackbarUtils import com.xuexiang.xui.utils.SnackbarUtils
import com.xuexiang.xui.widget.actionbar.TitleBar import com.xuexiang.xui.widget.actionbar.TitleBar
import com.xuexiang.xui.widget.searchview.MaterialSearchView import com.xuexiang.xui.widget.searchview.MaterialSearchView
import com.xuexiang.xutil.data.ConvertTools import com.xuexiang.xutil.data.ConvertTools
import com.xuexiang.xutil.system.ClipboardUtils import com.xuexiang.xutil.system.ClipboardUtils
import me.samlss.broccoli.Broccoli import me.samlss.broccoli.Broccoli
@Suppress("PropertyName") @Suppress("PropertyName")
@Page(name = "远程查话簿") @Page(name = "远程查话簿")
class ContactQueryFragment : BaseFragment<FragmentClientContactQueryBinding?>() { class ContactQueryFragment : BaseFragment<FragmentClientContactQueryBinding?>() {
val TAG: String = ContactQueryFragment::class.java.simpleName val TAG: String = ContactQueryFragment::class.java.simpleName
private var mAdapter: SimpleDelegateAdapter<ContactInfo>? = null private var mAdapter: SimpleDelegateAdapter<ContactInfo>? = null
private var keyword: String = "" private var keyword: String = ""
override fun viewBindingInflate( override fun viewBindingInflate(
inflater: LayoutInflater, inflater: LayoutInflater,
container: ViewGroup, container: ViewGroup,
): FragmentClientContactQueryBinding { ): FragmentClientContactQueryBinding {
return FragmentClientContactQueryBinding.inflate(inflater, container, false) return FragmentClientContactQueryBinding.inflate(inflater, container, false)
} }
override fun initTitle(): TitleBar { override fun initTitle(): TitleBar {
val titleBar = super.initTitle()!!.setImmersive(false) val titleBar = super.initTitle()!!.setImmersive(false)
titleBar.setTitle(R.string.api_contact_query) titleBar.setTitle(R.string.api_contact_query)
titleBar!!.addAction(object : TitleBar.ImageAction(R.drawable.ic_query) { titleBar!!.addAction(object : TitleBar.ImageAction(R.drawable.ic_query) {
@SingleClick @SingleClick
override fun performAction(view: View) { override fun performAction(view: View) {
binding!!.searchView.showSearch() binding!!.searchView.showSearch()
} }
}) })
return titleBar return titleBar
} }
/** /**
* 初始化控件 * 初始化控件
*/ */
override fun initViews() { override fun initViews() {
val virtualLayoutManager = VirtualLayoutManager(requireContext()) val virtualLayoutManager = VirtualLayoutManager(requireContext())
binding!!.recyclerView.layoutManager = virtualLayoutManager binding!!.recyclerView.layoutManager = virtualLayoutManager
val viewPool = RecyclerView.RecycledViewPool() val viewPool = RecyclerView.RecycledViewPool()
binding!!.recyclerView.setRecycledViewPool(viewPool) binding!!.recyclerView.setRecycledViewPool(viewPool)
viewPool.setMaxRecycledViews(0, 10) viewPool.setMaxRecycledViews(0, 10)
mAdapter = object : BroccoliSimpleDelegateAdapter<ContactInfo>( mAdapter = object : BroccoliSimpleDelegateAdapter<ContactInfo>(
R.layout.adapter_contact_card_view_list_item, R.layout.adapter_contact_card_view_list_item,
LinearLayoutHelper(), LinearLayoutHelper(),
DataProvider.emptyContactInfo DataProvider.emptyContactInfo
) { ) {
override fun onBindData( override fun onBindData(
holder: RecyclerViewHolder, holder: RecyclerViewHolder,
model: ContactInfo, model: ContactInfo,
position: Int, position: Int,
) { ) {
holder.text(R.id.sb_letter, model.firstLetter) holder.text(R.id.sb_letter, model.firstLetter)
holder.text(R.id.tv_name, model.name) holder.text(R.id.tv_name, model.name)
holder.text(R.id.tv_phone_number, model.phoneNumber) holder.text(R.id.tv_phone_number, model.phoneNumber)
holder.image(R.id.iv_copy, R.drawable.ic_copy) holder.image(R.id.iv_copy, R.drawable.ic_copy)
holder.image(R.id.iv_call, R.drawable.ic_phone_out) holder.image(R.id.iv_call, R.drawable.ic_phone_out)
holder.image(R.id.iv_reply, R.drawable.ic_reply) holder.image(R.id.iv_reply, R.drawable.ic_reply)
holder.click(R.id.iv_copy) { holder.click(R.id.iv_copy) {
val str = model.toString() val str = model.toString()
XToastUtils.info(String.format(getString(R.string.copied_to_clipboard), str)) XToastUtils.info(String.format(getString(R.string.copied_to_clipboard), str))
ClipboardUtils.copyText(str) ClipboardUtils.copyText(str)
} }
holder.click(R.id.iv_call) { holder.click(R.id.iv_call) {
XToastUtils.info(getString(R.string.local_call) + model.phoneNumber) XToastUtils.info(getString(R.string.local_call) + model.phoneNumber)
PhoneUtils.dial(model.phoneNumber) PhoneUtils.dial(model.phoneNumber)
} }
holder.click(R.id.iv_reply) { holder.click(R.id.iv_reply) {
XToastUtils.info(getString(R.string.remote_sms) + model.phoneNumber) XToastUtils.info(getString(R.string.remote_sms) + model.phoneNumber)
/*val params = Bundle() /*val params = Bundle()
params.putString(KEY_PHONE_NUMBERS, model.phoneNumber) params.putString(KEY_PHONE_NUMBERS, model.phoneNumber)
openPage(SmsSendFragment::class.java, params)*/ openPage(SmsSendFragment::class.java, params)*/
LiveEventBus.get<String>(EVENT_KEY_PHONE_NUMBERS).post(model.phoneNumber) LiveEventBus.get<String>(EVENT_KEY_PHONE_NUMBERS).post(model.phoneNumber)
PageOption.to(SmsSendFragment::class.java).setNewActivity(true).open((context as XPageActivity?)!!) PageOption.to(SmsSendFragment::class.java).setNewActivity(true).open((context as XPageActivity?)!!)
} }
} }
override fun onBindBroccoli(holder: RecyclerViewHolder, broccoli: Broccoli) { override fun onBindBroccoli(holder: RecyclerViewHolder, broccoli: Broccoli) {
broccoli.addPlaceholder(PlaceholderHelper.getParameter(holder.findView(R.id.tv_name))) broccoli.addPlaceholder(PlaceholderHelper.getParameter(holder.findView(R.id.tv_name)))
.addPlaceholder(PlaceholderHelper.getParameter(holder.findView(R.id.tv_phone_number))) .addPlaceholder(PlaceholderHelper.getParameter(holder.findView(R.id.tv_phone_number)))
.addPlaceholder(PlaceholderHelper.getParameter(holder.findView(R.id.sb_letter))) .addPlaceholder(PlaceholderHelper.getParameter(holder.findView(R.id.sb_letter)))
.addPlaceholder(PlaceholderHelper.getParameter(holder.findView(R.id.iv_copy))) .addPlaceholder(PlaceholderHelper.getParameter(holder.findView(R.id.iv_copy)))
.addPlaceholder(PlaceholderHelper.getParameter(holder.findView(R.id.iv_call))) .addPlaceholder(PlaceholderHelper.getParameter(holder.findView(R.id.iv_call)))
.addPlaceholder(PlaceholderHelper.getParameter(holder.findView(R.id.iv_reply))) .addPlaceholder(PlaceholderHelper.getParameter(holder.findView(R.id.iv_reply)))
} }
} }
val delegateAdapter = DelegateAdapter(virtualLayoutManager) val delegateAdapter = DelegateAdapter(virtualLayoutManager)
delegateAdapter.addAdapter(mAdapter) delegateAdapter.addAdapter(mAdapter)
binding!!.recyclerView.adapter = delegateAdapter binding!!.recyclerView.adapter = delegateAdapter
//搜索框 //搜索框
binding!!.searchView.findViewById<View>(com.xuexiang.xui.R.id.search_layout).visibility = View.GONE binding!!.searchView.findViewById<View>(com.xuexiang.xui.R.id.search_layout).visibility = View.GONE
binding!!.searchView.setVoiceSearch(true) binding!!.searchView.setVoiceSearch(true)
binding!!.searchView.setEllipsize(true) binding!!.searchView.setEllipsize(true)
binding!!.searchView.setSuggestions(resources.getStringArray(R.array.query_suggestions)) binding!!.searchView.setSuggestions(resources.getStringArray(R.array.query_suggestions))
binding!!.searchView.setOnQueryTextListener(object : MaterialSearchView.OnQueryTextListener { binding!!.searchView.setOnQueryTextListener(object : MaterialSearchView.OnQueryTextListener {
override fun onQueryTextSubmit(query: String): Boolean { override fun onQueryTextSubmit(query: String): Boolean {
SnackbarUtils.Indefinite(view, String.format(getString(R.string.search_keyword), query)).info() SnackbarUtils.Indefinite(view, String.format(getString(R.string.search_keyword), query)).info()
.actionColor(ResUtils.getColor(R.color.xui_config_color_white)) .actionColor(ResUtils.getColor(R.color.xui_config_color_white))
.setAction(getString(R.string.clear)) { .setAction(getString(R.string.clear)) {
keyword = "" keyword = ""
loadRemoteData() loadRemoteData()
}.show() }.show()
if (keyword != query) { if (keyword != query) {
keyword = query keyword = query
loadRemoteData() loadRemoteData()
} }
return false return false
} }
override fun onQueryTextChange(newText: String): Boolean { override fun onQueryTextChange(newText: String): Boolean {
//Do some magic //Do some magic
return false return false
} }
}) })
binding!!.searchView.setOnSearchViewListener(object : MaterialSearchView.SearchViewListener { binding!!.searchView.setOnSearchViewListener(object : MaterialSearchView.SearchViewListener {
override fun onSearchViewShown() { override fun onSearchViewShown() {
//Do some magic //Do some magic
} }
override fun onSearchViewClosed() { override fun onSearchViewClosed() {
//Do some magic //Do some magic
} }
}) })
binding!!.searchView.setSubmitOnClick(true) binding!!.searchView.setSubmitOnClick(true)
} }
override fun initListeners() { override fun initListeners() {
//下拉刷新 //下拉刷新
binding!!.refreshLayout.setOnRefreshListener { refreshLayout: RefreshLayout -> binding!!.refreshLayout.setOnRefreshListener { refreshLayout: RefreshLayout ->
refreshLayout.layout.postDelayed({ refreshLayout.layout.postDelayed({
loadRemoteData() loadRemoteData()
}, 1000) }, 1000)
} }
binding!!.refreshLayout.autoRefresh() //第一次进入触发自动刷新,演示效果 binding!!.refreshLayout.autoRefresh() //第一次进入触发自动刷新,演示效果
} }
private fun loadRemoteData() { private fun loadRemoteData() {
val requestUrl: String = HttpServerUtils.serverAddress + "/contact/query" val requestUrl: String = HttpServerUtils.serverAddress + "/contact/query"
Log.i(TAG, "requestUrl:$requestUrl") Log.i(TAG, "requestUrl:$requestUrl")
val msgMap: MutableMap<String, Any> = mutableMapOf() val msgMap: MutableMap<String, Any> = mutableMapOf()
val timestamp = System.currentTimeMillis() val timestamp = System.currentTimeMillis()
msgMap["timestamp"] = timestamp msgMap["timestamp"] = timestamp
val clientSignKey = HttpServerUtils.clientSignKey val clientSignKey = HttpServerUtils.clientSignKey
if (!TextUtils.isEmpty(clientSignKey)) { if (!TextUtils.isEmpty(clientSignKey)) {
msgMap["sign"] = HttpServerUtils.calcSign(timestamp.toString(), clientSignKey.toString()) msgMap["sign"] = HttpServerUtils.calcSign(timestamp.toString(), clientSignKey)
} }
msgMap["data"] = if (keyword.isDigitsOnly()) msgMap["data"] = if (keyword.isDigitsOnly())
ContactQueryData(1, 20, keyword, null) ContactQueryData(1, 20, keyword, null)
else else
ContactQueryData(1, 20, null, keyword) ContactQueryData(1, 20, null, keyword)
var requestMsg: String = Gson().toJson(msgMap) var requestMsg: String = Gson().toJson(msgMap)
Log.i(TAG, "requestMsg:$requestMsg") Log.i(TAG, "requestMsg:$requestMsg")
val postRequest = XHttp.post(requestUrl) val postRequest = XHttp.post(requestUrl)
.keepJson(true) .keepJson(true)
.timeOut((SettingUtils.requestTimeout * 1000).toLong()) //超时时间10s .timeOut((SettingUtils.requestTimeout * 1000).toLong()) //超时时间10s
.cacheMode(CacheMode.NO_CACHE) .cacheMode(CacheMode.NO_CACHE)
.timeStamp(true) .timeStamp(true)
when (HttpServerUtils.clientSafetyMeasures) { when (HttpServerUtils.clientSafetyMeasures) {
2 -> { 2 -> {
val publicKey = RSACrypt.getPublicKey(HttpServerUtils.clientSignKey.toString()) val publicKey = RSACrypt.getPublicKey(HttpServerUtils.clientSignKey)
try { try {
requestMsg = Base64.encode(requestMsg.toByteArray()) requestMsg = Base64.encode(requestMsg.toByteArray())
requestMsg = RSACrypt.encryptByPublicKey(requestMsg, publicKey) requestMsg = RSACrypt.encryptByPublicKey(requestMsg, publicKey)
Log.i(TAG, "requestMsg: $requestMsg") Log.i(TAG, "requestMsg: $requestMsg")
} catch (e: Exception) { } catch (e: Exception) {
XToastUtils.error(ResUtils.getString(R.string.request_failed) + e.message) XToastUtils.error(ResUtils.getString(R.string.request_failed) + e.message)
e.printStackTrace() e.printStackTrace()
return return
} }
postRequest.upString(requestMsg) postRequest.upString(requestMsg)
} }
3 -> { 3 -> {
try { try {
val sm4Key = ConvertTools.hexStringToByteArray(HttpServerUtils.clientSignKey.toString()) val sm4Key = ConvertTools.hexStringToByteArray(HttpServerUtils.clientSignKey)
//requestMsg = Base64.encode(requestMsg.toByteArray()) //requestMsg = Base64.encode(requestMsg.toByteArray())
val encryptCBC = SM4Crypt.encrypt(requestMsg.toByteArray(), sm4Key) val encryptCBC = SM4Crypt.encrypt(requestMsg.toByteArray(), sm4Key)
requestMsg = ConvertTools.bytes2HexString(encryptCBC) requestMsg = ConvertTools.bytes2HexString(encryptCBC)
Log.i(TAG, "requestMsg: $requestMsg") Log.i(TAG, "requestMsg: $requestMsg")
} catch (e: Exception) { } catch (e: Exception) {
XToastUtils.error(ResUtils.getString(R.string.request_failed) + e.message) XToastUtils.error(ResUtils.getString(R.string.request_failed) + e.message)
e.printStackTrace() e.printStackTrace()
return return
} }
postRequest.upString(requestMsg) postRequest.upString(requestMsg)
} }
else -> { else -> {
postRequest.upJson(requestMsg) postRequest.upJson(requestMsg)
} }
} }
postRequest.execute(object : SimpleCallBack<String>() { postRequest.execute(object : SimpleCallBack<String>() {
override fun onError(e: ApiException) { override fun onError(e: ApiException) {
XToastUtils.error(e.displayMessage) XToastUtils.error(e.displayMessage)
} }
override fun onSuccess(response: String) { override fun onSuccess(response: String) {
Log.i(TAG, response) Log.i(TAG, response)
try { try {
var json = response var json = response
if (HttpServerUtils.clientSafetyMeasures == 2) { if (HttpServerUtils.clientSafetyMeasures == 2) {
val publicKey = RSACrypt.getPublicKey(HttpServerUtils.clientSignKey.toString()) val publicKey = RSACrypt.getPublicKey(HttpServerUtils.clientSignKey)
json = RSACrypt.decryptByPublicKey(json, publicKey) json = RSACrypt.decryptByPublicKey(json, publicKey)
json = String(Base64.decode(json)) json = String(Base64.decode(json))
} else if (HttpServerUtils.clientSafetyMeasures == 3) { } else if (HttpServerUtils.clientSafetyMeasures == 3) {
val sm4Key = ConvertTools.hexStringToByteArray(HttpServerUtils.clientSignKey.toString()) val sm4Key = ConvertTools.hexStringToByteArray(HttpServerUtils.clientSignKey)
val encryptCBC = ConvertTools.hexStringToByteArray(json) val encryptCBC = ConvertTools.hexStringToByteArray(json)
val decryptCBC = SM4Crypt.decrypt(encryptCBC, sm4Key) val decryptCBC = SM4Crypt.decrypt(encryptCBC, sm4Key)
json = String(decryptCBC) json = String(decryptCBC)
} }
val resp: BaseResponse<List<ContactInfo>?> = Gson().fromJson(json, object : TypeToken<BaseResponse<List<ContactInfo>?>>() {}.type) val resp: BaseResponse<List<ContactInfo>?> = Gson().fromJson(json, object : TypeToken<BaseResponse<List<ContactInfo>?>>() {}.type)
if (resp.code == 200) { if (resp.code == 200) {
//XToastUtils.success(ResUtils.getString(R.string.request_succeeded)) //XToastUtils.success(ResUtils.getString(R.string.request_succeeded))
mAdapter!!.refresh(resp.data) mAdapter!!.refresh(resp.data)
binding!!.refreshLayout.finishRefresh() binding!!.refreshLayout.finishRefresh()
binding!!.recyclerView.scrollToPosition(0) binding!!.recyclerView.scrollToPosition(0)
} else { } else {
XToastUtils.error(ResUtils.getString(R.string.request_failed) + resp.msg) XToastUtils.error(ResUtils.getString(R.string.request_failed) + resp.msg)
} }
} catch (e: Exception) { } catch (e: Exception) {
e.printStackTrace() e.printStackTrace()
XToastUtils.error(ResUtils.getString(R.string.request_failed) + response) XToastUtils.error(ResUtils.getString(R.string.request_failed) + response)
} }
} }
}) })
} }
} }

@ -1,286 +1,286 @@
package com.idormy.sms.forwarder.fragment.client package com.idormy.sms.forwarder.fragment.client
import android.util.Log import android.util.Log
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.alibaba.android.vlayout.DelegateAdapter import com.alibaba.android.vlayout.DelegateAdapter
import com.alibaba.android.vlayout.VirtualLayoutManager import com.alibaba.android.vlayout.VirtualLayoutManager
import com.alibaba.android.vlayout.layout.LinearLayoutHelper import com.alibaba.android.vlayout.layout.LinearLayoutHelper
import com.google.gson.Gson import com.google.gson.Gson
import com.google.gson.reflect.TypeToken import com.google.gson.reflect.TypeToken
import com.idormy.sms.forwarder.R import com.idormy.sms.forwarder.R
import com.idormy.sms.forwarder.adapter.base.broccoli.BroccoliSimpleDelegateAdapter import com.idormy.sms.forwarder.adapter.base.broccoli.BroccoliSimpleDelegateAdapter
import com.idormy.sms.forwarder.adapter.base.delegate.SimpleDelegateAdapter import com.idormy.sms.forwarder.adapter.base.delegate.SimpleDelegateAdapter
import com.idormy.sms.forwarder.core.BaseFragment import com.idormy.sms.forwarder.core.BaseFragment
import com.idormy.sms.forwarder.databinding.FragmentClientSmsQueryBinding import com.idormy.sms.forwarder.databinding.FragmentClientSmsQueryBinding
import com.idormy.sms.forwarder.entity.SmsInfo import com.idormy.sms.forwarder.entity.SmsInfo
import com.idormy.sms.forwarder.server.model.BaseResponse import com.idormy.sms.forwarder.server.model.BaseResponse
import com.idormy.sms.forwarder.server.model.SmsQueryData import com.idormy.sms.forwarder.server.model.SmsQueryData
import com.idormy.sms.forwarder.utils.* import com.idormy.sms.forwarder.utils.*
import com.idormy.sms.forwarder.utils.DataProvider.emptySmsInfo import com.idormy.sms.forwarder.utils.DataProvider.emptySmsInfo
import com.jeremyliao.liveeventbus.LiveEventBus import com.jeremyliao.liveeventbus.LiveEventBus
import com.scwang.smartrefresh.layout.api.RefreshLayout import com.scwang.smartrefresh.layout.api.RefreshLayout
import com.xuexiang.xaop.annotation.SingleClick import com.xuexiang.xaop.annotation.SingleClick
import com.xuexiang.xhttp2.XHttp import com.xuexiang.xhttp2.XHttp
import com.xuexiang.xhttp2.cache.model.CacheMode import com.xuexiang.xhttp2.cache.model.CacheMode
import com.xuexiang.xhttp2.callback.SimpleCallBack import com.xuexiang.xhttp2.callback.SimpleCallBack
import com.xuexiang.xhttp2.exception.ApiException import com.xuexiang.xhttp2.exception.ApiException
import com.xuexiang.xpage.annotation.Page import com.xuexiang.xpage.annotation.Page
import com.xuexiang.xpage.base.XPageActivity import com.xuexiang.xpage.base.XPageActivity
import com.xuexiang.xpage.core.PageOption import com.xuexiang.xpage.core.PageOption
import com.xuexiang.xrouter.utils.TextUtils import com.xuexiang.xrouter.utils.TextUtils
import com.xuexiang.xui.adapter.recyclerview.RecyclerViewHolder import com.xuexiang.xui.adapter.recyclerview.RecyclerViewHolder
import com.xuexiang.xui.utils.ResUtils import com.xuexiang.xui.utils.ResUtils
import com.xuexiang.xui.utils.SnackbarUtils import com.xuexiang.xui.utils.SnackbarUtils
import com.xuexiang.xui.widget.actionbar.TitleBar import com.xuexiang.xui.widget.actionbar.TitleBar
import com.xuexiang.xui.widget.searchview.MaterialSearchView import com.xuexiang.xui.widget.searchview.MaterialSearchView
import com.xuexiang.xui.widget.searchview.MaterialSearchView.SearchViewListener import com.xuexiang.xui.widget.searchview.MaterialSearchView.SearchViewListener
import com.xuexiang.xutil.data.ConvertTools import com.xuexiang.xutil.data.ConvertTools
import com.xuexiang.xutil.data.DateUtils import com.xuexiang.xutil.data.DateUtils
import me.samlss.broccoli.Broccoli import me.samlss.broccoli.Broccoli
@Suppress("PropertyName") @Suppress("PropertyName")
@Page(name = "远程查短信") @Page(name = "远程查短信")
class SmsQueryFragment : BaseFragment<FragmentClientSmsQueryBinding?>() { class SmsQueryFragment : BaseFragment<FragmentClientSmsQueryBinding?>() {
val TAG: String = SmsQueryFragment::class.java.simpleName val TAG: String = SmsQueryFragment::class.java.simpleName
private var mAdapter: SimpleDelegateAdapter<SmsInfo>? = null private var mAdapter: SimpleDelegateAdapter<SmsInfo>? = null
private var smsType: Int = 1 private var smsType: Int = 1
private var pageNum: Int = 1 private var pageNum: Int = 1
private val pageSize: Int = 20 private val pageSize: Int = 20
private var keyword: String = "" private var keyword: String = ""
override fun viewBindingInflate( override fun viewBindingInflate(
inflater: LayoutInflater, inflater: LayoutInflater,
container: ViewGroup, container: ViewGroup,
): FragmentClientSmsQueryBinding { ): FragmentClientSmsQueryBinding {
return FragmentClientSmsQueryBinding.inflate(inflater, container, false) return FragmentClientSmsQueryBinding.inflate(inflater, container, false)
} }
override fun initTitle(): TitleBar { override fun initTitle(): TitleBar {
val titleBar = super.initTitle()!!.setImmersive(false) val titleBar = super.initTitle()!!.setImmersive(false)
titleBar.setTitle(R.string.api_sms_query) titleBar.setTitle(R.string.api_sms_query)
titleBar!!.addAction(object : TitleBar.ImageAction(R.drawable.ic_query) { titleBar!!.addAction(object : TitleBar.ImageAction(R.drawable.ic_query) {
@SingleClick @SingleClick
override fun performAction(view: View) { override fun performAction(view: View) {
binding!!.searchView.showSearch() binding!!.searchView.showSearch()
} }
}) })
return titleBar return titleBar
} }
/** /**
* 初始化控件 * 初始化控件
*/ */
override fun initViews() { override fun initViews() {
val virtualLayoutManager = VirtualLayoutManager(requireContext()) val virtualLayoutManager = VirtualLayoutManager(requireContext())
binding!!.recyclerView.layoutManager = virtualLayoutManager binding!!.recyclerView.layoutManager = virtualLayoutManager
val viewPool = RecyclerView.RecycledViewPool() val viewPool = RecyclerView.RecycledViewPool()
binding!!.recyclerView.setRecycledViewPool(viewPool) binding!!.recyclerView.setRecycledViewPool(viewPool)
viewPool.setMaxRecycledViews(0, 10) viewPool.setMaxRecycledViews(0, 10)
mAdapter = object : BroccoliSimpleDelegateAdapter<SmsInfo>( mAdapter = object : BroccoliSimpleDelegateAdapter<SmsInfo>(
R.layout.adapter_sms_card_view_list_item, R.layout.adapter_sms_card_view_list_item,
LinearLayoutHelper(), LinearLayoutHelper(),
emptySmsInfo emptySmsInfo
) { ) {
override fun onBindData( override fun onBindData(
holder: RecyclerViewHolder, holder: RecyclerViewHolder,
model: SmsInfo, model: SmsInfo,
position: Int, position: Int,
) { ) {
holder.text(R.id.tv_from, model.number) holder.text(R.id.tv_from, model.number)
holder.text(R.id.tv_time, DateUtils.getFriendlyTimeSpanByNow(model.date)) holder.text(R.id.tv_time, DateUtils.getFriendlyTimeSpanByNow(model.date))
holder.image(R.id.iv_image, model.typeImageId) holder.image(R.id.iv_image, model.typeImageId)
holder.image(R.id.iv_sim_image, model.simImageId) holder.image(R.id.iv_sim_image, model.simImageId)
holder.text(R.id.tv_content, model.content) holder.text(R.id.tv_content, model.content)
holder.image(R.id.iv_reply, R.drawable.ic_reply) holder.image(R.id.iv_reply, R.drawable.ic_reply)
holder.click(R.id.iv_reply) { holder.click(R.id.iv_reply) {
XToastUtils.info(getString(R.string.remote_sms) + model.number) XToastUtils.info(getString(R.string.remote_sms) + model.number)
LiveEventBus.get<Int>(EVENT_KEY_SIM_SLOT).post(model.simId) LiveEventBus.get<Int>(EVENT_KEY_SIM_SLOT).post(model.simId)
LiveEventBus.get<String>(EVENT_KEY_PHONE_NUMBERS).post(model.number) LiveEventBus.get<String>(EVENT_KEY_PHONE_NUMBERS).post(model.number)
PageOption.to(SmsSendFragment::class.java).setNewActivity(true).open((context as XPageActivity?)!!) PageOption.to(SmsSendFragment::class.java).setNewActivity(true).open((context as XPageActivity?)!!)
} }
} }
override fun onBindBroccoli(holder: RecyclerViewHolder, broccoli: Broccoli) { override fun onBindBroccoli(holder: RecyclerViewHolder, broccoli: Broccoli) {
broccoli.addPlaceholder(PlaceholderHelper.getParameter(holder.findView(R.id.tv_from))) broccoli.addPlaceholder(PlaceholderHelper.getParameter(holder.findView(R.id.tv_from)))
.addPlaceholder(PlaceholderHelper.getParameter(holder.findView(R.id.tv_time))) .addPlaceholder(PlaceholderHelper.getParameter(holder.findView(R.id.tv_time)))
.addPlaceholder(PlaceholderHelper.getParameter(holder.findView(R.id.iv_sim_image))) .addPlaceholder(PlaceholderHelper.getParameter(holder.findView(R.id.iv_sim_image)))
.addPlaceholder(PlaceholderHelper.getParameter(holder.findView(R.id.tv_content))) .addPlaceholder(PlaceholderHelper.getParameter(holder.findView(R.id.tv_content)))
.addPlaceholder(PlaceholderHelper.getParameter(holder.findView(R.id.iv_image))) .addPlaceholder(PlaceholderHelper.getParameter(holder.findView(R.id.iv_image)))
.addPlaceholder(PlaceholderHelper.getParameter(holder.findView(R.id.iv_reply))) .addPlaceholder(PlaceholderHelper.getParameter(holder.findView(R.id.iv_reply)))
} }
} }
val delegateAdapter = DelegateAdapter(virtualLayoutManager) val delegateAdapter = DelegateAdapter(virtualLayoutManager)
delegateAdapter.addAdapter(mAdapter) delegateAdapter.addAdapter(mAdapter)
binding!!.recyclerView.adapter = delegateAdapter binding!!.recyclerView.adapter = delegateAdapter
binding!!.tabBar.setTabTitles(ResUtils.getStringArray(R.array.sms_type_option)) binding!!.tabBar.setTabTitles(ResUtils.getStringArray(R.array.sms_type_option))
binding!!.tabBar.setOnTabClickListener { _, position -> binding!!.tabBar.setOnTabClickListener { _, position ->
//XToastUtils.toast("点击了$title--$position") //XToastUtils.toast("点击了$title--$position")
smsType = position + 1 smsType = position + 1
loadRemoteData(true) loadRemoteData(true)
binding!!.recyclerView.scrollToPosition(0) binding!!.recyclerView.scrollToPosition(0)
} }
//搜索框 //搜索框
binding!!.searchView.findViewById<View>(com.xuexiang.xui.R.id.search_layout).visibility = View.GONE binding!!.searchView.findViewById<View>(com.xuexiang.xui.R.id.search_layout).visibility = View.GONE
binding!!.searchView.setVoiceSearch(true) binding!!.searchView.setVoiceSearch(true)
binding!!.searchView.setEllipsize(true) binding!!.searchView.setEllipsize(true)
binding!!.searchView.setSuggestions(resources.getStringArray(R.array.query_suggestions)) binding!!.searchView.setSuggestions(resources.getStringArray(R.array.query_suggestions))
binding!!.searchView.setOnQueryTextListener(object : MaterialSearchView.OnQueryTextListener { binding!!.searchView.setOnQueryTextListener(object : MaterialSearchView.OnQueryTextListener {
override fun onQueryTextSubmit(query: String): Boolean { override fun onQueryTextSubmit(query: String): Boolean {
SnackbarUtils.Indefinite(view, String.format(getString(R.string.search_keyword), query)).info() SnackbarUtils.Indefinite(view, String.format(getString(R.string.search_keyword), query)).info()
.actionColor(ResUtils.getColor(R.color.xui_config_color_white)) .actionColor(ResUtils.getColor(R.color.xui_config_color_white))
.setAction(getString(R.string.clear)) { .setAction(getString(R.string.clear)) {
keyword = "" keyword = ""
loadRemoteData(true) loadRemoteData(true)
}.show() }.show()
if (keyword != query) { if (keyword != query) {
keyword = query keyword = query
loadRemoteData(true) loadRemoteData(true)
} }
return false return false
} }
override fun onQueryTextChange(newText: String): Boolean { override fun onQueryTextChange(newText: String): Boolean {
//Do some magic //Do some magic
return false return false
} }
}) })
binding!!.searchView.setOnSearchViewListener(object : SearchViewListener { binding!!.searchView.setOnSearchViewListener(object : SearchViewListener {
override fun onSearchViewShown() { override fun onSearchViewShown() {
//Do some magic //Do some magic
} }
override fun onSearchViewClosed() { override fun onSearchViewClosed() {
//Do some magic //Do some magic
} }
}) })
binding!!.searchView.setSubmitOnClick(true) binding!!.searchView.setSubmitOnClick(true)
} }
override fun initListeners() { override fun initListeners() {
//下拉刷新 //下拉刷新
binding!!.refreshLayout.setOnRefreshListener { refreshLayout: RefreshLayout -> binding!!.refreshLayout.setOnRefreshListener { refreshLayout: RefreshLayout ->
refreshLayout.layout.postDelayed({ refreshLayout.layout.postDelayed({
loadRemoteData(true) loadRemoteData(true)
}, 1000) }, 1000)
} }
//上拉加载 //上拉加载
binding!!.refreshLayout.setOnLoadMoreListener { refreshLayout: RefreshLayout -> binding!!.refreshLayout.setOnLoadMoreListener { refreshLayout: RefreshLayout ->
refreshLayout.layout.postDelayed({ refreshLayout.layout.postDelayed({
loadRemoteData(false) loadRemoteData(false)
}, 1000) }, 1000)
} }
binding!!.refreshLayout.autoRefresh() //第一次进入触发自动刷新,演示效果 binding!!.refreshLayout.autoRefresh() //第一次进入触发自动刷新,演示效果
} }
private fun loadRemoteData(refresh: Boolean) { private fun loadRemoteData(refresh: Boolean) {
val requestUrl: String = HttpServerUtils.serverAddress + "/sms/query" val requestUrl: String = HttpServerUtils.serverAddress + "/sms/query"
Log.i(TAG, "requestUrl:$requestUrl") Log.i(TAG, "requestUrl:$requestUrl")
val msgMap: MutableMap<String, Any> = mutableMapOf() val msgMap: MutableMap<String, Any> = mutableMapOf()
val timestamp = System.currentTimeMillis() val timestamp = System.currentTimeMillis()
msgMap["timestamp"] = timestamp msgMap["timestamp"] = timestamp
val clientSignKey = HttpServerUtils.clientSignKey val clientSignKey = HttpServerUtils.clientSignKey
if (!TextUtils.isEmpty(clientSignKey)) { if (!TextUtils.isEmpty(clientSignKey)) {
msgMap["sign"] = HttpServerUtils.calcSign(timestamp.toString(), clientSignKey.toString()) msgMap["sign"] = HttpServerUtils.calcSign(timestamp.toString(), clientSignKey)
} }
if (refresh) pageNum = 1 if (refresh) pageNum = 1
msgMap["data"] = SmsQueryData(smsType, pageNum, pageSize, keyword) msgMap["data"] = SmsQueryData(smsType, pageNum, pageSize, keyword)
var requestMsg: String = Gson().toJson(msgMap) var requestMsg: String = Gson().toJson(msgMap)
Log.i(TAG, "requestMsg:$requestMsg") Log.i(TAG, "requestMsg:$requestMsg")
val postRequest = XHttp.post(requestUrl) val postRequest = XHttp.post(requestUrl)
.keepJson(true) .keepJson(true)
.timeOut((SettingUtils.requestTimeout * 1000).toLong()) //超时时间10s .timeOut((SettingUtils.requestTimeout * 1000).toLong()) //超时时间10s
.cacheMode(CacheMode.NO_CACHE) .cacheMode(CacheMode.NO_CACHE)
.timeStamp(true) .timeStamp(true)
when (HttpServerUtils.clientSafetyMeasures) { when (HttpServerUtils.clientSafetyMeasures) {
2 -> { 2 -> {
val publicKey = RSACrypt.getPublicKey(HttpServerUtils.clientSignKey.toString()) val publicKey = RSACrypt.getPublicKey(HttpServerUtils.clientSignKey)
try { try {
requestMsg = Base64.encode(requestMsg.toByteArray()) requestMsg = Base64.encode(requestMsg.toByteArray())
requestMsg = RSACrypt.encryptByPublicKey(requestMsg, publicKey) requestMsg = RSACrypt.encryptByPublicKey(requestMsg, publicKey)
Log.i(TAG, "requestMsg: $requestMsg") Log.i(TAG, "requestMsg: $requestMsg")
} catch (e: Exception) { } catch (e: Exception) {
XToastUtils.error(ResUtils.getString(R.string.request_failed) + e.message) XToastUtils.error(ResUtils.getString(R.string.request_failed) + e.message)
e.printStackTrace() e.printStackTrace()
return return
} }
postRequest.upString(requestMsg) postRequest.upString(requestMsg)
} }
3 -> { 3 -> {
try { try {
val sm4Key = ConvertTools.hexStringToByteArray(HttpServerUtils.clientSignKey.toString()) val sm4Key = ConvertTools.hexStringToByteArray(HttpServerUtils.clientSignKey)
//requestMsg = Base64.encode(requestMsg.toByteArray()) //requestMsg = Base64.encode(requestMsg.toByteArray())
val encryptCBC = SM4Crypt.encrypt(requestMsg.toByteArray(), sm4Key) val encryptCBC = SM4Crypt.encrypt(requestMsg.toByteArray(), sm4Key)
requestMsg = ConvertTools.bytes2HexString(encryptCBC) requestMsg = ConvertTools.bytes2HexString(encryptCBC)
Log.i(TAG, "requestMsg: $requestMsg") Log.i(TAG, "requestMsg: $requestMsg")
} catch (e: Exception) { } catch (e: Exception) {
XToastUtils.error(ResUtils.getString(R.string.request_failed) + e.message) XToastUtils.error(ResUtils.getString(R.string.request_failed) + e.message)
e.printStackTrace() e.printStackTrace()
return return
} }
postRequest.upString(requestMsg) postRequest.upString(requestMsg)
} }
else -> { else -> {
postRequest.upJson(requestMsg) postRequest.upJson(requestMsg)
} }
} }
postRequest.execute(object : SimpleCallBack<String>() { postRequest.execute(object : SimpleCallBack<String>() {
override fun onError(e: ApiException) { override fun onError(e: ApiException) {
XToastUtils.error(e.displayMessage) XToastUtils.error(e.displayMessage)
} }
override fun onSuccess(response: String) { override fun onSuccess(response: String) {
Log.i(TAG, response) Log.i(TAG, response)
try { try {
var json = response var json = response
if (HttpServerUtils.clientSafetyMeasures == 2) { if (HttpServerUtils.clientSafetyMeasures == 2) {
val publicKey = RSACrypt.getPublicKey(HttpServerUtils.clientSignKey.toString()) val publicKey = RSACrypt.getPublicKey(HttpServerUtils.clientSignKey)
json = RSACrypt.decryptByPublicKey(json, publicKey) json = RSACrypt.decryptByPublicKey(json, publicKey)
json = String(Base64.decode(json)) json = String(Base64.decode(json))
} else if (HttpServerUtils.clientSafetyMeasures == 3) { } else if (HttpServerUtils.clientSafetyMeasures == 3) {
val sm4Key = ConvertTools.hexStringToByteArray(HttpServerUtils.clientSignKey.toString()) val sm4Key = ConvertTools.hexStringToByteArray(HttpServerUtils.clientSignKey)
val encryptCBC = ConvertTools.hexStringToByteArray(json) val encryptCBC = ConvertTools.hexStringToByteArray(json)
val decryptCBC = SM4Crypt.decrypt(encryptCBC, sm4Key) val decryptCBC = SM4Crypt.decrypt(encryptCBC, sm4Key)
json = String(decryptCBC) json = String(decryptCBC)
} }
val resp: BaseResponse<List<SmsInfo>?> = Gson().fromJson(json, object : TypeToken<BaseResponse<List<SmsInfo>?>>() {}.type) val resp: BaseResponse<List<SmsInfo>?> = Gson().fromJson(json, object : TypeToken<BaseResponse<List<SmsInfo>?>>() {}.type)
if (resp.code == 200) { if (resp.code == 200) {
//XToastUtils.success(ResUtils.getString(R.string.request_succeeded)) //XToastUtils.success(ResUtils.getString(R.string.request_succeeded))
pageNum++ pageNum++
if (refresh) { if (refresh) {
mAdapter!!.refresh(resp.data) mAdapter!!.refresh(resp.data)
binding!!.refreshLayout.finishRefresh() binding!!.refreshLayout.finishRefresh()
binding!!.recyclerView.scrollToPosition(0) binding!!.recyclerView.scrollToPosition(0)
} else { } else {
mAdapter!!.loadMore(resp.data) mAdapter!!.loadMore(resp.data)
binding!!.refreshLayout.finishLoadMore() binding!!.refreshLayout.finishLoadMore()
} }
} else { } else {
XToastUtils.error(ResUtils.getString(R.string.request_failed) + resp.msg) XToastUtils.error(ResUtils.getString(R.string.request_failed) + resp.msg)
} }
} catch (e: Exception) { } catch (e: Exception) {
e.printStackTrace() e.printStackTrace()
XToastUtils.error(ResUtils.getString(R.string.request_failed) + response) XToastUtils.error(ResUtils.getString(R.string.request_failed) + response)
} }
} }
}) })
} }
} }

@ -1,202 +1,204 @@
package com.idormy.sms.forwarder.fragment.client package com.idormy.sms.forwarder.fragment.client
import android.util.Log import android.annotation.SuppressLint
import android.view.LayoutInflater import android.util.Log
import android.view.View import android.view.LayoutInflater
import android.view.ViewGroup import android.view.View
import com.google.gson.Gson import android.view.ViewGroup
import com.google.gson.reflect.TypeToken import com.google.gson.Gson
import com.idormy.sms.forwarder.R import com.google.gson.reflect.TypeToken
import com.idormy.sms.forwarder.core.BaseFragment import com.idormy.sms.forwarder.R
import com.idormy.sms.forwarder.databinding.FragmentClientSmsSendBinding import com.idormy.sms.forwarder.core.BaseFragment
import com.idormy.sms.forwarder.server.model.BaseResponse import com.idormy.sms.forwarder.databinding.FragmentClientSmsSendBinding
import com.idormy.sms.forwarder.server.model.ConfigData import com.idormy.sms.forwarder.server.model.BaseResponse
import com.idormy.sms.forwarder.utils.* import com.idormy.sms.forwarder.server.model.ConfigData
import com.jeremyliao.liveeventbus.LiveEventBus import com.idormy.sms.forwarder.utils.*
import com.xuexiang.xaop.annotation.SingleClick import com.jeremyliao.liveeventbus.LiveEventBus
import com.xuexiang.xhttp2.XHttp import com.xuexiang.xaop.annotation.SingleClick
import com.xuexiang.xhttp2.cache.model.CacheMode import com.xuexiang.xhttp2.XHttp
import com.xuexiang.xhttp2.callback.SimpleCallBack import com.xuexiang.xhttp2.cache.model.CacheMode
import com.xuexiang.xhttp2.exception.ApiException import com.xuexiang.xhttp2.callback.SimpleCallBack
import com.xuexiang.xpage.annotation.Page import com.xuexiang.xhttp2.exception.ApiException
import com.xuexiang.xrouter.utils.TextUtils import com.xuexiang.xpage.annotation.Page
import com.xuexiang.xui.utils.CountDownButtonHelper import com.xuexiang.xrouter.utils.TextUtils
import com.xuexiang.xui.utils.ResUtils import com.xuexiang.xui.utils.CountDownButtonHelper
import com.xuexiang.xui.widget.actionbar.TitleBar import com.xuexiang.xui.utils.ResUtils
import com.xuexiang.xutil.data.ConvertTools import com.xuexiang.xui.widget.actionbar.TitleBar
import com.xuexiang.xutil.data.ConvertTools
@Suppress("PropertyName")
@Page(name = "远程发短信") @Suppress("PropertyName")
class SmsSendFragment : BaseFragment<FragmentClientSmsSendBinding?>(), View.OnClickListener { @Page(name = "远程发短信")
class SmsSendFragment : BaseFragment<FragmentClientSmsSendBinding?>(), View.OnClickListener {
val TAG: String = SmsSendFragment::class.java.simpleName
private var mCountDownHelper: CountDownButtonHelper? = null val TAG: String = SmsSendFragment::class.java.simpleName
private var mCountDownHelper: CountDownButtonHelper? = null
override fun viewBindingInflate(
inflater: LayoutInflater, override fun viewBindingInflate(
container: ViewGroup, inflater: LayoutInflater,
): FragmentClientSmsSendBinding { container: ViewGroup,
return FragmentClientSmsSendBinding.inflate(inflater, container, false) ): FragmentClientSmsSendBinding {
} return FragmentClientSmsSendBinding.inflate(inflater, container, false)
}
override fun initTitle(): TitleBar? {
return super.initTitle()!!.setImmersive(false).setTitle(R.string.api_sms_send) override fun initTitle(): TitleBar? {
} return super.initTitle()!!.setImmersive(false).setTitle(R.string.api_sms_send)
}
/**
* 初始化控件 /**
*/ * 初始化控件
override fun initViews() { */
//发送按钮增加倒计时,避免重复点击 @SuppressLint("SetTextI18n")
mCountDownHelper = CountDownButtonHelper(binding!!.btnSubmit, SettingUtils.requestTimeout) override fun initViews() {
mCountDownHelper!!.setOnCountDownListener(object : CountDownButtonHelper.OnCountDownListener { //发送按钮增加倒计时,避免重复点击
override fun onCountDown(time: Int) { mCountDownHelper = CountDownButtonHelper(binding!!.btnSubmit, SettingUtils.requestTimeout)
binding!!.btnSubmit.text = String.format(getString(R.string.seconds_n), time) mCountDownHelper!!.setOnCountDownListener(object : CountDownButtonHelper.OnCountDownListener {
} override fun onCountDown(time: Int) {
binding!!.btnSubmit.text = String.format(getString(R.string.seconds_n), time)
override fun onFinished() { }
binding!!.btnSubmit.text = getString(R.string.send)
} override fun onFinished() {
}) binding!!.btnSubmit.text = getString(R.string.send)
}
//卡槽信息 })
val serverConfigStr = HttpServerUtils.serverConfig
if (!TextUtils.isEmpty(serverConfigStr)) { //卡槽信息
val serverConfig: ConfigData = Gson().fromJson(serverConfigStr, object : TypeToken<ConfigData>() {}.type) val serverConfigStr = HttpServerUtils.serverConfig
binding!!.rbSimSlot1.text = "SIM1" + serverConfig.extraSim1 if (!TextUtils.isEmpty(serverConfigStr)) {
binding!!.rbSimSlot2.text = "SIM2" + serverConfig.extraSim2 val serverConfig: ConfigData = Gson().fromJson(serverConfigStr, object : TypeToken<ConfigData>() {}.type)
} binding!!.rbSimSlot1.text = "SIM1" + serverConfig.extraSim1
} binding!!.rbSimSlot2.text = "SIM2" + serverConfig.extraSim2
}
override fun initListeners() { }
binding!!.btnSubmit.setOnClickListener(this)
LiveEventBus.get(EVENT_KEY_SIM_SLOT, Int::class.java).observeSticky(this) { value: Int -> override fun initListeners() {
binding!!.rgSimSlot.check(if (value == 1) R.id.rb_sim_slot_2 else R.id.rb_sim_slot_1) binding!!.btnSubmit.setOnClickListener(this)
} LiveEventBus.get(EVENT_KEY_SIM_SLOT, Int::class.java).observeSticky(this) { value: Int ->
LiveEventBus.get(EVENT_KEY_PHONE_NUMBERS, String::class.java).observeSticky(this) { value: String -> binding!!.rgSimSlot.check(if (value == 1) R.id.rb_sim_slot_2 else R.id.rb_sim_slot_1)
binding!!.etPhoneNumbers.setText(value) }
} LiveEventBus.get(EVENT_KEY_PHONE_NUMBERS, String::class.java).observeSticky(this) { value: String ->
} binding!!.etPhoneNumbers.setText(value)
}
@SingleClick }
override fun onClick(v: View) {
when (v.id) { @SingleClick
R.id.btn_submit -> { override fun onClick(v: View) {
val requestUrl: String = HttpServerUtils.serverAddress + "/sms/send" when (v.id) {
Log.i(TAG, "requestUrl:$requestUrl") R.id.btn_submit -> {
val requestUrl: String = HttpServerUtils.serverAddress + "/sms/send"
val msgMap: MutableMap<String, Any> = mutableMapOf() Log.i(TAG, "requestUrl:$requestUrl")
val timestamp = System.currentTimeMillis()
msgMap["timestamp"] = timestamp val msgMap: MutableMap<String, Any> = mutableMapOf()
val clientSignKey = HttpServerUtils.clientSignKey val timestamp = System.currentTimeMillis()
if (!TextUtils.isEmpty(clientSignKey)) { msgMap["timestamp"] = timestamp
msgMap["sign"] = HttpServerUtils.calcSign(timestamp.toString(), clientSignKey.toString()) val clientSignKey = HttpServerUtils.clientSignKey
} if (!TextUtils.isEmpty(clientSignKey)) {
msgMap["sign"] = HttpServerUtils.calcSign(timestamp.toString(), clientSignKey)
val phoneNumbers = binding!!.etPhoneNumbers.text.toString() }
val phoneRegex = getString(R.string.phone_numbers_regex).toRegex()
if (!phoneRegex.matches(phoneNumbers)) { val phoneNumbers = binding!!.etPhoneNumbers.text.toString()
XToastUtils.error(ResUtils.getString(R.string.phone_numbers_error)) val phoneRegex = getString(R.string.phone_numbers_regex).toRegex()
return if (!phoneRegex.matches(phoneNumbers)) {
} XToastUtils.error(ResUtils.getString(R.string.phone_numbers_error))
return
val msgContent = binding!!.etMsgContent.text.toString() }
val msgRegex = getString(R.string.msg_content_regex).toRegex()
if (!msgRegex.matches(msgContent)) { val msgContent = binding!!.etMsgContent.text.toString()
XToastUtils.error(ResUtils.getString(R.string.msg_content_error)) val msgRegex = getString(R.string.msg_content_regex).toRegex()
return if (!msgRegex.matches(msgContent)) {
} XToastUtils.error(ResUtils.getString(R.string.msg_content_error))
return
val dataMap: MutableMap<String, Any> = mutableMapOf() }
dataMap["sim_slot"] = if (binding!!.rgSimSlot.checkedRadioButtonId == R.id.rb_sim_slot_2) 2 else 1
dataMap["phone_numbers"] = phoneNumbers val dataMap: MutableMap<String, Any> = mutableMapOf()
dataMap["msg_content"] = msgContent dataMap["sim_slot"] = if (binding!!.rgSimSlot.checkedRadioButtonId == R.id.rb_sim_slot_2) 2 else 1
msgMap["data"] = dataMap dataMap["phone_numbers"] = phoneNumbers
dataMap["msg_content"] = msgContent
var requestMsg: String = Gson().toJson(msgMap) msgMap["data"] = dataMap
Log.i(TAG, "requestMsg:$requestMsg")
var requestMsg: String = Gson().toJson(msgMap)
val postRequest = XHttp.post(requestUrl) Log.i(TAG, "requestMsg:$requestMsg")
.keepJson(true)
.timeOut((SettingUtils.requestTimeout * 1000).toLong()) //超时时间10s val postRequest = XHttp.post(requestUrl)
.cacheMode(CacheMode.NO_CACHE) .keepJson(true)
.timeStamp(true) .timeOut((SettingUtils.requestTimeout * 1000).toLong()) //超时时间10s
.cacheMode(CacheMode.NO_CACHE)
when (HttpServerUtils.clientSafetyMeasures) { .timeStamp(true)
2 -> {
val publicKey = RSACrypt.getPublicKey(HttpServerUtils.clientSignKey.toString()) when (HttpServerUtils.clientSafetyMeasures) {
try { 2 -> {
requestMsg = Base64.encode(requestMsg.toByteArray()) val publicKey = RSACrypt.getPublicKey(HttpServerUtils.clientSignKey)
requestMsg = RSACrypt.encryptByPublicKey(requestMsg, publicKey) try {
Log.i(TAG, "requestMsg: $requestMsg") requestMsg = Base64.encode(requestMsg.toByteArray())
} catch (e: Exception) { requestMsg = RSACrypt.encryptByPublicKey(requestMsg, publicKey)
XToastUtils.error(ResUtils.getString(R.string.request_failed) + e.message) Log.i(TAG, "requestMsg: $requestMsg")
e.printStackTrace() } catch (e: Exception) {
return XToastUtils.error(ResUtils.getString(R.string.request_failed) + e.message)
} e.printStackTrace()
postRequest.upString(requestMsg) return
} }
3 -> { postRequest.upString(requestMsg)
try { }
val sm4Key = ConvertTools.hexStringToByteArray(HttpServerUtils.clientSignKey.toString()) 3 -> {
//requestMsg = Base64.encode(requestMsg.toByteArray()) try {
val encryptCBC = SM4Crypt.encrypt(requestMsg.toByteArray(), sm4Key) val sm4Key = ConvertTools.hexStringToByteArray(HttpServerUtils.clientSignKey)
requestMsg = ConvertTools.bytes2HexString(encryptCBC) //requestMsg = Base64.encode(requestMsg.toByteArray())
Log.i(TAG, "requestMsg: $requestMsg") val encryptCBC = SM4Crypt.encrypt(requestMsg.toByteArray(), sm4Key)
} catch (e: Exception) { requestMsg = ConvertTools.bytes2HexString(encryptCBC)
XToastUtils.error(ResUtils.getString(R.string.request_failed) + e.message) Log.i(TAG, "requestMsg: $requestMsg")
e.printStackTrace() } catch (e: Exception) {
return XToastUtils.error(ResUtils.getString(R.string.request_failed) + e.message)
} e.printStackTrace()
postRequest.upString(requestMsg) return
} }
else -> { postRequest.upString(requestMsg)
postRequest.upJson(requestMsg) }
} else -> {
} postRequest.upJson(requestMsg)
}
mCountDownHelper?.start() }
postRequest.execute(object : SimpleCallBack<String>() {
override fun onError(e: ApiException) { mCountDownHelper?.start()
XToastUtils.error(e.displayMessage) postRequest.execute(object : SimpleCallBack<String>() {
mCountDownHelper?.finish() override fun onError(e: ApiException) {
} XToastUtils.error(e.displayMessage)
mCountDownHelper?.finish()
override fun onSuccess(response: String) { }
Log.i(TAG, response)
try { override fun onSuccess(response: String) {
var json = response Log.i(TAG, response)
if (HttpServerUtils.clientSafetyMeasures == 2) { try {
val publicKey = RSACrypt.getPublicKey(HttpServerUtils.clientSignKey.toString()) var json = response
json = RSACrypt.decryptByPublicKey(json, publicKey) if (HttpServerUtils.clientSafetyMeasures == 2) {
json = String(Base64.decode(json)) val publicKey = RSACrypt.getPublicKey(HttpServerUtils.clientSignKey)
} else if (HttpServerUtils.clientSafetyMeasures == 3) { json = RSACrypt.decryptByPublicKey(json, publicKey)
val sm4Key = ConvertTools.hexStringToByteArray(HttpServerUtils.clientSignKey.toString()) json = String(Base64.decode(json))
val encryptCBC = ConvertTools.hexStringToByteArray(json) } else if (HttpServerUtils.clientSafetyMeasures == 3) {
val decryptCBC = SM4Crypt.decrypt(encryptCBC, sm4Key) val sm4Key = ConvertTools.hexStringToByteArray(HttpServerUtils.clientSignKey)
json = String(decryptCBC) val encryptCBC = ConvertTools.hexStringToByteArray(json)
} val decryptCBC = SM4Crypt.decrypt(encryptCBC, sm4Key)
val resp: BaseResponse<String> = Gson().fromJson(json, object : TypeToken<BaseResponse<String>>() {}.type) json = String(decryptCBC)
if (resp.code == 200) { }
XToastUtils.success(ResUtils.getString(R.string.request_succeeded)) val resp: BaseResponse<String> = Gson().fromJson(json, object : TypeToken<BaseResponse<String>>() {}.type)
} else { if (resp.code == 200) {
XToastUtils.error(ResUtils.getString(R.string.request_failed) + resp.msg) XToastUtils.success(ResUtils.getString(R.string.request_succeeded))
} } else {
} catch (e: Exception) { XToastUtils.error(ResUtils.getString(R.string.request_failed) + resp.msg)
e.printStackTrace() }
XToastUtils.error(ResUtils.getString(R.string.request_failed) + response) } catch (e: Exception) {
} e.printStackTrace()
mCountDownHelper?.finish() XToastUtils.error(ResUtils.getString(R.string.request_failed) + response)
} }
}) mCountDownHelper?.finish()
} }
else -> {} })
} }
} else -> {}
}
override fun onDestroyView() { }
if (mCountDownHelper != null) mCountDownHelper!!.recycle()
super.onDestroyView() override fun onDestroyView() {
} if (mCountDownHelper != null) mCountDownHelper!!.recycle()
super.onDestroyView()
}
} }

@ -1,235 +1,235 @@
package com.idormy.sms.forwarder.fragment.client package com.idormy.sms.forwarder.fragment.client
import android.util.Log import android.util.Log
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import com.google.gson.Gson import com.google.gson.Gson
import com.google.gson.reflect.TypeToken import com.google.gson.reflect.TypeToken
import com.idormy.sms.forwarder.R import com.idormy.sms.forwarder.R
import com.idormy.sms.forwarder.core.BaseFragment import com.idormy.sms.forwarder.core.BaseFragment
import com.idormy.sms.forwarder.databinding.FragmentClientWolSendBinding import com.idormy.sms.forwarder.databinding.FragmentClientWolSendBinding
import com.idormy.sms.forwarder.server.model.BaseResponse import com.idormy.sms.forwarder.server.model.BaseResponse
import com.idormy.sms.forwarder.utils.* import com.idormy.sms.forwarder.utils.*
import com.xuexiang.xaop.annotation.SingleClick import com.xuexiang.xaop.annotation.SingleClick
import com.xuexiang.xhttp2.XHttp import com.xuexiang.xhttp2.XHttp
import com.xuexiang.xhttp2.cache.model.CacheMode import com.xuexiang.xhttp2.cache.model.CacheMode
import com.xuexiang.xhttp2.callback.SimpleCallBack import com.xuexiang.xhttp2.callback.SimpleCallBack
import com.xuexiang.xhttp2.exception.ApiException import com.xuexiang.xhttp2.exception.ApiException
import com.xuexiang.xpage.annotation.Page import com.xuexiang.xpage.annotation.Page
import com.xuexiang.xrouter.utils.TextUtils import com.xuexiang.xrouter.utils.TextUtils
import com.xuexiang.xui.utils.CountDownButtonHelper import com.xuexiang.xui.utils.CountDownButtonHelper
import com.xuexiang.xui.utils.ResUtils import com.xuexiang.xui.utils.ResUtils
import com.xuexiang.xui.widget.actionbar.TitleBar import com.xuexiang.xui.widget.actionbar.TitleBar
import com.xuexiang.xui.widget.dialog.materialdialog.DialogAction import com.xuexiang.xui.widget.dialog.materialdialog.DialogAction
import com.xuexiang.xui.widget.dialog.materialdialog.MaterialDialog import com.xuexiang.xui.widget.dialog.materialdialog.MaterialDialog
import com.xuexiang.xutil.data.ConvertTools import com.xuexiang.xutil.data.ConvertTools
@Suppress("PropertyName") @Suppress("PropertyName")
@Page(name = "远程WOL") @Page(name = "远程WOL")
class WolSendFragment : BaseFragment<FragmentClientWolSendBinding?>(), View.OnClickListener { class WolSendFragment : BaseFragment<FragmentClientWolSendBinding?>(), View.OnClickListener {
val TAG: String = WolSendFragment::class.java.simpleName val TAG: String = WolSendFragment::class.java.simpleName
private var mCountDownHelper: CountDownButtonHelper? = null private var mCountDownHelper: CountDownButtonHelper? = null
private var wolHistory: MutableMap<String, String> = mutableMapOf() private var wolHistory: MutableMap<String, String> = mutableMapOf()
override fun viewBindingInflate( override fun viewBindingInflate(
inflater: LayoutInflater, inflater: LayoutInflater,
container: ViewGroup, container: ViewGroup,
): FragmentClientWolSendBinding { ): FragmentClientWolSendBinding {
return FragmentClientWolSendBinding.inflate(inflater, container, false) return FragmentClientWolSendBinding.inflate(inflater, container, false)
} }
override fun initTitle(): TitleBar? { override fun initTitle(): TitleBar? {
return super.initTitle()!!.setImmersive(false).setTitle(R.string.api_wol) return super.initTitle()!!.setImmersive(false).setTitle(R.string.api_wol)
} }
/** /**
* 初始化控件 * 初始化控件
*/ */
override fun initViews() { override fun initViews() {
//发送按钮增加倒计时,避免重复点击 //发送按钮增加倒计时,避免重复点击
mCountDownHelper = CountDownButtonHelper(binding!!.btnSubmit, SettingUtils.requestTimeout) mCountDownHelper = CountDownButtonHelper(binding!!.btnSubmit, SettingUtils.requestTimeout)
mCountDownHelper!!.setOnCountDownListener(object : mCountDownHelper!!.setOnCountDownListener(object :
CountDownButtonHelper.OnCountDownListener { CountDownButtonHelper.OnCountDownListener {
override fun onCountDown(time: Int) { override fun onCountDown(time: Int) {
binding!!.btnSubmit.text = String.format(getString(R.string.seconds_n), time) binding!!.btnSubmit.text = String.format(getString(R.string.seconds_n), time)
} }
override fun onFinished() { override fun onFinished() {
binding!!.btnSubmit.text = getString(R.string.send) binding!!.btnSubmit.text = getString(R.string.send)
} }
}) })
//取出历史记录 //取出历史记录
val history = HttpServerUtils.wolHistory val history = HttpServerUtils.wolHistory
if (!TextUtils.isEmpty(history)) { if (!TextUtils.isEmpty(history)) {
wolHistory = wolHistory =
Gson().fromJson(history, object : TypeToken<MutableMap<String, String>>() {}.type) Gson().fromJson(history, object : TypeToken<MutableMap<String, String>>() {}.type)
} }
} }
override fun initListeners() { override fun initListeners() {
binding!!.btnServerHistory.setOnClickListener(this) binding!!.btnServerHistory.setOnClickListener(this)
binding!!.btnSubmit.setOnClickListener(this) binding!!.btnSubmit.setOnClickListener(this)
} }
@SingleClick @SingleClick
override fun onClick(v: View) { override fun onClick(v: View) {
when (v.id) { when (v.id) {
R.id.btn_server_history -> { R.id.btn_server_history -> {
if (wolHistory.isEmpty()) { if (wolHistory.isEmpty()) {
XToastUtils.warning(getString(R.string.no_server_history)) XToastUtils.warning(getString(R.string.no_server_history))
return return
} }
Log.d(TAG, "wolHistory = $wolHistory") Log.d(TAG, "wolHistory = $wolHistory")
MaterialDialog.Builder(requireContext()) MaterialDialog.Builder(requireContext())
.title(R.string.server_history) .title(R.string.server_history)
.items(wolHistory.keys) .items(wolHistory.keys)
.itemsCallbackSingleChoice(0) { _: MaterialDialog?, _: View?, _: Int, text: CharSequence -> .itemsCallbackSingleChoice(0) { _: MaterialDialog?, _: View?, _: Int, text: CharSequence ->
//XToastUtils.info("$which: $text") //XToastUtils.info("$which: $text")
binding!!.etMac.setText(text) binding!!.etMac.setText(text)
binding!!.etIp.setText(wolHistory[text]) binding!!.etIp.setText(wolHistory[text])
true // allow selection true // allow selection
} }
.positiveText(R.string.select) .positiveText(R.string.select)
.negativeText(R.string.cancel) .negativeText(R.string.cancel)
.neutralText(R.string.clear_history) .neutralText(R.string.clear_history)
.neutralColor(ResUtils.getColors(R.color.red)) .neutralColor(ResUtils.getColors(R.color.red))
.onNeutral { _: MaterialDialog?, _: DialogAction? -> .onNeutral { _: MaterialDialog?, _: DialogAction? ->
wolHistory.clear() wolHistory.clear()
HttpServerUtils.wolHistory = "" HttpServerUtils.wolHistory = ""
} }
.show() .show()
} }
R.id.btn_submit -> { R.id.btn_submit -> {
val requestUrl: String = HttpServerUtils.serverAddress + "/wol/send" val requestUrl: String = HttpServerUtils.serverAddress + "/wol/send"
Log.i(TAG, "requestUrl:$requestUrl") Log.i(TAG, "requestUrl:$requestUrl")
val msgMap: MutableMap<String, Any> = mutableMapOf() val msgMap: MutableMap<String, Any> = mutableMapOf()
val timestamp = System.currentTimeMillis() val timestamp = System.currentTimeMillis()
msgMap["timestamp"] = timestamp msgMap["timestamp"] = timestamp
val clientSignKey = HttpServerUtils.clientSignKey val clientSignKey = HttpServerUtils.clientSignKey
if (!TextUtils.isEmpty(clientSignKey)) { if (!TextUtils.isEmpty(clientSignKey)) {
msgMap["sign"] = msgMap["sign"] =
HttpServerUtils.calcSign(timestamp.toString(), clientSignKey.toString()) HttpServerUtils.calcSign(timestamp.toString(), clientSignKey)
} }
val mac = binding!!.etMac.text.toString() val mac = binding!!.etMac.text.toString()
val macRegex = getString(R.string.mac_regex).toRegex() val macRegex = getString(R.string.mac_regex).toRegex()
if (!macRegex.matches(mac)) { if (!macRegex.matches(mac)) {
XToastUtils.error(ResUtils.getString(R.string.mac_error)) XToastUtils.error(ResUtils.getString(R.string.mac_error))
return return
} }
val ip = binding!!.etIp.text.toString() val ip = binding!!.etIp.text.toString()
val ipRegex = getString(R.string.ip_regex).toRegex() val ipRegex = getString(R.string.ip_regex).toRegex()
if (!TextUtils.isEmpty(ip) && !ipRegex.matches(ip)) { if (!TextUtils.isEmpty(ip) && !ipRegex.matches(ip)) {
XToastUtils.error(ResUtils.getString(R.string.ip_error)) XToastUtils.error(ResUtils.getString(R.string.ip_error))
return return
} }
val port = binding!!.etPort.text.toString() val port = binding!!.etPort.text.toString()
val portRegex = getString(R.string.wol_port_regex).toRegex() val portRegex = getString(R.string.wol_port_regex).toRegex()
if (!TextUtils.isEmpty(port) && !portRegex.matches(port)) { if (!TextUtils.isEmpty(port) && !portRegex.matches(port)) {
XToastUtils.error(ResUtils.getString(R.string.wol_port_error)) XToastUtils.error(ResUtils.getString(R.string.wol_port_error))
return return
} }
val dataMap: MutableMap<String, Any> = mutableMapOf() val dataMap: MutableMap<String, Any> = mutableMapOf()
dataMap["ip"] = ip dataMap["ip"] = ip
dataMap["mac"] = mac dataMap["mac"] = mac
dataMap["port"] = port dataMap["port"] = port
msgMap["data"] = dataMap msgMap["data"] = dataMap
var requestMsg: String = Gson().toJson(msgMap) var requestMsg: String = Gson().toJson(msgMap)
Log.i(TAG, "requestMsg:$requestMsg") Log.i(TAG, "requestMsg:$requestMsg")
val postRequest = XHttp.post(requestUrl) val postRequest = XHttp.post(requestUrl)
.keepJson(true) .keepJson(true)
.timeOut((SettingUtils.requestTimeout * 1000).toLong()) //超时时间10s .timeOut((SettingUtils.requestTimeout * 1000).toLong()) //超时时间10s
.cacheMode(CacheMode.NO_CACHE) .cacheMode(CacheMode.NO_CACHE)
.timeStamp(true) .timeStamp(true)
when (HttpServerUtils.clientSafetyMeasures) { when (HttpServerUtils.clientSafetyMeasures) {
2 -> { 2 -> {
val publicKey = RSACrypt.getPublicKey(HttpServerUtils.clientSignKey.toString()) val publicKey = RSACrypt.getPublicKey(HttpServerUtils.clientSignKey)
try { try {
requestMsg = Base64.encode(requestMsg.toByteArray()) requestMsg = Base64.encode(requestMsg.toByteArray())
requestMsg = RSACrypt.encryptByPublicKey(requestMsg, publicKey) requestMsg = RSACrypt.encryptByPublicKey(requestMsg, publicKey)
Log.i(TAG, "requestMsg: $requestMsg") Log.i(TAG, "requestMsg: $requestMsg")
} catch (e: Exception) { } catch (e: Exception) {
XToastUtils.error(ResUtils.getString(R.string.request_failed) + e.message) XToastUtils.error(ResUtils.getString(R.string.request_failed) + e.message)
e.printStackTrace() e.printStackTrace()
return return
} }
postRequest.upString(requestMsg) postRequest.upString(requestMsg)
} }
3 -> { 3 -> {
try { try {
val sm4Key = ConvertTools.hexStringToByteArray(HttpServerUtils.clientSignKey.toString()) val sm4Key = ConvertTools.hexStringToByteArray(HttpServerUtils.clientSignKey)
//requestMsg = Base64.encode(requestMsg.toByteArray()) //requestMsg = Base64.encode(requestMsg.toByteArray())
val encryptCBC = SM4Crypt.encrypt(requestMsg.toByteArray(), sm4Key) val encryptCBC = SM4Crypt.encrypt(requestMsg.toByteArray(), sm4Key)
requestMsg = ConvertTools.bytes2HexString(encryptCBC) requestMsg = ConvertTools.bytes2HexString(encryptCBC)
Log.i(TAG, "requestMsg: $requestMsg") Log.i(TAG, "requestMsg: $requestMsg")
} catch (e: Exception) { } catch (e: Exception) {
XToastUtils.error(ResUtils.getString(R.string.request_failed) + e.message) XToastUtils.error(ResUtils.getString(R.string.request_failed) + e.message)
e.printStackTrace() e.printStackTrace()
return return
} }
postRequest.upString(requestMsg) postRequest.upString(requestMsg)
} }
else -> { else -> {
postRequest.upJson(requestMsg) postRequest.upJson(requestMsg)
} }
} }
mCountDownHelper?.start() mCountDownHelper?.start()
postRequest.execute(object : SimpleCallBack<String>() { postRequest.execute(object : SimpleCallBack<String>() {
override fun onError(e: ApiException) { override fun onError(e: ApiException) {
XToastUtils.error(e.displayMessage) XToastUtils.error(e.displayMessage)
mCountDownHelper?.finish() mCountDownHelper?.finish()
} }
override fun onSuccess(response: String) { override fun onSuccess(response: String) {
Log.i(TAG, response) Log.i(TAG, response)
try { try {
var json = response var json = response
if (HttpServerUtils.clientSafetyMeasures == 2) { if (HttpServerUtils.clientSafetyMeasures == 2) {
val publicKey = RSACrypt.getPublicKey(HttpServerUtils.clientSignKey.toString()) val publicKey = RSACrypt.getPublicKey(HttpServerUtils.clientSignKey)
json = RSACrypt.decryptByPublicKey(json, publicKey) json = RSACrypt.decryptByPublicKey(json, publicKey)
json = String(Base64.decode(json)) json = String(Base64.decode(json))
} else if (HttpServerUtils.clientSafetyMeasures == 3) { } else if (HttpServerUtils.clientSafetyMeasures == 3) {
val sm4Key = ConvertTools.hexStringToByteArray(HttpServerUtils.clientSignKey.toString()) val sm4Key = ConvertTools.hexStringToByteArray(HttpServerUtils.clientSignKey)
val encryptCBC = ConvertTools.hexStringToByteArray(json) val encryptCBC = ConvertTools.hexStringToByteArray(json)
val decryptCBC = SM4Crypt.decrypt(encryptCBC, sm4Key) val decryptCBC = SM4Crypt.decrypt(encryptCBC, sm4Key)
json = String(decryptCBC) json = String(decryptCBC)
} }
val resp: BaseResponse<String> = Gson().fromJson(json, object : TypeToken<BaseResponse<String>>() {}.type) val resp: BaseResponse<String> = Gson().fromJson(json, object : TypeToken<BaseResponse<String>>() {}.type)
if (resp.code == 200) { if (resp.code == 200) {
XToastUtils.success(ResUtils.getString(R.string.request_succeeded)) XToastUtils.success(ResUtils.getString(R.string.request_succeeded))
//添加到历史记录 //添加到历史记录
wolHistory[mac] = ip wolHistory[mac] = ip
HttpServerUtils.wolHistory = Gson().toJson(wolHistory) HttpServerUtils.wolHistory = Gson().toJson(wolHistory)
} else { } else {
XToastUtils.error(ResUtils.getString(R.string.request_failed) + resp.msg) XToastUtils.error(ResUtils.getString(R.string.request_failed) + resp.msg)
} }
} catch (e: Exception) { } catch (e: Exception) {
e.printStackTrace() e.printStackTrace()
XToastUtils.error(ResUtils.getString(R.string.request_failed) + response) XToastUtils.error(ResUtils.getString(R.string.request_failed) + response)
} }
mCountDownHelper?.finish() mCountDownHelper?.finish()
} }
}) })
} }
else -> {} else -> {}
} }
} }
override fun onDestroyView() { override fun onDestroyView() {
if (mCountDownHelper != null) mCountDownHelper!!.recycle() if (mCountDownHelper != null) mCountDownHelper!!.recycle()
super.onDestroyView() super.onDestroyView()
} }
} }

@ -3,11 +3,8 @@ package com.idormy.sms.forwarder.receiver
import android.content.BroadcastReceiver import android.content.BroadcastReceiver
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.os.Build
import android.util.Log import android.util.Log
import com.idormy.sms.forwarder.activity.SplashActivity import com.idormy.sms.forwarder.activity.SplashActivity
import com.idormy.sms.forwarder.service.ForegroundService
import com.idormy.sms.forwarder.utils.SettingUtils
@Suppress("PropertyName") @Suppress("PropertyName")
class BootReceiver : BroadcastReceiver() { class BootReceiver : BroadcastReceiver() {
@ -17,32 +14,13 @@ class BootReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent?) { override fun onReceive(context: Context, intent: Intent?) {
val receiveAction: String? = intent?.action val receiveAction: String? = intent?.action
Log.d(TAG, "onReceive intent $receiveAction") Log.d(TAG, "onReceive intent $receiveAction")
if (receiveAction == "android.intent.action.BOOT_COMPLETED") { if (receiveAction == "android.intent.action.BOOT_COMPLETED" || receiveAction == "android.intent.action.LOCKED_BOOT_COMPLETED") {
try { try {
val i = Intent(context, SplashActivity::class.java) Log.d(TAG, "强制重启APP一次")
i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) val intent1 = Intent(context, SplashActivity::class.java)
context.startActivity(i) intent1.flags = Intent.FLAG_ACTIVITY_NEW_TASK
context.startActivity(intent1)
//纯客户端模式 android.os.Process.killProcess(android.os.Process.myPid()) //杀掉以前进程
if (SettingUtils.enablePureClientMode) return
//前台服务
val frontServiceIntent = Intent(context, ForegroundService::class.java)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
context.startForegroundService(frontServiceIntent)
} else {
context.startService(frontServiceIntent)
}
/*InitUtils.init(context)
//电池状态监听
val batteryServiceIntent = Intent(context, BatteryService::class.java)
context.startService(batteryServiceIntent)
//后台播放无声音乐
if (SettingUtils.getPlaySilenceMusic()) {
context.startService(Intent(context, MusicService::class.java))
}*/
} catch (e: Exception) { } catch (e: Exception) {
e.printStackTrace() e.printStackTrace()
} }

@ -1,206 +1,175 @@
package com.idormy.sms.forwarder.receiver package com.idormy.sms.forwarder.receiver
import android.Manifest import android.Manifest
import android.content.BroadcastReceiver import android.content.BroadcastReceiver
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.telephony.TelephonyManager import android.telephony.TelephonyManager
import android.text.TextUtils import android.text.TextUtils
import android.util.Log import android.util.Log
import androidx.core.app.ActivityCompat import androidx.core.app.ActivityCompat
import androidx.work.OneTimeWorkRequestBuilder import androidx.work.OneTimeWorkRequestBuilder
import androidx.work.WorkManager import androidx.work.WorkManager
import androidx.work.workDataOf import androidx.work.workDataOf
import com.google.gson.Gson import com.google.gson.Gson
import com.idormy.sms.forwarder.R import com.idormy.sms.forwarder.R
import com.idormy.sms.forwarder.entity.CallInfo import com.idormy.sms.forwarder.entity.CallInfo
import com.idormy.sms.forwarder.entity.MsgInfo import com.idormy.sms.forwarder.entity.MsgInfo
import com.idormy.sms.forwarder.utils.MMKVUtils import com.idormy.sms.forwarder.utils.*
import com.idormy.sms.forwarder.utils.PhoneUtils import com.idormy.sms.forwarder.workers.SendWorker
import com.idormy.sms.forwarder.utils.SettingUtils import com.xuexiang.xutil.resource.ResUtils.getString
import com.idormy.sms.forwarder.utils.Worker import java.util.*
import com.idormy.sms.forwarder.workers.SendWorker
import com.xuexiang.xutil.resource.ResUtils.getString @Suppress("DEPRECATION")
import java.util.* class PhoneStateReceiver : BroadcastReceiver() {
@Suppress("DEPRECATION") override fun onReceive(context: Context, intent: Intent) {
class PhoneStateReceiver : BroadcastReceiver() { try {
//纯客户端模式
override fun onReceive(context: Context, intent: Intent) { if (SettingUtils.enablePureClientMode) return
try {
//纯客户端模式 //总开关
if (SettingUtils.enablePureClientMode) return if (!SettingUtils.enablePhone) return
//总开关 //过滤广播
if (!SettingUtils.enablePhone) return if (TelephonyManager.ACTION_PHONE_STATE_CHANGED != intent.action) return
//过滤广播 //权限判断
if (TelephonyManager.ACTION_PHONE_STATE_CHANGED != intent.action) return if (ActivityCompat.checkSelfPermission(
context, Manifest.permission.READ_PHONE_STATE
//权限判断 ) != PackageManager.PERMISSION_GRANTED
if (ActivityCompat.checkSelfPermission( ) return
context,
Manifest.permission.READ_PHONE_STATE //获取来电号码
) != PackageManager.PERMISSION_GRANTED val number = intent.extras!!.getString(TelephonyManager.EXTRA_INCOMING_NUMBER)
) return val stateStr = intent.extras!!.getString(TelephonyManager.EXTRA_STATE)
var state = 0
//获取来电号码 when (stateStr) {
val number = intent.extras!!.getString(TelephonyManager.EXTRA_INCOMING_NUMBER) TelephonyManager.EXTRA_STATE_IDLE -> state = TelephonyManager.CALL_STATE_IDLE
val stateStr = intent.extras!!.getString(TelephonyManager.EXTRA_STATE) TelephonyManager.EXTRA_STATE_OFFHOOK -> state = TelephonyManager.CALL_STATE_OFFHOOK
var state = 0 TelephonyManager.EXTRA_STATE_RINGING -> state = TelephonyManager.CALL_STATE_RINGING
when (stateStr) { }
TelephonyManager.EXTRA_STATE_IDLE -> state = TelephonyManager.CALL_STATE_IDLE Log.d(TAG, "state=$state, number=$number")
TelephonyManager.EXTRA_STATE_OFFHOOK -> state = TelephonyManager.CALL_STATE_OFFHOOK
TelephonyManager.EXTRA_STATE_RINGING -> state = TelephonyManager.CALL_STATE_RINGING onCallStateChanged(context, state, number)
}
Log.d(TAG, "state=$state, number=$number") } catch (e: Exception) {
Log.e(TAG, e.message.toString())
onCallStateChanged(context, state, number) }
}
} catch (e: Exception) {
Log.e(TAG, e.message.toString()) //Incoming call- goes from IDLE to RINGING when it rings, to OFFHOOK when it's answered, to IDLE when its hung up
} //Outgoing call- goes from IDLE to OFFHOOK when it dials out, to IDLE when hung up
} private fun onCallStateChanged(context: Context, state: Int, number: String?) {
var lastState: Int by SharedPreference("CALL_LAST_STATE", TelephonyManager.CALL_STATE_IDLE)
//Incoming call- goes from IDLE to RINGING when it rings, to OFFHOOK when it's answered, to IDLE when its hung up if (lastState == state || (state == TelephonyManager.CALL_STATE_RINGING && number == null)) {
//Outgoing call- goes from IDLE to OFFHOOK when it dials out, to IDLE when hung up //No change, debounce extras
private fun onCallStateChanged(context: Context, state: Int, number: String?) { return
val lastState = MMKVUtils.getInt("CALL_LAST_STATE", TelephonyManager.CALL_STATE_IDLE) }
if (lastState == state || (state == TelephonyManager.CALL_STATE_RINGING && number == null)) {
//No change, debounce extras lastState = state
return var callIsIncoming: Boolean by SharedPreference("CALL_IS_INCOMING", false)
} var callSavedNumber: String by SharedPreference("CALL_SAVED_NUMBER", "")
when (state) {
MMKVUtils.put("CALL_LAST_STATE", state) TelephonyManager.CALL_STATE_RINGING -> {
when (state) { Log.d(TAG, "来电响铃")
TelephonyManager.CALL_STATE_RINGING -> { callIsIncoming = true
Log.d(TAG, "来电响铃") callSavedNumber = number.toString()
MMKVUtils.put("CALL_IS_INCOMING", true)
//MMKVUtils.put("CALL_START_TIME", Date()) //来电提醒
MMKVUtils.put("CALL_SAVED_NUMBER", number) if (!TextUtils.isEmpty(number) && SettingUtils.enableCallType4) {
val contacts = PhoneUtils.getContactByNumber(number)
//来电提醒 val contactName = if (contacts.isNotEmpty()) contacts[0].name else getString(R.string.unknown_number)
if (!TextUtils.isEmpty(number) && SettingUtils.enableCallType4) {
val contacts = PhoneUtils.getContactByNumber(number) val sb = StringBuilder()
val contactName = sb.append(getString(R.string.linkman)).append(contactName).append("\n")
if (contacts.isNotEmpty()) contacts[0].name else getString(R.string.unknown_number) sb.append(getString(R.string.mandatory_type))
sb.append(getString(R.string.incoming_call))
val sb = StringBuilder()
sb.append(getString(R.string.linkman)).append(contactName).append("\n") val msgInfo = MsgInfo("call", number.toString(), sb.toString(), Date(), "", -1)
sb.append(getString(R.string.mandatory_type)) val request = OneTimeWorkRequestBuilder<SendWorker>().setInputData(
sb.append(getString(R.string.incoming_call)) workDataOf(
Worker.sendMsgInfo to Gson().toJson(msgInfo)
val msgInfo = MsgInfo("call", number.toString(), sb.toString(), Date(), "", -1) )
val request = OneTimeWorkRequestBuilder<SendWorker>() ).build()
.setInputData( WorkManager.getInstance(context).enqueue(request)
workDataOf( }
Worker.sendMsgInfo to Gson().toJson(msgInfo) }
) TelephonyManager.CALL_STATE_OFFHOOK ->
) //Transition of ringing->offhook are pickups of incoming calls. Nothing done on them
.build() callIsIncoming = when {
WorkManager.getInstance(context).enqueue(request) lastState != TelephonyManager.CALL_STATE_RINGING -> {
} Log.d(TAG, "去电接通")
} false
TelephonyManager.CALL_STATE_OFFHOOK -> }
//Transition of ringing->offhook are pickups of incoming calls. Nothing done on them else -> {
when { Log.d(TAG, "来电接通")
lastState != TelephonyManager.CALL_STATE_RINGING -> { true
Log.d(TAG, "去电接通") }
MMKVUtils.put("CALL_IS_INCOMING", false) }
//MMKVUtils.put("CALL_START_TIME", Date()) TelephonyManager.CALL_STATE_IDLE ->
} //Went to idle- this is the end of a call. What type depends on previous state(s)
else -> { when {
Log.d(TAG, "来电接通") lastState == TelephonyManager.CALL_STATE_RINGING -> {
MMKVUtils.put("CALL_IS_INCOMING", true) Log.d(TAG, "来电未接")
//MMKVUtils.put("CALL_START_TIME", Date()) sendReceiveCallMsg(context, 3, callSavedNumber)
} }
} callIsIncoming -> {
TelephonyManager.CALL_STATE_IDLE -> Log.d(TAG, "来电挂机")
//Went to idle- this is the end of a call. What type depends on previous state(s) sendReceiveCallMsg(context, 1, callSavedNumber)
when { }
lastState == TelephonyManager.CALL_STATE_RINGING -> { else -> {
Log.d(TAG, "来电未接") Log.d(TAG, "去电挂机")
sendReceiveCallMsg( sendReceiveCallMsg(context, 2, callSavedNumber)
context, }
3, }
MMKVUtils.getString("CALL_SAVED_NUMBER", null) }
) }
}
MMKVUtils.getBoolean("CALL_IS_INCOMING", false) -> { private fun sendReceiveCallMsg(context: Context, callType: Int, phoneNumber: String?) {
Log.d(TAG, "来电挂机") //必须休眠才能获取来电记录,否则可能获取到上一次通话的
sendReceiveCallMsg( Thread.sleep(500)
context, //获取后一条通话记录
1, Log.d(TAG, "callType = $callType, phoneNumber = $phoneNumber")
MMKVUtils.getString("CALL_SAVED_NUMBER", null) val callInfo: CallInfo? = PhoneUtils.getLastCallInfo(callType, phoneNumber)
) Log.d(TAG, "callInfo = $callInfo")
} if (callInfo?.number == null) return
else -> {
Log.d(TAG, "去电挂机") //判断是否开启该类型转发
sendReceiveCallMsg( if ((callInfo.type == 1 && !SettingUtils.enableCallType1) || (callInfo.type == 2 && !SettingUtils.enableCallType2) || (callInfo.type == 3 && !SettingUtils.enableCallType3)) {
context, Log.w(TAG, "未开启该类型转发type=" + callInfo.type)
2, return
MMKVUtils.getString("CALL_SAVED_NUMBER", null) }
)
} //卡槽id-1=获取失败、0=卡槽1、1=卡槽2
} val simSlot = callInfo.simId
} //获取卡槽信息
} val simInfo = when (simSlot) {
0 -> "SIM1_" + SettingUtils.extraSim1
private fun sendReceiveCallMsg(context: Context, callType: Int, phoneNumber: String?) { 1 -> "SIM2_" + SettingUtils.extraSim2
//必须休眠才能获取来电记录,否则可能获取到上一次通话的 else -> ""
Thread.sleep(500) }
//获取后一条通话记录
Log.d(TAG, "callType = $callType, phoneNumber = $phoneNumber") //获取联系人姓名
val callInfo: CallInfo? = PhoneUtils.getLastCallInfo(callType, phoneNumber) if (TextUtils.isEmpty(callInfo.name)) {
Log.d(TAG, "callInfo = $callInfo") val contacts = PhoneUtils.getContactByNumber(phoneNumber)
if (callInfo?.number == null) return callInfo.name = if (contacts.isNotEmpty()) contacts[0].name else getString(R.string.unknown_number)
}
//判断是否开启该类型转发
if ((callInfo.type == 1 && !SettingUtils.enableCallType1) val msgInfo = MsgInfo(
|| (callInfo.type == 2 && !SettingUtils.enableCallType2) "call", callInfo.number, PhoneUtils.getCallMsg(callInfo), Date(), simInfo, simSlot
|| (callInfo.type == 3 && !SettingUtils.enableCallType3) )
) { val request = OneTimeWorkRequestBuilder<SendWorker>().setInputData(
Log.w(TAG, "未开启该类型转发type=" + callInfo.type) workDataOf(
return Worker.sendMsgInfo to Gson().toJson(msgInfo)
} )
).build()
//卡槽id-1=获取失败、0=卡槽1、1=卡槽2 WorkManager.getInstance(context).enqueue(request)
val simSlot = callInfo.simId
//获取卡槽信息 }
val simInfo = when (simSlot) {
0 -> "SIM1_" + SettingUtils.extraSim1 companion object {
1 -> "SIM2_" + SettingUtils.extraSim2 private const val TAG = "PhoneStateReceiver"
else -> "" }
}
//获取联系人姓名
if (TextUtils.isEmpty(callInfo.name)) {
val contacts = PhoneUtils.getContactByNumber(phoneNumber)
callInfo.name =
if (contacts.isNotEmpty()) contacts[0].name else getString(R.string.unknown_number)
}
val msgInfo = MsgInfo(
"call",
callInfo.number,
PhoneUtils.getCallMsg(callInfo),
Date(),
simInfo,
simSlot
)
val request = OneTimeWorkRequestBuilder<SendWorker>()
.setInputData(
workDataOf(
Worker.sendMsgInfo to Gson().toJson(msgInfo)
)
)
.build()
WorkManager.getInstance(context).enqueue(request)
}
companion object {
private const val TAG = "PhoneStateReceiver"
}
} }

@ -1,35 +1,35 @@
package com.idormy.sms.forwarder.server.component package com.idormy.sms.forwarder.server.component
import android.content.Context import android.content.Context
import com.idormy.sms.forwarder.utils.HttpServerUtils import com.idormy.sms.forwarder.utils.HttpServerUtils
import com.xuexiang.xrouter.utils.TextUtils import com.xuexiang.xrouter.utils.TextUtils
import com.yanzhenjie.andserver.annotation.Config import com.yanzhenjie.andserver.annotation.Config
import com.yanzhenjie.andserver.framework.config.WebConfig import com.yanzhenjie.andserver.framework.config.WebConfig
import com.yanzhenjie.andserver.framework.website.AssetsWebsite import com.yanzhenjie.andserver.framework.website.AssetsWebsite
import com.yanzhenjie.andserver.framework.website.StorageWebsite import com.yanzhenjie.andserver.framework.website.StorageWebsite
@Config @Config
class AppConfig : WebConfig { class AppConfig : WebConfig {
override fun onConfig(context: Context, delegate: WebConfig.Delegate) { override fun onConfig(context: Context, delegate: WebConfig.Delegate) {
val serverWebPath = HttpServerUtils.serverWebPath val serverWebPath = HttpServerUtils.serverWebPath
if (!TextUtils.isEmpty(serverWebPath)) { if (!TextUtils.isEmpty(serverWebPath)) {
// 增加一个位于/sdcard/Download/目录下的网站 // 增加一个位于/sdcard/Download/目录下的网站
delegate.addWebsite(StorageWebsite(serverWebPath.toString())) delegate.addWebsite(StorageWebsite(serverWebPath))
} else { } else {
// 增加一个位于assets的web目录的网站 // 增加一个位于assets的web目录的网站
delegate.addWebsite(AssetsWebsite(context, "/web/")) delegate.addWebsite(AssetsWebsite(context, "/web/"))
} }
/*delegate.setMultipart( /*delegate.setMultipart(
Multipart.newBuilder() Multipart.newBuilder()
.allFileMaxSize(1024 * 1024 * 20) // 单个请求所有文件总大小 .allFileMaxSize(1024 * 1024 * 20) // 单个请求所有文件总大小
.fileMaxSize(1024 * 1024 * 5) // 单个请求每个文件大小 .fileMaxSize(1024 * 1024 * 5) // 单个请求每个文件大小
.maxInMemorySize(1024 * 20) // 内存缓存大小 .maxInMemorySize(1024 * 20) // 内存缓存大小
.uploadTempDir(context.cacheDir) // 上传文件保存目录 .uploadTempDir(context.cacheDir) // 上传文件保存目录
.build() .build()
)*/ )*/
} }
} }

@ -1,56 +1,56 @@
package com.idormy.sms.forwarder.server.component package com.idormy.sms.forwarder.server.component
import android.util.Log import android.util.Log
import com.idormy.sms.forwarder.utils.Base64 import com.idormy.sms.forwarder.utils.Base64
import com.idormy.sms.forwarder.utils.HttpServerUtils import com.idormy.sms.forwarder.utils.HttpServerUtils
import com.idormy.sms.forwarder.utils.RSACrypt import com.idormy.sms.forwarder.utils.RSACrypt
import com.idormy.sms.forwarder.utils.SM4Crypt import com.idormy.sms.forwarder.utils.SM4Crypt
import com.xuexiang.xutil.data.ConvertTools import com.xuexiang.xutil.data.ConvertTools
import com.yanzhenjie.andserver.annotation.Resolver import com.yanzhenjie.andserver.annotation.Resolver
import com.yanzhenjie.andserver.error.HttpException import com.yanzhenjie.andserver.error.HttpException
import com.yanzhenjie.andserver.framework.ExceptionResolver import com.yanzhenjie.andserver.framework.ExceptionResolver
import com.yanzhenjie.andserver.framework.body.JsonBody import com.yanzhenjie.andserver.framework.body.JsonBody
import com.yanzhenjie.andserver.framework.body.StringBody import com.yanzhenjie.andserver.framework.body.StringBody
import com.yanzhenjie.andserver.http.HttpRequest import com.yanzhenjie.andserver.http.HttpRequest
import com.yanzhenjie.andserver.http.HttpResponse import com.yanzhenjie.andserver.http.HttpResponse
import com.yanzhenjie.andserver.http.StatusCode import com.yanzhenjie.andserver.http.StatusCode
@Suppress("PrivatePropertyName") @Suppress("PrivatePropertyName")
@Resolver @Resolver
class AppExceptionResolver : ExceptionResolver { class AppExceptionResolver : ExceptionResolver {
private val TAG: String = "AppExceptionResolver" private val TAG: String = "AppExceptionResolver"
override fun onResolve(request: HttpRequest, response: HttpResponse, e: Throwable) { override fun onResolve(request: HttpRequest, response: HttpResponse, e: Throwable) {
e.printStackTrace() e.printStackTrace()
if (e is HttpException) { if (e is HttpException) {
//response.status = e.statusCode //response.status = e.statusCode
//异常捕获返回 http 200 //异常捕获返回 http 200
response.status = StatusCode.SC_OK response.status = StatusCode.SC_OK
} else { } else {
response.status = StatusCode.SC_INTERNAL_SERVER_ERROR response.status = StatusCode.SC_INTERNAL_SERVER_ERROR
} }
//返回统一结构报文 //返回统一结构报文
var resp = HttpServerUtils.response(e.message.toString()) var resp = HttpServerUtils.response(e.message.toString())
Log.d(TAG, "resp: $resp") Log.d(TAG, "resp: $resp")
when (HttpServerUtils.safetyMeasures) { when (HttpServerUtils.safetyMeasures) {
2 -> { 2 -> {
val privateKey = RSACrypt.getPrivateKey(HttpServerUtils.serverPrivateKey.toString()) val privateKey = RSACrypt.getPrivateKey(HttpServerUtils.serverPrivateKey)
resp = Base64.encode(resp.toByteArray()) resp = Base64.encode(resp.toByteArray())
resp = RSACrypt.encryptByPrivateKey(resp, privateKey) resp = RSACrypt.encryptByPrivateKey(resp, privateKey)
response.setBody(StringBody(resp)) response.setBody(StringBody(resp))
} }
3 -> { 3 -> {
val sm4Key = ConvertTools.hexStringToByteArray(HttpServerUtils.serverSm4Key.toString()) val sm4Key = ConvertTools.hexStringToByteArray(HttpServerUtils.serverSm4Key)
//response = Base64.encode(response.toByteArray()) //response = Base64.encode(response.toByteArray())
val encryptCBC = SM4Crypt.encrypt(resp.toByteArray(), sm4Key) val encryptCBC = SM4Crypt.encrypt(resp.toByteArray(), sm4Key)
response.setBody(StringBody(ConvertTools.bytes2HexString(encryptCBC))) response.setBody(StringBody(ConvertTools.bytes2HexString(encryptCBC)))
} }
else -> { else -> {
response.setBody(JsonBody(resp)) response.setBody(JsonBody(resp))
} }
} }
} }
} }

@ -1,102 +1,102 @@
package com.idormy.sms.forwarder.server.component package com.idormy.sms.forwarder.server.component
import android.util.Log import android.util.Log
import com.google.gson.GsonBuilder import com.google.gson.GsonBuilder
import com.idormy.sms.forwarder.server.model.BaseRequest import com.idormy.sms.forwarder.server.model.BaseRequest
import com.idormy.sms.forwarder.utils.Base64 import com.idormy.sms.forwarder.utils.Base64
import com.idormy.sms.forwarder.utils.HttpServerUtils import com.idormy.sms.forwarder.utils.HttpServerUtils
import com.idormy.sms.forwarder.utils.RSACrypt import com.idormy.sms.forwarder.utils.RSACrypt
import com.idormy.sms.forwarder.utils.SM4Crypt import com.idormy.sms.forwarder.utils.SM4Crypt
import com.xuexiang.xrouter.utils.TextUtils import com.xuexiang.xrouter.utils.TextUtils
import com.xuexiang.xutil.data.ConvertTools import com.xuexiang.xutil.data.ConvertTools
import com.yanzhenjie.andserver.annotation.Converter import com.yanzhenjie.andserver.annotation.Converter
import com.yanzhenjie.andserver.error.HttpException import com.yanzhenjie.andserver.error.HttpException
import com.yanzhenjie.andserver.framework.MessageConverter import com.yanzhenjie.andserver.framework.MessageConverter
import com.yanzhenjie.andserver.framework.body.JsonBody import com.yanzhenjie.andserver.framework.body.JsonBody
import com.yanzhenjie.andserver.framework.body.StringBody import com.yanzhenjie.andserver.framework.body.StringBody
import com.yanzhenjie.andserver.http.ResponseBody import com.yanzhenjie.andserver.http.ResponseBody
import com.yanzhenjie.andserver.util.IOUtils import com.yanzhenjie.andserver.util.IOUtils
import com.yanzhenjie.andserver.util.MediaType import com.yanzhenjie.andserver.util.MediaType
import java.io.IOException import java.io.IOException
import java.io.InputStream import java.io.InputStream
import java.lang.reflect.Type import java.lang.reflect.Type
import java.nio.charset.Charset import java.nio.charset.Charset
@Suppress("PrivatePropertyName") @Suppress("PrivatePropertyName")
@Converter @Converter
class AppMessageConverter : MessageConverter { class AppMessageConverter : MessageConverter {
private val TAG: String = "AppMessageConverter" private val TAG: String = "AppMessageConverter"
override fun convert(output: Any?, mediaType: MediaType?): ResponseBody { override fun convert(output: Any?, mediaType: MediaType?): ResponseBody {
//返回统一结构报文 //返回统一结构报文
var response = HttpServerUtils.response(output) var response = HttpServerUtils.response(output)
Log.d(TAG, "response: $response") Log.d(TAG, "response: $response")
return when (HttpServerUtils.safetyMeasures) { return when (HttpServerUtils.safetyMeasures) {
2 -> { 2 -> {
val privateKey = RSACrypt.getPrivateKey(HttpServerUtils.serverPrivateKey.toString()) val privateKey = RSACrypt.getPrivateKey(HttpServerUtils.serverPrivateKey)
response = Base64.encode(response.toByteArray()) response = Base64.encode(response.toByteArray())
response = RSACrypt.encryptByPrivateKey(response, privateKey) response = RSACrypt.encryptByPrivateKey(response, privateKey)
StringBody(response) StringBody(response)
} }
3 -> { 3 -> {
val sm4Key = ConvertTools.hexStringToByteArray(HttpServerUtils.serverSm4Key.toString()) val sm4Key = ConvertTools.hexStringToByteArray(HttpServerUtils.serverSm4Key)
//response = Base64.encode(response.toByteArray()) //response = Base64.encode(response.toByteArray())
val encryptCBC = SM4Crypt.encrypt(response.toByteArray(), sm4Key) val encryptCBC = SM4Crypt.encrypt(response.toByteArray(), sm4Key)
StringBody(ConvertTools.bytes2HexString(encryptCBC)) StringBody(ConvertTools.bytes2HexString(encryptCBC))
} }
else -> JsonBody(response) else -> JsonBody(response)
} }
} }
@Throws(IOException::class) @Throws(IOException::class)
override fun <T : Any?> convert(stream: InputStream, mediaType: MediaType?, type: Type?): T? { override fun <T : Any?> convert(stream: InputStream, mediaType: MediaType?, type: Type?): T? {
val charset: Charset? = mediaType?.charset val charset: Charset? = mediaType?.charset
Log.d(TAG, "Charset: $charset") Log.d(TAG, "Charset: $charset")
var json = if (charset == null) IOUtils.toString(stream) else IOUtils.toString(stream, charset) var json = if (charset == null) IOUtils.toString(stream) else IOUtils.toString(stream, charset)
Log.d(TAG, "Json: $json") Log.d(TAG, "Json: $json")
if (HttpServerUtils.safetyMeasures == 2) { if (HttpServerUtils.safetyMeasures == 2) {
if (TextUtils.isEmpty(HttpServerUtils.serverPrivateKey)) { if (TextUtils.isEmpty(HttpServerUtils.serverPrivateKey)) {
Log.e(TAG, "RSA解密失败: 私钥为空") Log.e(TAG, "RSA解密失败: 私钥为空")
throw HttpException(500, "服务端未配置私钥") throw HttpException(500, "服务端未配置私钥")
} }
val privateKey = RSACrypt.getPrivateKey(HttpServerUtils.serverPrivateKey.toString()) val privateKey = RSACrypt.getPrivateKey(HttpServerUtils.serverPrivateKey)
json = RSACrypt.decryptByPrivateKey(json, privateKey) json = RSACrypt.decryptByPrivateKey(json, privateKey)
json = String(Base64.decode(json)) json = String(Base64.decode(json))
Log.d(TAG, "Json: $json") Log.d(TAG, "Json: $json")
} else if (HttpServerUtils.safetyMeasures == 3) { } else if (HttpServerUtils.safetyMeasures == 3) {
if (TextUtils.isEmpty(HttpServerUtils.serverSm4Key)) { if (TextUtils.isEmpty(HttpServerUtils.serverSm4Key)) {
Log.e(TAG, "SM4解密失败: SM4密钥为空") Log.e(TAG, "SM4解密失败: SM4密钥为空")
throw HttpException(500, "服务端未配置SM4密钥") throw HttpException(500, "服务端未配置SM4密钥")
} }
val sm4Key = ConvertTools.hexStringToByteArray(HttpServerUtils.serverSm4Key.toString()) val sm4Key = ConvertTools.hexStringToByteArray(HttpServerUtils.serverSm4Key)
val encryptCBC = ConvertTools.hexStringToByteArray(json) val encryptCBC = ConvertTools.hexStringToByteArray(json)
val decryptCBC = SM4Crypt.decrypt(encryptCBC, sm4Key) val decryptCBC = SM4Crypt.decrypt(encryptCBC, sm4Key)
//json = String(Base64.decode(decryptCBC.toString())) //json = String(Base64.decode(decryptCBC.toString()))
json = String(decryptCBC) json = String(decryptCBC)
Log.d(TAG, "Json: $json") Log.d(TAG, "Json: $json")
} }
//修改接口数据中的null、“”为默认值 //修改接口数据中的null、“”为默认值
val builder = GsonBuilder() val builder = GsonBuilder()
builder.registerTypeAdapter(Int::class.java, IntegerDefaultAdapter()) builder.registerTypeAdapter(Int::class.java, IntegerDefaultAdapter())
builder.registerTypeAdapter(String::class.java, StringDefaultAdapter()) builder.registerTypeAdapter(String::class.java, StringDefaultAdapter())
val gson = builder.create() val gson = builder.create()
val t: T? = gson.fromJson(json, type) val t: T? = gson.fromJson(json, type)
Log.d(TAG, "Bean: $t") Log.d(TAG, "Bean: $t")
//校验时间戳时间误差不能超过1小时&& 签名 //校验时间戳时间误差不能超过1小时&& 签名
if (HttpServerUtils.safetyMeasures == 1) { if (HttpServerUtils.safetyMeasures == 1) {
HttpServerUtils.checkSign(t as BaseRequest<*>) HttpServerUtils.checkSign(t as BaseRequest<*>)
} }
return t return t
} }
} }

@ -1,49 +1,49 @@
package com.idormy.sms.forwarder.server.controller package com.idormy.sms.forwarder.server.controller
import android.util.Log import android.util.Log
import com.idormy.sms.forwarder.App import com.idormy.sms.forwarder.App
import com.idormy.sms.forwarder.server.model.BaseRequest import com.idormy.sms.forwarder.server.model.BaseRequest
import com.idormy.sms.forwarder.server.model.ConfigData import com.idormy.sms.forwarder.server.model.ConfigData
import com.idormy.sms.forwarder.utils.HttpServerUtils import com.idormy.sms.forwarder.utils.HttpServerUtils
import com.idormy.sms.forwarder.utils.PhoneUtils import com.idormy.sms.forwarder.utils.PhoneUtils
import com.idormy.sms.forwarder.utils.SettingUtils import com.idormy.sms.forwarder.utils.SettingUtils
import com.xuexiang.xutil.app.AppUtils import com.xuexiang.xutil.app.AppUtils
import com.yanzhenjie.andserver.annotation.* import com.yanzhenjie.andserver.annotation.*
@Suppress("PrivatePropertyName") @Suppress("PrivatePropertyName")
@RestController @RestController
@RequestMapping(path = ["/config"]) @RequestMapping(path = ["/config"])
class ConfigController { class ConfigController {
private val TAG: String = CloneController::class.java.simpleName private val TAG: String = CloneController::class.java.simpleName
//远程查配置 //远程查配置
@CrossOrigin(methods = [RequestMethod.POST]) @CrossOrigin(methods = [RequestMethod.POST])
@PostMapping("/query") @PostMapping("/query")
fun test(@RequestBody bean: BaseRequest<*>): ConfigData { fun test(@RequestBody bean: BaseRequest<*>): ConfigData {
Log.d(TAG, bean.data.toString()) Log.d(TAG, bean.data.toString())
//获取卡槽信息 //获取卡槽信息
if (App.SimInfoList.isEmpty()) { if (App.SimInfoList.isEmpty()) {
App.SimInfoList = PhoneUtils.getSimMultiInfo() App.SimInfoList = PhoneUtils.getSimMultiInfo()
} }
Log.d(TAG, App.SimInfoList.toString()) Log.d(TAG, App.SimInfoList.toString())
return ConfigData( return ConfigData(
HttpServerUtils.enableApiClone, HttpServerUtils.enableApiClone,
HttpServerUtils.enableApiSmsSend, HttpServerUtils.enableApiSmsSend,
HttpServerUtils.enableApiSmsQuery, HttpServerUtils.enableApiSmsQuery,
HttpServerUtils.enableApiCallQuery, HttpServerUtils.enableApiCallQuery,
HttpServerUtils.enableApiContactQuery, HttpServerUtils.enableApiContactQuery,
HttpServerUtils.enableApiBatteryQuery, HttpServerUtils.enableApiBatteryQuery,
HttpServerUtils.enableApiWol, HttpServerUtils.enableApiWol,
SettingUtils.extraDeviceMark.toString(), SettingUtils.extraDeviceMark,
SettingUtils.extraSim1.toString(), SettingUtils.extraSim1,
SettingUtils.extraSim2.toString(), SettingUtils.extraSim2,
App.SimInfoList, App.SimInfoList,
AppUtils.getAppVersionCode(), AppUtils.getAppVersionCode(),
AppUtils.getAppVersionName(), AppUtils.getAppVersionName(),
) )
} }
} }

@ -1,32 +1,15 @@
package com.idormy.sms.forwarder.utils package com.idormy.sms.forwarder.utils
object CactusSave { object CactusSave {
//Cactus存活时间 //Cactus存活时间
var timer: Long var timer: Long by SharedPreference(CACTUS_TIMER, 0L)
get() = MMKVUtils.getLong(CACTUS_TIMER, 0L)
set(timer) { //Cactus上次存活时间
MMKVUtils.put(CACTUS_TIMER, timer) var lastTimer: Long by SharedPreference(CACTUS_LAST_TIMER, 0L)
}
//Cactus运行时间
//Cactus上次存活时间 var date: String by SharedPreference(SP_EXTRA_DEVICE_MARK, "0000-01-01 00:00:00")
var lastTimer: Long
get() = MMKVUtils.getLong(CACTUS_LAST_TIMER, 0L) //Cactus结束时间
set(timer) { var endDate: String by SharedPreference(CACTUS_DATE, "0000-01-01 00:00:00")
MMKVUtils.put(CACTUS_LAST_TIMER, timer)
}
//Cactus运行时间
var date: String?
get() = MMKVUtils.getString(SP_EXTRA_DEVICE_MARK, "0000-01-01 00:00:00")
set(extraDeviceMark) {
MMKVUtils.put(SP_EXTRA_DEVICE_MARK, extraDeviceMark)
}
//Cactus结束时间
var endDate: String?
get() = MMKVUtils.getString(CACTUS_DATE, "0000-01-01 00:00:00")
set(extraDeviceMark) {
MMKVUtils.put(CACTUS_END_DATE, extraDeviceMark)
}
} }

@ -1,8 +1,11 @@
package com.idormy.sms.forwarder.utils package com.idormy.sms.forwarder.utils
import android.content.Context import android.content.Context
import android.os.Parcelable import android.content.SharedPreferences
import com.tencent.mmkv.MMKV import android.os.Build
import java.io.*
import kotlin.properties.ReadWriteProperty
import kotlin.reflect.KProperty
/** /**
* 转发历史工具类 * 转发历史工具类
@ -11,264 +14,102 @@ import com.tencent.mmkv.MMKV
* @since 2022年5月9日 * @since 2022年5月9日
*/ */
@Suppress("PropertyName", "UNCHECKED_CAST", "MemberVisibilityCanBePrivate", "unused") @Suppress("PropertyName", "UNCHECKED_CAST", "MemberVisibilityCanBePrivate", "unused")
class HistoryUtils private constructor() { class HistoryUtils<T>(private val name: String, private val default: T) : ReadWriteProperty<Any?, T> {
companion object { companion object {
private var sMMKV: MMKV? = null lateinit var preference: SharedPreferences
/**
* 初始化
*
* @param context
*/
fun init(context: Context) { fun init(context: Context) {
MMKV.initialize(context.applicationContext) preference = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
sMMKV = MMKV.mmkvWithID("History") val directBootContext: Context = context.createDeviceProtectedStorageContext()
} directBootContext.getSharedPreferences(context.packageName + ".history", Context.MODE_PRIVATE)
} else {
fun getsMMKV(): MMKV? { context.getSharedPreferences(context.packageName + ".history", Context.MODE_PRIVATE)
if (sMMKV == null) {
sMMKV = MMKV.mmkvWithID("History")
}
return sMMKV
}
//=======================================键值保存==================================================//
/**
* 保存键值
*
* @param key
* @param value
* @return
*/
fun put(key: String?, value: Any?): Boolean {
when (value) {
is Int -> {
return getsMMKV()!!.encode(key, (value as Int?)!!)
}
is Float -> {
return getsMMKV()!!.encode(key, (value as Float?)!!)
}
is String -> {
return getsMMKV()!!.encode(key, value as String?)
}
is Boolean -> {
return getsMMKV()!!.encode(key, (value as Boolean?)!!)
}
is Long -> {
return getsMMKV()!!.encode(key, (value as Long?)!!)
}
is Double -> {
return getsMMKV()!!.encode(key, (value as Double?)!!)
}
is Parcelable -> {
return getsMMKV()!!.encode(key, value as Parcelable?)
}
is ByteArray -> {
return getsMMKV()!!.encode(key, value as ByteArray?)
}
is Set<*> -> {
return getsMMKV()!!.encode(key, value as Set<String?>?)
}
else -> return false
}
}
//=======================================键值获取==================================================//
/**
* 获取键值
*
* @param key
* @param defaultValue
* @return
*/
operator fun get(key: String?, defaultValue: Any?): Any? {
when (defaultValue) {
is Int -> {
return getsMMKV()!!
.decodeInt(key, (defaultValue as Int?)!!)
}
is Float -> {
return getsMMKV()!!
.decodeFloat(key, (defaultValue as Float?)!!)
}
is String -> {
return getsMMKV()!!.decodeString(key, defaultValue as String?)
}
is Boolean -> {
return getsMMKV()!!
.decodeBool(key, (defaultValue as Boolean?)!!)
}
is Long -> {
return getsMMKV()!!
.decodeLong(key, (defaultValue as Long?)!!)
}
is Double -> {
return getsMMKV()!!
.decodeDouble(key, (defaultValue as Double?)!!)
}
is ByteArray -> {
return getsMMKV()!!.decodeBytes(key)
}
is Set<*> -> {
return getsMMKV()!!.decodeStringSet(key, defaultValue as Set<String?>?)
}
else -> return null
}
}
/**
* 根据key获取boolean值
*
* @param key
* @param defValue
* @return
*/
fun getBoolean(key: String?, defValue: Boolean): Boolean {
try {
return getsMMKV()!!.getBoolean(key, defValue)
} catch (e: Exception) {
e.printStackTrace()
} }
return defValue
} }
/** //删除全部数据
* 根据key获取long值 fun clearPreference() = preference.edit().clear().apply()
*
* @param key
* @param defValue
* @return
*/
fun getLong(key: String?, defValue: Long): Long {
try {
return getsMMKV()!!.getLong(key, defValue)
} catch (e: Exception) {
e.printStackTrace()
}
return defValue
}
/**
* 根据key获取float值
*
* @param key
* @param defValue
* @return
*/
fun getFloat(key: String?, defValue: Float): Float {
try {
return getsMMKV()!!.getFloat(key, defValue)
} catch (e: Exception) {
e.printStackTrace()
}
return defValue
}
/**
* 根据key获取String值
*
* @param key
* @param defValue
* @return
*/
fun getString(key: String?, defValue: String?): String? {
try {
return getsMMKV()!!.getString(key, defValue)
} catch (e: Exception) {
e.printStackTrace()
}
return defValue
}
/** //根据key删除存储数据
* 根据key获取int值 fun clearPreference(key: String) = preference.edit().remove(key).commit()
* }
* @param key
* @param defValue
* @return
*/
fun getInt(key: String?, defValue: Int): Int {
try {
return getsMMKV()!!.getInt(key, defValue)
} catch (e: Exception) {
e.printStackTrace()
}
return defValue
}
/**
* 根据key获取double值
*
* @param key
* @param defValue
* @return
*/
fun getDouble(key: String?, defValue: Double): Double {
try {
return getsMMKV()!!.decodeDouble(key, defValue)
} catch (e: Exception) {
e.printStackTrace()
}
return defValue
}
/**
* 获取对象
*
* @param key
* @param tClass 类型
* @param <T>
* @return
</T> */
fun <T : Parcelable?> getObject(key: String?, tClass: Class<T>?): T? {
return getsMMKV()!!.decodeParcelable(key, tClass)
}
/** override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
* 获取对象 return putPreference(name, value)
* }
* @param key
* @param tClass 类型
* @param <T>
* @return
</T> */
fun <T : Parcelable?> getObject(key: String?, tClass: Class<T>?, defValue: T): T? {
try {
return getsMMKV()!!.decodeParcelable(key, tClass, defValue)
} catch (e: Exception) {
e.printStackTrace()
}
return defValue
}
/** override fun getValue(thisRef: Any?, property: KProperty<*>): T {
* 判断键值对是否存在 return getPreference(name, default)
* }
* @param key
* @return 键值对是否存在
*/
fun containsKey(key: String?): Boolean {
return getsMMKV()!!.containsKey(key)
}
/** /**
* 清除指定键值对 * 查找数据 返回给调用方法一个具体的对象
* * 如果查找不到类型就采用反序列化方法来返回类型
* @param key * default是默认对象 以防止会返回空对象的异常
*/ * 即如果name没有查找到value 就返回默认的序列化对象然后经过反序列化返回
fun remove(key: String?) { */
getsMMKV()!!.remove(key).apply() private fun getPreference(name: String, default: T): T = with(preference) {
val res: Any = when (default) {
is Long -> getLong(name, default)
is String -> this.getString(name, default)!!
is Int -> getInt(name, default)
is Boolean -> getBoolean(name, default)
is Float -> getFloat(name, default)
//else -> throw IllegalArgumentException("This type can be get from Preferences")
else -> deSerialization(getString(name, serialize(default)).toString())
} }
return res as T
}
/** private fun putPreference(name: String, value: T) = with(preference.edit()) {
* 清除所有键值对 when (value) {
*/ is Long -> putLong(name, value)
fun clear() { is Int -> putInt(name, value)
getsMMKV()!!.clearAll() is String -> putString(name, value)
} is Boolean -> putBoolean(name, value)
is Float -> putFloat(name, value)
//else -> throw IllegalArgumentException("This type can be saved into Preferences")
else -> putString(name, serialize(value))
}.apply()
}
/**
* 序列化对象
* @throws IOException
*/
@Throws(IOException::class)
private fun <T> serialize(obj: T): String {
val byteArrayOutputStream = ByteArrayOutputStream()
val objectOutputStream = ObjectOutputStream(
byteArrayOutputStream
)
objectOutputStream.writeObject(obj)
var serStr = byteArrayOutputStream.toString("ISO-8859-1")
serStr = java.net.URLEncoder.encode(serStr, "UTF-8")
objectOutputStream.close()
byteArrayOutputStream.close()
return serStr
} }
init { /**
throw UnsupportedOperationException("u can't instantiate me...") * 反序列化对象
* @param str
* @throws IOException
* @throws ClassNotFoundException
*/
@Throws(IOException::class, ClassNotFoundException::class)
private fun <T> deSerialization(str: String): T {
val redStr = java.net.URLDecoder.decode(str, "UTF-8")
val byteArrayInputStream = ByteArrayInputStream(
redStr.toByteArray(charset("ISO-8859-1"))
)
val objectInputStream = ObjectInputStream(
byteArrayInputStream
)
val obj = objectInputStream.readObject() as T
objectInputStream.close()
byteArrayInputStream.close()
return obj
} }
} }

@ -1,366 +1,261 @@
package com.idormy.sms.forwarder.utils package com.idormy.sms.forwarder.utils
import android.text.TextUtils import android.text.TextUtils
import android.util.Base64 import android.util.Base64
import android.util.Log import android.util.Log
import com.google.gson.Gson import com.google.gson.Gson
import com.idormy.sms.forwarder.R import com.idormy.sms.forwarder.R
import com.idormy.sms.forwarder.core.Core import com.idormy.sms.forwarder.core.Core
import com.idormy.sms.forwarder.entity.CloneInfo import com.idormy.sms.forwarder.entity.CloneInfo
import com.idormy.sms.forwarder.server.model.BaseRequest import com.idormy.sms.forwarder.server.model.BaseRequest
import com.xuexiang.xui.utils.ResUtils.getString import com.xuexiang.xui.utils.ResUtils.getString
import com.xuexiang.xutil.app.AppUtils import com.xuexiang.xutil.app.AppUtils
import com.yanzhenjie.andserver.error.HttpException import com.yanzhenjie.andserver.error.HttpException
import java.net.URLEncoder import java.net.URLEncoder
import java.nio.charset.StandardCharsets import java.nio.charset.StandardCharsets
import javax.crypto.Mac import javax.crypto.Mac
import javax.crypto.spec.SecretKeySpec import javax.crypto.spec.SecretKeySpec
/** /**
* HttpServer工具类 * HttpServer工具类
*/ */
class HttpServerUtils private constructor() { class HttpServerUtils private constructor() {
companion object { companion object {
//是否启用HttpServer开机自启 //是否启用HttpServer开机自启
@JvmStatic var enableServerAutorun: Boolean by SharedPreference(SP_ENABLE_SERVER_AUTORUN, true)
var enableServerAutorun: Boolean
get() = MMKVUtils.getBoolean(SP_ENABLE_SERVER_AUTORUN, false) //服务端签名密钥
set(enableServerAutorun) { var serverSignKey: String by SharedPreference(SP_SERVER_SIGN_KEY, "")
MMKVUtils.put(SP_ENABLE_SERVER_AUTORUN, enableServerAutorun)
} //服务端安全设置
var safetyMeasures: Int by SharedPreference(SP_SERVER_SAFETY_MEASURES, if (TextUtils.isEmpty(serverSignKey)) 0 else 1)
//服务端安全设置
@JvmStatic //服务端SM4密钥
var safetyMeasures: Int var serverSm4Key: String by SharedPreference(SP_SERVER_SM4_KEY, "")
get() = MMKVUtils.getInt(SP_SERVER_SAFETY_MEASURES, if (TextUtils.isEmpty(serverSignKey)) 0 else 1)
set(safetyMeasures) { //服务端RSA公钥
MMKVUtils.put(SP_SERVER_SAFETY_MEASURES, safetyMeasures) var serverPublicKey: String by SharedPreference(SP_SERVER_PUBLIC_KEY, "")
}
//服务端RSA私钥
//服务端SM4密钥 var serverPrivateKey: String by SharedPreference(SP_SERVER_PRIVATE_KEY, "")
@JvmStatic
var serverSm4Key: String? //时间容差
get() = MMKVUtils.getString(SP_SERVER_SM4_KEY, "") var timeTolerance: Int by SharedPreference(SP_SERVER_TIME_TOLERANCE, 600)
set(serverSm4Key) {
MMKVUtils.put(SP_SERVER_SM4_KEY, serverSm4Key) //自定义web客户端目录
} var serverWebPath: String by SharedPreference(SP_SERVER_WEB_PATH, "")
//服务端RSA公钥 //服务地址
@JvmStatic var serverAddress: String by SharedPreference(SP_SERVER_ADDRESS, "")
var serverPublicKey: String?
get() = MMKVUtils.getString(SP_SERVER_PUBLIC_KEY, "") //服务地址历史记录
set(serverPublicKey) { var serverHistory: String by SharedPreference(SP_SERVER_HISTORY, "")
MMKVUtils.put(SP_SERVER_PUBLIC_KEY, serverPublicKey)
} //服务端配置
var serverConfig: String by SharedPreference(SP_SERVER_CONFIG, "")
//服务端RSA私钥
@JvmStatic //客户端签名密钥/RSA公钥
var serverPrivateKey: String? var clientSignKey: String by SharedPreference(SP_CLIENT_SIGN_KEY, "")
get() = MMKVUtils.getString(SP_SERVER_PRIVATE_KEY, "")
set(serverPrivateKey) { //服务端安全设置
MMKVUtils.put(SP_SERVER_PRIVATE_KEY, serverPrivateKey) var clientSafetyMeasures: Int by SharedPreference(SP_CLIENT_SAFETY_MEASURES, if (TextUtils.isEmpty(clientSignKey)) 0 else 1)
}
//是否启用一键克隆
//服务端签名密钥 var enableApiClone: Boolean by SharedPreference(SP_ENABLE_API_CLONE, true)
@JvmStatic
var serverSignKey: String? //是否启用远程发短信
get() = MMKVUtils.getString(SP_SERVER_SIGN_KEY, "") var enableApiSmsSend: Boolean by SharedPreference(SP_ENABLE_API_SMS_SEND, true)
set(serverSignKey) {
MMKVUtils.put(SP_SERVER_SIGN_KEY, serverSignKey) //是否启用远程查短信
} var enableApiSmsQuery: Boolean by SharedPreference(SP_ENABLE_API_SMS_QUERY, true)
//时间容差 //是否启用远程查通话
@JvmStatic var enableApiCallQuery: Boolean by SharedPreference(SP_ENABLE_API_CALL_QUERY, true)
var timeTolerance: Int
get() = MMKVUtils.getInt(SP_SERVER_TIME_TOLERANCE, 600) //是否启用远程查话簿
set(timeTolerance) { var enableApiContactQuery: Boolean by SharedPreference(SP_ENABLE_API_CONTACT_QUERY, true)
MMKVUtils.put(SP_SERVER_TIME_TOLERANCE, timeTolerance)
} //是否启用远程查电量
var enableApiBatteryQuery: Boolean by SharedPreference(SP_ENABLE_API_BATTERY_QUERY, true)
//自定义web客户端目录
@JvmStatic //是否启用远程WOL
var serverWebPath: String? var enableApiWol: Boolean by SharedPreference(SP_ENABLE_API_WOL, true)
get() = MMKVUtils.getString(SP_SERVER_WEB_PATH, "")
set(serverWebPath) { //WOL历史记录
MMKVUtils.put(SP_SERVER_WEB_PATH, serverWebPath) var wolHistory: String by SharedPreference(SP_WOL_HISTORY, "")
}
//计算签名
//服务地址 fun calcSign(timestamp: String, signSecret: String): String {
@JvmStatic val stringToSign = "$timestamp\n" + signSecret
var serverAddress: String? val mac = Mac.getInstance("HmacSHA256")
get() = MMKVUtils.getString(SP_SERVER_ADDRESS, "") mac.init(SecretKeySpec(signSecret.toByteArray(StandardCharsets.UTF_8), "HmacSHA256"))
set(clientSignKey) { val signData = mac.doFinal(stringToSign.toByteArray(StandardCharsets.UTF_8))
MMKVUtils.put(SP_SERVER_ADDRESS, clientSignKey) return URLEncoder.encode(String(Base64.encode(signData, Base64.NO_WRAP)), "UTF-8")
} }
//服务地址历史记录 //校验签名
@JvmStatic @Throws(HttpException::class)
var serverHistory: String? fun checkSign(req: BaseRequest<*>) {
get() = MMKVUtils.getString(SP_SERVER_HISTORY, "") val signSecret = serverSignKey
set(serverHistory) { if (TextUtils.isEmpty(signSecret)) return
MMKVUtils.put(SP_SERVER_HISTORY, serverHistory)
} if (TextUtils.isEmpty(req.sign)) throw HttpException(500, getString(R.string.sign_required))
if (req.timestamp == 0L) throw HttpException(500, getString(R.string.timestamp_required))
//服务端配置
@JvmStatic val timestamp = System.currentTimeMillis()
var serverConfig: String? val diffTime = kotlin.math.abs(timestamp - req.timestamp)
get() = MMKVUtils.getString(SP_SERVER_CONFIG, "") val tolerance = timeTolerance * 1000L
set(serverConfig) { if (diffTime > tolerance) {
MMKVUtils.put(SP_SERVER_CONFIG, serverConfig) throw HttpException(500, String.format(getString(R.string.timestamp_verify_failed), timestamp, timeTolerance, diffTime))
} }
//服务端安全设置 val sign = calcSign(req.timestamp.toString(), signSecret)
@JvmStatic if (sign != req.sign) {
var clientSafetyMeasures: Int Log.e("calcSign", sign)
get() = MMKVUtils.getInt(SP_CLIENT_SAFETY_MEASURES, if (TextUtils.isEmpty(clientSignKey)) 0 else 1) Log.e("reqSign", req.sign.toString())
set(clientSafetyMeasures) { throw HttpException(500, getString(R.string.sign_verify_failed))
MMKVUtils.put(SP_CLIENT_SAFETY_MEASURES, clientSafetyMeasures) }
} }
//客户端签名密钥/RSA公钥 //判断版本是否一致
@JvmStatic @Throws(HttpException::class)
var clientSignKey: String? fun compareVersion(cloneInfo: CloneInfo) {
get() = MMKVUtils.getString(SP_CLIENT_SIGN_KEY, "") val versionCodeRequest = cloneInfo.versionCode
set(clientSignKey) { if (versionCodeRequest == 0) throw HttpException(500, getString(R.string.version_code_required))
MMKVUtils.put(SP_CLIENT_SIGN_KEY, clientSignKey) val versionCodeLocal = AppUtils.getAppVersionCode().toString().substring(1)
} if (!versionCodeRequest.toString().endsWith(versionCodeLocal)) throw HttpException(500, getString(R.string.inconsistent_version))
}
//是否启用一键克隆
@JvmStatic //导出设置
var enableApiClone: Boolean fun exportSettings(): CloneInfo {
get() = MMKVUtils.getBoolean(SP_ENABLE_API_CLONE, false) val cloneInfo = CloneInfo()
set(enableApiClone) { cloneInfo.versionCode = AppUtils.getAppVersionCode()
MMKVUtils.put(SP_ENABLE_API_CLONE, enableApiClone) cloneInfo.versionName = AppUtils.getAppVersionName()
} cloneInfo.enableSms = SettingUtils.enableSms
cloneInfo.enablePhone = SettingUtils.enablePhone
//是否启用远程发短信 cloneInfo.callType1 = SettingUtils.enableCallType1
@JvmStatic cloneInfo.callType2 = SettingUtils.enableCallType2
var enableApiSmsSend: Boolean cloneInfo.callType3 = SettingUtils.enableCallType3
get() = MMKVUtils.getBoolean(SP_ENABLE_API_SMS_SEND, false) cloneInfo.enableAppNotify = SettingUtils.enableAppNotify
set(enableApiSendSms) { cloneInfo.cancelAppNotify = SettingUtils.enableCancelAppNotify
MMKVUtils.put(SP_ENABLE_API_SMS_SEND, enableApiSendSms) cloneInfo.enableNotUserPresent = SettingUtils.enableNotUserPresent
} cloneInfo.enableLoadAppList = SettingUtils.enableLoadAppList
cloneInfo.enableLoadUserAppList = SettingUtils.enableLoadUserAppList
//是否启用远程查短信 cloneInfo.enableLoadSystemAppList = SettingUtils.enableLoadSystemAppList
@JvmStatic cloneInfo.duplicateMessagesLimits = SettingUtils.duplicateMessagesLimits
var enableApiSmsQuery: Boolean cloneInfo.enableBatteryReceiver = SettingUtils.enableBatteryReceiver
get() = MMKVUtils.getBoolean(SP_ENABLE_API_SMS_QUERY, false) cloneInfo.batteryLevelMin = SettingUtils.batteryLevelMin
set(enableApiQuerySms) { cloneInfo.batteryLevelMax = SettingUtils.batteryLevelMax
MMKVUtils.put(SP_ENABLE_API_SMS_QUERY, enableApiQuerySms) cloneInfo.batteryLevelOnce = SettingUtils.batteryLevelOnce
} cloneInfo.enableBatteryCron = SettingUtils.enableBatteryCron
cloneInfo.batteryCronStartTime = SettingUtils.batteryCronStartTime
//是否启用远程查通话 cloneInfo.batteryCronInterval = SettingUtils.batteryCronInterval
@JvmStatic cloneInfo.enableExcludeFromRecents = SettingUtils.enableExcludeFromRecents
var enableApiCallQuery: Boolean cloneInfo.enableCactus = SettingUtils.enableCactus
get() = MMKVUtils.getBoolean(SP_ENABLE_API_CALL_QUERY, false) cloneInfo.enablePlaySilenceMusic = SettingUtils.enablePlaySilenceMusic
set(enableApiQueryCall) { cloneInfo.enableOnePixelActivity = SettingUtils.enableOnePixelActivity
MMKVUtils.put(SP_ENABLE_API_CALL_QUERY, enableApiQueryCall) cloneInfo.requestRetryTimes = SettingUtils.requestRetryTimes
} cloneInfo.requestDelayTime = SettingUtils.requestDelayTime
cloneInfo.requestTimeout = SettingUtils.requestTimeout
//是否启用远程查话簿 cloneInfo.notifyContent = SettingUtils.notifyContent
@JvmStatic cloneInfo.enableSmsTemplate = SettingUtils.enableSmsTemplate
var enableApiContactQuery: Boolean cloneInfo.smsTemplate = SettingUtils.smsTemplate
get() = MMKVUtils.getBoolean(SP_ENABLE_API_CONTACT_QUERY, false) cloneInfo.enableHelpTip = SettingUtils.enableHelpTip
set(enableApiQueryLinkman) { cloneInfo.enablePureClientMode = SettingUtils.enablePureClientMode
MMKVUtils.put(SP_ENABLE_API_CONTACT_QUERY, enableApiQueryLinkman) cloneInfo.senderList = Core.sender.all
} cloneInfo.ruleList = Core.rule.all
cloneInfo.frpcList = Core.frpc.all
//是否启用远程查电量
@JvmStatic return cloneInfo
var enableApiBatteryQuery: Boolean }
get() = MMKVUtils.getBoolean(SP_ENABLE_API_BATTERY_QUERY, false)
set(enableApiQueryBattery) { //还原设置
MMKVUtils.put(SP_ENABLE_API_BATTERY_QUERY, enableApiQueryBattery) fun restoreSettings(cloneInfo: CloneInfo): Boolean {
} return try {
//应用配置
//是否启用远程WOL SettingUtils.enableSms = cloneInfo.enableSms
@JvmStatic SettingUtils.enablePhone = cloneInfo.enablePhone
var enableApiWol: Boolean SettingUtils.enableCallType1 = cloneInfo.callType1
get() = MMKVUtils.getBoolean(SP_ENABLE_API_WOL, false) SettingUtils.enableCallType2 = cloneInfo.callType2
set(enableApiWol) { SettingUtils.enableCallType3 = cloneInfo.callType3
MMKVUtils.put(SP_ENABLE_API_WOL, enableApiWol) SettingUtils.enableAppNotify = cloneInfo.enableAppNotify
} SettingUtils.enableCancelAppNotify = cloneInfo.cancelAppNotify
SettingUtils.enableNotUserPresent = cloneInfo.enableNotUserPresent
//WOL历史记录 SettingUtils.enableLoadAppList = cloneInfo.enableLoadAppList
@JvmStatic SettingUtils.enableLoadUserAppList = cloneInfo.enableLoadUserAppList
var wolHistory: String? SettingUtils.enableLoadSystemAppList = cloneInfo.enableLoadSystemAppList
get() = MMKVUtils.getString(SP_WOL_HISTORY, "") SettingUtils.duplicateMessagesLimits = cloneInfo.duplicateMessagesLimits
set(wolHistory) { SettingUtils.enableBatteryReceiver = cloneInfo.enableBatteryReceiver
MMKVUtils.put(SP_WOL_HISTORY, wolHistory) SettingUtils.batteryLevelMin = cloneInfo.batteryLevelMin
} SettingUtils.batteryLevelMax = cloneInfo.batteryLevelMax
SettingUtils.batteryLevelOnce = cloneInfo.batteryLevelOnce
//计算签名 SettingUtils.enableBatteryCron = cloneInfo.enableBatteryCron
fun calcSign(timestamp: String, signSecret: String): String { SettingUtils.batteryCronStartTime = cloneInfo.batteryCronStartTime.toString()
val stringToSign = "$timestamp\n" + signSecret SettingUtils.batteryCronInterval = cloneInfo.batteryCronInterval
val mac = Mac.getInstance("HmacSHA256") SettingUtils.enableExcludeFromRecents = cloneInfo.enableExcludeFromRecents
mac.init(SecretKeySpec(signSecret.toByteArray(StandardCharsets.UTF_8), "HmacSHA256")) SettingUtils.enableCactus = cloneInfo.enableCactus
val signData = mac.doFinal(stringToSign.toByteArray(StandardCharsets.UTF_8)) SettingUtils.enablePlaySilenceMusic = cloneInfo.enablePlaySilenceMusic
return URLEncoder.encode(String(Base64.encode(signData, Base64.NO_WRAP)), "UTF-8") SettingUtils.enableOnePixelActivity = cloneInfo.enableOnePixelActivity
} SettingUtils.requestRetryTimes = cloneInfo.requestRetryTimes
SettingUtils.requestDelayTime = cloneInfo.requestDelayTime
//校验签名 SettingUtils.requestTimeout = cloneInfo.requestTimeout
@Throws(HttpException::class) SettingUtils.notifyContent = cloneInfo.notifyContent.toString()
fun checkSign(req: BaseRequest<*>) { SettingUtils.enableSmsTemplate = cloneInfo.enableSmsTemplate
val signSecret = serverSignKey SettingUtils.smsTemplate = cloneInfo.smsTemplate.toString()
if (TextUtils.isEmpty(signSecret)) return SettingUtils.enableHelpTip = cloneInfo.enableHelpTip
SettingUtils.enablePureClientMode = cloneInfo.enablePureClientMode
if (TextUtils.isEmpty(req.sign)) throw HttpException(500, getString(R.string.sign_required)) //删除发送通道、转发规则、转发日志
if (req.timestamp == 0L) throw HttpException(500, getString(R.string.timestamp_required)) Core.sender.deleteAll()
//发送通道
val timestamp = System.currentTimeMillis() if (!cloneInfo.senderList.isNullOrEmpty()) {
val diffTime = kotlin.math.abs(timestamp - req.timestamp) for (sender in cloneInfo.senderList!!) {
val tolerance = timeTolerance * 1000L Core.sender.insert(sender)
if (diffTime > tolerance) { }
throw HttpException(500, String.format(getString(R.string.timestamp_verify_failed), timestamp, timeTolerance, diffTime)) }
} //转发规则
if (!cloneInfo.ruleList.isNullOrEmpty()) {
val sign = calcSign(req.timestamp.toString(), signSecret.toString()) for (rule in cloneInfo.ruleList!!) {
if (sign != req.sign) { Core.rule.insert(rule)
Log.e("calcSign", sign) }
Log.e("reqSign", req.sign.toString()) }
throw HttpException(500, getString(R.string.sign_verify_failed)) //Frpc配置
} Core.frpc.deleteAll()
} if (!cloneInfo.frpcList.isNullOrEmpty()) {
for (frpc in cloneInfo.frpcList!!) {
//判断版本是否一致 Core.frpc.insert(frpc)
@Throws(HttpException::class) }
fun compareVersion(cloneInfo: CloneInfo) { }
val versionCodeRequest = cloneInfo.versionCode true
if (versionCodeRequest == 0) throw HttpException(500, getString(R.string.version_code_required)) } catch (e: Exception) {
val versionCodeLocal = AppUtils.getAppVersionCode().toString().substring(1) e.printStackTrace()
if (!versionCodeRequest.toString().endsWith(versionCodeLocal)) throw HttpException(500, getString(R.string.inconsistent_version)) throw HttpException(500, e.message)
} //false
}
//导出设置 }
fun exportSettings(): CloneInfo {
val cloneInfo = CloneInfo() //返回统一结构报文
cloneInfo.versionCode = AppUtils.getAppVersionCode() fun response(output: Any?): String {
cloneInfo.versionName = AppUtils.getAppVersionName() val resp: MutableMap<String, Any> = mutableMapOf()
cloneInfo.enableSms = SettingUtils.enableSms val timestamp = System.currentTimeMillis()
cloneInfo.enablePhone = SettingUtils.enablePhone resp["timestamp"] = timestamp
cloneInfo.callType1 = SettingUtils.enableCallType1 if (output is String && output != "success") {
cloneInfo.callType2 = SettingUtils.enableCallType2 resp["code"] = HTTP_FAILURE_CODE
cloneInfo.callType3 = SettingUtils.enableCallType3 resp["msg"] = output
cloneInfo.enableAppNotify = SettingUtils.enableAppNotify } else {
cloneInfo.cancelAppNotify = SettingUtils.enableCancelAppNotify resp["code"] = HTTP_SUCCESS_CODE
cloneInfo.enableNotUserPresent = SettingUtils.enableNotUserPresent resp["msg"] = "success"
cloneInfo.enableLoadAppList = SettingUtils.enableLoadAppList if (output != null) {
cloneInfo.enableLoadUserAppList = SettingUtils.enableLoadUserAppList resp["data"] = output
cloneInfo.enableLoadSystemAppList = SettingUtils.enableLoadSystemAppList }
cloneInfo.duplicateMessagesLimits = SettingUtils.duplicateMessagesLimits if (safetyMeasures == 1) {
cloneInfo.enableBatteryReceiver = SettingUtils.enableBatteryReceiver resp["sign"] = calcSign(timestamp.toString(), serverSignKey)
cloneInfo.batteryLevelMin = SettingUtils.batteryLevelMin }
cloneInfo.batteryLevelMax = SettingUtils.batteryLevelMax }
cloneInfo.batteryLevelOnce = SettingUtils.batteryLevelOnce
cloneInfo.enableBatteryCron = SettingUtils.enableBatteryCron return Gson().toJson(resp)
cloneInfo.batteryCronStartTime = SettingUtils.batteryCronStartTime }
cloneInfo.batteryCronInterval = SettingUtils.batteryCronInterval }
cloneInfo.enableExcludeFromRecents = SettingUtils.enableExcludeFromRecents
cloneInfo.enableCactus = SettingUtils.enableCactus
cloneInfo.enablePlaySilenceMusic = SettingUtils.enablePlaySilenceMusic
cloneInfo.enableOnePixelActivity = SettingUtils.enableOnePixelActivity
cloneInfo.requestRetryTimes = SettingUtils.requestRetryTimes
cloneInfo.requestDelayTime = SettingUtils.requestDelayTime
cloneInfo.requestTimeout = SettingUtils.requestTimeout
cloneInfo.notifyContent = SettingUtils.notifyContent
cloneInfo.enableSmsTemplate = SettingUtils.enableSmsTemplate
cloneInfo.smsTemplate = SettingUtils.smsTemplate
cloneInfo.enableHelpTip = SettingUtils.enableHelpTip
cloneInfo.enablePureClientMode = SettingUtils.enablePureClientMode
cloneInfo.senderList = Core.sender.all
cloneInfo.ruleList = Core.rule.all
cloneInfo.frpcList = Core.frpc.all
return cloneInfo
}
//还原设置
fun restoreSettings(cloneInfo: CloneInfo): Boolean {
return try {
//应用配置
SettingUtils.enableSms = cloneInfo.enableSms
SettingUtils.enablePhone = cloneInfo.enablePhone
SettingUtils.enableCallType1 = cloneInfo.callType1
SettingUtils.enableCallType2 = cloneInfo.callType2
SettingUtils.enableCallType3 = cloneInfo.callType3
SettingUtils.enableAppNotify = cloneInfo.enableAppNotify
SettingUtils.enableCancelAppNotify = cloneInfo.cancelAppNotify
SettingUtils.enableNotUserPresent = cloneInfo.enableNotUserPresent
SettingUtils.enableLoadAppList = cloneInfo.enableLoadAppList
SettingUtils.enableLoadUserAppList = cloneInfo.enableLoadUserAppList
SettingUtils.enableLoadSystemAppList = cloneInfo.enableLoadSystemAppList
SettingUtils.duplicateMessagesLimits = cloneInfo.duplicateMessagesLimits
SettingUtils.enableBatteryReceiver = cloneInfo.enableBatteryReceiver
SettingUtils.batteryLevelMin = cloneInfo.batteryLevelMin
SettingUtils.batteryLevelMax = cloneInfo.batteryLevelMax
SettingUtils.batteryLevelOnce = cloneInfo.batteryLevelOnce
SettingUtils.enableBatteryCron = cloneInfo.enableBatteryCron
SettingUtils.batteryCronStartTime = cloneInfo.batteryCronStartTime
SettingUtils.batteryCronInterval = cloneInfo.batteryCronInterval
SettingUtils.enableExcludeFromRecents = cloneInfo.enableExcludeFromRecents
SettingUtils.enableCactus = cloneInfo.enableCactus
SettingUtils.enablePlaySilenceMusic = cloneInfo.enablePlaySilenceMusic
SettingUtils.enableOnePixelActivity = cloneInfo.enableOnePixelActivity
SettingUtils.requestRetryTimes = cloneInfo.requestRetryTimes
SettingUtils.requestDelayTime = cloneInfo.requestDelayTime
SettingUtils.requestTimeout = cloneInfo.requestTimeout
SettingUtils.notifyContent = cloneInfo.notifyContent
SettingUtils.enableSmsTemplate = cloneInfo.enableSmsTemplate
SettingUtils.smsTemplate = cloneInfo.smsTemplate
SettingUtils.enableHelpTip = cloneInfo.enableHelpTip
SettingUtils.enablePureClientMode = cloneInfo.enablePureClientMode
//删除发送通道、转发规则、转发日志
Core.sender.deleteAll()
//发送通道
if (!cloneInfo.senderList.isNullOrEmpty()) {
for (sender in cloneInfo.senderList!!) {
Core.sender.insert(sender)
}
}
//转发规则
if (!cloneInfo.ruleList.isNullOrEmpty()) {
for (rule in cloneInfo.ruleList!!) {
Core.rule.insert(rule)
}
}
//Frpc配置
Core.frpc.deleteAll()
if (!cloneInfo.frpcList.isNullOrEmpty()) {
for (frpc in cloneInfo.frpcList!!) {
Core.frpc.insert(frpc)
}
}
true
} catch (e: Exception) {
e.printStackTrace()
throw HttpException(500, e.message)
//false
}
}
//返回统一结构报文
fun response(output: Any?): String {
val resp: MutableMap<String, Any> = mutableMapOf()
val timestamp = System.currentTimeMillis()
resp["timestamp"] = timestamp
if (output is String && output != "success") {
resp["code"] = HTTP_FAILURE_CODE
resp["msg"] = output
} else {
resp["code"] = HTTP_SUCCESS_CODE
resp["msg"] = "success"
if (output != null) {
resp["data"] = output
}
if (safetyMeasures == 1) {
resp["sign"] = calcSign(timestamp.toString(), serverSignKey.toString())
}
}
return Gson().toJson(resp)
}
}
} }

@ -1,332 +0,0 @@
package com.idormy.sms.forwarder.utils
import android.content.Context
import android.os.Parcelable
import android.util.Log
import androidx.preference.PreferenceManager
import com.tencent.mmkv.MMKV
/**
* MMKV工具类
*
* @author xuexiang
* @since 2019-07-04 10:20
*/
@Suppress("PropertyName", "UNCHECKED_CAST", "MemberVisibilityCanBePrivate", "unused")
class MMKVUtils private constructor() {
companion object {
private var TAG: String = "MMKVUtils"
private var sMMKV: MMKV? = null
/**
* 初始化
*
* @param context
*/
fun init(context: Context) {
MMKV.initialize(context.applicationContext)
sMMKV = MMKV.defaultMMKV()
}
fun getsMMKV(): MMKV? {
if (sMMKV == null) {
sMMKV = MMKV.defaultMMKV()
}
return sMMKV
}
//=======================================键值保存==================================================//
/**
* 保存键值
*
* @param key
* @param value
* @return
*/
fun put(key: String?, value: Any?): Boolean {
when (value) {
is Int -> {
return getsMMKV()!!.encode(key, (value as Int?)!!)
}
is Float -> {
return getsMMKV()!!.encode(key, (value as Float?)!!)
}
is String -> {
return getsMMKV()!!.encode(key, value as String?)
}
is Boolean -> {
return getsMMKV()!!.encode(key, (value as Boolean?)!!)
}
is Long -> {
return getsMMKV()!!.encode(key, (value as Long?)!!)
}
is Double -> {
return getsMMKV()!!.encode(key, (value as Double?)!!)
}
is Parcelable -> {
return getsMMKV()!!.encode(key, value as Parcelable?)
}
is ByteArray -> {
return getsMMKV()!!.encode(key, value as ByteArray?)
}
is Set<*> -> {
return getsMMKV()!!.encode(key, value as Set<String?>?)
}
else -> return false
}
}
//=======================================键值获取==================================================//
/**
* 获取键值
*
* @param key
* @param defaultValue
* @return
*/
operator fun get(key: String?, defaultValue: Any?): Any? {
when (defaultValue) {
is Int -> {
return getsMMKV()!!
.decodeInt(key, (defaultValue as Int?)!!)
}
is Float -> {
return getsMMKV()!!
.decodeFloat(key, (defaultValue as Float?)!!)
}
is String -> {
return getsMMKV()!!.decodeString(key, defaultValue as String?)
}
is Boolean -> {
return getsMMKV()!!
.decodeBool(key, (defaultValue as Boolean?)!!)
}
is Long -> {
return getsMMKV()!!
.decodeLong(key, (defaultValue as Long?)!!)
}
is Double -> {
return getsMMKV()!!
.decodeDouble(key, (defaultValue as Double?)!!)
}
is ByteArray -> {
return getsMMKV()!!.decodeBytes(key)
}
is Set<*> -> {
return getsMMKV()!!.decodeStringSet(key, defaultValue as Set<String?>?)
}
else -> return null
}
}
/**
* 根据key获取boolean值
*
* @param key
* @param defValue
* @return
*/
fun getBoolean(key: String?, defValue: Boolean): Boolean {
try {
return getsMMKV()!!.getBoolean(key, defValue)
} catch (e: Exception) {
e.printStackTrace()
}
return defValue
}
/**
* 根据key获取long值
*
* @param key
* @param defValue
* @return
*/
fun getLong(key: String?, defValue: Long): Long {
try {
return getsMMKV()!!.getLong(key, defValue)
} catch (e: Exception) {
e.printStackTrace()
}
return defValue
}
/**
* 根据key获取float值
*
* @param key
* @param defValue
* @return
*/
fun getFloat(key: String?, defValue: Float): Float {
try {
return getsMMKV()!!.getFloat(key, defValue)
} catch (e: Exception) {
e.printStackTrace()
}
return defValue
}
/**
* 根据key获取String值
*
* @param key
* @param defValue
* @return
*/
fun getString(key: String?, defValue: String?): String? {
try {
return getsMMKV()!!.getString(key, defValue)
} catch (e: Exception) {
e.printStackTrace()
}
return defValue
}
/**
* 根据key获取int值
*
* @param key
* @param defValue
* @return
*/
fun getInt(key: String?, defValue: Int): Int {
try {
return getsMMKV()!!.getInt(key, defValue)
} catch (e: Exception) {
e.printStackTrace()
}
return defValue
}
/**
* 根据key获取double值
*
* @param key
* @param defValue
* @return
*/
fun getDouble(key: String?, defValue: Double): Double {
try {
return getsMMKV()!!.decodeDouble(key, defValue)
} catch (e: Exception) {
e.printStackTrace()
}
return defValue
}
/**
* 获取对象
*
* @param key
* @param tClass 类型
* @param <T>
* @return
</T> */
fun <T : Parcelable?> getObject(key: String?, tClass: Class<T>?): T? {
return getsMMKV()!!.decodeParcelable(key, tClass)
}
/**
* 获取对象
*
* @param key
* @param tClass 类型
* @param <T>
* @return
</T> */
fun <T : Parcelable?> getObject(key: String?, tClass: Class<T>?, defValue: T): T? {
try {
return getsMMKV()!!.decodeParcelable(key, tClass, defValue)
} catch (e: Exception) {
e.printStackTrace()
}
return defValue
}
/**
* 判断键值对是否存在
*
* @param key
* @return 键值对是否存在
*/
fun containsKey(key: String?): Boolean {
return getsMMKV()!!.containsKey(key)
}
/**
* 清除指定键值对
*
* @param key
*/
fun remove(key: String?) {
getsMMKV()!!.remove(key).apply()
}
/**
* 从SP迁移数据
*/
fun importSharedPreferences(context: Context) {
Log.d(TAG, "从SP迁移数据")
val preferences = PreferenceManager.getDefaultSharedPreferences(context)
val editor = preferences.edit()
getsMMKV()!!.importFromSharedPreferences(preferences)
editor.clear().apply()
Log.d(TAG, "转换旧的SP配置")
loop@ for (key: String in getsMMKV()!!.allKeys()!!) {
when {
key.startsWith("tsms_msg_key_switch_") || key.startsWith("tsms_msg_key_string_enable_") || key.endsWith("battery_level_once") -> {
val newKey = key.replace("tsms_msg_key_switch_", "enable_")
.replace("tsms_msg_key_string_", "enable_")
.replace("enable_enable_", "enable_")
val value = getBoolean(key, false)
Log.d(TAG, String.format("oldKey=%s, newKey=%s, value=%s", key, newKey, value.toString()))
put(newKey, value)
remove(key)
continue@loop
}
key.endsWith("battery_level_alarm") || key.endsWith("battery_level_max") || key.endsWith("battery_level_current") || key.endsWith("battery_status") || key.endsWith("battery_cron_interval") -> {
val newKey = key.replace("tsms_msg_key_switch_", "")
.replace("tsms_msg_key_string_", "")
.replace("alarm", "min")
.replace("tsms_msg_key_", "request_")
val value = getInt(key, 0)
Log.d(TAG, String.format("oldKey=%s, newKey=%s, value=%s", key, newKey, value.toString()))
put(newKey, value)
remove(key)
continue@loop
}
key.startsWith("tsms_msg_key_") -> {
val newKey = key.replace("tsms_msg_key_string_", "")
.replace("add_", "")
.replace("tsms_msg_key_", "request_")
val value = getString(key, "")
Log.d(TAG, String.format("oldKey=%s, newKey=%s, value=%s", key, newKey, value.toString()))
put(newKey, value)
remove(key)
continue@loop
}
}
}
Log.d(TAG, "转换后的数据")
for (key: String in getsMMKV()!!.allKeys()!!) {
when {
key.startsWith("enable_") -> {
Log.d(TAG, String.format("key=%s, value=%s", key, getBoolean(key, false).toString()))
}
key.startsWith("battery_") || key.startsWith("request_") -> {
Log.d(TAG, String.format("key=%s, value=%s", key, getInt(key, 0).toString()))
}
else -> {
Log.d(TAG, String.format("key=%s, value=%s", key, getString(key, "").toString()))
}
}
}
}
}
init {
throw UnsupportedOperationException("u can't instantiate me...")
}
}

@ -7,339 +7,131 @@ class SettingUtils private constructor() {
companion object { companion object {
//是否是第一次启动 //是否是第一次启动
var isFirstOpen: Boolean var isFirstOpen: Boolean by SharedPreference(IS_FIRST_OPEN_KEY, true)
get() = MMKVUtils.getBoolean(IS_FIRST_OPEN_KEY, true)
set(isFirstOpen) {
MMKVUtils.put(IS_FIRST_OPEN_KEY, isFirstOpen)
}
//是否同意隐私政策 //是否同意隐私政策
@JvmStatic var isAgreePrivacy: Boolean by SharedPreference(IS_AGREE_PRIVACY_KEY, false)
var isAgreePrivacy: Boolean
get() = MMKVUtils.getBoolean(IS_AGREE_PRIVACY_KEY, false)
set(isAgreePrivacy) {
MMKVUtils.put(IS_AGREE_PRIVACY_KEY, isAgreePrivacy)
}
//是否转发短信 //是否转发短信
@JvmStatic var enableSms: Boolean by SharedPreference(SP_ENABLE_SMS, false)
var enableSms: Boolean
get() = MMKVUtils.getBoolean(SP_ENABLE_SMS, false)
set(enableSms) {
MMKVUtils.put(SP_ENABLE_SMS, enableSms)
}
//是否转发通话 //是否转发通话
@JvmStatic var enablePhone: Boolean by SharedPreference(SP_ENABLE_PHONE, false)
var enablePhone: Boolean
get() = MMKVUtils.getBoolean(SP_ENABLE_PHONE, false)
set(enablePhone) {
MMKVUtils.put(SP_ENABLE_PHONE, enablePhone)
}
//是否转发通话——已接来电 //是否转发通话——已接来电
@JvmStatic var enableCallType1: Boolean by SharedPreference(SP_ENABLE_CALL_TYPE_1, false)
var enableCallType1: Boolean
get() = MMKVUtils.getBoolean(SP_ENABLE_CALL_TYPE_1, false)
set(enableCallType1) {
MMKVUtils.put(SP_ENABLE_CALL_TYPE_1, enableCallType1)
}
//是否转发通话——本机去电 //是否转发通话——本机去电
@JvmStatic var enableCallType2: Boolean by SharedPreference(SP_ENABLE_CALL_TYPE_2, false)
var enableCallType2: Boolean
get() = MMKVUtils.getBoolean(SP_ENABLE_CALL_TYPE_2, false)
set(enableCallType2) {
MMKVUtils.put(SP_ENABLE_CALL_TYPE_2, enableCallType2)
}
//是否转发通话——未接来电 //是否转发通话——未接来电
@JvmStatic var enableCallType3: Boolean by SharedPreference(SP_ENABLE_CALL_TYPE_3, false)
var enableCallType3: Boolean
get() = MMKVUtils.getBoolean(SP_ENABLE_CALL_TYPE_3, false)
set(enableCallType3) {
MMKVUtils.put(SP_ENABLE_CALL_TYPE_3, enableCallType3)
}
//是否转发通话——来电提醒 //是否转发通话——来电提醒
@JvmStatic var enableCallType4: Boolean by SharedPreference(SP_ENABLE_CALL_TYPE_4, false)
var enableCallType4: Boolean
get() = MMKVUtils.getBoolean(SP_ENABLE_CALL_TYPE_4, false)
set(enableCallType4) {
MMKVUtils.put(SP_ENABLE_CALL_TYPE_4, enableCallType4)
}
//是否转发应用通知 //是否转发应用通知
@JvmStatic var enableAppNotify: Boolean by SharedPreference(SP_ENABLE_APP_NOTIFY, false)
var enableAppNotify: Boolean
get() = MMKVUtils.getBoolean(SP_ENABLE_APP_NOTIFY, false)
set(enableAppNotify) {
MMKVUtils.put(SP_ENABLE_APP_NOTIFY, enableAppNotify)
}
//是否转发应用通知——自动消除通知 //是否转发应用通知——自动消除通知
@JvmStatic var enableCancelAppNotify: Boolean by SharedPreference(SP_ENABLE_CANCEL_APP_NOTIFY, false)
var enableCancelAppNotify: Boolean
get() = MMKVUtils.getBoolean(SP_ENABLE_CANCEL_APP_NOTIFY, false)
set(enableCancelAppNotify) {
MMKVUtils.put(SP_ENABLE_CANCEL_APP_NOTIFY, enableCancelAppNotify)
}
//是否转发应用通知——仅锁屏状态 //是否转发应用通知——仅锁屏状态
@JvmStatic var enableNotUserPresent: Boolean by SharedPreference(SP_ENABLE_NOT_USER_PRESENT, false)
var enableNotUserPresent: Boolean
get() = MMKVUtils.getBoolean(SP_ENABLE_NOT_USER_PRESENT, false)
set(enableNotUserPresent) {
MMKVUtils.put(SP_ENABLE_NOT_USER_PRESENT, enableNotUserPresent)
}
//是否加载应用列表 //是否加载应用列表
@JvmStatic var enableLoadAppList: Boolean by SharedPreference(ENABLE_LOAD_APP_LIST, false)
var enableLoadAppList: Boolean
get() = MMKVUtils.getBoolean(ENABLE_LOAD_APP_LIST, false)
set(enableLoadAppList) {
MMKVUtils.put(ENABLE_LOAD_APP_LIST, enableLoadAppList)
}
//是否加载应用列表——用户应用 //是否加载应用列表——用户应用
@JvmStatic var enableLoadUserAppList: Boolean by SharedPreference(ENABLE_LOAD_USER_APP_LIST, false)
var enableLoadUserAppList: Boolean
get() = MMKVUtils.getBoolean(ENABLE_LOAD_USER_APP_LIST, false)
set(enableLoadUserAppList) {
MMKVUtils.put(ENABLE_LOAD_USER_APP_LIST, enableLoadUserAppList)
}
//是否加载应用列表——系统应用 //是否加载应用列表——系统应用
@JvmStatic var enableLoadSystemAppList: Boolean by SharedPreference(ENABLE_LOAD_SYSTEM_APP_LIST, false)
var enableLoadSystemAppList: Boolean
get() = MMKVUtils.getBoolean(ENABLE_LOAD_SYSTEM_APP_LIST, false)
set(enableLoadSystemAppList) {
MMKVUtils.put(ENABLE_LOAD_SYSTEM_APP_LIST, enableLoadSystemAppList)
}
//过滤多久内重复消息 //过滤多久内重复消息
@JvmStatic var duplicateMessagesLimits: Int by SharedPreference(SP_DUPLICATE_MESSAGES_LIMITS, 0)
var duplicateMessagesLimits: Int
get() = MMKVUtils.getInt(SP_DUPLICATE_MESSAGES_LIMITS, 0)
set(duplicateMessagesLimits) {
MMKVUtils.put(SP_DUPLICATE_MESSAGES_LIMITS, duplicateMessagesLimits)
}
//免打扰(禁用转发)时间段——开始 //免打扰(禁用转发)时间段——开始
@JvmStatic var silentPeriodStart: Int by SharedPreference(SP_SILENT_PERIOD_START, 0)
var silentPeriodStart: Int
get() = MMKVUtils.getInt(SP_SILENT_PERIOD_START, 0)
set(silentPeriodStart) {
MMKVUtils.put(SP_SILENT_PERIOD_START, silentPeriodStart)
}
//免打扰(禁用转发)时间段——结束 //免打扰(禁用转发)时间段——结束
@JvmStatic var silentPeriodEnd: Int by SharedPreference(SP_SILENT_PERIOD_END, 0)
var silentPeriodEnd: Int
get() = MMKVUtils.getInt(SP_SILENT_PERIOD_END, 0)
set(silentPeriodEnd) {
MMKVUtils.put(SP_SILENT_PERIOD_END, silentPeriodEnd)
}
//自动删除N天前的转发记录 //自动删除N天前的转发记录
@JvmStatic var autoCleanLogsDays: Int by SharedPreference(SP_AUTO_CLEAN_LOGS_DAYS, 0)
var autoCleanLogsDays: Int
get() = MMKVUtils.getInt(SP_AUTO_CLEAN_LOGS_DAYS, 0)
set(autoCleanLogsDays) {
MMKVUtils.put(SP_AUTO_CLEAN_LOGS_DAYS, autoCleanLogsDays)
}
//是否监听电池状态变化 //是否监听电池状态变化
@JvmStatic var enableBatteryReceiver: Boolean by SharedPreference(SP_BATTERY_RECEIVER, false)
var enableBatteryReceiver: Boolean
get() = MMKVUtils.getBoolean(SP_BATTERY_RECEIVER, false)
set(enableBatteryReceiver) {
MMKVUtils.put(SP_BATTERY_RECEIVER, enableBatteryReceiver)
}
//电量预警当前状态 //电量预警当前状态
@JvmStatic var batteryStatus: Int by SharedPreference(SP_BATTERY_STATUS, 0)
var batteryStatus: Int
get() = MMKVUtils.getInt(SP_BATTERY_STATUS, 0)
set(batteryStatus) {
MMKVUtils.put(SP_BATTERY_STATUS, batteryStatus)
}
//电量预警当前值 //电量预警当前值
@JvmStatic var batteryLevelCurrent: Int by SharedPreference(SP_BATTERY_LEVEL_CURRENT, 0)
var batteryLevelCurrent: Int
get() = MMKVUtils.getInt(SP_BATTERY_LEVEL_CURRENT, 0)
set(batteryLevelCurrent) {
MMKVUtils.put(SP_BATTERY_LEVEL_CURRENT, batteryLevelCurrent)
}
//电量预警最低值 //电量预警最低值
@JvmStatic var batteryLevelMin: Int by SharedPreference(SP_BATTERY_LEVEL_MIN, 0)
var batteryLevelMin: Int
get() = MMKVUtils.getInt(SP_BATTERY_LEVEL_MIN, 0)
set(batteryLevelMin) {
MMKVUtils.put(SP_BATTERY_LEVEL_MIN, batteryLevelMin)
}
//电量预警最高值 //电量预警最高值
@JvmStatic var batteryLevelMax: Int by SharedPreference(SP_BATTERY_LEVEL_MAX, 100)
var batteryLevelMax: Int
get() = MMKVUtils.getInt(SP_BATTERY_LEVEL_MAX, 100)
set(batteryLevelMax) {
MMKVUtils.put(SP_BATTERY_LEVEL_MAX, batteryLevelMax)
}
//是否持续电量预警 //是否持续电量预警
@JvmStatic var batteryLevelOnce: Boolean by SharedPreference(SP_BATTERY_LEVEL_ONCE, false)
var batteryLevelOnce: Boolean
get() = MMKVUtils.getBoolean(SP_BATTERY_LEVEL_ONCE, false)
set(batteryLevelOnce) {
MMKVUtils.put(SP_BATTERY_LEVEL_ONCE, batteryLevelOnce)
}
//是否定时推送电池状态 //是否定时推送电池状态
@JvmStatic var enableBatteryCron: Boolean by SharedPreference(SP_BATTERY_CRON, false)
var enableBatteryCron: Boolean
get() = MMKVUtils.getBoolean(SP_BATTERY_CRON, false)
set(enableBatteryCron) {
MMKVUtils.put(SP_BATTERY_CRON, enableBatteryCron)
}
//是否定时推送电池状态——开始时间 //是否定时推送电池状态——开始时间
@JvmStatic var batteryCronStartTime: String by SharedPreference(SP_BATTERY_CRON_START_TIME, "00:00")
var batteryCronStartTime: String?
get() = MMKVUtils.getString(SP_BATTERY_CRON_START_TIME, "00:00")
set(batteryCronStartTime) {
MMKVUtils.put(SP_BATTERY_CRON_START_TIME, batteryCronStartTime)
}
//是否定时推送电池状态——间隔时间(分钟) //是否定时推送电池状态——间隔时间(分钟)
@JvmStatic var batteryCronInterval: Int by SharedPreference(SP_BATTERY_CRON_INTERVAL, 60)
var batteryCronInterval: Int
get() = MMKVUtils.getInt(SP_BATTERY_CRON_INTERVAL, 60)
set(batteryCronInterval) {
MMKVUtils.put(SP_BATTERY_CRON_INTERVAL, batteryCronInterval)
}
//是否不在最近任务列表中显示 //是否不在最近任务列表中显示
@JvmStatic var enableExcludeFromRecents: Boolean by SharedPreference(SP_ENABLE_EXCLUDE_FROM_RECENTS, false)
var enableExcludeFromRecents: Boolean
get() = MMKVUtils.getBoolean(SP_ENABLE_EXCLUDE_FROM_RECENTS, false)
set(enableExcludeFromRecents) {
MMKVUtils.put(SP_ENABLE_EXCLUDE_FROM_RECENTS, enableExcludeFromRecents)
}
//是否转发应用通知 //是否转发应用通知
@JvmStatic var enableCactus: Boolean by SharedPreference(SP_ENABLE_CACTUS, false)
var enableCactus: Boolean
get() = MMKVUtils.getBoolean(SP_ENABLE_CACTUS, false)
set(enableAppNotify) {
MMKVUtils.put(SP_ENABLE_CACTUS, enableAppNotify)
}
//是否播放静音音乐 //是否播放静音音乐
@JvmStatic var enablePlaySilenceMusic: Boolean by SharedPreference(SP_ENABLE_PLAY_SILENCE_MUSIC, false)
var enablePlaySilenceMusic: Boolean
get() = MMKVUtils.getBoolean(SP_ENABLE_PLAY_SILENCE_MUSIC, false)
set(enablePlaySilenceMusic) {
MMKVUtils.put(SP_ENABLE_PLAY_SILENCE_MUSIC, enablePlaySilenceMusic)
}
//是否启用1像素 //是否启用1像素
@JvmStatic var enableOnePixelActivity: Boolean by SharedPreference(SP_ENABLE_ONE_PIXEL_ACTIVITY, false)
var enableOnePixelActivity: Boolean
get() = MMKVUtils.getBoolean(SP_ENABLE_ONE_PIXEL_ACTIVITY, false)
set(enableOnePixelActivity) {
MMKVUtils.put(SP_ENABLE_ONE_PIXEL_ACTIVITY, enableOnePixelActivity)
}
//请求接口失败重试次数 //请求接口失败重试次数
@JvmStatic var requestRetryTimes: Int by SharedPreference(SP_REQUEST_RETRY_TIMES, 0)
var requestRetryTimes: Int
get() = MMKVUtils.getInt(SP_REQUEST_RETRY_TIMES, 0)
set(requestRetryTimes) {
MMKVUtils.put(SP_REQUEST_RETRY_TIMES, requestRetryTimes)
}
//请求接口失败重试间隔(秒) //请求接口失败重试间隔(秒)
@JvmStatic var requestDelayTime: Int by SharedPreference(SP_REQUEST_DELAY_TIME, 1)
var requestDelayTime: Int
get() = MMKVUtils.getInt(SP_REQUEST_DELAY_TIME, 1)
set(requestDelayTime) {
MMKVUtils.put(SP_REQUEST_DELAY_TIME, requestDelayTime)
}
//请求接口失败超时时间(秒) //请求接口失败超时时间(秒)
@JvmStatic var requestTimeout: Int by SharedPreference(SP_REQUEST_TIMEOUT, 10)
var requestTimeout: Int
get() = MMKVUtils.getInt(SP_REQUEST_TIMEOUT, 10)
set(requestTimeout) {
MMKVUtils.put(SP_REQUEST_TIMEOUT, requestTimeout)
}
//通知内容 //通知内容
@JvmStatic var notifyContent: String by SharedPreference(SP_NOTIFY_CONTENT, getString(R.string.notification_content))
var notifyContent: String?
get() = MMKVUtils.getString(SP_NOTIFY_CONTENT, getString(R.string.notification_content))
set(notificationContent) {
MMKVUtils.put(SP_NOTIFY_CONTENT, notificationContent)
}
//设备名称 //设备名称
@JvmStatic var extraDeviceMark: String by SharedPreference(SP_EXTRA_DEVICE_MARK, "")
var extraDeviceMark: String?
get() = MMKVUtils.getString(SP_EXTRA_DEVICE_MARK, "")
set(extraDeviceMark) {
MMKVUtils.put(SP_EXTRA_DEVICE_MARK, extraDeviceMark)
}
//SM1备注 //SM1备注
@JvmStatic var extraSim1: String by SharedPreference(SP_EXTRA_SIM1, "")
var extraSim1: String?
get() = MMKVUtils.getString(SP_EXTRA_SIM1, "")
set(extraSim1) {
MMKVUtils.put(SP_EXTRA_SIM1, extraSim1)
}
//SM2备注 //SM2备注
@JvmStatic var extraSim2: String by SharedPreference(SP_EXTRA_SIM2, "")
var extraSim2: String?
get() = MMKVUtils.getString(SP_EXTRA_SIM2, "")
set(extraSim2) {
MMKVUtils.put(SP_EXTRA_SIM2, extraSim2)
}
//是否启用自定义模板 //是否启用自定义模板
@JvmStatic var enableSmsTemplate: Boolean by SharedPreference(SP_ENABLE_SMS_TEMPLATE, false)
var enableSmsTemplate: Boolean
get() = MMKVUtils.getBoolean(SP_ENABLE_SMS_TEMPLATE, false)
set(enableSmsTemplate) {
MMKVUtils.put(SP_ENABLE_SMS_TEMPLATE, enableSmsTemplate)
}
//自定义模板 //自定义模板
@JvmStatic var smsTemplate: String by SharedPreference(SP_SMS_TEMPLATE, "")
var smsTemplate: String?
get() = MMKVUtils.getString(SP_SMS_TEMPLATE, "")
set(smsTemplate) {
MMKVUtils.put(SP_SMS_TEMPLATE, smsTemplate)
}
//是否显示页面帮助 //是否显示页面帮助
@JvmStatic var enableHelpTip: Boolean by SharedPreference(SP_ENABLE_HELP_TIP, false)
var enableHelpTip: Boolean
get() = MMKVUtils.getBoolean(SP_ENABLE_HELP_TIP, false)
set(enableHelpTip) {
MMKVUtils.put(SP_ENABLE_HELP_TIP, enableHelpTip)
}
//是否纯客户端模式 //是否纯客户端模式
@JvmStatic var enablePureClientMode: Boolean by SharedPreference(SP_PURE_CLIENT_MODE, false)
var enablePureClientMode: Boolean
get() = MMKVUtils.getBoolean(SP_PURE_CLIENT_MODE, false)
set(enablePureClientMode) {
MMKVUtils.put(SP_PURE_CLIENT_MODE, enablePureClientMode)
}
} }
init { init {

@ -0,0 +1,108 @@
package com.idormy.sms.forwarder.utils
import android.content.Context
import android.content.SharedPreferences
import android.os.Build
import java.io.*
import kotlin.properties.ReadWriteProperty
import kotlin.reflect.KProperty
class SharedPreference<T>(private val name: String, private val default: T) : ReadWriteProperty<Any?, T> {
companion object {
lateinit var preference: SharedPreferences
fun init(context: Context) {
preference = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
val directBootContext: Context = context.createDeviceProtectedStorageContext()
directBootContext.getSharedPreferences(context.packageName, Context.MODE_PRIVATE)
} else {
context.getSharedPreferences(context.packageName, Context.MODE_PRIVATE)
}
}
//删除全部数据
fun clearPreference() = preference.edit().clear().apply()
//根据key删除存储数据
fun clearPreference(key: String) = preference.edit().remove(key).commit()
}
override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
return putPreference(name, value)
}
override fun getValue(thisRef: Any?, property: KProperty<*>): T {
return getPreference(name, default)
}
/**
* 查找数据 返回给调用方法一个具体的对象
* 如果查找不到类型就采用反序列化方法来返回类型
* default是默认对象 以防止会返回空对象的异常
* 即如果name没有查找到value 就返回默认的序列化对象然后经过反序列化返回
*/
private fun getPreference(name: String, default: T): T = with(preference) {
val res: Any = when (default) {
is Long -> getLong(name, default)
is String -> this.getString(name, default)!!
is Int -> getInt(name, default)
is Boolean -> getBoolean(name, default)
is Float -> getFloat(name, default)
//else -> throw IllegalArgumentException("This type can be get from Preferences")
else -> deSerialization(getString(name, serialize(default)).toString())
}
return res as T
}
private fun putPreference(name: String, value: T) = with(preference.edit()) {
when (value) {
is Long -> putLong(name, value)
is Int -> putInt(name, value)
is String -> putString(name, value)
is Boolean -> putBoolean(name, value)
is Float -> putFloat(name, value)
//else -> throw IllegalArgumentException("This type can be saved into Preferences")
else -> putString(name, serialize(value))
}.apply()
}
/**
* 序列化对象
* @throws IOException
*/
@Throws(IOException::class)
private fun <T> serialize(obj: T): String {
val byteArrayOutputStream = ByteArrayOutputStream()
val objectOutputStream = ObjectOutputStream(
byteArrayOutputStream
)
objectOutputStream.writeObject(obj)
var serStr = byteArrayOutputStream.toString("ISO-8859-1")
serStr = java.net.URLEncoder.encode(serStr, "UTF-8")
objectOutputStream.close()
byteArrayOutputStream.close()
return serStr
}
/**
* 反序列化对象
* @param str
* @throws IOException
* @throws ClassNotFoundException
*/
@Throws(IOException::class, ClassNotFoundException::class)
private fun <T> deSerialization(str: String): T {
val redStr = java.net.URLDecoder.decode(str, "UTF-8")
val byteArrayInputStream = ByteArrayInputStream(
redStr.toByteArray(charset("ISO-8859-1"))
)
val objectInputStream = ObjectInputStream(
byteArrayInputStream
)
val obj = objectInputStream.readObject() as T
objectInputStream.close()
byteArrayInputStream.close()
return obj
}
}

@ -8,9 +8,9 @@ import com.idormy.sms.forwarder.database.entity.Rule
import com.idormy.sms.forwarder.entity.MsgInfo import com.idormy.sms.forwarder.entity.MsgInfo
import com.idormy.sms.forwarder.entity.result.DingtalkInnerRobotResult import com.idormy.sms.forwarder.entity.result.DingtalkInnerRobotResult
import com.idormy.sms.forwarder.entity.setting.DingtalkInnerRobotSetting import com.idormy.sms.forwarder.entity.setting.DingtalkInnerRobotSetting
import com.idormy.sms.forwarder.utils.MMKVUtils
import com.idormy.sms.forwarder.utils.SendUtils import com.idormy.sms.forwarder.utils.SendUtils
import com.idormy.sms.forwarder.utils.SettingUtils import com.idormy.sms.forwarder.utils.SettingUtils
import com.idormy.sms.forwarder.utils.SharedPreference
import com.xuexiang.xhttp2.XHttp import com.xuexiang.xhttp2.XHttp
import com.xuexiang.xhttp2.cache.model.CacheMode import com.xuexiang.xhttp2.cache.model.CacheMode
import com.xuexiang.xhttp2.callback.SimpleCallBack import com.xuexiang.xhttp2.callback.SimpleCallBack
@ -38,9 +38,8 @@ class DingtalkInnerRobotUtils private constructor() {
rule: Rule?, rule: Rule?,
logId: Long?, logId: Long?,
) { ) {
var accessToken: String by SharedPreference("accessToken_" + setting.agentID, "")
val accessToken: String? = MMKVUtils.getString("accessToken_" + setting.agentID, "") var expiresIn: Long by SharedPreference("expiresIn_" + setting.agentID, 0L)
val expiresIn: Long = MMKVUtils.getLong("expiresIn_" + setting.agentID, 0L)
if (!TextUtils.isEmpty(accessToken) && expiresIn > System.currentTimeMillis()) { if (!TextUtils.isEmpty(accessToken) && expiresIn > System.currentTimeMillis()) {
return sendTextMsg(setting, msgInfo, rule, logId) return sendTextMsg(setting, msgInfo, rule, logId)
} }
@ -57,9 +56,7 @@ class DingtalkInnerRobotUtils private constructor() {
val request = XHttp.post(requestUrl) val request = XHttp.post(requestUrl)
//设置代理 //设置代理
if ((setting.proxyType == Proxy.Type.HTTP || setting.proxyType == Proxy.Type.SOCKS) if ((setting.proxyType == Proxy.Type.HTTP || setting.proxyType == Proxy.Type.SOCKS) && !TextUtils.isEmpty(setting.proxyHost) && !TextUtils.isEmpty(setting.proxyPort)) {
&& !TextUtils.isEmpty(setting.proxyHost) && !TextUtils.isEmpty(setting.proxyPort)
) {
//代理服务器的IP和端口号 //代理服务器的IP和端口号
Log.d(TAG, "proxyHost = ${setting.proxyHost}, proxyPort = ${setting.proxyPort}") Log.d(TAG, "proxyHost = ${setting.proxyHost}, proxyPort = ${setting.proxyPort}")
val proxyHost = if (NetworkUtils.isIP(setting.proxyHost)) setting.proxyHost else NetworkUtils.getDomainAddress(setting.proxyHost) val proxyHost = if (NetworkUtils.isIP(setting.proxyHost)) setting.proxyHost else NetworkUtils.getDomainAddress(setting.proxyHost)
@ -72,18 +69,14 @@ class DingtalkInnerRobotUtils private constructor() {
request.okproxy(Proxy(setting.proxyType, InetSocketAddress(proxyHost, proxyPort))) request.okproxy(Proxy(setting.proxyType, InetSocketAddress(proxyHost, proxyPort)))
//代理的鉴权账号密码 //代理的鉴权账号密码
if (setting.proxyAuthenticator == true if (setting.proxyAuthenticator == true && (!TextUtils.isEmpty(setting.proxyUsername) || !TextUtils.isEmpty(setting.proxyPassword))) {
&& (!TextUtils.isEmpty(setting.proxyUsername) || !TextUtils.isEmpty(setting.proxyPassword))
) {
Log.i(TAG, "proxyUsername = ${setting.proxyUsername}, proxyPassword = ${setting.proxyPassword}") Log.i(TAG, "proxyUsername = ${setting.proxyUsername}, proxyPassword = ${setting.proxyPassword}")
if (setting.proxyType == Proxy.Type.HTTP) { if (setting.proxyType == Proxy.Type.HTTP) {
request.okproxyAuthenticator { _: Route?, response: Response -> request.okproxyAuthenticator { _: Route?, response: Response ->
//设置代理服务器账号密码 //设置代理服务器账号密码
val credential = Credentials.basic(setting.proxyUsername.toString(), setting.proxyPassword.toString()) val credential = Credentials.basic(setting.proxyUsername.toString(), setting.proxyPassword.toString())
response.request().newBuilder() response.request().newBuilder().header("Proxy-Authorization", credential).build()
.header("Proxy-Authorization", credential)
.build()
} }
} else { } else {
Authenticator.setDefault(object : Authenticator() { Authenticator.setDefault(object : Authenticator() {
@ -95,13 +88,8 @@ class DingtalkInnerRobotUtils private constructor() {
} }
} }
request.upJson(requestMsg) request.upJson(requestMsg).keepJson(true).ignoreHttpsCert().timeOut((SettingUtils.requestTimeout * 1000).toLong()) //超时时间10s
.keepJson(true) .cacheMode(CacheMode.NO_CACHE).timeStamp(true).execute(object : SimpleCallBack<String>() {
.ignoreHttpsCert()
.timeOut((SettingUtils.requestTimeout * 1000).toLong()) //超时时间10s
.cacheMode(CacheMode.NO_CACHE)
.timeStamp(true)
.execute(object : SimpleCallBack<String>() {
override fun onError(e: ApiException) { override fun onError(e: ApiException) {
Log.e(TAG, e.detailMessage) Log.e(TAG, e.detailMessage)
@ -113,8 +101,8 @@ class DingtalkInnerRobotUtils private constructor() {
val resp = Gson().fromJson(response, DingtalkInnerRobotResult::class.java) val resp = Gson().fromJson(response, DingtalkInnerRobotResult::class.java)
if (!TextUtils.isEmpty(resp?.accessToken)) { if (!TextUtils.isEmpty(resp?.accessToken)) {
MMKVUtils.put("accessToken_" + setting.agentID, resp.accessToken) accessToken = resp.accessToken.toString()
MMKVUtils.put("expiresIn_" + setting.agentID, System.currentTimeMillis() + ((resp.expireIn ?: 7200) - 120) * 1000L) //提前2分钟过期 expiresIn = System.currentTimeMillis() + ((resp.expireIn ?: 7200) - 120) * 1000L //提前2分钟过期
sendTextMsg(setting, msgInfo, rule, logId) sendTextMsg(setting, msgInfo, rule, logId)
} else { } else {
SendUtils.updateLogs(logId, 0, String.format(getString(R.string.request_failed_tips), response)) SendUtils.updateLogs(logId, 0, String.format(getString(R.string.request_failed_tips), response))
@ -165,9 +153,7 @@ class DingtalkInnerRobotUtils private constructor() {
val request = XHttp.post(requestUrl) val request = XHttp.post(requestUrl)
//设置代理 //设置代理
if ((setting.proxyType == Proxy.Type.HTTP || setting.proxyType == Proxy.Type.SOCKS) if ((setting.proxyType == Proxy.Type.HTTP || setting.proxyType == Proxy.Type.SOCKS) && !TextUtils.isEmpty(setting.proxyHost) && !TextUtils.isEmpty(setting.proxyPort)) {
&& !TextUtils.isEmpty(setting.proxyHost) && !TextUtils.isEmpty(setting.proxyPort)
) {
//代理服务器的IP和端口号 //代理服务器的IP和端口号
Log.d(TAG, "proxyHost = ${setting.proxyHost}, proxyPort = ${setting.proxyPort}") Log.d(TAG, "proxyHost = ${setting.proxyHost}, proxyPort = ${setting.proxyPort}")
val proxyHost = if (NetworkUtils.isIP(setting.proxyHost)) setting.proxyHost else NetworkUtils.getDomainAddress(setting.proxyHost) val proxyHost = if (NetworkUtils.isIP(setting.proxyHost)) setting.proxyHost else NetworkUtils.getDomainAddress(setting.proxyHost)
@ -180,18 +166,14 @@ class DingtalkInnerRobotUtils private constructor() {
request.okproxy(Proxy(setting.proxyType, InetSocketAddress(proxyHost, proxyPort))) request.okproxy(Proxy(setting.proxyType, InetSocketAddress(proxyHost, proxyPort)))
//代理的鉴权账号密码 //代理的鉴权账号密码
if (setting.proxyAuthenticator == true if (setting.proxyAuthenticator == true && (!TextUtils.isEmpty(setting.proxyUsername) || !TextUtils.isEmpty(setting.proxyPassword))) {
&& (!TextUtils.isEmpty(setting.proxyUsername) || !TextUtils.isEmpty(setting.proxyPassword))
) {
Log.i(TAG, "proxyUsername = ${setting.proxyUsername}, proxyPassword = ${setting.proxyPassword}") Log.i(TAG, "proxyUsername = ${setting.proxyUsername}, proxyPassword = ${setting.proxyPassword}")
if (setting.proxyType == Proxy.Type.HTTP) { if (setting.proxyType == Proxy.Type.HTTP) {
request.okproxyAuthenticator { _: Route?, response: Response -> request.okproxyAuthenticator { _: Route?, response: Response ->
//设置代理服务器账号密码 //设置代理服务器账号密码
val credential = Credentials.basic(setting.proxyUsername.toString(), setting.proxyPassword.toString()) val credential = Credentials.basic(setting.proxyUsername.toString(), setting.proxyPassword.toString())
response.request().newBuilder() response.request().newBuilder().header("Proxy-Authorization", credential).build()
.header("Proxy-Authorization", credential)
.build()
} }
} else { } else {
Authenticator.setDefault(object : Authenticator() { Authenticator.setDefault(object : Authenticator() {
@ -203,17 +185,15 @@ class DingtalkInnerRobotUtils private constructor() {
} }
} }
request.upJson(requestMsg) val accessToken: String by SharedPreference("accessToken_" + setting.agentID, "")
.headers("x-acs-dingtalk-access-token", MMKVUtils.getString("accessToken_" + setting.agentID, "")) request.upJson(requestMsg).headers("x-acs-dingtalk-access-token", accessToken)
.keepJson(true) .keepJson(true)
.ignoreHttpsCert() .ignoreHttpsCert()
.timeOut((SettingUtils.requestTimeout * 1000).toLong()) //超时时间10s .timeOut((SettingUtils.requestTimeout * 1000).toLong()) //超时时间10s
.cacheMode(CacheMode.NO_CACHE) .cacheMode(CacheMode.NO_CACHE).retryCount(SettingUtils.requestRetryTimes) //超时重试的次数
.retryCount(SettingUtils.requestRetryTimes) //超时重试的次数
.retryDelay(SettingUtils.requestDelayTime) //超时重试的延迟时间 .retryDelay(SettingUtils.requestDelayTime) //超时重试的延迟时间
.retryIncreaseDelay(SettingUtils.requestDelayTime) //超时重试叠加延时 .retryIncreaseDelay(SettingUtils.requestDelayTime) //超时重试叠加延时
.timeStamp(true) .timeStamp(true).execute(object : SimpleCallBack<String>() {
.execute(object : SimpleCallBack<String>() {
override fun onError(e: ApiException) { override fun onError(e: ApiException) {
Log.e(TAG, e.detailMessage) Log.e(TAG, e.detailMessage)

@ -8,9 +8,9 @@ import com.idormy.sms.forwarder.database.entity.Rule
import com.idormy.sms.forwarder.entity.MsgInfo import com.idormy.sms.forwarder.entity.MsgInfo
import com.idormy.sms.forwarder.entity.result.FeishuAppResult import com.idormy.sms.forwarder.entity.result.FeishuAppResult
import com.idormy.sms.forwarder.entity.setting.FeishuAppSetting import com.idormy.sms.forwarder.entity.setting.FeishuAppSetting
import com.idormy.sms.forwarder.utils.MMKVUtils
import com.idormy.sms.forwarder.utils.SendUtils import com.idormy.sms.forwarder.utils.SendUtils
import com.idormy.sms.forwarder.utils.SettingUtils import com.idormy.sms.forwarder.utils.SettingUtils
import com.idormy.sms.forwarder.utils.SharedPreference
import com.xuexiang.xhttp2.XHttp import com.xuexiang.xhttp2.XHttp
import com.xuexiang.xhttp2.cache.model.CacheMode import com.xuexiang.xhttp2.cache.model.CacheMode
import com.xuexiang.xhttp2.callback.SimpleCallBack import com.xuexiang.xhttp2.callback.SimpleCallBack
@ -31,8 +31,8 @@ class FeishuAppUtils private constructor() {
logId: Long?, logId: Long?,
) { ) {
val accessToken: String? = MMKVUtils.getString("feishu_access_token_" + setting.appId, "") var accessToken: String by SharedPreference("feishu_access_token_" + setting.appId, "")
val expiresIn: Long = MMKVUtils.getLong("feishu_expires_in_" + setting.appId, 0L) var expiresIn: Long by SharedPreference("feishu_expires_in_" + setting.appId, 0L)
if (!TextUtils.isEmpty(accessToken) && expiresIn > System.currentTimeMillis()) { if (!TextUtils.isEmpty(accessToken) && expiresIn > System.currentTimeMillis()) {
return sendTextMsg(setting, msgInfo, rule, logId) return sendTextMsg(setting, msgInfo, rule, logId)
} }
@ -46,14 +46,8 @@ class FeishuAppUtils private constructor() {
val requestMsg: String = Gson().toJson(msgMap) val requestMsg: String = Gson().toJson(msgMap)
Log.i(TAG, "requestMsg:$requestMsg") Log.i(TAG, "requestMsg:$requestMsg")
XHttp.post(requestUrl) XHttp.post(requestUrl).upJson(requestMsg).keepJson(true).ignoreHttpsCert().timeOut((SettingUtils.requestTimeout * 1000).toLong()) //超时时间10s
.upJson(requestMsg) .cacheMode(CacheMode.NO_CACHE).timeStamp(true).execute(object : SimpleCallBack<String>() {
.keepJson(true)
.ignoreHttpsCert()
.timeOut((SettingUtils.requestTimeout * 1000).toLong()) //超时时间10s
.cacheMode(CacheMode.NO_CACHE)
.timeStamp(true)
.execute(object : SimpleCallBack<String>() {
override fun onError(e: ApiException) { override fun onError(e: ApiException) {
Log.e(TAG, e.detailMessage) Log.e(TAG, e.detailMessage)
@ -65,8 +59,8 @@ class FeishuAppUtils private constructor() {
val resp = Gson().fromJson(response, FeishuAppResult::class.java) val resp = Gson().fromJson(response, FeishuAppResult::class.java)
if (!TextUtils.isEmpty(resp?.tenant_access_token)) { if (!TextUtils.isEmpty(resp?.tenant_access_token)) {
MMKVUtils.put("feishu_access_token_" + setting.appId, resp.tenant_access_token) accessToken = resp.tenant_access_token.toString()
MMKVUtils.put("feishu_expires_in_" + setting.appId, System.currentTimeMillis() + ((resp.expire ?: 7010) - 120) * 1000L) //提前2分钟过期 expiresIn = System.currentTimeMillis() + ((resp.expire ?: 7010) - 120) * 1000L //提前2分钟过期
sendTextMsg(setting, msgInfo, rule, logId) sendTextMsg(setting, msgInfo, rule, logId)
} else { } else {
SendUtils.updateLogs(logId, 0, String.format(getString(R.string.request_failed_tips), response)) SendUtils.updateLogs(logId, 0, String.format(getString(R.string.request_failed_tips), response))
@ -99,9 +93,7 @@ class FeishuAppUtils private constructor() {
} else { } else {
msgInfo.getTitleForSend(setting.titleTemplate) msgInfo.getTitleForSend(setting.titleTemplate)
} }
"{\"elements\":[{\"tag\":\"markdown\",\"content\":\"**[{{MSG_TITLE}}]({{MSG_URL}})**\\n --------------\\n{{MSG_CONTENT}}\"}]}".trimIndent().replace("{{MSG_TITLE}}", jsonInnerStr(title)) "{\"elements\":[{\"tag\":\"markdown\",\"content\":\"**[{{MSG_TITLE}}]({{MSG_URL}})**\\n --------------\\n{{MSG_CONTENT}}\"}]}".trimIndent().replace("{{MSG_TITLE}}", jsonInnerStr(title)).replace("{{MSG_URL}}", jsonInnerStr("https://github.com/pppscn/SmsForwarder")).replace("{{MSG_CONTENT}}", jsonInnerStr(content))
.replace("{{MSG_URL}}", jsonInnerStr("https://github.com/pppscn/SmsForwarder"))
.replace("{{MSG_CONTENT}}", jsonInnerStr(content))
} else { } else {
"{\"text\":\"{{MSG_CONTENT}}\"}".trimIndent().replace("{{MSG_CONTENT}}", jsonInnerStr(content)) "{\"text\":\"{{MSG_CONTENT}}\"}".trimIndent().replace("{{MSG_CONTENT}}", jsonInnerStr(content))
} }
@ -114,18 +106,14 @@ class FeishuAppUtils private constructor() {
val requestMsg: String = Gson().toJson(textMsgMap) val requestMsg: String = Gson().toJson(textMsgMap)
Log.i(TAG, "requestMsg:$requestMsg") Log.i(TAG, "requestMsg:$requestMsg")
XHttp.post(requestUrl) val accessToken: String by SharedPreference("feishu_access_token_" + setting.appId, "")
.upJson(requestMsg) XHttp.post(requestUrl).upJson(requestMsg).headers("Authorization", "Bearer $accessToken").keepJson(true)
.headers("Authorization", "Bearer " + MMKVUtils.getString("feishu_access_token_" + setting.appId, ""))
.keepJson(true)
//.ignoreHttpsCert() //.ignoreHttpsCert()
.timeOut((SettingUtils.requestTimeout * 1000).toLong()) //超时时间10s .timeOut((SettingUtils.requestTimeout * 1000).toLong()) //超时时间10s
.cacheMode(CacheMode.NO_CACHE) .cacheMode(CacheMode.NO_CACHE).retryCount(SettingUtils.requestRetryTimes) //超时重试的次数
.retryCount(SettingUtils.requestRetryTimes) //超时重试的次数
.retryDelay(SettingUtils.requestDelayTime) //超时重试的延迟时间 .retryDelay(SettingUtils.requestDelayTime) //超时重试的延迟时间
.retryIncreaseDelay(SettingUtils.requestDelayTime) //超时重试叠加延时 .retryIncreaseDelay(SettingUtils.requestDelayTime) //超时重试叠加延时
.timeStamp(true) .timeStamp(true).execute(object : SimpleCallBack<String>() {
.execute(object : SimpleCallBack<String>() {
override fun onError(e: ApiException) { override fun onError(e: ApiException) {
Log.e(TAG, e.detailMessage) Log.e(TAG, e.detailMessage)

@ -9,9 +9,9 @@ import com.idormy.sms.forwarder.entity.MsgInfo
import com.idormy.sms.forwarder.entity.result.DingtalkResult import com.idormy.sms.forwarder.entity.result.DingtalkResult
import com.idormy.sms.forwarder.entity.result.WeworkAgentResult import com.idormy.sms.forwarder.entity.result.WeworkAgentResult
import com.idormy.sms.forwarder.entity.setting.WeworkAgentSetting import com.idormy.sms.forwarder.entity.setting.WeworkAgentSetting
import com.idormy.sms.forwarder.utils.MMKVUtils
import com.idormy.sms.forwarder.utils.SendUtils import com.idormy.sms.forwarder.utils.SendUtils
import com.idormy.sms.forwarder.utils.SettingUtils import com.idormy.sms.forwarder.utils.SettingUtils
import com.idormy.sms.forwarder.utils.SharedPreference
import com.xuexiang.xhttp2.XHttp import com.xuexiang.xhttp2.XHttp
import com.xuexiang.xhttp2.cache.model.CacheMode import com.xuexiang.xhttp2.cache.model.CacheMode
import com.xuexiang.xhttp2.callback.SimpleCallBack import com.xuexiang.xhttp2.callback.SimpleCallBack
@ -39,8 +39,8 @@ class WeworkAgentUtils private constructor() {
logId: Long?, logId: Long?,
) { ) {
val accessToken: String? = MMKVUtils.getString("access_token_" + setting.agentID, "") var accessToken: String by SharedPreference("access_token_" + setting.agentID, "")
val expiresIn: Long = MMKVUtils.getLong("expires_in_" + setting.agentID, 0L) var expiresIn: Long by SharedPreference("expires_in_" + setting.agentID, 0L)
if (!TextUtils.isEmpty(accessToken) && expiresIn > System.currentTimeMillis()) { if (!TextUtils.isEmpty(accessToken) && expiresIn > System.currentTimeMillis()) {
return sendTextMsg(setting, msgInfo, rule, logId) return sendTextMsg(setting, msgInfo, rule, logId)
} }
@ -53,9 +53,7 @@ class WeworkAgentUtils private constructor() {
val request = XHttp.get(getTokenUrl) val request = XHttp.get(getTokenUrl)
//设置代理 //设置代理
if ((setting.proxyType == Proxy.Type.HTTP || setting.proxyType == Proxy.Type.SOCKS) if ((setting.proxyType == Proxy.Type.HTTP || setting.proxyType == Proxy.Type.SOCKS) && !TextUtils.isEmpty(setting.proxyHost) && !TextUtils.isEmpty(setting.proxyPort)) {
&& !TextUtils.isEmpty(setting.proxyHost) && !TextUtils.isEmpty(setting.proxyPort)
) {
//代理服务器的IP和端口号 //代理服务器的IP和端口号
Log.d(TAG, "proxyHost = ${setting.proxyHost}, proxyPort = ${setting.proxyPort}") Log.d(TAG, "proxyHost = ${setting.proxyHost}, proxyPort = ${setting.proxyPort}")
val proxyHost = if (NetworkUtils.isIP(setting.proxyHost)) setting.proxyHost else NetworkUtils.getDomainAddress(setting.proxyHost) val proxyHost = if (NetworkUtils.isIP(setting.proxyHost)) setting.proxyHost else NetworkUtils.getDomainAddress(setting.proxyHost)
@ -68,18 +66,14 @@ class WeworkAgentUtils private constructor() {
request.okproxy(Proxy(setting.proxyType, InetSocketAddress(proxyHost, proxyPort))) request.okproxy(Proxy(setting.proxyType, InetSocketAddress(proxyHost, proxyPort)))
//代理的鉴权账号密码 //代理的鉴权账号密码
if (setting.proxyAuthenticator == true if (setting.proxyAuthenticator == true && (!TextUtils.isEmpty(setting.proxyUsername) || !TextUtils.isEmpty(setting.proxyPassword))) {
&& (!TextUtils.isEmpty(setting.proxyUsername) || !TextUtils.isEmpty(setting.proxyPassword))
) {
Log.i(TAG, "proxyUsername = ${setting.proxyUsername}, proxyPassword = ${setting.proxyPassword}") Log.i(TAG, "proxyUsername = ${setting.proxyUsername}, proxyPassword = ${setting.proxyPassword}")
if (setting.proxyType == Proxy.Type.HTTP) { if (setting.proxyType == Proxy.Type.HTTP) {
request.okproxyAuthenticator { _: Route?, response: Response -> request.okproxyAuthenticator { _: Route?, response: Response ->
//设置代理服务器账号密码 //设置代理服务器账号密码
val credential = Credentials.basic(setting.proxyUsername.toString(), setting.proxyPassword.toString()) val credential = Credentials.basic(setting.proxyUsername.toString(), setting.proxyPassword.toString())
response.request().newBuilder() response.request().newBuilder().header("Proxy-Authorization", credential).build()
.header("Proxy-Authorization", credential)
.build()
} }
} else { } else {
Authenticator.setDefault(object : Authenticator() { Authenticator.setDefault(object : Authenticator() {
@ -91,12 +85,8 @@ class WeworkAgentUtils private constructor() {
} }
} }
request.keepJson(true) request.keepJson(true).ignoreHttpsCert().timeOut((SettingUtils.requestTimeout * 1000).toLong()) //超时时间10s
.ignoreHttpsCert() .cacheMode(CacheMode.NO_CACHE).timeStamp(true).execute(object : SimpleCallBack<String>() {
.timeOut((SettingUtils.requestTimeout * 1000).toLong()) //超时时间10s
.cacheMode(CacheMode.NO_CACHE)
.timeStamp(true)
.execute(object : SimpleCallBack<String>() {
override fun onError(e: ApiException) { override fun onError(e: ApiException) {
Log.e(TAG, e.detailMessage) Log.e(TAG, e.detailMessage)
@ -108,8 +98,8 @@ class WeworkAgentUtils private constructor() {
val resp = Gson().fromJson(response, WeworkAgentResult::class.java) val resp = Gson().fromJson(response, WeworkAgentResult::class.java)
if (resp?.errcode == 0L) { if (resp?.errcode == 0L) {
MMKVUtils.put("access_token_" + setting.agentID, resp.access_token) accessToken = resp.access_token.toString()
MMKVUtils.put("expires_in_" + setting.agentID, System.currentTimeMillis() + ((resp.expires_in ?: 7200) - 120) * 1000L) //提前2分钟过期 expiresIn = System.currentTimeMillis() + ((resp.expires_in ?: 7200) - 120) * 1000L //提前2分钟过期
sendTextMsg(setting, msgInfo, rule, logId) sendTextMsg(setting, msgInfo, rule, logId)
} else { } else {
SendUtils.updateLogs(logId, 0, String.format(getString(R.string.request_failed_tips), response)) SendUtils.updateLogs(logId, 0, String.format(getString(R.string.request_failed_tips), response))
@ -142,7 +132,8 @@ class WeworkAgentUtils private constructor() {
val textText: MutableMap<String, Any> = mutableMapOf() val textText: MutableMap<String, Any> = mutableMapOf()
textText["content"] = content textText["content"] = content
textMsgMap["text"] = textText textMsgMap["text"] = textText
val requestUrl = "https://qyapi.weixin.qq.com/cgi-bin/message/send?access_token=" + MMKVUtils.getString("access_token_" + setting.agentID, "") var accessToken: String by SharedPreference("access_token_" + setting.agentID, "")
val requestUrl = "https://qyapi.weixin.qq.com/cgi-bin/message/send?access_token=$accessToken"
Log.i(TAG, "requestUrl:$requestUrl") Log.i(TAG, "requestUrl:$requestUrl")
val requestMsg: String = Gson().toJson(textMsgMap) val requestMsg: String = Gson().toJson(textMsgMap)
Log.i(TAG, "requestMsg:$requestMsg") Log.i(TAG, "requestMsg:$requestMsg")
@ -150,9 +141,7 @@ class WeworkAgentUtils private constructor() {
val request = XHttp.post(requestUrl) val request = XHttp.post(requestUrl)
//设置代理 //设置代理
if ((setting.proxyType == Proxy.Type.HTTP || setting.proxyType == Proxy.Type.SOCKS) if ((setting.proxyType == Proxy.Type.HTTP || setting.proxyType == Proxy.Type.SOCKS) && !TextUtils.isEmpty(setting.proxyHost) && !TextUtils.isEmpty(setting.proxyPort)) {
&& !TextUtils.isEmpty(setting.proxyHost) && !TextUtils.isEmpty(setting.proxyPort)
) {
//代理服务器的IP和端口号 //代理服务器的IP和端口号
Log.d(TAG, "proxyHost = ${setting.proxyHost}, proxyPort = ${setting.proxyPort}") Log.d(TAG, "proxyHost = ${setting.proxyHost}, proxyPort = ${setting.proxyPort}")
val proxyHost = if (NetworkUtils.isIP(setting.proxyHost)) setting.proxyHost else NetworkUtils.getDomainAddress(setting.proxyHost) val proxyHost = if (NetworkUtils.isIP(setting.proxyHost)) setting.proxyHost else NetworkUtils.getDomainAddress(setting.proxyHost)
@ -165,18 +154,14 @@ class WeworkAgentUtils private constructor() {
request.okproxy(Proxy(setting.proxyType, InetSocketAddress(proxyHost, proxyPort))) request.okproxy(Proxy(setting.proxyType, InetSocketAddress(proxyHost, proxyPort)))
//代理的鉴权账号密码 //代理的鉴权账号密码
if (setting.proxyAuthenticator == true if (setting.proxyAuthenticator == true && (!TextUtils.isEmpty(setting.proxyUsername) || !TextUtils.isEmpty(setting.proxyPassword))) {
&& (!TextUtils.isEmpty(setting.proxyUsername) || !TextUtils.isEmpty(setting.proxyPassword))
) {
Log.i(TAG, "proxyUsername = ${setting.proxyUsername}, proxyPassword = ${setting.proxyPassword}") Log.i(TAG, "proxyUsername = ${setting.proxyUsername}, proxyPassword = ${setting.proxyPassword}")
if (setting.proxyType == Proxy.Type.HTTP) { if (setting.proxyType == Proxy.Type.HTTP) {
request.okproxyAuthenticator { _: Route?, response: Response -> request.okproxyAuthenticator { _: Route?, response: Response ->
//设置代理服务器账号密码 //设置代理服务器账号密码
val credential = Credentials.basic(setting.proxyUsername.toString(), setting.proxyPassword.toString()) val credential = Credentials.basic(setting.proxyUsername.toString(), setting.proxyPassword.toString())
response.request().newBuilder() response.request().newBuilder().header("Proxy-Authorization", credential).build()
.header("Proxy-Authorization", credential)
.build()
} }
} else { } else {
Authenticator.setDefault(object : Authenticator() { Authenticator.setDefault(object : Authenticator() {
@ -188,16 +173,11 @@ class WeworkAgentUtils private constructor() {
} }
} }
request.upJson(requestMsg) request.upJson(requestMsg).keepJson(true).ignoreHttpsCert().timeOut((SettingUtils.requestTimeout * 1000).toLong()) //超时时间10s
.keepJson(true) .cacheMode(CacheMode.NO_CACHE).retryCount(SettingUtils.requestRetryTimes) //超时重试的次数
.ignoreHttpsCert()
.timeOut((SettingUtils.requestTimeout * 1000).toLong()) //超时时间10s
.cacheMode(CacheMode.NO_CACHE)
.retryCount(SettingUtils.requestRetryTimes) //超时重试的次数
.retryDelay(SettingUtils.requestDelayTime) //超时重试的延迟时间 .retryDelay(SettingUtils.requestDelayTime) //超时重试的延迟时间
.retryIncreaseDelay(SettingUtils.requestDelayTime) //超时重试叠加延时 .retryIncreaseDelay(SettingUtils.requestDelayTime) //超时重试叠加延时
.timeStamp(true) .timeStamp(true).execute(object : SimpleCallBack<String>() {
.execute(object : SimpleCallBack<String>() {
override fun onError(e: ApiException) { override fun onError(e: ApiException) {
Log.e(TAG, e.detailMessage) Log.e(TAG, e.detailMessage)

@ -10,7 +10,7 @@ import com.idormy.sms.forwarder.R
import com.idormy.sms.forwarder.core.http.api.ApiService.IGetService import com.idormy.sms.forwarder.core.http.api.ApiService.IGetService
import com.idormy.sms.forwarder.core.http.callback.NoTipCallBack import com.idormy.sms.forwarder.core.http.callback.NoTipCallBack
import com.idormy.sms.forwarder.core.http.entity.TipInfo import com.idormy.sms.forwarder.core.http.entity.TipInfo
import com.idormy.sms.forwarder.utils.MMKVUtils import com.idormy.sms.forwarder.utils.SharedPreference
import com.xuexiang.constant.TimeConstants import com.xuexiang.constant.TimeConstants
import com.xuexiang.xaop.annotation.SingleClick import com.xuexiang.xaop.annotation.SingleClick
import com.xuexiang.xhttp2.XHttp import com.xuexiang.xhttp2.XHttp
@ -175,11 +175,12 @@ class GuideTipsDialog(context: Context?, tips: List<TipInfo>) :
} }
fun setIsIgnoreTips(isIgnore: Boolean): Boolean { fun setIsIgnoreTips(isIgnore: Boolean): Boolean {
return MMKVUtils.put(KEY_IS_IGNORE_TIPS + AppUtils.getAppVersionCode(), isIgnore) this.isIgnoreTips = isIgnore
return true
} }
val isIgnoreTips: Boolean var isIgnoreTips: Boolean by SharedPreference(KEY_IS_IGNORE_TIPS + AppUtils.getAppVersionCode(), false)
get() = MMKVUtils.getBoolean(KEY_IS_IGNORE_TIPS + AppUtils.getAppVersionCode(), false)
} }
init { init {

@ -1,111 +1,101 @@
package com.idormy.sms.forwarder.workers package com.idormy.sms.forwarder.workers
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.content.Context import android.content.Context
import android.util.Log import android.util.Log
import androidx.work.CoroutineWorker import androidx.work.CoroutineWorker
import androidx.work.WorkerParameters import androidx.work.WorkerParameters
import androidx.work.workDataOf import androidx.work.workDataOf
import com.google.gson.Gson import com.google.gson.Gson
import com.idormy.sms.forwarder.core.Core import com.idormy.sms.forwarder.core.Core
import com.idormy.sms.forwarder.database.entity.Logs import com.idormy.sms.forwarder.database.entity.Logs
import com.idormy.sms.forwarder.database.entity.RuleAndSender import com.idormy.sms.forwarder.database.entity.RuleAndSender
import com.idormy.sms.forwarder.entity.MsgInfo import com.idormy.sms.forwarder.entity.MsgInfo
import com.idormy.sms.forwarder.utils.* import com.idormy.sms.forwarder.utils.*
import com.xuexiang.xutil.security.CipherUtils import com.xuexiang.xutil.security.CipherUtils
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import java.text.ParsePosition import java.text.ParsePosition
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.* import java.util.*
class SendWorker( class SendWorker(
context: Context, context: Context,
workerParams: WorkerParameters, workerParams: WorkerParameters,
) : CoroutineWorker(context, workerParams) { ) : CoroutineWorker(context, workerParams) {
@SuppressLint("SimpleDateFormat") @SuppressLint("SimpleDateFormat")
override suspend fun doWork(): Result { override suspend fun doWork(): Result {
return withContext(Dispatchers.IO) { return withContext(Dispatchers.IO) {
try { try {
// 免打扰(禁用转发)时间段 // 免打扰(禁用转发)时间段
if (SettingUtils.silentPeriodStart != SettingUtils.silentPeriodEnd) { if (SettingUtils.silentPeriodStart != SettingUtils.silentPeriodEnd) {
val periodStartDay = Date() val periodStartDay = Date()
var periodStartEnd = Date() var periodStartEnd = Date()
//跨天了 //跨天了
if (SettingUtils.silentPeriodStart > SettingUtils.silentPeriodEnd) { if (SettingUtils.silentPeriodStart > SettingUtils.silentPeriodEnd) {
val c: Calendar = Calendar.getInstance() val c: Calendar = Calendar.getInstance()
c.time = periodStartEnd c.time = periodStartEnd
c.add(Calendar.DAY_OF_MONTH, 1) c.add(Calendar.DAY_OF_MONTH, 1)
periodStartEnd = c.time periodStartEnd = c.time
} }
val dateFmt = SimpleDateFormat("yyyy-MM-dd") val dateFmt = SimpleDateFormat("yyyy-MM-dd")
val mTimeOption = DataProvider.timePeriodOption val mTimeOption = DataProvider.timePeriodOption
val periodStartStr = val periodStartStr = dateFmt.format(periodStartDay) + " " + mTimeOption[SettingUtils.silentPeriodStart] + ":00"
dateFmt.format(periodStartDay) + " " + mTimeOption[SettingUtils.silentPeriodStart] + ":00" val periodEndStr = dateFmt.format(periodStartEnd) + " " + mTimeOption[SettingUtils.silentPeriodEnd] + ":00"
val periodEndStr =
dateFmt.format(periodStartEnd) + " " + mTimeOption[SettingUtils.silentPeriodEnd] + ":00" val timeFmt = SimpleDateFormat("yyyy-MM-dd HH:mm:ss")
val periodStart = timeFmt.parse(periodStartStr, ParsePosition(0))?.time
val timeFmt = SimpleDateFormat("yyyy-MM-dd HH:mm:ss") val periodEnd = timeFmt.parse(periodEndStr, ParsePosition(0))?.time
val periodStart = timeFmt.parse(periodStartStr, ParsePosition(0)).time
val periodEnd = timeFmt.parse(periodEndStr, ParsePosition(0)).time val now = System.currentTimeMillis()
if (periodStart != null && periodEnd != null && now in periodStart..periodEnd) {
val now = System.currentTimeMillis() Log.e("SendWorker", "免打扰(禁用转发)时间段")
if (now in periodStart..periodEnd) { return@withContext Result.failure(workDataOf("send" to "failed"))
Log.e("SendWorker", "免打扰(禁用转发)时间段") }
return@withContext Result.failure(workDataOf("send" to "failed"))
} }
} val msgInfoJson = inputData.getString(Worker.sendMsgInfo)
val msgInfo = Gson().fromJson(msgInfoJson, MsgInfo::class.java)
val msgInfoJson = inputData.getString(Worker.sendMsgInfo)
val msgInfo = Gson().fromJson(msgInfoJson, MsgInfo::class.java) // 过滤重复消息机制
if (SettingUtils.duplicateMessagesLimits > 0) {
// 过滤重复消息机制 val key = CipherUtils.md5(msgInfo.type + msgInfo.from + msgInfo.content)
if (SettingUtils.duplicateMessagesLimits > 0) { val timestamp: Long = System.currentTimeMillis() / 1000L
val key = CipherUtils.md5(msgInfo.type + msgInfo.from + msgInfo.content) var timestampPrev: Long by HistoryUtils(key, timestamp)
val timestamp: Long = System.currentTimeMillis() / 1000L if (timestampPrev != timestamp && timestamp - timestampPrev <= SettingUtils.duplicateMessagesLimits) {
if (HistoryUtils.containsKey(key)) { Log.e("SendWorker", "过滤重复消息机制")
val timestampPrev = HistoryUtils.getLong(key, timestamp) return@withContext Result.failure(workDataOf("send" to "failed"))
if (timestamp - timestampPrev <= SettingUtils.duplicateMessagesLimits) { }
Log.e("SendWorker", "过滤重复消息机制") timestampPrev = timestamp
return@withContext Result.failure(workDataOf("send" to "failed")) }
}
} //【注意】卡槽id-1=获取失败、0=卡槽1、1=卡槽2但是 Rule 表里存的是 SIM1/SIM2
HistoryUtils.put(key, timestamp) val simSlot = "SIM" + (msgInfo.simSlot + 1)
} val ruleList: List<RuleAndSender> = Core.rule.getRuleAndSender(msgInfo.type, 1, simSlot)
if (ruleList.isEmpty()) {
//【注意】卡槽id-1=获取失败、0=卡槽1、1=卡槽2但是 Rule 表里存的是 SIM1/SIM2 return@withContext Result.failure(workDataOf("send" to "failed"))
val simSlot = "SIM" + (msgInfo.simSlot + 1) }
val ruleList: List<RuleAndSender> =
Core.rule.getRuleAndSender(msgInfo.type, 1, simSlot) for (rule in ruleList) {
if (ruleList.isEmpty()) { if (!rule.rule.checkMsg(msgInfo)) continue
return@withContext Result.failure(workDataOf("send" to "failed")) val log = Logs(
} 0, msgInfo.type, msgInfo.from, msgInfo.content, rule.rule.id, msgInfo.simInfo
)
for (rule in ruleList) { val logId = Core.logs.insert(log)
if (!rule.rule.checkMsg(msgInfo)) continue SendUtils.sendMsgSender(msgInfo, rule.rule, rule.sender, logId)
val log = Logs( }
0,
msgInfo.type, } catch (e: Exception) {
msgInfo.from, e.printStackTrace()
msgInfo.content, return@withContext Result.failure(workDataOf("send" to e.message.toString()))
rule.rule.id, }
msgInfo.simInfo
) return@withContext Result.success()
val logId = Core.logs.insert(log) }
SendUtils.sendMsgSender(msgInfo, rule.rule, rule.sender, logId) }
}
} catch (e: Exception) {
e.printStackTrace()
return@withContext Result.failure(workDataOf("send" to e.message.toString()))
}
return@withContext Result.success()
}
}
} }

@ -5,8 +5,8 @@ def build_versions = [:]
build_versions.version_code = 49 build_versions.version_code = 49
build_versions.version_name = "3.1.1" build_versions.version_name = "3.1.1"
build_versions.min_sdk = 19 build_versions.min_sdk = 19
build_versions.target_sdk = 33 build_versions.target_sdk = 32
build_versions.build_tools = "33.0.0" build_versions.build_tools = "33.0.1"
ext.build_versions = build_versions ext.build_versions = build_versions
ext.deps = [:] ext.deps = [:]

Loading…
Cancel
Save