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

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

@ -181,11 +181,11 @@ dependencies {
implementation files('libs/frpclib.aar')
testImplementation deps.junit
androidTestImplementation 'androidx.test.ext:junit:1.1.4'
androidTestImplementation 'androidx.test.ext:junit:1.1.5'
androidTestImplementation deps.espresso.core
implementation 'androidx.core:core-ktx:1.9.0'
implementation "androidx.activity:activity-ktx:1.6.1"
implementation 'androidx.core:core-ktx:1.8.0'
implementation "androidx.activity:activity-ktx:1.5.1"
implementation "androidx.fragment:fragment-ktx:1.5.4"
implementation "androidx.cardview:cardview:1.0.0"
implementation 'androidx.appcompat:appcompat:1.5.1'
@ -202,8 +202,6 @@ dependencies {
//WebView
implementation 'com.github.xuexiangjys.AgentWeb:agentweb-core: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
implementation 'me.jessyan:autosize:1.2.1'
//umeng

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

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

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

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

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

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

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

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

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

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

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

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

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

@ -3,11 +3,8 @@ package com.idormy.sms.forwarder.receiver
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.os.Build
import android.util.Log
import com.idormy.sms.forwarder.activity.SplashActivity
import com.idormy.sms.forwarder.service.ForegroundService
import com.idormy.sms.forwarder.utils.SettingUtils
@Suppress("PropertyName")
class BootReceiver : BroadcastReceiver() {
@ -17,32 +14,13 @@ class BootReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent?) {
val receiveAction: String? = intent?.action
Log.d(TAG, "onReceive intent $receiveAction")
if (receiveAction == "android.intent.action.BOOT_COMPLETED") {
if (receiveAction == "android.intent.action.BOOT_COMPLETED" || receiveAction == "android.intent.action.LOCKED_BOOT_COMPLETED") {
try {
val i = Intent(context, SplashActivity::class.java)
i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
context.startActivity(i)
//纯客户端模式
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))
}*/
Log.d(TAG, "强制重启APP一次")
val intent1 = Intent(context, SplashActivity::class.java)
intent1.flags = Intent.FLAG_ACTIVITY_NEW_TASK
context.startActivity(intent1)
android.os.Process.killProcess(android.os.Process.myPid()) //杀掉以前进程
} catch (e: Exception) {
e.printStackTrace()
}

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

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

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

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

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

@ -1,8 +1,11 @@
package com.idormy.sms.forwarder.utils
import android.content.Context
import android.os.Parcelable
import com.tencent.mmkv.MMKV
import android.content.SharedPreferences
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日
*/
@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 {
private var sMMKV: MMKV? = null
lateinit var preference: SharedPreferences
/**
* 初始化
*
* @param context
*/
fun init(context: Context) {
MMKV.initialize(context.applicationContext)
sMMKV = MMKV.mmkvWithID("History")
}
fun getsMMKV(): MMKV? {
if (sMMKV == null) {
sMMKV = MMKV.mmkvWithID("History")
}
return sMMKV
}
//=======================================键值保存==================================================//
/**
* 保存键值
*
* @param key
* @param value
* @return
*/
fun put(key: String?, value: Any?): Boolean {
when (value) {
is Int -> {
return getsMMKV()!!.encode(key, (value as Int?)!!)
}
is Float -> {
return getsMMKV()!!.encode(key, (value as Float?)!!)
}
is String -> {
return getsMMKV()!!.encode(key, value as String?)
}
is Boolean -> {
return getsMMKV()!!.encode(key, (value as Boolean?)!!)
}
is Long -> {
return getsMMKV()!!.encode(key, (value as Long?)!!)
}
is Double -> {
return getsMMKV()!!.encode(key, (value as Double?)!!)
}
is Parcelable -> {
return getsMMKV()!!.encode(key, value as Parcelable?)
}
is ByteArray -> {
return getsMMKV()!!.encode(key, value as ByteArray?)
}
is Set<*> -> {
return getsMMKV()!!.encode(key, value as Set<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()
preference = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
val directBootContext: Context = context.createDeviceProtectedStorageContext()
directBootContext.getSharedPreferences(context.packageName + ".history", Context.MODE_PRIVATE)
} else {
context.getSharedPreferences(context.packageName + ".history", Context.MODE_PRIVATE)
}
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
}
//删除全部数据
fun clearPreference() = preference.edit().clear().apply()
/**
* 根据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)
}
//根据key删除存储数据
fun clearPreference(key: String) = preference.edit().remove(key).commit()
}
/**
* 获取对象
*
* @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 setValue(thisRef: Any?, property: KProperty<*>, value: T) {
return putPreference(name, value)
}
/**
* 判断键值对是否存在
*
* @param key
* @return 键值对是否存在
*/
fun containsKey(key: String?): Boolean {
return getsMMKV()!!.containsKey(key)
}
override fun getValue(thisRef: Any?, property: KProperty<*>): T {
return getPreference(name, default)
}
/**
* 清除指定键值对
*
* @param key
*/
fun remove(key: String?) {
getsMMKV()!!.remove(key).apply()
/**
* 查找数据 返回给调用方法一个具体的对象
* 如果查找不到类型就采用反序列化方法来返回类型
* 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
}
/**
* 清除所有键值对
*/
fun clear() {
getsMMKV()!!.clearAll()
}
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
}
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
import android.text.TextUtils
import android.util.Base64
import android.util.Log
import com.google.gson.Gson
import com.idormy.sms.forwarder.R
import com.idormy.sms.forwarder.core.Core
import com.idormy.sms.forwarder.entity.CloneInfo
import com.idormy.sms.forwarder.server.model.BaseRequest
import com.xuexiang.xui.utils.ResUtils.getString
import com.xuexiang.xutil.app.AppUtils
import com.yanzhenjie.andserver.error.HttpException
import java.net.URLEncoder
import java.nio.charset.StandardCharsets
import javax.crypto.Mac
import javax.crypto.spec.SecretKeySpec
/**
* HttpServer工具类
*/
class HttpServerUtils private constructor() {
companion object {
//是否启用HttpServer开机自启
@JvmStatic
var enableServerAutorun: Boolean
get() = MMKVUtils.getBoolean(SP_ENABLE_SERVER_AUTORUN, false)
set(enableServerAutorun) {
MMKVUtils.put(SP_ENABLE_SERVER_AUTORUN, enableServerAutorun)
}
//服务端安全设置
@JvmStatic
var safetyMeasures: Int
get() = MMKVUtils.getInt(SP_SERVER_SAFETY_MEASURES, if (TextUtils.isEmpty(serverSignKey)) 0 else 1)
set(safetyMeasures) {
MMKVUtils.put(SP_SERVER_SAFETY_MEASURES, safetyMeasures)
}
//服务端SM4密钥
@JvmStatic
var serverSm4Key: String?
get() = MMKVUtils.getString(SP_SERVER_SM4_KEY, "")
set(serverSm4Key) {
MMKVUtils.put(SP_SERVER_SM4_KEY, serverSm4Key)
}
//服务端RSA公钥
@JvmStatic
var serverPublicKey: String?
get() = MMKVUtils.getString(SP_SERVER_PUBLIC_KEY, "")
set(serverPublicKey) {
MMKVUtils.put(SP_SERVER_PUBLIC_KEY, serverPublicKey)
}
//服务端RSA私钥
@JvmStatic
var serverPrivateKey: String?
get() = MMKVUtils.getString(SP_SERVER_PRIVATE_KEY, "")
set(serverPrivateKey) {
MMKVUtils.put(SP_SERVER_PRIVATE_KEY, serverPrivateKey)
}
//服务端签名密钥
@JvmStatic
var serverSignKey: String?
get() = MMKVUtils.getString(SP_SERVER_SIGN_KEY, "")
set(serverSignKey) {
MMKVUtils.put(SP_SERVER_SIGN_KEY, serverSignKey)
}
//时间容差
@JvmStatic
var timeTolerance: Int
get() = MMKVUtils.getInt(SP_SERVER_TIME_TOLERANCE, 600)
set(timeTolerance) {
MMKVUtils.put(SP_SERVER_TIME_TOLERANCE, timeTolerance)
}
//自定义web客户端目录
@JvmStatic
var serverWebPath: String?
get() = MMKVUtils.getString(SP_SERVER_WEB_PATH, "")
set(serverWebPath) {
MMKVUtils.put(SP_SERVER_WEB_PATH, serverWebPath)
}
//服务地址
@JvmStatic
var serverAddress: String?
get() = MMKVUtils.getString(SP_SERVER_ADDRESS, "")
set(clientSignKey) {
MMKVUtils.put(SP_SERVER_ADDRESS, clientSignKey)
}
//服务地址历史记录
@JvmStatic
var serverHistory: String?
get() = MMKVUtils.getString(SP_SERVER_HISTORY, "")
set(serverHistory) {
MMKVUtils.put(SP_SERVER_HISTORY, serverHistory)
}
//服务端配置
@JvmStatic
var serverConfig: String?
get() = MMKVUtils.getString(SP_SERVER_CONFIG, "")
set(serverConfig) {
MMKVUtils.put(SP_SERVER_CONFIG, serverConfig)
}
//服务端安全设置
@JvmStatic
var clientSafetyMeasures: Int
get() = MMKVUtils.getInt(SP_CLIENT_SAFETY_MEASURES, if (TextUtils.isEmpty(clientSignKey)) 0 else 1)
set(clientSafetyMeasures) {
MMKVUtils.put(SP_CLIENT_SAFETY_MEASURES, clientSafetyMeasures)
}
//客户端签名密钥/RSA公钥
@JvmStatic
var clientSignKey: String?
get() = MMKVUtils.getString(SP_CLIENT_SIGN_KEY, "")
set(clientSignKey) {
MMKVUtils.put(SP_CLIENT_SIGN_KEY, clientSignKey)
}
//是否启用一键克隆
@JvmStatic
var enableApiClone: Boolean
get() = MMKVUtils.getBoolean(SP_ENABLE_API_CLONE, false)
set(enableApiClone) {
MMKVUtils.put(SP_ENABLE_API_CLONE, enableApiClone)
}
//是否启用远程发短信
@JvmStatic
var enableApiSmsSend: Boolean
get() = MMKVUtils.getBoolean(SP_ENABLE_API_SMS_SEND, false)
set(enableApiSendSms) {
MMKVUtils.put(SP_ENABLE_API_SMS_SEND, enableApiSendSms)
}
//是否启用远程查短信
@JvmStatic
var enableApiSmsQuery: Boolean
get() = MMKVUtils.getBoolean(SP_ENABLE_API_SMS_QUERY, false)
set(enableApiQuerySms) {
MMKVUtils.put(SP_ENABLE_API_SMS_QUERY, enableApiQuerySms)
}
//是否启用远程查通话
@JvmStatic
var enableApiCallQuery: Boolean
get() = MMKVUtils.getBoolean(SP_ENABLE_API_CALL_QUERY, false)
set(enableApiQueryCall) {
MMKVUtils.put(SP_ENABLE_API_CALL_QUERY, enableApiQueryCall)
}
//是否启用远程查话簿
@JvmStatic
var enableApiContactQuery: Boolean
get() = MMKVUtils.getBoolean(SP_ENABLE_API_CONTACT_QUERY, false)
set(enableApiQueryLinkman) {
MMKVUtils.put(SP_ENABLE_API_CONTACT_QUERY, enableApiQueryLinkman)
}
//是否启用远程查电量
@JvmStatic
var enableApiBatteryQuery: Boolean
get() = MMKVUtils.getBoolean(SP_ENABLE_API_BATTERY_QUERY, false)
set(enableApiQueryBattery) {
MMKVUtils.put(SP_ENABLE_API_BATTERY_QUERY, enableApiQueryBattery)
}
//是否启用远程WOL
@JvmStatic
var enableApiWol: Boolean
get() = MMKVUtils.getBoolean(SP_ENABLE_API_WOL, false)
set(enableApiWol) {
MMKVUtils.put(SP_ENABLE_API_WOL, enableApiWol)
}
//WOL历史记录
@JvmStatic
var wolHistory: String?
get() = MMKVUtils.getString(SP_WOL_HISTORY, "")
set(wolHistory) {
MMKVUtils.put(SP_WOL_HISTORY, wolHistory)
}
//计算签名
fun calcSign(timestamp: String, signSecret: String): String {
val stringToSign = "$timestamp\n" + signSecret
val mac = Mac.getInstance("HmacSHA256")
mac.init(SecretKeySpec(signSecret.toByteArray(StandardCharsets.UTF_8), "HmacSHA256"))
val signData = mac.doFinal(stringToSign.toByteArray(StandardCharsets.UTF_8))
return URLEncoder.encode(String(Base64.encode(signData, Base64.NO_WRAP)), "UTF-8")
}
//校验签名
@Throws(HttpException::class)
fun checkSign(req: BaseRequest<*>) {
val signSecret = serverSignKey
if (TextUtils.isEmpty(signSecret)) return
if (TextUtils.isEmpty(req.sign)) throw HttpException(500, getString(R.string.sign_required))
if (req.timestamp == 0L) throw HttpException(500, getString(R.string.timestamp_required))
val timestamp = System.currentTimeMillis()
val diffTime = kotlin.math.abs(timestamp - req.timestamp)
val tolerance = timeTolerance * 1000L
if (diffTime > tolerance) {
throw HttpException(500, String.format(getString(R.string.timestamp_verify_failed), timestamp, timeTolerance, diffTime))
}
val sign = calcSign(req.timestamp.toString(), signSecret.toString())
if (sign != req.sign) {
Log.e("calcSign", sign)
Log.e("reqSign", req.sign.toString())
throw HttpException(500, getString(R.string.sign_verify_failed))
}
}
//判断版本是否一致
@Throws(HttpException::class)
fun compareVersion(cloneInfo: CloneInfo) {
val versionCodeRequest = cloneInfo.versionCode
if (versionCodeRequest == 0) throw HttpException(500, getString(R.string.version_code_required))
val versionCodeLocal = AppUtils.getAppVersionCode().toString().substring(1)
if (!versionCodeRequest.toString().endsWith(versionCodeLocal)) throw HttpException(500, getString(R.string.inconsistent_version))
}
//导出设置
fun exportSettings(): CloneInfo {
val cloneInfo = CloneInfo()
cloneInfo.versionCode = AppUtils.getAppVersionCode()
cloneInfo.versionName = AppUtils.getAppVersionName()
cloneInfo.enableSms = SettingUtils.enableSms
cloneInfo.enablePhone = SettingUtils.enablePhone
cloneInfo.callType1 = SettingUtils.enableCallType1
cloneInfo.callType2 = SettingUtils.enableCallType2
cloneInfo.callType3 = SettingUtils.enableCallType3
cloneInfo.enableAppNotify = SettingUtils.enableAppNotify
cloneInfo.cancelAppNotify = SettingUtils.enableCancelAppNotify
cloneInfo.enableNotUserPresent = SettingUtils.enableNotUserPresent
cloneInfo.enableLoadAppList = SettingUtils.enableLoadAppList
cloneInfo.enableLoadUserAppList = SettingUtils.enableLoadUserAppList
cloneInfo.enableLoadSystemAppList = SettingUtils.enableLoadSystemAppList
cloneInfo.duplicateMessagesLimits = SettingUtils.duplicateMessagesLimits
cloneInfo.enableBatteryReceiver = SettingUtils.enableBatteryReceiver
cloneInfo.batteryLevelMin = SettingUtils.batteryLevelMin
cloneInfo.batteryLevelMax = SettingUtils.batteryLevelMax
cloneInfo.batteryLevelOnce = SettingUtils.batteryLevelOnce
cloneInfo.enableBatteryCron = SettingUtils.enableBatteryCron
cloneInfo.batteryCronStartTime = SettingUtils.batteryCronStartTime
cloneInfo.batteryCronInterval = SettingUtils.batteryCronInterval
cloneInfo.enableExcludeFromRecents = SettingUtils.enableExcludeFromRecents
cloneInfo.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)
}
}
package com.idormy.sms.forwarder.utils
import android.text.TextUtils
import android.util.Base64
import android.util.Log
import com.google.gson.Gson
import com.idormy.sms.forwarder.R
import com.idormy.sms.forwarder.core.Core
import com.idormy.sms.forwarder.entity.CloneInfo
import com.idormy.sms.forwarder.server.model.BaseRequest
import com.xuexiang.xui.utils.ResUtils.getString
import com.xuexiang.xutil.app.AppUtils
import com.yanzhenjie.andserver.error.HttpException
import java.net.URLEncoder
import java.nio.charset.StandardCharsets
import javax.crypto.Mac
import javax.crypto.spec.SecretKeySpec
/**
* HttpServer工具类
*/
class HttpServerUtils private constructor() {
companion object {
//是否启用HttpServer开机自启
var enableServerAutorun: Boolean by SharedPreference(SP_ENABLE_SERVER_AUTORUN, true)
//服务端签名密钥
var serverSignKey: String by SharedPreference(SP_SERVER_SIGN_KEY, "")
//服务端安全设置
var safetyMeasures: Int by SharedPreference(SP_SERVER_SAFETY_MEASURES, if (TextUtils.isEmpty(serverSignKey)) 0 else 1)
//服务端SM4密钥
var serverSm4Key: String by SharedPreference(SP_SERVER_SM4_KEY, "")
//服务端RSA公钥
var serverPublicKey: String by SharedPreference(SP_SERVER_PUBLIC_KEY, "")
//服务端RSA私钥
var serverPrivateKey: String by SharedPreference(SP_SERVER_PRIVATE_KEY, "")
//时间容差
var timeTolerance: Int by SharedPreference(SP_SERVER_TIME_TOLERANCE, 600)
//自定义web客户端目录
var serverWebPath: String by SharedPreference(SP_SERVER_WEB_PATH, "")
//服务地址
var serverAddress: String by SharedPreference(SP_SERVER_ADDRESS, "")
//服务地址历史记录
var serverHistory: String by SharedPreference(SP_SERVER_HISTORY, "")
//服务端配置
var serverConfig: String by SharedPreference(SP_SERVER_CONFIG, "")
//客户端签名密钥/RSA公钥
var clientSignKey: String by SharedPreference(SP_CLIENT_SIGN_KEY, "")
//服务端安全设置
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)
//是否启用远程发短信
var enableApiSmsSend: Boolean by SharedPreference(SP_ENABLE_API_SMS_SEND, true)
//是否启用远程查短信
var enableApiSmsQuery: Boolean by SharedPreference(SP_ENABLE_API_SMS_QUERY, true)
//是否启用远程查通话
var enableApiCallQuery: Boolean by SharedPreference(SP_ENABLE_API_CALL_QUERY, true)
//是否启用远程查话簿
var enableApiContactQuery: Boolean by SharedPreference(SP_ENABLE_API_CONTACT_QUERY, true)
//是否启用远程查电量
var enableApiBatteryQuery: Boolean by SharedPreference(SP_ENABLE_API_BATTERY_QUERY, true)
//是否启用远程WOL
var enableApiWol: Boolean by SharedPreference(SP_ENABLE_API_WOL, true)
//WOL历史记录
var wolHistory: String by SharedPreference(SP_WOL_HISTORY, "")
//计算签名
fun calcSign(timestamp: String, signSecret: String): String {
val stringToSign = "$timestamp\n" + signSecret
val mac = Mac.getInstance("HmacSHA256")
mac.init(SecretKeySpec(signSecret.toByteArray(StandardCharsets.UTF_8), "HmacSHA256"))
val signData = mac.doFinal(stringToSign.toByteArray(StandardCharsets.UTF_8))
return URLEncoder.encode(String(Base64.encode(signData, Base64.NO_WRAP)), "UTF-8")
}
//校验签名
@Throws(HttpException::class)
fun checkSign(req: BaseRequest<*>) {
val signSecret = serverSignKey
if (TextUtils.isEmpty(signSecret)) return
if (TextUtils.isEmpty(req.sign)) throw HttpException(500, getString(R.string.sign_required))
if (req.timestamp == 0L) throw HttpException(500, getString(R.string.timestamp_required))
val timestamp = System.currentTimeMillis()
val diffTime = kotlin.math.abs(timestamp - req.timestamp)
val tolerance = timeTolerance * 1000L
if (diffTime > tolerance) {
throw HttpException(500, String.format(getString(R.string.timestamp_verify_failed), timestamp, timeTolerance, diffTime))
}
val sign = calcSign(req.timestamp.toString(), signSecret)
if (sign != req.sign) {
Log.e("calcSign", sign)
Log.e("reqSign", req.sign.toString())
throw HttpException(500, getString(R.string.sign_verify_failed))
}
}
//判断版本是否一致
@Throws(HttpException::class)
fun compareVersion(cloneInfo: CloneInfo) {
val versionCodeRequest = cloneInfo.versionCode
if (versionCodeRequest == 0) throw HttpException(500, getString(R.string.version_code_required))
val versionCodeLocal = AppUtils.getAppVersionCode().toString().substring(1)
if (!versionCodeRequest.toString().endsWith(versionCodeLocal)) throw HttpException(500, getString(R.string.inconsistent_version))
}
//导出设置
fun exportSettings(): CloneInfo {
val cloneInfo = CloneInfo()
cloneInfo.versionCode = AppUtils.getAppVersionCode()
cloneInfo.versionName = AppUtils.getAppVersionName()
cloneInfo.enableSms = SettingUtils.enableSms
cloneInfo.enablePhone = SettingUtils.enablePhone
cloneInfo.callType1 = SettingUtils.enableCallType1
cloneInfo.callType2 = SettingUtils.enableCallType2
cloneInfo.callType3 = SettingUtils.enableCallType3
cloneInfo.enableAppNotify = SettingUtils.enableAppNotify
cloneInfo.cancelAppNotify = SettingUtils.enableCancelAppNotify
cloneInfo.enableNotUserPresent = SettingUtils.enableNotUserPresent
cloneInfo.enableLoadAppList = SettingUtils.enableLoadAppList
cloneInfo.enableLoadUserAppList = SettingUtils.enableLoadUserAppList
cloneInfo.enableLoadSystemAppList = SettingUtils.enableLoadSystemAppList
cloneInfo.duplicateMessagesLimits = SettingUtils.duplicateMessagesLimits
cloneInfo.enableBatteryReceiver = SettingUtils.enableBatteryReceiver
cloneInfo.batteryLevelMin = SettingUtils.batteryLevelMin
cloneInfo.batteryLevelMax = SettingUtils.batteryLevelMax
cloneInfo.batteryLevelOnce = SettingUtils.batteryLevelOnce
cloneInfo.enableBatteryCron = SettingUtils.enableBatteryCron
cloneInfo.batteryCronStartTime = SettingUtils.batteryCronStartTime
cloneInfo.batteryCronInterval = SettingUtils.batteryCronInterval
cloneInfo.enableExcludeFromRecents = SettingUtils.enableExcludeFromRecents
cloneInfo.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.toString()
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.toString()
SettingUtils.enableSmsTemplate = cloneInfo.enableSmsTemplate
SettingUtils.smsTemplate = cloneInfo.smsTemplate.toString()
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)
}
}
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 {
//是否是第一次启动
var isFirstOpen: Boolean
get() = MMKVUtils.getBoolean(IS_FIRST_OPEN_KEY, true)
set(isFirstOpen) {
MMKVUtils.put(IS_FIRST_OPEN_KEY, isFirstOpen)
}
var isFirstOpen: Boolean by SharedPreference(IS_FIRST_OPEN_KEY, true)
//是否同意隐私政策
@JvmStatic
var isAgreePrivacy: Boolean
get() = MMKVUtils.getBoolean(IS_AGREE_PRIVACY_KEY, false)
set(isAgreePrivacy) {
MMKVUtils.put(IS_AGREE_PRIVACY_KEY, isAgreePrivacy)
}
var isAgreePrivacy: Boolean by SharedPreference(IS_AGREE_PRIVACY_KEY, false)
//是否转发短信
@JvmStatic
var enableSms: Boolean
get() = MMKVUtils.getBoolean(SP_ENABLE_SMS, false)
set(enableSms) {
MMKVUtils.put(SP_ENABLE_SMS, enableSms)
}
var enableSms: Boolean by SharedPreference(SP_ENABLE_SMS, false)
//是否转发通话
@JvmStatic
var enablePhone: Boolean
get() = MMKVUtils.getBoolean(SP_ENABLE_PHONE, false)
set(enablePhone) {
MMKVUtils.put(SP_ENABLE_PHONE, enablePhone)
}
var enablePhone: Boolean by SharedPreference(SP_ENABLE_PHONE, false)
//是否转发通话——已接来电
@JvmStatic
var enableCallType1: Boolean
get() = MMKVUtils.getBoolean(SP_ENABLE_CALL_TYPE_1, false)
set(enableCallType1) {
MMKVUtils.put(SP_ENABLE_CALL_TYPE_1, enableCallType1)
}
var enableCallType1: Boolean by SharedPreference(SP_ENABLE_CALL_TYPE_1, false)
//是否转发通话——本机去电
@JvmStatic
var enableCallType2: Boolean
get() = MMKVUtils.getBoolean(SP_ENABLE_CALL_TYPE_2, false)
set(enableCallType2) {
MMKVUtils.put(SP_ENABLE_CALL_TYPE_2, enableCallType2)
}
var enableCallType2: Boolean by SharedPreference(SP_ENABLE_CALL_TYPE_2, false)
//是否转发通话——未接来电
@JvmStatic
var enableCallType3: Boolean
get() = MMKVUtils.getBoolean(SP_ENABLE_CALL_TYPE_3, false)
set(enableCallType3) {
MMKVUtils.put(SP_ENABLE_CALL_TYPE_3, enableCallType3)
}
var enableCallType3: Boolean by SharedPreference(SP_ENABLE_CALL_TYPE_3, false)
//是否转发通话——来电提醒
@JvmStatic
var enableCallType4: Boolean
get() = MMKVUtils.getBoolean(SP_ENABLE_CALL_TYPE_4, false)
set(enableCallType4) {
MMKVUtils.put(SP_ENABLE_CALL_TYPE_4, enableCallType4)
}
var enableCallType4: Boolean by SharedPreference(SP_ENABLE_CALL_TYPE_4, false)
//是否转发应用通知
@JvmStatic
var enableAppNotify: Boolean
get() = MMKVUtils.getBoolean(SP_ENABLE_APP_NOTIFY, false)
set(enableAppNotify) {
MMKVUtils.put(SP_ENABLE_APP_NOTIFY, enableAppNotify)
}
var enableAppNotify: Boolean by SharedPreference(SP_ENABLE_APP_NOTIFY, false)
//是否转发应用通知——自动消除通知
@JvmStatic
var enableCancelAppNotify: Boolean
get() = MMKVUtils.getBoolean(SP_ENABLE_CANCEL_APP_NOTIFY, false)
set(enableCancelAppNotify) {
MMKVUtils.put(SP_ENABLE_CANCEL_APP_NOTIFY, enableCancelAppNotify)
}
var enableCancelAppNotify: Boolean by SharedPreference(SP_ENABLE_CANCEL_APP_NOTIFY, false)
//是否转发应用通知——仅锁屏状态
@JvmStatic
var enableNotUserPresent: Boolean
get() = MMKVUtils.getBoolean(SP_ENABLE_NOT_USER_PRESENT, false)
set(enableNotUserPresent) {
MMKVUtils.put(SP_ENABLE_NOT_USER_PRESENT, enableNotUserPresent)
}
var enableNotUserPresent: Boolean by SharedPreference(SP_ENABLE_NOT_USER_PRESENT, false)
//是否加载应用列表
@JvmStatic
var enableLoadAppList: Boolean
get() = MMKVUtils.getBoolean(ENABLE_LOAD_APP_LIST, false)
set(enableLoadAppList) {
MMKVUtils.put(ENABLE_LOAD_APP_LIST, enableLoadAppList)
}
var enableLoadAppList: Boolean by SharedPreference(ENABLE_LOAD_APP_LIST, false)
//是否加载应用列表——用户应用
@JvmStatic
var enableLoadUserAppList: Boolean
get() = MMKVUtils.getBoolean(ENABLE_LOAD_USER_APP_LIST, false)
set(enableLoadUserAppList) {
MMKVUtils.put(ENABLE_LOAD_USER_APP_LIST, enableLoadUserAppList)
}
var enableLoadUserAppList: Boolean by SharedPreference(ENABLE_LOAD_USER_APP_LIST, false)
//是否加载应用列表——系统应用
@JvmStatic
var enableLoadSystemAppList: Boolean
get() = MMKVUtils.getBoolean(ENABLE_LOAD_SYSTEM_APP_LIST, false)
set(enableLoadSystemAppList) {
MMKVUtils.put(ENABLE_LOAD_SYSTEM_APP_LIST, enableLoadSystemAppList)
}
var enableLoadSystemAppList: Boolean by SharedPreference(ENABLE_LOAD_SYSTEM_APP_LIST, false)
//过滤多久内重复消息
@JvmStatic
var duplicateMessagesLimits: Int
get() = MMKVUtils.getInt(SP_DUPLICATE_MESSAGES_LIMITS, 0)
set(duplicateMessagesLimits) {
MMKVUtils.put(SP_DUPLICATE_MESSAGES_LIMITS, duplicateMessagesLimits)
}
var duplicateMessagesLimits: Int by SharedPreference(SP_DUPLICATE_MESSAGES_LIMITS, 0)
//免打扰(禁用转发)时间段——开始
@JvmStatic
var silentPeriodStart: Int
get() = MMKVUtils.getInt(SP_SILENT_PERIOD_START, 0)
set(silentPeriodStart) {
MMKVUtils.put(SP_SILENT_PERIOD_START, silentPeriodStart)
}
var silentPeriodStart: Int by SharedPreference(SP_SILENT_PERIOD_START, 0)
//免打扰(禁用转发)时间段——结束
@JvmStatic
var silentPeriodEnd: Int
get() = MMKVUtils.getInt(SP_SILENT_PERIOD_END, 0)
set(silentPeriodEnd) {
MMKVUtils.put(SP_SILENT_PERIOD_END, silentPeriodEnd)
}
var silentPeriodEnd: Int by SharedPreference(SP_SILENT_PERIOD_END, 0)
//自动删除N天前的转发记录
@JvmStatic
var autoCleanLogsDays: Int
get() = MMKVUtils.getInt(SP_AUTO_CLEAN_LOGS_DAYS, 0)
set(autoCleanLogsDays) {
MMKVUtils.put(SP_AUTO_CLEAN_LOGS_DAYS, autoCleanLogsDays)
}
var autoCleanLogsDays: Int by SharedPreference(SP_AUTO_CLEAN_LOGS_DAYS, 0)
//是否监听电池状态变化
@JvmStatic
var enableBatteryReceiver: Boolean
get() = MMKVUtils.getBoolean(SP_BATTERY_RECEIVER, false)
set(enableBatteryReceiver) {
MMKVUtils.put(SP_BATTERY_RECEIVER, enableBatteryReceiver)
}
var enableBatteryReceiver: Boolean by SharedPreference(SP_BATTERY_RECEIVER, false)
//电量预警当前状态
@JvmStatic
var batteryStatus: Int
get() = MMKVUtils.getInt(SP_BATTERY_STATUS, 0)
set(batteryStatus) {
MMKVUtils.put(SP_BATTERY_STATUS, batteryStatus)
}
var batteryStatus: Int by SharedPreference(SP_BATTERY_STATUS, 0)
//电量预警当前值
@JvmStatic
var batteryLevelCurrent: Int
get() = MMKVUtils.getInt(SP_BATTERY_LEVEL_CURRENT, 0)
set(batteryLevelCurrent) {
MMKVUtils.put(SP_BATTERY_LEVEL_CURRENT, batteryLevelCurrent)
}
var batteryLevelCurrent: Int by SharedPreference(SP_BATTERY_LEVEL_CURRENT, 0)
//电量预警最低值
@JvmStatic
var batteryLevelMin: Int
get() = MMKVUtils.getInt(SP_BATTERY_LEVEL_MIN, 0)
set(batteryLevelMin) {
MMKVUtils.put(SP_BATTERY_LEVEL_MIN, batteryLevelMin)
}
var batteryLevelMin: Int by SharedPreference(SP_BATTERY_LEVEL_MIN, 0)
//电量预警最高值
@JvmStatic
var batteryLevelMax: Int
get() = MMKVUtils.getInt(SP_BATTERY_LEVEL_MAX, 100)
set(batteryLevelMax) {
MMKVUtils.put(SP_BATTERY_LEVEL_MAX, batteryLevelMax)
}
var batteryLevelMax: Int by SharedPreference(SP_BATTERY_LEVEL_MAX, 100)
//是否持续电量预警
@JvmStatic
var batteryLevelOnce: Boolean
get() = MMKVUtils.getBoolean(SP_BATTERY_LEVEL_ONCE, false)
set(batteryLevelOnce) {
MMKVUtils.put(SP_BATTERY_LEVEL_ONCE, batteryLevelOnce)
}
var batteryLevelOnce: Boolean by SharedPreference(SP_BATTERY_LEVEL_ONCE, false)
//是否定时推送电池状态
@JvmStatic
var enableBatteryCron: Boolean
get() = MMKVUtils.getBoolean(SP_BATTERY_CRON, false)
set(enableBatteryCron) {
MMKVUtils.put(SP_BATTERY_CRON, enableBatteryCron)
}
var enableBatteryCron: Boolean by SharedPreference(SP_BATTERY_CRON, false)
//是否定时推送电池状态——开始时间
@JvmStatic
var batteryCronStartTime: String?
get() = MMKVUtils.getString(SP_BATTERY_CRON_START_TIME, "00:00")
set(batteryCronStartTime) {
MMKVUtils.put(SP_BATTERY_CRON_START_TIME, batteryCronStartTime)
}
var batteryCronStartTime: String by SharedPreference(SP_BATTERY_CRON_START_TIME, "00:00")
//是否定时推送电池状态——间隔时间(分钟)
@JvmStatic
var batteryCronInterval: Int
get() = MMKVUtils.getInt(SP_BATTERY_CRON_INTERVAL, 60)
set(batteryCronInterval) {
MMKVUtils.put(SP_BATTERY_CRON_INTERVAL, batteryCronInterval)
}
var batteryCronInterval: Int by SharedPreference(SP_BATTERY_CRON_INTERVAL, 60)
//是否不在最近任务列表中显示
@JvmStatic
var enableExcludeFromRecents: Boolean
get() = MMKVUtils.getBoolean(SP_ENABLE_EXCLUDE_FROM_RECENTS, false)
set(enableExcludeFromRecents) {
MMKVUtils.put(SP_ENABLE_EXCLUDE_FROM_RECENTS, enableExcludeFromRecents)
}
var enableExcludeFromRecents: Boolean by SharedPreference(SP_ENABLE_EXCLUDE_FROM_RECENTS, false)
//是否转发应用通知
@JvmStatic
var enableCactus: Boolean
get() = MMKVUtils.getBoolean(SP_ENABLE_CACTUS, false)
set(enableAppNotify) {
MMKVUtils.put(SP_ENABLE_CACTUS, enableAppNotify)
}
var enableCactus: Boolean by SharedPreference(SP_ENABLE_CACTUS, false)
//是否播放静音音乐
@JvmStatic
var enablePlaySilenceMusic: Boolean
get() = MMKVUtils.getBoolean(SP_ENABLE_PLAY_SILENCE_MUSIC, false)
set(enablePlaySilenceMusic) {
MMKVUtils.put(SP_ENABLE_PLAY_SILENCE_MUSIC, enablePlaySilenceMusic)
}
var enablePlaySilenceMusic: Boolean by SharedPreference(SP_ENABLE_PLAY_SILENCE_MUSIC, false)
//是否启用1像素
@JvmStatic
var enableOnePixelActivity: Boolean
get() = MMKVUtils.getBoolean(SP_ENABLE_ONE_PIXEL_ACTIVITY, false)
set(enableOnePixelActivity) {
MMKVUtils.put(SP_ENABLE_ONE_PIXEL_ACTIVITY, enableOnePixelActivity)
}
var enableOnePixelActivity: Boolean by SharedPreference(SP_ENABLE_ONE_PIXEL_ACTIVITY, false)
//请求接口失败重试次数
@JvmStatic
var requestRetryTimes: Int
get() = MMKVUtils.getInt(SP_REQUEST_RETRY_TIMES, 0)
set(requestRetryTimes) {
MMKVUtils.put(SP_REQUEST_RETRY_TIMES, requestRetryTimes)
}
var requestRetryTimes: Int by SharedPreference(SP_REQUEST_RETRY_TIMES, 0)
//请求接口失败重试间隔(秒)
@JvmStatic
var requestDelayTime: Int
get() = MMKVUtils.getInt(SP_REQUEST_DELAY_TIME, 1)
set(requestDelayTime) {
MMKVUtils.put(SP_REQUEST_DELAY_TIME, requestDelayTime)
}
var requestDelayTime: Int by SharedPreference(SP_REQUEST_DELAY_TIME, 1)
//请求接口失败超时时间(秒)
@JvmStatic
var requestTimeout: Int
get() = MMKVUtils.getInt(SP_REQUEST_TIMEOUT, 10)
set(requestTimeout) {
MMKVUtils.put(SP_REQUEST_TIMEOUT, requestTimeout)
}
var requestTimeout: Int by SharedPreference(SP_REQUEST_TIMEOUT, 10)
//通知内容
@JvmStatic
var notifyContent: String?
get() = MMKVUtils.getString(SP_NOTIFY_CONTENT, getString(R.string.notification_content))
set(notificationContent) {
MMKVUtils.put(SP_NOTIFY_CONTENT, notificationContent)
}
var notifyContent: String by SharedPreference(SP_NOTIFY_CONTENT, getString(R.string.notification_content))
//设备名称
@JvmStatic
var extraDeviceMark: String?
get() = MMKVUtils.getString(SP_EXTRA_DEVICE_MARK, "")
set(extraDeviceMark) {
MMKVUtils.put(SP_EXTRA_DEVICE_MARK, extraDeviceMark)
}
var extraDeviceMark: String by SharedPreference(SP_EXTRA_DEVICE_MARK, "")
//SM1备注
@JvmStatic
var extraSim1: String?
get() = MMKVUtils.getString(SP_EXTRA_SIM1, "")
set(extraSim1) {
MMKVUtils.put(SP_EXTRA_SIM1, extraSim1)
}
var extraSim1: String by SharedPreference(SP_EXTRA_SIM1, "")
//SM2备注
@JvmStatic
var extraSim2: String?
get() = MMKVUtils.getString(SP_EXTRA_SIM2, "")
set(extraSim2) {
MMKVUtils.put(SP_EXTRA_SIM2, extraSim2)
}
var extraSim2: String by SharedPreference(SP_EXTRA_SIM2, "")
//是否启用自定义模板
@JvmStatic
var enableSmsTemplate: Boolean
get() = MMKVUtils.getBoolean(SP_ENABLE_SMS_TEMPLATE, false)
set(enableSmsTemplate) {
MMKVUtils.put(SP_ENABLE_SMS_TEMPLATE, enableSmsTemplate)
}
var enableSmsTemplate: Boolean by SharedPreference(SP_ENABLE_SMS_TEMPLATE, false)
//自定义模板
@JvmStatic
var smsTemplate: String?
get() = MMKVUtils.getString(SP_SMS_TEMPLATE, "")
set(smsTemplate) {
MMKVUtils.put(SP_SMS_TEMPLATE, smsTemplate)
}
var smsTemplate: String by SharedPreference(SP_SMS_TEMPLATE, "")
//是否显示页面帮助
@JvmStatic
var enableHelpTip: Boolean
get() = MMKVUtils.getBoolean(SP_ENABLE_HELP_TIP, false)
set(enableHelpTip) {
MMKVUtils.put(SP_ENABLE_HELP_TIP, enableHelpTip)
}
var enableHelpTip: Boolean by SharedPreference(SP_ENABLE_HELP_TIP, false)
//是否纯客户端模式
@JvmStatic
var enablePureClientMode: Boolean
get() = MMKVUtils.getBoolean(SP_PURE_CLIENT_MODE, false)
set(enablePureClientMode) {
MMKVUtils.put(SP_PURE_CLIENT_MODE, enablePureClientMode)
}
var enablePureClientMode: Boolean by SharedPreference(SP_PURE_CLIENT_MODE, false)
}
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.result.DingtalkInnerRobotResult
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.SettingUtils
import com.idormy.sms.forwarder.utils.SharedPreference
import com.xuexiang.xhttp2.XHttp
import com.xuexiang.xhttp2.cache.model.CacheMode
import com.xuexiang.xhttp2.callback.SimpleCallBack
@ -38,9 +38,8 @@ class DingtalkInnerRobotUtils private constructor() {
rule: Rule?,
logId: Long?,
) {
val accessToken: String? = MMKVUtils.getString("accessToken_" + setting.agentID, "")
val expiresIn: Long = MMKVUtils.getLong("expiresIn_" + setting.agentID, 0L)
var accessToken: String by SharedPreference("accessToken_" + setting.agentID, "")
var expiresIn: Long by SharedPreference("expiresIn_" + setting.agentID, 0L)
if (!TextUtils.isEmpty(accessToken) && expiresIn > System.currentTimeMillis()) {
return sendTextMsg(setting, msgInfo, rule, logId)
}
@ -57,9 +56,7 @@ class DingtalkInnerRobotUtils private constructor() {
val request = XHttp.post(requestUrl)
//设置代理
if ((setting.proxyType == Proxy.Type.HTTP || setting.proxyType == Proxy.Type.SOCKS)
&& !TextUtils.isEmpty(setting.proxyHost) && !TextUtils.isEmpty(setting.proxyPort)
) {
if ((setting.proxyType == Proxy.Type.HTTP || setting.proxyType == Proxy.Type.SOCKS) && !TextUtils.isEmpty(setting.proxyHost) && !TextUtils.isEmpty(setting.proxyPort)) {
//代理服务器的IP和端口号
Log.d(TAG, "proxyHost = ${setting.proxyHost}, proxyPort = ${setting.proxyPort}")
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)))
//代理的鉴权账号密码
if (setting.proxyAuthenticator == true
&& (!TextUtils.isEmpty(setting.proxyUsername) || !TextUtils.isEmpty(setting.proxyPassword))
) {
if (setting.proxyAuthenticator == true && (!TextUtils.isEmpty(setting.proxyUsername) || !TextUtils.isEmpty(setting.proxyPassword))) {
Log.i(TAG, "proxyUsername = ${setting.proxyUsername}, proxyPassword = ${setting.proxyPassword}")
if (setting.proxyType == Proxy.Type.HTTP) {
request.okproxyAuthenticator { _: Route?, response: Response ->
//设置代理服务器账号密码
val credential = Credentials.basic(setting.proxyUsername.toString(), setting.proxyPassword.toString())
response.request().newBuilder()
.header("Proxy-Authorization", credential)
.build()
response.request().newBuilder().header("Proxy-Authorization", credential).build()
}
} else {
Authenticator.setDefault(object : Authenticator() {
@ -95,13 +88,8 @@ class DingtalkInnerRobotUtils private constructor() {
}
}
request.upJson(requestMsg)
.keepJson(true)
.ignoreHttpsCert()
.timeOut((SettingUtils.requestTimeout * 1000).toLong()) //超时时间10s
.cacheMode(CacheMode.NO_CACHE)
.timeStamp(true)
.execute(object : SimpleCallBack<String>() {
request.upJson(requestMsg).keepJson(true).ignoreHttpsCert().timeOut((SettingUtils.requestTimeout * 1000).toLong()) //超时时间10s
.cacheMode(CacheMode.NO_CACHE).timeStamp(true).execute(object : SimpleCallBack<String>() {
override fun onError(e: ApiException) {
Log.e(TAG, e.detailMessage)
@ -113,8 +101,8 @@ class DingtalkInnerRobotUtils private constructor() {
val resp = Gson().fromJson(response, DingtalkInnerRobotResult::class.java)
if (!TextUtils.isEmpty(resp?.accessToken)) {
MMKVUtils.put("accessToken_" + setting.agentID, resp.accessToken)
MMKVUtils.put("expiresIn_" + setting.agentID, System.currentTimeMillis() + ((resp.expireIn ?: 7200) - 120) * 1000L) //提前2分钟过期
accessToken = resp.accessToken.toString()
expiresIn = System.currentTimeMillis() + ((resp.expireIn ?: 7200) - 120) * 1000L //提前2分钟过期
sendTextMsg(setting, msgInfo, rule, logId)
} else {
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)
//设置代理
if ((setting.proxyType == Proxy.Type.HTTP || setting.proxyType == Proxy.Type.SOCKS)
&& !TextUtils.isEmpty(setting.proxyHost) && !TextUtils.isEmpty(setting.proxyPort)
) {
if ((setting.proxyType == Proxy.Type.HTTP || setting.proxyType == Proxy.Type.SOCKS) && !TextUtils.isEmpty(setting.proxyHost) && !TextUtils.isEmpty(setting.proxyPort)) {
//代理服务器的IP和端口号
Log.d(TAG, "proxyHost = ${setting.proxyHost}, proxyPort = ${setting.proxyPort}")
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)))
//代理的鉴权账号密码
if (setting.proxyAuthenticator == true
&& (!TextUtils.isEmpty(setting.proxyUsername) || !TextUtils.isEmpty(setting.proxyPassword))
) {
if (setting.proxyAuthenticator == true && (!TextUtils.isEmpty(setting.proxyUsername) || !TextUtils.isEmpty(setting.proxyPassword))) {
Log.i(TAG, "proxyUsername = ${setting.proxyUsername}, proxyPassword = ${setting.proxyPassword}")
if (setting.proxyType == Proxy.Type.HTTP) {
request.okproxyAuthenticator { _: Route?, response: Response ->
//设置代理服务器账号密码
val credential = Credentials.basic(setting.proxyUsername.toString(), setting.proxyPassword.toString())
response.request().newBuilder()
.header("Proxy-Authorization", credential)
.build()
response.request().newBuilder().header("Proxy-Authorization", credential).build()
}
} else {
Authenticator.setDefault(object : Authenticator() {
@ -203,17 +185,15 @@ class DingtalkInnerRobotUtils private constructor() {
}
}
request.upJson(requestMsg)
.headers("x-acs-dingtalk-access-token", MMKVUtils.getString("accessToken_" + setting.agentID, ""))
val accessToken: String by SharedPreference("accessToken_" + setting.agentID, "")
request.upJson(requestMsg).headers("x-acs-dingtalk-access-token", accessToken)
.keepJson(true)
.ignoreHttpsCert()
.timeOut((SettingUtils.requestTimeout * 1000).toLong()) //超时时间10s
.cacheMode(CacheMode.NO_CACHE)
.retryCount(SettingUtils.requestRetryTimes) //超时重试的次数
.cacheMode(CacheMode.NO_CACHE).retryCount(SettingUtils.requestRetryTimes) //超时重试的次数
.retryDelay(SettingUtils.requestDelayTime) //超时重试的延迟时间
.retryIncreaseDelay(SettingUtils.requestDelayTime) //超时重试叠加延时
.timeStamp(true)
.execute(object : SimpleCallBack<String>() {
.timeStamp(true).execute(object : SimpleCallBack<String>() {
override fun onError(e: ApiException) {
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.result.FeishuAppResult
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.SettingUtils
import com.idormy.sms.forwarder.utils.SharedPreference
import com.xuexiang.xhttp2.XHttp
import com.xuexiang.xhttp2.cache.model.CacheMode
import com.xuexiang.xhttp2.callback.SimpleCallBack
@ -31,8 +31,8 @@ class FeishuAppUtils private constructor() {
logId: Long?,
) {
val accessToken: String? = MMKVUtils.getString("feishu_access_token_" + setting.appId, "")
val expiresIn: Long = MMKVUtils.getLong("feishu_expires_in_" + setting.appId, 0L)
var accessToken: String by SharedPreference("feishu_access_token_" + setting.appId, "")
var expiresIn: Long by SharedPreference("feishu_expires_in_" + setting.appId, 0L)
if (!TextUtils.isEmpty(accessToken) && expiresIn > System.currentTimeMillis()) {
return sendTextMsg(setting, msgInfo, rule, logId)
}
@ -46,14 +46,8 @@ class FeishuAppUtils private constructor() {
val requestMsg: String = Gson().toJson(msgMap)
Log.i(TAG, "requestMsg:$requestMsg")
XHttp.post(requestUrl)
.upJson(requestMsg)
.keepJson(true)
.ignoreHttpsCert()
.timeOut((SettingUtils.requestTimeout * 1000).toLong()) //超时时间10s
.cacheMode(CacheMode.NO_CACHE)
.timeStamp(true)
.execute(object : SimpleCallBack<String>() {
XHttp.post(requestUrl).upJson(requestMsg).keepJson(true).ignoreHttpsCert().timeOut((SettingUtils.requestTimeout * 1000).toLong()) //超时时间10s
.cacheMode(CacheMode.NO_CACHE).timeStamp(true).execute(object : SimpleCallBack<String>() {
override fun onError(e: ApiException) {
Log.e(TAG, e.detailMessage)
@ -65,8 +59,8 @@ class FeishuAppUtils private constructor() {
val resp = Gson().fromJson(response, FeishuAppResult::class.java)
if (!TextUtils.isEmpty(resp?.tenant_access_token)) {
MMKVUtils.put("feishu_access_token_" + setting.appId, resp.tenant_access_token)
MMKVUtils.put("feishu_expires_in_" + setting.appId, System.currentTimeMillis() + ((resp.expire ?: 7010) - 120) * 1000L) //提前2分钟过期
accessToken = resp.tenant_access_token.toString()
expiresIn = System.currentTimeMillis() + ((resp.expire ?: 7010) - 120) * 1000L //提前2分钟过期
sendTextMsg(setting, msgInfo, rule, logId)
} else {
SendUtils.updateLogs(logId, 0, String.format(getString(R.string.request_failed_tips), response))
@ -99,9 +93,7 @@ class FeishuAppUtils private constructor() {
} else {
msgInfo.getTitleForSend(setting.titleTemplate)
}
"{\"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))
"{\"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))
} else {
"{\"text\":\"{{MSG_CONTENT}}\"}".trimIndent().replace("{{MSG_CONTENT}}", jsonInnerStr(content))
}
@ -114,18 +106,14 @@ class FeishuAppUtils private constructor() {
val requestMsg: String = Gson().toJson(textMsgMap)
Log.i(TAG, "requestMsg:$requestMsg")
XHttp.post(requestUrl)
.upJson(requestMsg)
.headers("Authorization", "Bearer " + MMKVUtils.getString("feishu_access_token_" + setting.appId, ""))
.keepJson(true)
val accessToken: String by SharedPreference("feishu_access_token_" + setting.appId, "")
XHttp.post(requestUrl).upJson(requestMsg).headers("Authorization", "Bearer $accessToken").keepJson(true)
//.ignoreHttpsCert()
.timeOut((SettingUtils.requestTimeout * 1000).toLong()) //超时时间10s
.cacheMode(CacheMode.NO_CACHE)
.retryCount(SettingUtils.requestRetryTimes) //超时重试的次数
.cacheMode(CacheMode.NO_CACHE).retryCount(SettingUtils.requestRetryTimes) //超时重试的次数
.retryDelay(SettingUtils.requestDelayTime) //超时重试的延迟时间
.retryIncreaseDelay(SettingUtils.requestDelayTime) //超时重试叠加延时
.timeStamp(true)
.execute(object : SimpleCallBack<String>() {
.timeStamp(true).execute(object : SimpleCallBack<String>() {
override fun onError(e: ApiException) {
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.WeworkAgentResult
import com.idormy.sms.forwarder.entity.setting.WeworkAgentSetting
import com.idormy.sms.forwarder.utils.MMKVUtils
import com.idormy.sms.forwarder.utils.SendUtils
import com.idormy.sms.forwarder.utils.SettingUtils
import com.idormy.sms.forwarder.utils.SharedPreference
import com.xuexiang.xhttp2.XHttp
import com.xuexiang.xhttp2.cache.model.CacheMode
import com.xuexiang.xhttp2.callback.SimpleCallBack
@ -39,8 +39,8 @@ class WeworkAgentUtils private constructor() {
logId: Long?,
) {
val accessToken: String? = MMKVUtils.getString("access_token_" + setting.agentID, "")
val expiresIn: Long = MMKVUtils.getLong("expires_in_" + setting.agentID, 0L)
var accessToken: String by SharedPreference("access_token_" + setting.agentID, "")
var expiresIn: Long by SharedPreference("expires_in_" + setting.agentID, 0L)
if (!TextUtils.isEmpty(accessToken) && expiresIn > System.currentTimeMillis()) {
return sendTextMsg(setting, msgInfo, rule, logId)
}
@ -53,9 +53,7 @@ class WeworkAgentUtils private constructor() {
val request = XHttp.get(getTokenUrl)
//设置代理
if ((setting.proxyType == Proxy.Type.HTTP || setting.proxyType == Proxy.Type.SOCKS)
&& !TextUtils.isEmpty(setting.proxyHost) && !TextUtils.isEmpty(setting.proxyPort)
) {
if ((setting.proxyType == Proxy.Type.HTTP || setting.proxyType == Proxy.Type.SOCKS) && !TextUtils.isEmpty(setting.proxyHost) && !TextUtils.isEmpty(setting.proxyPort)) {
//代理服务器的IP和端口号
Log.d(TAG, "proxyHost = ${setting.proxyHost}, proxyPort = ${setting.proxyPort}")
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)))
//代理的鉴权账号密码
if (setting.proxyAuthenticator == true
&& (!TextUtils.isEmpty(setting.proxyUsername) || !TextUtils.isEmpty(setting.proxyPassword))
) {
if (setting.proxyAuthenticator == true && (!TextUtils.isEmpty(setting.proxyUsername) || !TextUtils.isEmpty(setting.proxyPassword))) {
Log.i(TAG, "proxyUsername = ${setting.proxyUsername}, proxyPassword = ${setting.proxyPassword}")
if (setting.proxyType == Proxy.Type.HTTP) {
request.okproxyAuthenticator { _: Route?, response: Response ->
//设置代理服务器账号密码
val credential = Credentials.basic(setting.proxyUsername.toString(), setting.proxyPassword.toString())
response.request().newBuilder()
.header("Proxy-Authorization", credential)
.build()
response.request().newBuilder().header("Proxy-Authorization", credential).build()
}
} else {
Authenticator.setDefault(object : Authenticator() {
@ -91,12 +85,8 @@ class WeworkAgentUtils private constructor() {
}
}
request.keepJson(true)
.ignoreHttpsCert()
.timeOut((SettingUtils.requestTimeout * 1000).toLong()) //超时时间10s
.cacheMode(CacheMode.NO_CACHE)
.timeStamp(true)
.execute(object : SimpleCallBack<String>() {
request.keepJson(true).ignoreHttpsCert().timeOut((SettingUtils.requestTimeout * 1000).toLong()) //超时时间10s
.cacheMode(CacheMode.NO_CACHE).timeStamp(true).execute(object : SimpleCallBack<String>() {
override fun onError(e: ApiException) {
Log.e(TAG, e.detailMessage)
@ -108,8 +98,8 @@ class WeworkAgentUtils private constructor() {
val resp = Gson().fromJson(response, WeworkAgentResult::class.java)
if (resp?.errcode == 0L) {
MMKVUtils.put("access_token_" + setting.agentID, resp.access_token)
MMKVUtils.put("expires_in_" + setting.agentID, System.currentTimeMillis() + ((resp.expires_in ?: 7200) - 120) * 1000L) //提前2分钟过期
accessToken = resp.access_token.toString()
expiresIn = System.currentTimeMillis() + ((resp.expires_in ?: 7200) - 120) * 1000L //提前2分钟过期
sendTextMsg(setting, msgInfo, rule, logId)
} else {
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()
textText["content"] = content
textMsgMap["text"] = textText
val requestUrl = "https://qyapi.weixin.qq.com/cgi-bin/message/send?access_token=" + MMKVUtils.getString("access_token_" + setting.agentID, "")
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")
val requestMsg: String = Gson().toJson(textMsgMap)
Log.i(TAG, "requestMsg:$requestMsg")
@ -150,9 +141,7 @@ class WeworkAgentUtils private constructor() {
val request = XHttp.post(requestUrl)
//设置代理
if ((setting.proxyType == Proxy.Type.HTTP || setting.proxyType == Proxy.Type.SOCKS)
&& !TextUtils.isEmpty(setting.proxyHost) && !TextUtils.isEmpty(setting.proxyPort)
) {
if ((setting.proxyType == Proxy.Type.HTTP || setting.proxyType == Proxy.Type.SOCKS) && !TextUtils.isEmpty(setting.proxyHost) && !TextUtils.isEmpty(setting.proxyPort)) {
//代理服务器的IP和端口号
Log.d(TAG, "proxyHost = ${setting.proxyHost}, proxyPort = ${setting.proxyPort}")
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)))
//代理的鉴权账号密码
if (setting.proxyAuthenticator == true
&& (!TextUtils.isEmpty(setting.proxyUsername) || !TextUtils.isEmpty(setting.proxyPassword))
) {
if (setting.proxyAuthenticator == true && (!TextUtils.isEmpty(setting.proxyUsername) || !TextUtils.isEmpty(setting.proxyPassword))) {
Log.i(TAG, "proxyUsername = ${setting.proxyUsername}, proxyPassword = ${setting.proxyPassword}")
if (setting.proxyType == Proxy.Type.HTTP) {
request.okproxyAuthenticator { _: Route?, response: Response ->
//设置代理服务器账号密码
val credential = Credentials.basic(setting.proxyUsername.toString(), setting.proxyPassword.toString())
response.request().newBuilder()
.header("Proxy-Authorization", credential)
.build()
response.request().newBuilder().header("Proxy-Authorization", credential).build()
}
} else {
Authenticator.setDefault(object : Authenticator() {
@ -188,16 +173,11 @@ class WeworkAgentUtils private constructor() {
}
}
request.upJson(requestMsg)
.keepJson(true)
.ignoreHttpsCert()
.timeOut((SettingUtils.requestTimeout * 1000).toLong()) //超时时间10s
.cacheMode(CacheMode.NO_CACHE)
.retryCount(SettingUtils.requestRetryTimes) //超时重试的次数
request.upJson(requestMsg).keepJson(true).ignoreHttpsCert().timeOut((SettingUtils.requestTimeout * 1000).toLong()) //超时时间10s
.cacheMode(CacheMode.NO_CACHE).retryCount(SettingUtils.requestRetryTimes) //超时重试的次数
.retryDelay(SettingUtils.requestDelayTime) //超时重试的延迟时间
.retryIncreaseDelay(SettingUtils.requestDelayTime) //超时重试叠加延时
.timeStamp(true)
.execute(object : SimpleCallBack<String>() {
.timeStamp(true).execute(object : SimpleCallBack<String>() {
override fun onError(e: ApiException) {
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.callback.NoTipCallBack
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.xaop.annotation.SingleClick
import com.xuexiang.xhttp2.XHttp
@ -175,11 +175,12 @@ class GuideTipsDialog(context: Context?, tips: List<TipInfo>) :
}
fun setIsIgnoreTips(isIgnore: Boolean): Boolean {
return MMKVUtils.put(KEY_IS_IGNORE_TIPS + AppUtils.getAppVersionCode(), isIgnore)
this.isIgnoreTips = isIgnore
return true
}
val isIgnoreTips: Boolean
get() = MMKVUtils.getBoolean(KEY_IS_IGNORE_TIPS + AppUtils.getAppVersionCode(), false)
var isIgnoreTips: Boolean by SharedPreference(KEY_IS_IGNORE_TIPS + AppUtils.getAppVersionCode(), false)
}
init {

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

Loading…
Cancel
Save