diff --git a/app/build.gradle b/app/build.gradle index ed2295f0..473ac470 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -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'//选填 - //腾讯的键值对存储mmkv:https://github.com/Tencent/MMKV - implementation 'com.tencent:mmkv:1.2.15' //屏幕适配AutoSize:https://github.com/JessYanCoding/AndroidAutoSize implementation 'me.jessyan:autosize:1.2.1' //umeng统计 diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 52c2ee3f..6f0a318c 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -58,6 +58,8 @@ diff --git a/app/src/main/java/com/idormy/sms/forwarder/App.kt b/app/src/main/java/com/idormy/sms/forwarder/App.kt index d27eff28..dd09f5ea 100644 --- a/app/src/main/java/com/idormy/sms/forwarder/App.kt +++ b/app/src/main/java/com/idormy/sms/forwarder/App.kt @@ -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) // 版本更新初始化 diff --git a/app/src/main/java/com/idormy/sms/forwarder/activity/SplashActivity.kt b/app/src/main/java/com/idormy/sms/forwarder/activity/SplashActivity.kt index a769a4c6..47f4e076 100644 --- a/app/src/main/java/com/idormy/sms/forwarder/activity/SplashActivity.kt +++ b/app/src/main/java/com/idormy/sms/forwarder/activity/SplashActivity.kt @@ -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 { diff --git a/app/src/main/java/com/idormy/sms/forwarder/fragment/AboutFragment.kt b/app/src/main/java/com/idormy/sms/forwarder/fragment/AboutFragment.kt index 4b232993..7a5cdf8f 100644 --- a/app/src/main/java/com/idormy/sms/forwarder/fragment/AboutFragment.kt +++ b/app/src/main/java/com/idormy/sms/forwarder/fragment/AboutFragment.kt @@ -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(), 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(), 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) + } + } + } } \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/fragment/ClientFragment.kt b/app/src/main/java/com/idormy/sms/forwarder/fragment/ClientFragment.kt index 60e645ae..f146d2fe 100644 --- a/app/src/main/java/com/idormy/sms/forwarder/fragment/ClientFragment.kt +++ b/app/src/main/java/com/idormy/sms/forwarder/fragment/ClientFragment.kt @@ -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(), View.OnClickListener, RecyclerViewHolder.OnItemClickListener { - - val TAG: String = ClientFragment::class.java.simpleName - private var appContext: App? = null - private var serverConfig: ConfigData? = null - private var serverHistory: MutableMap = 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>() {}.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) //跳转的fragment - .setNewActivity(true).open(this) - } catch (e: Exception) { - e.printStackTrace() - XToastUtils.error(e.message.toString()) - } - } - - private fun queryConfig(needToast: Boolean) { - val requestUrl: String = HttpServerUtils.serverAddress + "/config/query" - Log.i(TAG, "requestUrl:$requestUrl") - - val msgMap: MutableMap = mutableMapOf() - val timestamp = System.currentTimeMillis() - msgMap["timestamp"] = timestamp - - val clientSignKey = HttpServerUtils.clientSignKey.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 = 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() { - 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 = Gson().fromJson(json, object : TypeToken>() {}.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(), View.OnClickListener, RecyclerViewHolder.OnItemClickListener { + + val TAG: String = ClientFragment::class.java.simpleName + private var appContext: App? = null + private var serverConfig: ConfigData? = null + private var serverHistory: MutableMap = 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>() {}.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) //跳转的fragment + .setNewActivity(true).open(this) + } catch (e: Exception) { + e.printStackTrace() + XToastUtils.error(e.message.toString()) + } + } + + private fun queryConfig(needToast: Boolean) { + val requestUrl: String = HttpServerUtils.serverAddress + "/config/query" + Log.i(TAG, "requestUrl:$requestUrl") + + val msgMap: MutableMap = mutableMapOf() + val timestamp = System.currentTimeMillis() + msgMap["timestamp"] = timestamp + + val clientSignKey = HttpServerUtils.clientSignKey + if (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 = 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() { + 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 = Gson().fromJson(json, object : TypeToken>() {}.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() + } + } \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/fragment/SettingsFragment.kt b/app/src/main/java/com/idormy/sms/forwarder/fragment/SettingsFragment.kt index 588731c6..97a9c7ab 100644 --- a/app/src/main/java/com/idormy/sms/forwarder/fragment/SettingsFragment.kt +++ b/app/src/main/java/com/idormy/sms/forwarder/fragment/SettingsFragment.kt @@ -79,23 +79,15 @@ class SettingsFragment : BaseFragment(), 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(), 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(), View.OnClickL //Cactus增强保活措施 switchEnableCactus( - binding!!.sbEnableCactus, - binding!!.scbPlaySilenceMusic, - binding!!.scbOnePixelActivity + binding!!.sbEnableCactus, binding!!.scbPlaySilenceMusic, binding!!.scbOnePixelActivity ) //接口请求失败重试时间间隔 @@ -176,21 +165,17 @@ class SettingsFragment : BaseFragment(), 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().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().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(), 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(), 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(), 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(), 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(), 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(), 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, all: Boolean) { if (all) { XToastUtils.info(R.string.toast_granted_all) @@ -320,11 +297,7 @@ class SettingsFragment : BaseFragment(), 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(), 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, all: Boolean) { if (all) { XToastUtils.info(R.string.toast_granted_all) @@ -410,9 +382,7 @@ class SettingsFragment : BaseFragment(), 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(), 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, all: Boolean) { SettingUtils.enableAppNotify = true sbEnableAppNotify.isChecked = true @@ -441,17 +410,11 @@ class SettingsFragment : BaseFragment(), 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(), 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(), 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(), 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(), 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(), 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(), 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(), 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(), 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(), 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(), 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(), 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(), 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(), 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(), 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(), 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(), View.OnClickL } else { //找不到? 网上的做法都是跳转到设置... 这基本上是没意义的 基本上自启动这个功能是第三方厂商自己写的安全管家类app //所以我是直接跳转到对应的安全管家/安全中心 - intent = - act?.let { context.packageManager.getLaunchIntentForPackage(it) } + intent = act?.let { context.packageManager.getLaunchIntentForPackage(it) } } context.startActivity(intent) has = true diff --git a/app/src/main/java/com/idormy/sms/forwarder/fragment/client/BatteryQueryFragment.kt b/app/src/main/java/com/idormy/sms/forwarder/fragment/client/BatteryQueryFragment.kt index ca816e79..2127fc08 100644 --- a/app/src/main/java/com/idormy/sms/forwarder/fragment/client/BatteryQueryFragment.kt +++ b/app/src/main/java/com/idormy/sms/forwarder/fragment/client/BatteryQueryFragment.kt @@ -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() { - - val TAG: String = BatteryQueryFragment::class.java.simpleName - - override fun viewBindingInflate( - inflater: LayoutInflater, - container: ViewGroup, - ): FragmentClientBatteryQueryBinding { - return FragmentClientBatteryQueryBinding.inflate(inflater, container, false) - } - - override fun initTitle(): TitleBar? { - val titleBar = super.initTitle()!!.setImmersive(false) - titleBar.setTitle(R.string.api_battery_query) - return titleBar - } - - /** - * 初始化控件 - */ - override fun initViews() { - - val requestUrl: String = HttpServerUtils.serverAddress + "/battery/query" - Log.i(TAG, "requestUrl:$requestUrl") - - val msgMap: MutableMap = mutableMapOf() - val timestamp = System.currentTimeMillis() - msgMap["timestamp"] = timestamp - val clientSignKey = HttpServerUtils.clientSignKey - if (!TextUtils.isEmpty(clientSignKey)) { - msgMap["sign"] = HttpServerUtils.calcSign(timestamp.toString(), clientSignKey.toString()) - } - val dataMap: MutableMap = mutableMapOf() - msgMap["data"] = dataMap - - 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() { - 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 = Gson().fromJson(json, object : TypeToken>() {}.type) - if (resp.code == 200) { - XToastUtils.success(ResUtils.getString(R.string.request_succeeded)) - val batteryInfo = resp.data ?: return - - val groupListView = binding!!.infoList - val section = XUIGroupListView.newSection(context) - section.addItemView(groupListView.createItemView(String.format(ResUtils.getString(R.string.battery_level), batteryInfo.level))) {} - if (batteryInfo.scale != "") section.addItemView(groupListView.createItemView(String.format(ResUtils.getString(R.string.battery_scale), batteryInfo.scale))) {} - if (batteryInfo.voltage != "") section.addItemView(groupListView.createItemView(String.format(ResUtils.getString(R.string.battery_voltage), batteryInfo.voltage))) {} - if (batteryInfo.temperature != "") section.addItemView(groupListView.createItemView(String.format(ResUtils.getString(R.string.battery_temperature), batteryInfo.temperature))) {} - section.addItemView(groupListView.createItemView(String.format(ResUtils.getString(R.string.battery_status), batteryInfo.status))) {} - section.addItemView(groupListView.createItemView(String.format(ResUtils.getString(R.string.battery_health), batteryInfo.health))) {} - section.addItemView(groupListView.createItemView(String.format(ResUtils.getString(R.string.battery_plugged), batteryInfo.plugged))) {} - section.addTo(groupListView) - - } else { - XToastUtils.error(ResUtils.getString(R.string.request_failed) + resp.msg) - } - } catch (e: Exception) { - e.printStackTrace() - XToastUtils.error(ResUtils.getString(R.string.request_failed) + response) - } - } - }) - - } - +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() { + + val TAG: String = BatteryQueryFragment::class.java.simpleName + + override fun viewBindingInflate( + inflater: LayoutInflater, + container: ViewGroup, + ): FragmentClientBatteryQueryBinding { + return FragmentClientBatteryQueryBinding.inflate(inflater, container, false) + } + + override fun initTitle(): TitleBar? { + val titleBar = super.initTitle()!!.setImmersive(false) + titleBar.setTitle(R.string.api_battery_query) + return titleBar + } + + /** + * 初始化控件 + */ + override fun initViews() { + + val requestUrl: String = HttpServerUtils.serverAddress + "/battery/query" + Log.i(TAG, "requestUrl:$requestUrl") + + val msgMap: MutableMap = mutableMapOf() + val timestamp = System.currentTimeMillis() + msgMap["timestamp"] = timestamp + val clientSignKey = HttpServerUtils.clientSignKey + if (!TextUtils.isEmpty(clientSignKey)) { + msgMap["sign"] = HttpServerUtils.calcSign(timestamp.toString(), clientSignKey) + } + val dataMap: MutableMap = 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() { + 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 = Gson().fromJson(json, object : TypeToken>() {}.type) + if (resp.code == 200) { + XToastUtils.success(ResUtils.getString(R.string.request_succeeded)) + val batteryInfo = resp.data ?: return + + val groupListView = binding!!.infoList + val section = XUIGroupListView.newSection(context) + section.addItemView(groupListView.createItemView(String.format(ResUtils.getString(R.string.battery_level), batteryInfo.level))) {} + if (batteryInfo.scale != "") section.addItemView(groupListView.createItemView(String.format(ResUtils.getString(R.string.battery_scale), batteryInfo.scale))) {} + if (batteryInfo.voltage != "") section.addItemView(groupListView.createItemView(String.format(ResUtils.getString(R.string.battery_voltage), batteryInfo.voltage))) {} + if (batteryInfo.temperature != "") section.addItemView(groupListView.createItemView(String.format(ResUtils.getString(R.string.battery_temperature), batteryInfo.temperature))) {} + section.addItemView(groupListView.createItemView(String.format(ResUtils.getString(R.string.battery_status), batteryInfo.status))) {} + section.addItemView(groupListView.createItemView(String.format(ResUtils.getString(R.string.battery_health), batteryInfo.health))) {} + section.addItemView(groupListView.createItemView(String.format(ResUtils.getString(R.string.battery_plugged), batteryInfo.plugged))) {} + section.addTo(groupListView) + + } else { + XToastUtils.error(ResUtils.getString(R.string.request_failed) + resp.msg) + } + } catch (e: Exception) { + e.printStackTrace() + XToastUtils.error(ResUtils.getString(R.string.request_failed) + response) + } + } + }) + + } + } \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/fragment/client/CallQueryFragment.kt b/app/src/main/java/com/idormy/sms/forwarder/fragment/client/CallQueryFragment.kt index f6b0f789..c68a9ec5 100644 --- a/app/src/main/java/com/idormy/sms/forwarder/fragment/client/CallQueryFragment.kt +++ b/app/src/main/java/com/idormy/sms/forwarder/fragment/client/CallQueryFragment.kt @@ -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() { - - val TAG: String = CallQueryFragment::class.java.simpleName - private var mAdapter: SimpleDelegateAdapter? = null - private var callType: Int = 3 - private var pageNum: Int = 1 - private val pageSize: Int = 20 - private var keyword: String = "" - - override fun viewBindingInflate( - inflater: LayoutInflater, - container: ViewGroup, - ): FragmentClientCallQueryBinding { - return FragmentClientCallQueryBinding.inflate(inflater, container, false) - } - - override fun initTitle(): TitleBar { - val titleBar = super.initTitle()!!.setImmersive(false) - titleBar.setTitle(R.string.api_call_query) - titleBar!!.addAction(object : TitleBar.ImageAction(R.drawable.ic_query) { - @SingleClick - override fun performAction(view: View) { - binding!!.searchView.showSearch() - } - }) - return titleBar - } - - /** - * 初始化控件 - */ - override fun initViews() { - val virtualLayoutManager = VirtualLayoutManager(requireContext()) - binding!!.recyclerView.layoutManager = virtualLayoutManager - val viewPool = RecyclerView.RecycledViewPool() - binding!!.recyclerView.setRecycledViewPool(viewPool) - viewPool.setMaxRecycledViews(0, 10) - - mAdapter = object : BroccoliSimpleDelegateAdapter( - R.layout.adapter_call_card_view_list_item, - LinearLayoutHelper(), - DataProvider.emptyCallInfo - ) { - override fun onBindData( - holder: RecyclerViewHolder, - model: CallInfo, - position: Int, - ) { - val from = if (TextUtils.isEmpty(model.name)) model.number else model.number + " | " + model.name - holder.text(R.id.tv_from, from) - holder.text(R.id.tv_time, DateUtils.getFriendlyTimeSpanByNow(model.dateLong)) - holder.image(R.id.iv_image, model.typeImageId) - holder.image(R.id.iv_sim_image, model.simImageId) - holder.text(R.id.tv_duration, ResUtils.getString(R.string.call_duration) + model.duration + ResUtils.getString(R.string.seconds)) - holder.image(R.id.iv_copy, R.drawable.ic_copy) - holder.image(R.id.iv_call, R.drawable.ic_phone_out) - holder.image(R.id.iv_reply, R.drawable.ic_reply) - holder.click(R.id.iv_copy) { - XToastUtils.info(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(EVENT_KEY_SIM_SLOT).post(model.simId) - LiveEventBus.get(EVENT_KEY_PHONE_NUMBERS).post(model.number) - PageOption.to(SmsSendFragment::class.java).setNewActivity(true).open((context as XPageActivity?)!!) - } - } - - override fun onBindBroccoli(holder: RecyclerViewHolder, broccoli: Broccoli) { - broccoli.addPlaceholder(PlaceholderHelper.getParameter(holder.findView(R.id.tv_from))) - .addPlaceholder(PlaceholderHelper.getParameter(holder.findView(R.id.tv_time))) - .addPlaceholder(PlaceholderHelper.getParameter(holder.findView(R.id.iv_sim_image))) - .addPlaceholder(PlaceholderHelper.getParameter(holder.findView(R.id.tv_duration))) - .addPlaceholder(PlaceholderHelper.getParameter(holder.findView(R.id.iv_image))) - .addPlaceholder(PlaceholderHelper.getParameter(holder.findView(R.id.iv_copy))) - .addPlaceholder(PlaceholderHelper.getParameter(holder.findView(R.id.iv_call))) - .addPlaceholder(PlaceholderHelper.getParameter(holder.findView(R.id.iv_reply))) - } - } - - val delegateAdapter = DelegateAdapter(virtualLayoutManager) - delegateAdapter.addAdapter(mAdapter) - binding!!.recyclerView.adapter = delegateAdapter - - binding!!.tabBar.setTabTitles(ResUtils.getStringArray(R.array.call_type_option)) - binding!!.tabBar.setOnTabClickListener { _, position -> - //XToastUtils.toast("点击了$title--$position") - callType = 3 - position - loadRemoteData(true) - binding!!.recyclerView.scrollToPosition(0) - } - - //搜索框 - binding!!.searchView.findViewById(com.xuexiang.xui.R.id.search_layout).visibility = View.GONE - binding!!.searchView.setVoiceSearch(true) - binding!!.searchView.setEllipsize(true) - binding!!.searchView.setSuggestions(resources.getStringArray(R.array.query_suggestions)) - binding!!.searchView.setOnQueryTextListener(object : MaterialSearchView.OnQueryTextListener { - override fun onQueryTextSubmit(query: String): Boolean { - SnackbarUtils.Indefinite(view, 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 = 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() { - 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?> = Gson().fromJson(json, object : TypeToken?>>() {}.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() { + + val TAG: String = CallQueryFragment::class.java.simpleName + private var mAdapter: SimpleDelegateAdapter? = null + private var callType: Int = 3 + private var pageNum: Int = 1 + private val pageSize: Int = 20 + private var keyword: String = "" + + override fun viewBindingInflate( + inflater: LayoutInflater, + container: ViewGroup, + ): FragmentClientCallQueryBinding { + return FragmentClientCallQueryBinding.inflate(inflater, container, false) + } + + override fun initTitle(): TitleBar { + val titleBar = super.initTitle()!!.setImmersive(false) + titleBar.setTitle(R.string.api_call_query) + titleBar!!.addAction(object : TitleBar.ImageAction(R.drawable.ic_query) { + @SingleClick + override fun performAction(view: View) { + binding!!.searchView.showSearch() + } + }) + return titleBar + } + + /** + * 初始化控件 + */ + override fun initViews() { + val virtualLayoutManager = VirtualLayoutManager(requireContext()) + binding!!.recyclerView.layoutManager = virtualLayoutManager + val viewPool = RecyclerView.RecycledViewPool() + binding!!.recyclerView.setRecycledViewPool(viewPool) + viewPool.setMaxRecycledViews(0, 10) + + mAdapter = object : BroccoliSimpleDelegateAdapter( + R.layout.adapter_call_card_view_list_item, + LinearLayoutHelper(), + DataProvider.emptyCallInfo + ) { + override fun onBindData( + holder: RecyclerViewHolder, + model: CallInfo, + position: Int, + ) { + val from = if (TextUtils.isEmpty(model.name)) model.number else model.number + " | " + model.name + holder.text(R.id.tv_from, from) + holder.text(R.id.tv_time, DateUtils.getFriendlyTimeSpanByNow(model.dateLong)) + holder.image(R.id.iv_image, model.typeImageId) + holder.image(R.id.iv_sim_image, model.simImageId) + holder.text(R.id.tv_duration, ResUtils.getString(R.string.call_duration) + model.duration + ResUtils.getString(R.string.seconds)) + holder.image(R.id.iv_copy, R.drawable.ic_copy) + holder.image(R.id.iv_call, R.drawable.ic_phone_out) + holder.image(R.id.iv_reply, R.drawable.ic_reply) + holder.click(R.id.iv_copy) { + XToastUtils.info(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(EVENT_KEY_SIM_SLOT).post(model.simId) + LiveEventBus.get(EVENT_KEY_PHONE_NUMBERS).post(model.number) + PageOption.to(SmsSendFragment::class.java).setNewActivity(true).open((context as XPageActivity?)!!) + } + } + + override fun onBindBroccoli(holder: RecyclerViewHolder, broccoli: Broccoli) { + broccoli.addPlaceholder(PlaceholderHelper.getParameter(holder.findView(R.id.tv_from))) + .addPlaceholder(PlaceholderHelper.getParameter(holder.findView(R.id.tv_time))) + .addPlaceholder(PlaceholderHelper.getParameter(holder.findView(R.id.iv_sim_image))) + .addPlaceholder(PlaceholderHelper.getParameter(holder.findView(R.id.tv_duration))) + .addPlaceholder(PlaceholderHelper.getParameter(holder.findView(R.id.iv_image))) + .addPlaceholder(PlaceholderHelper.getParameter(holder.findView(R.id.iv_copy))) + .addPlaceholder(PlaceholderHelper.getParameter(holder.findView(R.id.iv_call))) + .addPlaceholder(PlaceholderHelper.getParameter(holder.findView(R.id.iv_reply))) + } + } + + val delegateAdapter = DelegateAdapter(virtualLayoutManager) + delegateAdapter.addAdapter(mAdapter) + binding!!.recyclerView.adapter = delegateAdapter + + binding!!.tabBar.setTabTitles(ResUtils.getStringArray(R.array.call_type_option)) + binding!!.tabBar.setOnTabClickListener { _, position -> + //XToastUtils.toast("点击了$title--$position") + callType = 3 - position + loadRemoteData(true) + binding!!.recyclerView.scrollToPosition(0) + } + + //搜索框 + binding!!.searchView.findViewById(com.xuexiang.xui.R.id.search_layout).visibility = View.GONE + binding!!.searchView.setVoiceSearch(true) + binding!!.searchView.setEllipsize(true) + binding!!.searchView.setSuggestions(resources.getStringArray(R.array.query_suggestions)) + binding!!.searchView.setOnQueryTextListener(object : MaterialSearchView.OnQueryTextListener { + override fun onQueryTextSubmit(query: String): Boolean { + SnackbarUtils.Indefinite(view, 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 = 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() { + 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?> = Gson().fromJson(json, object : TypeToken?>>() {}.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) + } + } + }) + + } + } \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/fragment/client/CloneFragment.kt b/app/src/main/java/com/idormy/sms/forwarder/fragment/client/CloneFragment.kt index 7fd0430d..4d37d57b 100644 --- a/app/src/main/java/com/idormy/sms/forwarder/fragment/client/CloneFragment.kt +++ b/app/src/main/java/com/idormy/sms/forwarder/fragment/client/CloneFragment.kt @@ -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(), 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, all: Boolean) { - backupPath = - Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).path - binding!!.tvBackupPath.text = backupPath + File.separator + backupFile - } - - override fun onDenied(permissions: List, never: Boolean) { - if (never) { - XToastUtils.error(R.string.toast_denied_never) - // 如果是被永久拒绝就跳转到应用权限系统设置页面 - XXPermissions.startPermissionActivity(requireContext(), permissions) - } else { - XToastUtils.error(R.string.toast_denied) - } - binding!!.tvBackupPath.text = 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 { _, _, _ -> 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 = 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() { - 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 = Gson().fromJson(json, object : TypeToken>() {}.type) - if (resp.code == 200) { - XToastUtils.success(ResUtils.getString(R.string.request_succeeded)) - } else { - XToastUtils.error(ResUtils.getString(R.string.request_failed) + resp.msg) - } - } catch (e: Exception) { - e.printStackTrace() - XToastUtils.error(ResUtils.getString(R.string.request_failed) + response) - } - 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 = mutableMapOf() - val timestamp = System.currentTimeMillis() - msgMap["timestamp"] = timestamp - val clientSignKey = HttpServerUtils.clientSignKey - if (!TextUtils.isEmpty(clientSignKey)) { - msgMap["sign"] = - HttpServerUtils.calcSign(timestamp.toString(), clientSignKey.toString()) - } - - val dataMap: MutableMap = mutableMapOf() - dataMap["version_code"] = AppUtils.getAppVersionCode() - msgMap["data"] = dataMap - - 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() { - 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 { _, _, _ -> Date() }) - val gson = builder.create() - val resp: BaseResponse = gson.fromJson(json, object : TypeToken>() {}.type) - if (resp.code == 200) { - val cloneInfo = resp.data - Log.d(TAG, "cloneInfo = $cloneInfo") - - if (cloneInfo == null) { - XToastUtils.error(ResUtils.getString(R.string.request_failed)) - 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(), 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, all: Boolean) { + backupPath = + Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).path + binding!!.tvBackupPath.text = backupPath + File.separator + backupFile + } + + override fun onDenied(permissions: List, never: Boolean) { + if (never) { + XToastUtils.error(R.string.toast_denied_never) + // 如果是被永久拒绝就跳转到应用权限系统设置页面 + XXPermissions.startPermissionActivity(requireContext(), permissions) + } else { + XToastUtils.error(R.string.toast_denied) + } + binding!!.tvBackupPath.text = 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 { _, _, _ -> 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 = 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() { + 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 = Gson().fromJson(json, object : TypeToken>() {}.type) + if (resp.code == 200) { + XToastUtils.success(ResUtils.getString(R.string.request_succeeded)) + } else { + XToastUtils.error(ResUtils.getString(R.string.request_failed) + resp.msg) + } + } catch (e: Exception) { + e.printStackTrace() + XToastUtils.error(ResUtils.getString(R.string.request_failed) + response) + } + 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 = 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 = 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() { + 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 { _, _, _ -> Date() }) + val gson = builder.create() + val resp: BaseResponse = gson.fromJson(json, object : TypeToken>() {}.type) + if (resp.code == 200) { + val cloneInfo = resp.data + Log.d(TAG, "cloneInfo = $cloneInfo") + + if (cloneInfo == null) { + XToastUtils.error(ResUtils.getString(R.string.request_failed)) + 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() + } } \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/fragment/client/ContactQueryFragment.kt b/app/src/main/java/com/idormy/sms/forwarder/fragment/client/ContactQueryFragment.kt index 75b6aa18..cccd5273 100644 --- a/app/src/main/java/com/idormy/sms/forwarder/fragment/client/ContactQueryFragment.kt +++ b/app/src/main/java/com/idormy/sms/forwarder/fragment/client/ContactQueryFragment.kt @@ -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() { - - val TAG: String = ContactQueryFragment::class.java.simpleName - private var mAdapter: SimpleDelegateAdapter? = null - private var keyword: String = "" - - override fun viewBindingInflate( - inflater: LayoutInflater, - container: ViewGroup, - ): FragmentClientContactQueryBinding { - return FragmentClientContactQueryBinding.inflate(inflater, container, false) - } - - override fun initTitle(): TitleBar { - val titleBar = super.initTitle()!!.setImmersive(false) - titleBar.setTitle(R.string.api_contact_query) - titleBar!!.addAction(object : TitleBar.ImageAction(R.drawable.ic_query) { - @SingleClick - override fun performAction(view: View) { - binding!!.searchView.showSearch() - } - }) - return titleBar - } - - /** - * 初始化控件 - */ - override fun initViews() { - val virtualLayoutManager = VirtualLayoutManager(requireContext()) - binding!!.recyclerView.layoutManager = virtualLayoutManager - val viewPool = RecyclerView.RecycledViewPool() - binding!!.recyclerView.setRecycledViewPool(viewPool) - viewPool.setMaxRecycledViews(0, 10) - - mAdapter = object : BroccoliSimpleDelegateAdapter( - R.layout.adapter_contact_card_view_list_item, - LinearLayoutHelper(), - DataProvider.emptyContactInfo - ) { - override fun onBindData( - holder: RecyclerViewHolder, - model: ContactInfo, - position: Int, - ) { - holder.text(R.id.sb_letter, model.firstLetter) - holder.text(R.id.tv_name, model.name) - holder.text(R.id.tv_phone_number, model.phoneNumber) - holder.image(R.id.iv_copy, R.drawable.ic_copy) - holder.image(R.id.iv_call, R.drawable.ic_phone_out) - holder.image(R.id.iv_reply, R.drawable.ic_reply) - holder.click(R.id.iv_copy) { - val str = model.toString() - XToastUtils.info(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(EVENT_KEY_PHONE_NUMBERS).post(model.phoneNumber) - PageOption.to(SmsSendFragment::class.java).setNewActivity(true).open((context as XPageActivity?)!!) - } - } - - override fun onBindBroccoli(holder: RecyclerViewHolder, broccoli: Broccoli) { - broccoli.addPlaceholder(PlaceholderHelper.getParameter(holder.findView(R.id.tv_name))) - .addPlaceholder(PlaceholderHelper.getParameter(holder.findView(R.id.tv_phone_number))) - .addPlaceholder(PlaceholderHelper.getParameter(holder.findView(R.id.sb_letter))) - .addPlaceholder(PlaceholderHelper.getParameter(holder.findView(R.id.iv_copy))) - .addPlaceholder(PlaceholderHelper.getParameter(holder.findView(R.id.iv_call))) - .addPlaceholder(PlaceholderHelper.getParameter(holder.findView(R.id.iv_reply))) - } - } - - val delegateAdapter = DelegateAdapter(virtualLayoutManager) - delegateAdapter.addAdapter(mAdapter) - binding!!.recyclerView.adapter = delegateAdapter - - //搜索框 - binding!!.searchView.findViewById(com.xuexiang.xui.R.id.search_layout).visibility = View.GONE - binding!!.searchView.setVoiceSearch(true) - binding!!.searchView.setEllipsize(true) - binding!!.searchView.setSuggestions(resources.getStringArray(R.array.query_suggestions)) - binding!!.searchView.setOnQueryTextListener(object : MaterialSearchView.OnQueryTextListener { - override fun onQueryTextSubmit(query: String): Boolean { - SnackbarUtils.Indefinite(view, 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 = 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() { - 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?> = Gson().fromJson(json, object : TypeToken?>>() {}.type) - if (resp.code == 200) { - //XToastUtils.success(ResUtils.getString(R.string.request_succeeded)) - mAdapter!!.refresh(resp.data) - binding!!.refreshLayout.finishRefresh() - binding!!.recyclerView.scrollToPosition(0) - } else { - XToastUtils.error(ResUtils.getString(R.string.request_failed) + resp.msg) - } - } catch (e: Exception) { - e.printStackTrace() - XToastUtils.error(ResUtils.getString(R.string.request_failed) + response) - } - } - }) - - } - +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() { + + val TAG: String = ContactQueryFragment::class.java.simpleName + private var mAdapter: SimpleDelegateAdapter? = null + private var keyword: String = "" + + override fun viewBindingInflate( + inflater: LayoutInflater, + container: ViewGroup, + ): FragmentClientContactQueryBinding { + return FragmentClientContactQueryBinding.inflate(inflater, container, false) + } + + override fun initTitle(): TitleBar { + val titleBar = super.initTitle()!!.setImmersive(false) + titleBar.setTitle(R.string.api_contact_query) + titleBar!!.addAction(object : TitleBar.ImageAction(R.drawable.ic_query) { + @SingleClick + override fun performAction(view: View) { + binding!!.searchView.showSearch() + } + }) + return titleBar + } + + /** + * 初始化控件 + */ + override fun initViews() { + val virtualLayoutManager = VirtualLayoutManager(requireContext()) + binding!!.recyclerView.layoutManager = virtualLayoutManager + val viewPool = RecyclerView.RecycledViewPool() + binding!!.recyclerView.setRecycledViewPool(viewPool) + viewPool.setMaxRecycledViews(0, 10) + + mAdapter = object : BroccoliSimpleDelegateAdapter( + R.layout.adapter_contact_card_view_list_item, + LinearLayoutHelper(), + DataProvider.emptyContactInfo + ) { + override fun onBindData( + holder: RecyclerViewHolder, + model: ContactInfo, + position: Int, + ) { + holder.text(R.id.sb_letter, model.firstLetter) + holder.text(R.id.tv_name, model.name) + holder.text(R.id.tv_phone_number, model.phoneNumber) + holder.image(R.id.iv_copy, R.drawable.ic_copy) + holder.image(R.id.iv_call, R.drawable.ic_phone_out) + holder.image(R.id.iv_reply, R.drawable.ic_reply) + holder.click(R.id.iv_copy) { + val str = model.toString() + XToastUtils.info(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(EVENT_KEY_PHONE_NUMBERS).post(model.phoneNumber) + PageOption.to(SmsSendFragment::class.java).setNewActivity(true).open((context as XPageActivity?)!!) + } + } + + override fun onBindBroccoli(holder: RecyclerViewHolder, broccoli: Broccoli) { + broccoli.addPlaceholder(PlaceholderHelper.getParameter(holder.findView(R.id.tv_name))) + .addPlaceholder(PlaceholderHelper.getParameter(holder.findView(R.id.tv_phone_number))) + .addPlaceholder(PlaceholderHelper.getParameter(holder.findView(R.id.sb_letter))) + .addPlaceholder(PlaceholderHelper.getParameter(holder.findView(R.id.iv_copy))) + .addPlaceholder(PlaceholderHelper.getParameter(holder.findView(R.id.iv_call))) + .addPlaceholder(PlaceholderHelper.getParameter(holder.findView(R.id.iv_reply))) + } + } + + val delegateAdapter = DelegateAdapter(virtualLayoutManager) + delegateAdapter.addAdapter(mAdapter) + binding!!.recyclerView.adapter = delegateAdapter + + //搜索框 + binding!!.searchView.findViewById(com.xuexiang.xui.R.id.search_layout).visibility = View.GONE + binding!!.searchView.setVoiceSearch(true) + binding!!.searchView.setEllipsize(true) + binding!!.searchView.setSuggestions(resources.getStringArray(R.array.query_suggestions)) + binding!!.searchView.setOnQueryTextListener(object : MaterialSearchView.OnQueryTextListener { + override fun onQueryTextSubmit(query: String): Boolean { + SnackbarUtils.Indefinite(view, 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 = 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() { + 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?> = Gson().fromJson(json, object : TypeToken?>>() {}.type) + if (resp.code == 200) { + //XToastUtils.success(ResUtils.getString(R.string.request_succeeded)) + mAdapter!!.refresh(resp.data) + binding!!.refreshLayout.finishRefresh() + binding!!.recyclerView.scrollToPosition(0) + } else { + XToastUtils.error(ResUtils.getString(R.string.request_failed) + resp.msg) + } + } catch (e: Exception) { + e.printStackTrace() + XToastUtils.error(ResUtils.getString(R.string.request_failed) + response) + } + } + }) + + } + } \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/fragment/client/SmsQueryFragment.kt b/app/src/main/java/com/idormy/sms/forwarder/fragment/client/SmsQueryFragment.kt index ea995bea..51d5b8f1 100644 --- a/app/src/main/java/com/idormy/sms/forwarder/fragment/client/SmsQueryFragment.kt +++ b/app/src/main/java/com/idormy/sms/forwarder/fragment/client/SmsQueryFragment.kt @@ -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() { - - val TAG: String = SmsQueryFragment::class.java.simpleName - private var mAdapter: SimpleDelegateAdapter? = null - private var smsType: Int = 1 - private var pageNum: Int = 1 - private val pageSize: Int = 20 - private var keyword: String = "" - - override fun viewBindingInflate( - inflater: LayoutInflater, - container: ViewGroup, - ): FragmentClientSmsQueryBinding { - return FragmentClientSmsQueryBinding.inflate(inflater, container, false) - } - - override fun initTitle(): TitleBar { - val titleBar = super.initTitle()!!.setImmersive(false) - titleBar.setTitle(R.string.api_sms_query) - titleBar!!.addAction(object : TitleBar.ImageAction(R.drawable.ic_query) { - @SingleClick - override fun performAction(view: View) { - binding!!.searchView.showSearch() - } - }) - return titleBar - } - - /** - * 初始化控件 - */ - override fun initViews() { - val virtualLayoutManager = VirtualLayoutManager(requireContext()) - binding!!.recyclerView.layoutManager = virtualLayoutManager - val viewPool = RecyclerView.RecycledViewPool() - binding!!.recyclerView.setRecycledViewPool(viewPool) - viewPool.setMaxRecycledViews(0, 10) - - mAdapter = object : BroccoliSimpleDelegateAdapter( - R.layout.adapter_sms_card_view_list_item, - LinearLayoutHelper(), - emptySmsInfo - ) { - override fun onBindData( - holder: RecyclerViewHolder, - model: SmsInfo, - position: Int, - ) { - holder.text(R.id.tv_from, model.number) - holder.text(R.id.tv_time, DateUtils.getFriendlyTimeSpanByNow(model.date)) - holder.image(R.id.iv_image, model.typeImageId) - holder.image(R.id.iv_sim_image, model.simImageId) - holder.text(R.id.tv_content, model.content) - holder.image(R.id.iv_reply, R.drawable.ic_reply) - holder.click(R.id.iv_reply) { - XToastUtils.info(getString(R.string.remote_sms) + model.number) - LiveEventBus.get(EVENT_KEY_SIM_SLOT).post(model.simId) - LiveEventBus.get(EVENT_KEY_PHONE_NUMBERS).post(model.number) - PageOption.to(SmsSendFragment::class.java).setNewActivity(true).open((context as XPageActivity?)!!) - } - } - - override fun onBindBroccoli(holder: RecyclerViewHolder, broccoli: Broccoli) { - broccoli.addPlaceholder(PlaceholderHelper.getParameter(holder.findView(R.id.tv_from))) - .addPlaceholder(PlaceholderHelper.getParameter(holder.findView(R.id.tv_time))) - .addPlaceholder(PlaceholderHelper.getParameter(holder.findView(R.id.iv_sim_image))) - .addPlaceholder(PlaceholderHelper.getParameter(holder.findView(R.id.tv_content))) - .addPlaceholder(PlaceholderHelper.getParameter(holder.findView(R.id.iv_image))) - .addPlaceholder(PlaceholderHelper.getParameter(holder.findView(R.id.iv_reply))) - } - - } - - val delegateAdapter = DelegateAdapter(virtualLayoutManager) - delegateAdapter.addAdapter(mAdapter) - binding!!.recyclerView.adapter = delegateAdapter - - binding!!.tabBar.setTabTitles(ResUtils.getStringArray(R.array.sms_type_option)) - binding!!.tabBar.setOnTabClickListener { _, position -> - //XToastUtils.toast("点击了$title--$position") - smsType = position + 1 - loadRemoteData(true) - binding!!.recyclerView.scrollToPosition(0) - } - - //搜索框 - binding!!.searchView.findViewById(com.xuexiang.xui.R.id.search_layout).visibility = View.GONE - binding!!.searchView.setVoiceSearch(true) - binding!!.searchView.setEllipsize(true) - binding!!.searchView.setSuggestions(resources.getStringArray(R.array.query_suggestions)) - binding!!.searchView.setOnQueryTextListener(object : MaterialSearchView.OnQueryTextListener { - override fun onQueryTextSubmit(query: String): Boolean { - SnackbarUtils.Indefinite(view, 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 = 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() { - 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?> = Gson().fromJson(json, object : TypeToken?>>() {}.type) - if (resp.code == 200) { - //XToastUtils.success(ResUtils.getString(R.string.request_succeeded)) - pageNum++ - if (refresh) { - mAdapter!!.refresh(resp.data) - binding!!.refreshLayout.finishRefresh() - binding!!.recyclerView.scrollToPosition(0) - } else { - mAdapter!!.loadMore(resp.data) - binding!!.refreshLayout.finishLoadMore() - } - } else { - XToastUtils.error(ResUtils.getString(R.string.request_failed) + resp.msg) - } - } catch (e: Exception) { - e.printStackTrace() - XToastUtils.error(ResUtils.getString(R.string.request_failed) + response) - } - } - }) - - } - +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() { + + val TAG: String = SmsQueryFragment::class.java.simpleName + private var mAdapter: SimpleDelegateAdapter? = null + private var smsType: Int = 1 + private var pageNum: Int = 1 + private val pageSize: Int = 20 + private var keyword: String = "" + + override fun viewBindingInflate( + inflater: LayoutInflater, + container: ViewGroup, + ): FragmentClientSmsQueryBinding { + return FragmentClientSmsQueryBinding.inflate(inflater, container, false) + } + + override fun initTitle(): TitleBar { + val titleBar = super.initTitle()!!.setImmersive(false) + titleBar.setTitle(R.string.api_sms_query) + titleBar!!.addAction(object : TitleBar.ImageAction(R.drawable.ic_query) { + @SingleClick + override fun performAction(view: View) { + binding!!.searchView.showSearch() + } + }) + return titleBar + } + + /** + * 初始化控件 + */ + override fun initViews() { + val virtualLayoutManager = VirtualLayoutManager(requireContext()) + binding!!.recyclerView.layoutManager = virtualLayoutManager + val viewPool = RecyclerView.RecycledViewPool() + binding!!.recyclerView.setRecycledViewPool(viewPool) + viewPool.setMaxRecycledViews(0, 10) + + mAdapter = object : BroccoliSimpleDelegateAdapter( + R.layout.adapter_sms_card_view_list_item, + LinearLayoutHelper(), + emptySmsInfo + ) { + override fun onBindData( + holder: RecyclerViewHolder, + model: SmsInfo, + position: Int, + ) { + holder.text(R.id.tv_from, model.number) + holder.text(R.id.tv_time, DateUtils.getFriendlyTimeSpanByNow(model.date)) + holder.image(R.id.iv_image, model.typeImageId) + holder.image(R.id.iv_sim_image, model.simImageId) + holder.text(R.id.tv_content, model.content) + holder.image(R.id.iv_reply, R.drawable.ic_reply) + holder.click(R.id.iv_reply) { + XToastUtils.info(getString(R.string.remote_sms) + model.number) + LiveEventBus.get(EVENT_KEY_SIM_SLOT).post(model.simId) + LiveEventBus.get(EVENT_KEY_PHONE_NUMBERS).post(model.number) + PageOption.to(SmsSendFragment::class.java).setNewActivity(true).open((context as XPageActivity?)!!) + } + } + + override fun onBindBroccoli(holder: RecyclerViewHolder, broccoli: Broccoli) { + broccoli.addPlaceholder(PlaceholderHelper.getParameter(holder.findView(R.id.tv_from))) + .addPlaceholder(PlaceholderHelper.getParameter(holder.findView(R.id.tv_time))) + .addPlaceholder(PlaceholderHelper.getParameter(holder.findView(R.id.iv_sim_image))) + .addPlaceholder(PlaceholderHelper.getParameter(holder.findView(R.id.tv_content))) + .addPlaceholder(PlaceholderHelper.getParameter(holder.findView(R.id.iv_image))) + .addPlaceholder(PlaceholderHelper.getParameter(holder.findView(R.id.iv_reply))) + } + + } + + val delegateAdapter = DelegateAdapter(virtualLayoutManager) + delegateAdapter.addAdapter(mAdapter) + binding!!.recyclerView.adapter = delegateAdapter + + binding!!.tabBar.setTabTitles(ResUtils.getStringArray(R.array.sms_type_option)) + binding!!.tabBar.setOnTabClickListener { _, position -> + //XToastUtils.toast("点击了$title--$position") + smsType = position + 1 + loadRemoteData(true) + binding!!.recyclerView.scrollToPosition(0) + } + + //搜索框 + binding!!.searchView.findViewById(com.xuexiang.xui.R.id.search_layout).visibility = View.GONE + binding!!.searchView.setVoiceSearch(true) + binding!!.searchView.setEllipsize(true) + binding!!.searchView.setSuggestions(resources.getStringArray(R.array.query_suggestions)) + binding!!.searchView.setOnQueryTextListener(object : MaterialSearchView.OnQueryTextListener { + override fun onQueryTextSubmit(query: String): Boolean { + SnackbarUtils.Indefinite(view, 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 = 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() { + 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?> = Gson().fromJson(json, object : TypeToken?>>() {}.type) + if (resp.code == 200) { + //XToastUtils.success(ResUtils.getString(R.string.request_succeeded)) + pageNum++ + if (refresh) { + mAdapter!!.refresh(resp.data) + binding!!.refreshLayout.finishRefresh() + binding!!.recyclerView.scrollToPosition(0) + } else { + mAdapter!!.loadMore(resp.data) + binding!!.refreshLayout.finishLoadMore() + } + } else { + XToastUtils.error(ResUtils.getString(R.string.request_failed) + resp.msg) + } + } catch (e: Exception) { + e.printStackTrace() + XToastUtils.error(ResUtils.getString(R.string.request_failed) + response) + } + } + }) + + } + } \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/fragment/client/SmsSendFragment.kt b/app/src/main/java/com/idormy/sms/forwarder/fragment/client/SmsSendFragment.kt index a0bc8b01..7ba2a56e 100644 --- a/app/src/main/java/com/idormy/sms/forwarder/fragment/client/SmsSendFragment.kt +++ b/app/src/main/java/com/idormy/sms/forwarder/fragment/client/SmsSendFragment.kt @@ -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(), 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() {}.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 = mutableMapOf() - val timestamp = System.currentTimeMillis() - msgMap["timestamp"] = timestamp - val clientSignKey = HttpServerUtils.clientSignKey - if (!TextUtils.isEmpty(clientSignKey)) { - msgMap["sign"] = HttpServerUtils.calcSign(timestamp.toString(), clientSignKey.toString()) - } - - val phoneNumbers = binding!!.etPhoneNumbers.text.toString() - val phoneRegex = getString(R.string.phone_numbers_regex).toRegex() - if (!phoneRegex.matches(phoneNumbers)) { - XToastUtils.error(ResUtils.getString(R.string.phone_numbers_error)) - return - } - - val msgContent = binding!!.etMsgContent.text.toString() - val msgRegex = getString(R.string.msg_content_regex).toRegex() - if (!msgRegex.matches(msgContent)) { - XToastUtils.error(ResUtils.getString(R.string.msg_content_error)) - return - } - - val dataMap: MutableMap = mutableMapOf() - dataMap["sim_slot"] = if (binding!!.rgSimSlot.checkedRadioButtonId == R.id.rb_sim_slot_2) 2 else 1 - dataMap["phone_numbers"] = phoneNumbers - dataMap["msg_content"] = msgContent - msgMap["data"] = dataMap - - 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() { - 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 = Gson().fromJson(json, object : TypeToken>() {}.type) - if (resp.code == 200) { - XToastUtils.success(ResUtils.getString(R.string.request_succeeded)) - } else { - XToastUtils.error(ResUtils.getString(R.string.request_failed) + resp.msg) - } - } catch (e: Exception) { - e.printStackTrace() - XToastUtils.error(ResUtils.getString(R.string.request_failed) + response) - } - 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(), 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() {}.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 = 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 = 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() { + 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 = Gson().fromJson(json, object : TypeToken>() {}.type) + if (resp.code == 200) { + XToastUtils.success(ResUtils.getString(R.string.request_succeeded)) + } else { + XToastUtils.error(ResUtils.getString(R.string.request_failed) + resp.msg) + } + } catch (e: Exception) { + e.printStackTrace() + XToastUtils.error(ResUtils.getString(R.string.request_failed) + response) + } + mCountDownHelper?.finish() + } + }) + } + else -> {} + } + } + + override fun onDestroyView() { + if (mCountDownHelper != null) mCountDownHelper!!.recycle() + super.onDestroyView() + } + } \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/fragment/client/WolSendFragment.kt b/app/src/main/java/com/idormy/sms/forwarder/fragment/client/WolSendFragment.kt index 97afb608..987ef71a 100644 --- a/app/src/main/java/com/idormy/sms/forwarder/fragment/client/WolSendFragment.kt +++ b/app/src/main/java/com/idormy/sms/forwarder/fragment/client/WolSendFragment.kt @@ -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(), View.OnClickListener { - - val TAG: String = WolSendFragment::class.java.simpleName - private var mCountDownHelper: CountDownButtonHelper? = null - private var wolHistory: MutableMap = 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>() {}.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 = 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 = 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() { - 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 = Gson().fromJson(json, object : TypeToken>() {}.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(), View.OnClickListener { + + val TAG: String = WolSendFragment::class.java.simpleName + private var mCountDownHelper: CountDownButtonHelper? = null + private var wolHistory: MutableMap = 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>() {}.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 = 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 = 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() { + 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 = Gson().fromJson(json, object : TypeToken>() {}.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() + } + } \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/receiver/BootReceiver.kt b/app/src/main/java/com/idormy/sms/forwarder/receiver/BootReceiver.kt index da74601b..af853204 100644 --- a/app/src/main/java/com/idormy/sms/forwarder/receiver/BootReceiver.kt +++ b/app/src/main/java/com/idormy/sms/forwarder/receiver/BootReceiver.kt @@ -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() } diff --git a/app/src/main/java/com/idormy/sms/forwarder/receiver/PhoneStateReceiver.kt b/app/src/main/java/com/idormy/sms/forwarder/receiver/PhoneStateReceiver.kt index bd61a890..988f9bc8 100644 --- a/app/src/main/java/com/idormy/sms/forwarder/receiver/PhoneStateReceiver.kt +++ b/app/src/main/java/com/idormy/sms/forwarder/receiver/PhoneStateReceiver.kt @@ -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() - .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() - .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().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().setInputData( + workDataOf( + Worker.sendMsgInfo to Gson().toJson(msgInfo) + ) + ).build() + WorkManager.getInstance(context).enqueue(request) + + } + + companion object { + private const val TAG = "PhoneStateReceiver" + } } \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/server/component/AppConfig.kt b/app/src/main/java/com/idormy/sms/forwarder/server/component/AppConfig.kt index 0079d07c..a1616e44 100644 --- a/app/src/main/java/com/idormy/sms/forwarder/server/component/AppConfig.kt +++ b/app/src/main/java/com/idormy/sms/forwarder/server/component/AppConfig.kt @@ -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() + )*/ + } + } \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/server/component/AppExceptionResolver.kt b/app/src/main/java/com/idormy/sms/forwarder/server/component/AppExceptionResolver.kt index 63a9b93c..f12bd435 100644 --- a/app/src/main/java/com/idormy/sms/forwarder/server/component/AppExceptionResolver.kt +++ b/app/src/main/java/com/idormy/sms/forwarder/server/component/AppExceptionResolver.kt @@ -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)) + } + } + } + } \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/server/component/AppMessageConverter.kt b/app/src/main/java/com/idormy/sms/forwarder/server/component/AppMessageConverter.kt index d6b2e555..cd58af6d 100644 --- a/app/src/main/java/com/idormy/sms/forwarder/server/component/AppMessageConverter.kt +++ b/app/src/main/java/com/idormy/sms/forwarder/server/component/AppMessageConverter.kt @@ -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 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 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 + } + } \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/server/controller/ConfigController.kt b/app/src/main/java/com/idormy/sms/forwarder/server/controller/ConfigController.kt index 9d9b9769..685b9af8 100644 --- a/app/src/main/java/com/idormy/sms/forwarder/server/controller/ConfigController.kt +++ b/app/src/main/java/com/idormy/sms/forwarder/server/controller/ConfigController.kt @@ -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(), + ) + } + } \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/utils/CactusSave.kt b/app/src/main/java/com/idormy/sms/forwarder/utils/CactusSave.kt index 158565e2..29629a9f 100644 --- a/app/src/main/java/com/idormy/sms/forwarder/utils/CactusSave.kt +++ b/app/src/main/java/com/idormy/sms/forwarder/utils/CactusSave.kt @@ -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") } \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/utils/HistoryUtils.kt b/app/src/main/java/com/idormy/sms/forwarder/utils/HistoryUtils.kt index 8a280e30..bd2b4d93 100644 --- a/app/src/main/java/com/idormy/sms/forwarder/utils/HistoryUtils.kt +++ b/app/src/main/java/com/idormy/sms/forwarder/utils/HistoryUtils.kt @@ -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(private val name: String, private val default: T) : ReadWriteProperty { 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?) - } - else -> return false - } - } - //=======================================键值获取==================================================// - /** - * 获取键值 - * - * @param key - * @param defaultValue - * @return - */ - operator fun get(key: String?, defaultValue: Any?): Any? { - when (defaultValue) { - is Int -> { - return getsMMKV()!! - .decodeInt(key, (defaultValue as Int?)!!) - } - is Float -> { - return getsMMKV()!! - .decodeFloat(key, (defaultValue as Float?)!!) - } - is String -> { - return getsMMKV()!!.decodeString(key, defaultValue as String?) - } - is Boolean -> { - return getsMMKV()!! - .decodeBool(key, (defaultValue as Boolean?)!!) - } - is Long -> { - return getsMMKV()!! - .decodeLong(key, (defaultValue as Long?)!!) - } - is Double -> { - return getsMMKV()!! - .decodeDouble(key, (defaultValue as Double?)!!) - } - is ByteArray -> { - return getsMMKV()!!.decodeBytes(key) - } - is Set<*> -> { - return getsMMKV()!!.decodeStringSet(key, defaultValue as Set?) - } - else -> return null - } - } - - /** - * 根据key获取boolean值 - * - * @param key - * @param defValue - * @return - */ - fun getBoolean(key: String?, defValue: Boolean): Boolean { - try { - return getsMMKV()!!.getBoolean(key, defValue) - } catch (e: Exception) { - e.printStackTrace() + 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 - * @return - */ - fun getObject(key: String?, tClass: Class?): T? { - return getsMMKV()!!.decodeParcelable(key, tClass) - } + //根据key删除存储数据 + fun clearPreference(key: String) = preference.edit().remove(key).commit() + } - /** - * 获取对象 - * - * @param key - * @param tClass 类型 - * @param - * @return - */ - fun getObject(key: String?, tClass: Class?, defValue: T): T? { - try { - return getsMMKV()!!.decodeParcelable(key, tClass, defValue) - } catch (e: Exception) { - e.printStackTrace() - } - return defValue - } + 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 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 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 } } \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/utils/HttpServerUtils.kt b/app/src/main/java/com/idormy/sms/forwarder/utils/HttpServerUtils.kt index 3a53d73b..c373e37b 100644 --- a/app/src/main/java/com/idormy/sms/forwarder/utils/HttpServerUtils.kt +++ b/app/src/main/java/com/idormy/sms/forwarder/utils/HttpServerUtils.kt @@ -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 = 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 = 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) + } + } } \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/utils/MMKVUtils.kt b/app/src/main/java/com/idormy/sms/forwarder/utils/MMKVUtils.kt deleted file mode 100644 index 47ba6ace..00000000 --- a/app/src/main/java/com/idormy/sms/forwarder/utils/MMKVUtils.kt +++ /dev/null @@ -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?) - } - else -> return false - } - } - //=======================================键值获取==================================================// - /** - * 获取键值 - * - * @param key - * @param defaultValue - * @return - */ - operator fun get(key: String?, defaultValue: Any?): Any? { - when (defaultValue) { - is Int -> { - return getsMMKV()!! - .decodeInt(key, (defaultValue as Int?)!!) - } - is Float -> { - return getsMMKV()!! - .decodeFloat(key, (defaultValue as Float?)!!) - } - is String -> { - return getsMMKV()!!.decodeString(key, defaultValue as String?) - } - is Boolean -> { - return getsMMKV()!! - .decodeBool(key, (defaultValue as Boolean?)!!) - } - is Long -> { - return getsMMKV()!! - .decodeLong(key, (defaultValue as Long?)!!) - } - is Double -> { - return getsMMKV()!! - .decodeDouble(key, (defaultValue as Double?)!!) - } - is ByteArray -> { - return getsMMKV()!!.decodeBytes(key) - } - is Set<*> -> { - return getsMMKV()!!.decodeStringSet(key, defaultValue as Set?) - } - else -> return null - } - } - - /** - * 根据key获取boolean值 - * - * @param key - * @param defValue - * @return - */ - fun getBoolean(key: String?, defValue: Boolean): Boolean { - try { - return getsMMKV()!!.getBoolean(key, defValue) - } catch (e: Exception) { - e.printStackTrace() - } - return defValue - } - - /** - * 根据key获取long值 - * - * @param key - * @param defValue - * @return - */ - fun getLong(key: String?, defValue: Long): Long { - try { - return getsMMKV()!!.getLong(key, defValue) - } catch (e: Exception) { - e.printStackTrace() - } - return defValue - } - - /** - * 根据key获取float值 - * - * @param key - * @param defValue - * @return - */ - fun getFloat(key: String?, defValue: Float): Float { - try { - return getsMMKV()!!.getFloat(key, defValue) - } catch (e: Exception) { - e.printStackTrace() - } - return defValue - } - - /** - * 根据key获取String值 - * - * @param key - * @param defValue - * @return - */ - fun getString(key: String?, defValue: String?): String? { - try { - return getsMMKV()!!.getString(key, defValue) - } catch (e: Exception) { - e.printStackTrace() - } - return defValue - } - - /** - * 根据key获取int值 - * - * @param key - * @param defValue - * @return - */ - fun getInt(key: String?, defValue: Int): Int { - try { - return getsMMKV()!!.getInt(key, defValue) - } catch (e: Exception) { - e.printStackTrace() - } - return defValue - } - - /** - * 根据key获取double值 - * - * @param key - * @param defValue - * @return - */ - fun getDouble(key: String?, defValue: Double): Double { - try { - return getsMMKV()!!.decodeDouble(key, defValue) - } catch (e: Exception) { - e.printStackTrace() - } - return defValue - } - - /** - * 获取对象 - * - * @param key - * @param tClass 类型 - * @param - * @return - */ - fun getObject(key: String?, tClass: Class?): T? { - return getsMMKV()!!.decodeParcelable(key, tClass) - } - - /** - * 获取对象 - * - * @param key - * @param tClass 类型 - * @param - * @return - */ - fun getObject(key: String?, tClass: Class?, defValue: T): T? { - try { - return getsMMKV()!!.decodeParcelable(key, tClass, defValue) - } catch (e: Exception) { - e.printStackTrace() - } - return defValue - } - - /** - * 判断键值对是否存在 - * - * @param key 键 - * @return 键值对是否存在 - */ - fun containsKey(key: String?): Boolean { - return getsMMKV()!!.containsKey(key) - } - - /** - * 清除指定键值对 - * - * @param key 键 - */ - fun remove(key: String?) { - getsMMKV()!!.remove(key).apply() - } - - /** - * 从SP迁移数据 - */ - fun importSharedPreferences(context: Context) { - Log.d(TAG, "从SP迁移数据") - val preferences = PreferenceManager.getDefaultSharedPreferences(context) - val editor = preferences.edit() - getsMMKV()!!.importFromSharedPreferences(preferences) - editor.clear().apply() - - Log.d(TAG, "转换旧的SP配置") - loop@ for (key: String in getsMMKV()!!.allKeys()!!) { - when { - key.startsWith("tsms_msg_key_switch_") || key.startsWith("tsms_msg_key_string_enable_") || key.endsWith("battery_level_once") -> { - val newKey = key.replace("tsms_msg_key_switch_", "enable_") - .replace("tsms_msg_key_string_", "enable_") - .replace("enable_enable_", "enable_") - val value = getBoolean(key, false) - Log.d(TAG, String.format("oldKey=%s, newKey=%s, value=%s", key, newKey, value.toString())) - put(newKey, value) - remove(key) - continue@loop - } - key.endsWith("battery_level_alarm") || key.endsWith("battery_level_max") || key.endsWith("battery_level_current") || key.endsWith("battery_status") || key.endsWith("battery_cron_interval") -> { - val newKey = key.replace("tsms_msg_key_switch_", "") - .replace("tsms_msg_key_string_", "") - .replace("alarm", "min") - .replace("tsms_msg_key_", "request_") - val value = getInt(key, 0) - Log.d(TAG, String.format("oldKey=%s, newKey=%s, value=%s", key, newKey, value.toString())) - put(newKey, value) - remove(key) - continue@loop - } - key.startsWith("tsms_msg_key_") -> { - val newKey = key.replace("tsms_msg_key_string_", "") - .replace("add_", "") - .replace("tsms_msg_key_", "request_") - val value = getString(key, "") - Log.d(TAG, String.format("oldKey=%s, newKey=%s, value=%s", key, newKey, value.toString())) - put(newKey, value) - remove(key) - continue@loop - } - } - } - - Log.d(TAG, "转换后的数据") - for (key: String in getsMMKV()!!.allKeys()!!) { - when { - key.startsWith("enable_") -> { - Log.d(TAG, String.format("key=%s, value=%s", key, getBoolean(key, false).toString())) - } - key.startsWith("battery_") || key.startsWith("request_") -> { - Log.d(TAG, String.format("key=%s, value=%s", key, getInt(key, 0).toString())) - } - else -> { - Log.d(TAG, String.format("key=%s, value=%s", key, getString(key, "").toString())) - } - } - } - } - } - - init { - throw UnsupportedOperationException("u can't instantiate me...") - } -} \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/utils/SettingUtils.kt b/app/src/main/java/com/idormy/sms/forwarder/utils/SettingUtils.kt index 4080a86c..04b35351 100644 --- a/app/src/main/java/com/idormy/sms/forwarder/utils/SettingUtils.kt +++ b/app/src/main/java/com/idormy/sms/forwarder/utils/SettingUtils.kt @@ -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 { diff --git a/app/src/main/java/com/idormy/sms/forwarder/utils/SharedPreference.kt b/app/src/main/java/com/idormy/sms/forwarder/utils/SharedPreference.kt new file mode 100644 index 00000000..16950adb --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/utils/SharedPreference.kt @@ -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(private val name: String, private val default: T) : ReadWriteProperty { + + 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 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 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 + } +} \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/utils/sender/DingtalkInnerRobotUtils.kt b/app/src/main/java/com/idormy/sms/forwarder/utils/sender/DingtalkInnerRobotUtils.kt index 644e5f40..e3958c57 100644 --- a/app/src/main/java/com/idormy/sms/forwarder/utils/sender/DingtalkInnerRobotUtils.kt +++ b/app/src/main/java/com/idormy/sms/forwarder/utils/sender/DingtalkInnerRobotUtils.kt @@ -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() { + request.upJson(requestMsg).keepJson(true).ignoreHttpsCert().timeOut((SettingUtils.requestTimeout * 1000).toLong()) //超时时间10s + .cacheMode(CacheMode.NO_CACHE).timeStamp(true).execute(object : SimpleCallBack() { 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() { + .timeStamp(true).execute(object : SimpleCallBack() { override fun onError(e: ApiException) { Log.e(TAG, e.detailMessage) diff --git a/app/src/main/java/com/idormy/sms/forwarder/utils/sender/FeishuAppUtils.kt b/app/src/main/java/com/idormy/sms/forwarder/utils/sender/FeishuAppUtils.kt index 6697f501..f2ccc898 100644 --- a/app/src/main/java/com/idormy/sms/forwarder/utils/sender/FeishuAppUtils.kt +++ b/app/src/main/java/com/idormy/sms/forwarder/utils/sender/FeishuAppUtils.kt @@ -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() { + XHttp.post(requestUrl).upJson(requestMsg).keepJson(true).ignoreHttpsCert().timeOut((SettingUtils.requestTimeout * 1000).toLong()) //超时时间10s + .cacheMode(CacheMode.NO_CACHE).timeStamp(true).execute(object : SimpleCallBack() { 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() { + .timeStamp(true).execute(object : SimpleCallBack() { override fun onError(e: ApiException) { Log.e(TAG, e.detailMessage) diff --git a/app/src/main/java/com/idormy/sms/forwarder/utils/sender/WeworkAgentUtils.kt b/app/src/main/java/com/idormy/sms/forwarder/utils/sender/WeworkAgentUtils.kt index db5085e8..8e8c0baa 100644 --- a/app/src/main/java/com/idormy/sms/forwarder/utils/sender/WeworkAgentUtils.kt +++ b/app/src/main/java/com/idormy/sms/forwarder/utils/sender/WeworkAgentUtils.kt @@ -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() { + request.keepJson(true).ignoreHttpsCert().timeOut((SettingUtils.requestTimeout * 1000).toLong()) //超时时间10s + .cacheMode(CacheMode.NO_CACHE).timeStamp(true).execute(object : SimpleCallBack() { 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 = 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() { + .timeStamp(true).execute(object : SimpleCallBack() { override fun onError(e: ApiException) { Log.e(TAG, e.detailMessage) diff --git a/app/src/main/java/com/idormy/sms/forwarder/widget/GuideTipsDialog.kt b/app/src/main/java/com/idormy/sms/forwarder/widget/GuideTipsDialog.kt index 670d645b..e5bff1fa 100644 --- a/app/src/main/java/com/idormy/sms/forwarder/widget/GuideTipsDialog.kt +++ b/app/src/main/java/com/idormy/sms/forwarder/widget/GuideTipsDialog.kt @@ -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) : } 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 { diff --git a/app/src/main/java/com/idormy/sms/forwarder/workers/SendWorker.kt b/app/src/main/java/com/idormy/sms/forwarder/workers/SendWorker.kt index 3f8c1f4b..61888b00 100644 --- a/app/src/main/java/com/idormy/sms/forwarder/workers/SendWorker.kt +++ b/app/src/main/java/com/idormy/sms/forwarder/workers/SendWorker.kt @@ -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 = - 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 = 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() + } + } + } \ No newline at end of file diff --git a/versions.gradle b/versions.gradle index 1b97b01a..92f47c73 100644 --- a/versions.gradle +++ b/versions.gradle @@ -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 = [:]