You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
SmsForwarder/app/src/main/java/com/idormy/sms/forwarder/fragment/client/CloneFragment.kt

461 lines
20 KiB
Kotlin

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