新增:自动任务·快捷指令 —— 触发条件:短信广播、通话广播、APP通知 #385 #389

pull/436/head
pppscn 3 months ago
parent 8540822b67
commit d3899d9404

@ -89,7 +89,7 @@ data class Rule(
}
val SIM_SLOT_MAP = object : HashMap<String, String>() {
init {
put("ALL", getString(R.string.rule_all))
put("ALL", getString(R.string.rule_any))
put("SIM1", "SIM1")
put("SIM2", "SIM2")
}
@ -129,6 +129,23 @@ data class Rule(
return sb.toString()
}
val description: String
get() {
val card = SIM_SLOT_MAP[simSlot].toString() + getString(R.string.rule_card)
val sb = StringBuilder()
when (type) {
"app" -> sb.append(getString(R.string.task_app_when))
"call" -> sb.append(String.format(getString(R.string.task_call_when), card))
"sms" -> sb.append(String.format(getString(R.string.task_sms_when), card))
}
when (filed) {
FILED_TRANSPOND_ALL -> sb.append("")
FILED_CALL_TYPE -> sb.append(getString(R.string.rule_when) + FILED_MAP[filed] + CHECK_MAP[check] + CALL_TYPE_MAP[value])
else -> sb.append(getString(R.string.rule_when) + FILED_MAP[filed] + CHECK_MAP[check] + value)
}
return sb.toString()
}
val ruleMatch: String
get() {
val simStr = if ("app" == type) "" else SIM_SLOT_MAP[simSlot].toString() + getString(R.string.rule_card)

@ -58,7 +58,11 @@ data class MsgInfo(
var customSmsTemplate: String = getString(R.string.tag_from).toString() + "\n" +
getString(R.string.tag_sms) + "\n" +
getString(R.string.tag_card_slot) + "\n" +
(if (type == "app") "" else "SubId${getString(R.string.tag_card_subid)}\n") +
when (type) {
"sms", "call" -> "SubId${getString(R.string.tag_card_subid)}\n"
"app" -> "UID${getString(R.string.tag_uid)}\n"
else -> ""
} +
getString(R.string.tag_receive_time) + "\n" +
getString(R.string.tag_device_name)

@ -135,6 +135,27 @@ class TasksEditFragment : BaseFragment<FragmentTasksEditBinding?>(), View.OnClic
CoreAnim.slide,
R.drawable.auto_task_icon_lock_screen
),
PageInfo(
getString(R.string.task_sms),
"com.idormy.sms.forwarder.fragment.condition.MsgFragment",
"sms",
CoreAnim.slide,
R.drawable.auto_task_icon_sms
),
PageInfo(
getString(R.string.task_call),
"com.idormy.sms.forwarder.fragment.condition.MsgFragment",
"call",
CoreAnim.slide,
R.drawable.auto_task_icon_incall
),
PageInfo(
getString(R.string.task_app),
"com.idormy.sms.forwarder.fragment.condition.MsgFragment",
"app",
CoreAnim.slide,
R.drawable.auto_task_icon_start_activity
),
)
private var TASK_ACTION_FRAGMENT_LIST = listOf(
@ -438,13 +459,20 @@ class TasksEditFragment : BaseFragment<FragmentTasksEditBinding?>(), View.OnClic
private fun checkForm(): Task {
val taskName = binding!!.etName.text.toString().trim()
if (taskName.isEmpty()) {
throw Exception("请输入任务名称")
throw Exception(getString(R.string.invalid_task_name))
}
if (conditionsList.size <= 0) {
throw Exception("请添加触发条件")
throw Exception(getString(R.string.invalid_conditions))
}
if (actionsList.size <= 0) {
throw Exception("请添加执行动作")
throw Exception(getString(R.string.invalid_actions))
}
//短信广播/通话广播/APP通知 类型条件只能放在第一个
for (i in 1 until conditionsList.size) {
if (conditionsList[i].type == TASK_CONDITION_SMS || conditionsList[i].type == TASK_CONDITION_CALL || conditionsList[i].type == TASK_CONDITION_APP) {
throw Exception(getString(R.string.msg_condition_must_be_trigger))
}
}
val lastExecTime = Date()
@ -459,7 +487,7 @@ class TasksEditFragment : BaseFragment<FragmentTasksEditBinding?>(), View.OnClic
//检查定时任务的时间设置
val cronSetting = Gson().fromJson(firstCondition.setting, CronSetting::class.java)
if (cronSetting.expression.isEmpty()) {
throw Exception("请设置定时任务的时间")
throw Exception(getString(R.string.invalid_cron))
}
val cronExpression = CronExpression(cronSetting.expression)
nextExecTime = cronExpression.getNextValidTimeAfter(lastExecTime)
@ -504,6 +532,11 @@ class TasksEditFragment : BaseFragment<FragmentTasksEditBinding?>(), View.OnClic
//判断点击的是条件还是动作
if (widgetInfo.classPath.contains(".condition.")) {
val typeCondition = pos + KEY_BACK_CODE_CONDITION
//短信广播、通话广播、APP通知 类型条件必须作为触发提交
if ((typeCondition == TASK_CONDITION_SMS || typeCondition == TASK_CONDITION_CALL || typeCondition == TASK_CONDITION_APP) && actionsList.isNotEmpty()) {
XToastUtils.error(getString(R.string.msg_condition_must_be_trigger))
return
}
//判断是否已经添加过该类型条件
for (item in conditionsList) {
//注意TASK_CONDITION_XXX 枚举值 等于 TASK_CONDITION_FRAGMENT_LIST 索引加上 KEY_BACK_CODE_CONDITION不可改变
@ -534,6 +567,12 @@ class TasksEditFragment : BaseFragment<FragmentTasksEditBinding?>(), View.OnClic
XToastUtils.error(getString(R.string.only_one_location_condition))
return
}
//短信广播、通话广播、APP通知 类型条件互斥
if ((typeCondition == TASK_CONDITION_SMS || typeCondition == TASK_CONDITION_CALL || typeCondition == TASK_CONDITION_APP) && (item.type == TASK_CONDITION_SMS || item.type == TASK_CONDITION_CALL || item.type == TASK_CONDITION_APP)) {
XToastUtils.error(getString(R.string.only_one_msg_condition))
return
}
}
} else {
val typeAction = pos + KEY_BACK_CODE_ACTION
@ -546,8 +585,10 @@ class TasksEditFragment : BaseFragment<FragmentTasksEditBinding?>(), View.OnClic
}
}
}
@Suppress("UNCHECKED_CAST") PageOption.to(Class.forName(widgetInfo.classPath) as Class<XPageFragment>) //跳转的fragment
.setRequestCode(0) //requestCode: 0 新增 、>0 编辑itemListXxx 的索引加1
.putString(KEY_EVENT_PARAMS_CONDITION, widgetInfo.params)
.open(this)
} catch (e: Exception) {
e.printStackTrace()
@ -638,6 +679,7 @@ class TasksEditFragment : BaseFragment<FragmentTasksEditBinding?>(), View.OnClic
PageOption.to(Class.forName(widgetInfo.classPath) as Class<XPageFragment>) //跳转的fragment
.setRequestCode(position + 1) //requestCode: 0 新增 、>0 编辑conditionsList 的索引加1
.putString(KEY_EVENT_DATA_CONDITION, condition.setting)
.putString(KEY_EVENT_PARAMS_CONDITION, widgetInfo.params)
.open(this)
}

@ -0,0 +1,494 @@
package com.idormy.sms.forwarder.fragment.condition
import android.content.Intent
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.AdapterView
import android.widget.EditText
import android.widget.RadioGroup
import android.widget.TextView
import androidx.lifecycle.Observer
import androidx.work.OneTimeWorkRequestBuilder
import androidx.work.WorkManager
import com.google.gson.Gson
import com.idormy.sms.forwarder.App
import com.idormy.sms.forwarder.R
import com.idormy.sms.forwarder.adapter.spinner.AppListAdapterItem
import com.idormy.sms.forwarder.adapter.spinner.AppListSpinnerAdapter
import com.idormy.sms.forwarder.core.BaseFragment
import com.idormy.sms.forwarder.database.entity.Rule
import com.idormy.sms.forwarder.databinding.FragmentTasksConditionMsgBinding
import com.idormy.sms.forwarder.entity.MsgInfo
import com.idormy.sms.forwarder.utils.CHECK_CONTAIN
import com.idormy.sms.forwarder.utils.CHECK_END_WITH
import com.idormy.sms.forwarder.utils.CHECK_IS
import com.idormy.sms.forwarder.utils.CHECK_NOT_CONTAIN
import com.idormy.sms.forwarder.utils.CHECK_REGEX
import com.idormy.sms.forwarder.utils.CHECK_SIM_SLOT_1
import com.idormy.sms.forwarder.utils.CHECK_SIM_SLOT_2
import com.idormy.sms.forwarder.utils.CHECK_SIM_SLOT_ALL
import com.idormy.sms.forwarder.utils.CHECK_START_WITH
import com.idormy.sms.forwarder.utils.CommonUtils
import com.idormy.sms.forwarder.utils.EVENT_LOAD_APP_LIST
import com.idormy.sms.forwarder.utils.FILED_CALL_TYPE
import com.idormy.sms.forwarder.utils.FILED_INFORM_CONTENT
import com.idormy.sms.forwarder.utils.FILED_MSG_CONTENT
import com.idormy.sms.forwarder.utils.FILED_MULTI_MATCH
import com.idormy.sms.forwarder.utils.FILED_PACKAGE_NAME
import com.idormy.sms.forwarder.utils.FILED_PHONE_NUM
import com.idormy.sms.forwarder.utils.FILED_TRANSPOND_ALL
import com.idormy.sms.forwarder.utils.FILED_UID
import com.idormy.sms.forwarder.utils.KEY_BACK_DATA_CONDITION
import com.idormy.sms.forwarder.utils.KEY_BACK_DESCRIPTION_CONDITION
import com.idormy.sms.forwarder.utils.KEY_EVENT_DATA_CONDITION
import com.idormy.sms.forwarder.utils.KEY_EVENT_PARAMS_CONDITION
import com.idormy.sms.forwarder.utils.Log
import com.idormy.sms.forwarder.utils.PhoneUtils
import com.idormy.sms.forwarder.utils.STATUS_ON
import com.idormy.sms.forwarder.utils.SettingUtils
import com.idormy.sms.forwarder.utils.TASK_CONDITION_APP
import com.idormy.sms.forwarder.utils.TASK_CONDITION_CALL
import com.idormy.sms.forwarder.utils.TASK_CONDITION_SMS
import com.idormy.sms.forwarder.utils.XToastUtils
import com.idormy.sms.forwarder.workers.LoadAppListWorker
import com.jeremyliao.liveeventbus.LiveEventBus
import com.xuexiang.xaop.annotation.SingleClick
import com.xuexiang.xpage.annotation.Page
import com.xuexiang.xrouter.annotation.AutoWired
import com.xuexiang.xrouter.launcher.XRouter
import com.xuexiang.xrouter.utils.TextUtils
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.xui.widget.spinner.materialspinner.MaterialSpinner
import com.xuexiang.xutil.XUtil
import com.xuexiang.xutil.resource.ResUtils.getColors
import java.util.Date
@Page(name = "Msg")
@Suppress("PrivatePropertyName")
class MsgFragment : BaseFragment<FragmentTasksConditionMsgBinding?>(), View.OnClickListener {
private val TAG: String = MsgFragment::class.java.simpleName
private var titleBar: TitleBar? = null
//通话类型1.来电挂机 2.去电挂机 3.未接来电 4.来电提醒 5.来电接通 6.去电拨出
private val CALL_TYPE_MAP = mapOf(
//"0" to getString(R.string.unknown_call),
"1" to getString(R.string.incoming_call_ended),
"2" to getString(R.string.outgoing_call_ended),
"3" to getString(R.string.missed_call),
"4" to getString(R.string.incoming_call_received),
"5" to getString(R.string.incoming_call_answered),
"6" to getString(R.string.outgoing_call_started),
)
private var callType = 1
private var callTypeIndex = 0
private var resultCode: Int = TASK_CONDITION_SMS
//已安装App信息列表
private val appListSpinnerList = ArrayList<AppListAdapterItem>()
private lateinit var appListSpinnerAdapter: AppListSpinnerAdapter<*>
private val appListObserver = Observer { it: String ->
Log.d(TAG, "EVENT_LOAD_APP_LIST: $it")
initAppSpinner()
}
@JvmField
@AutoWired(name = KEY_EVENT_PARAMS_CONDITION)
var ruleType: String = "sms"
@JvmField
@AutoWired(name = KEY_EVENT_DATA_CONDITION)
var eventData: String? = null
override fun initArgs() {
XRouter.getInstance().inject(this)
}
override fun viewBindingInflate(
inflater: LayoutInflater,
container: ViewGroup,
): FragmentTasksConditionMsgBinding {
return FragmentTasksConditionMsgBinding.inflate(inflater, container, false)
}
override fun initTitle(): TitleBar? {
titleBar = super.initTitle()!!.setImmersive(false)
return titleBar
}
/**
* 初始化控件
*/
override fun initViews() {
when (ruleType) {
"app" -> {
resultCode = TASK_CONDITION_APP
titleBar?.setTitle(R.string.task_app)
binding!!.ivTaskApp.visibility = View.VISIBLE
binding!!.layoutSimSlot.visibility = View.GONE
binding!!.rbPhone.visibility = View.GONE
binding!!.rbCallType.visibility = View.GONE
binding!!.rbContent.visibility = View.GONE
binding!!.tvMuRuleTips.setText(R.string.mu_rule_app_tips)
//初始化APP下拉列表
initAppSpinner()
//监听已安装App信息列表加载完成事件
LiveEventBus.get(EVENT_LOAD_APP_LIST, String::class.java).observeStickyForever(appListObserver)
}
"call" -> {
resultCode = TASK_CONDITION_CALL
titleBar?.setTitle(R.string.task_call)
binding!!.ivTaskCall.visibility = View.VISIBLE
binding!!.rbContent.visibility = View.GONE
binding!!.rbPackageName.visibility = View.GONE
binding!!.rbUid.visibility = View.GONE
binding!!.rbInformContent.visibility = View.GONE
binding!!.tvMuRuleTips.setText(R.string.mu_rule_call_tips)
//通话类型1.来电挂机 2.去电挂机 3.未接来电 4.来电提醒 5.来电接通 6.去电拨出
binding!!.spCallType.setItems(CALL_TYPE_MAP.values.toList())
binding!!.spCallType.setOnItemSelectedListener { _: MaterialSpinner?, _: Int, _: Long, item: Any ->
CALL_TYPE_MAP.forEach {
if (it.value == item) callType = it.key.toInt()
}
}
binding!!.spCallType.setOnNothingSelectedListener {
callType = 1
callTypeIndex = 0
binding!!.spCallType.selectedIndex = callTypeIndex
}
binding!!.spCallType.selectedIndex = callTypeIndex
}
else -> {
titleBar?.setTitle(R.string.task_sms)
binding!!.ivTaskSms.visibility = View.VISIBLE
binding!!.rbCallType.visibility = View.GONE
binding!!.rbPackageName.visibility = View.GONE
binding!!.rbUid.visibility = View.GONE
binding!!.rbInformContent.visibility = View.GONE
}
}
}
override fun initListeners() {
binding!!.btnTest.setOnClickListener(this)
binding!!.btnDel.setOnClickListener(this)
binding!!.btnSave.setOnClickListener(this)
binding!!.rgFiled.setOnCheckedChangeListener { _: RadioGroup?, checkedId: Int ->
if (ruleType == "app" && appListSpinnerList.isNotEmpty()) {
binding!!.layoutAppList.visibility = if (checkedId == R.id.rb_inform_content) View.GONE else View.VISIBLE
}
when (checkedId) {
R.id.rb_transpond_all -> {
binding!!.rgCheck.check(R.id.rb_is)
binding!!.spCallType.visibility = View.GONE
binding!!.tvMuRuleTips.visibility = View.GONE
binding!!.layoutMatchType.visibility = View.GONE
binding!!.layoutMatchValue.visibility = View.GONE
}
R.id.rb_multi_match -> {
binding!!.rgCheck.check(R.id.rb_is)
binding!!.spCallType.visibility = View.GONE
binding!!.tvMuRuleTips.visibility = View.VISIBLE
binding!!.layoutMatchType.visibility = View.GONE
binding!!.layoutMatchValue.visibility = View.VISIBLE
binding!!.etValue.visibility = View.VISIBLE
}
R.id.rb_call_type -> {
binding!!.rgCheck.check(R.id.rb_is)
binding!!.tvMuRuleTips.visibility = View.GONE
binding!!.layoutMatchType.visibility = View.GONE
binding!!.layoutMatchValue.visibility = View.VISIBLE
binding!!.etValue.visibility = View.GONE
binding!!.spCallType.visibility = View.VISIBLE
}
else -> {
binding!!.spCallType.visibility = View.GONE
binding!!.tvMuRuleTips.visibility = View.GONE
binding!!.layoutMatchType.visibility = View.VISIBLE
binding!!.layoutMatchValue.visibility = View.VISIBLE
binding!!.etValue.visibility = View.VISIBLE
}
}
}
binding!!.rgCheck.setOnCheckedChangeListener { group: RadioGroup?, checkedId: Int ->
if (group != null && checkedId > 0) {
binding!!.rgCheck2.clearCheck()
group.check(checkedId)
}
}
binding!!.rgCheck2.setOnCheckedChangeListener { group: RadioGroup?, checkedId: Int ->
if (group != null && checkedId > 0) {
binding!!.rgCheck.clearCheck()
group.check(checkedId)
}
}
Log.d(TAG, "initViews eventData:$eventData")
if (eventData != null) {
val rule = Gson().fromJson(eventData, Rule::class.java)
Log.d(TAG, rule.toString())
binding!!.rgSimSlot.check(rule.getSimSlotCheckId())
binding!!.rgFiled.check(rule.getFiledCheckId())
val checkId = rule.getCheckCheckId()
if (checkId == R.id.rb_is || checkId == R.id.rb_contain || checkId == R.id.rb_not_contain) {
binding!!.rgCheck.check(checkId)
} else {
binding!!.rgCheck2.check(checkId)
}
binding!!.etValue.setText(rule.value)
if (ruleType == "call" && rule.filed == FILED_CALL_TYPE) {
callType = rule.value.toInt()
callTypeIndex = callType - 1
binding!!.spCallType.selectedIndex = callTypeIndex
}
}
}
@SingleClick
override fun onClick(v: View) {
try {
when (v.id) {
R.id.btn_test -> {
val ruleNew = checkForm()
testRule(ruleNew)
return
}
R.id.btn_del -> {
popToBack()
return
}
R.id.btn_save -> {
val settingVo = checkForm()
val intent = Intent()
intent.putExtra(KEY_BACK_DESCRIPTION_CONDITION, settingVo.description)
intent.putExtra(KEY_BACK_DATA_CONDITION, Gson().toJson(settingVo))
setFragmentResult(resultCode, intent)
popToBack()
return
}
}
} catch (e: Exception) {
XToastUtils.error(e.message.toString())
e.printStackTrace()
Log.e(TAG, e.toString())
}
}
//初始化APP下拉列表
private fun initAppSpinner() {
if (ruleType != "app") return
//未开启异步获取已安装App信息开关时规则编辑不显示已安装APP下拉框
if (!SettingUtils.enableLoadUserAppList && !SettingUtils.enableLoadSystemAppList) return
if (App.UserAppList.isEmpty() && App.SystemAppList.isEmpty()) {
XToastUtils.info(getString(R.string.loading_app_list))
val request = OneTimeWorkRequestBuilder<LoadAppListWorker>().build()
WorkManager.getInstance(XUtil.getContext()).enqueue(request)
return
}
appListSpinnerList.clear()
if (SettingUtils.enableLoadUserAppList) {
for (appInfo in App.UserAppList) {
if (TextUtils.isEmpty(appInfo.packageName)) continue
appListSpinnerList.add(AppListAdapterItem(appInfo.name, appInfo.icon, appInfo.packageName))
}
}
if (SettingUtils.enableLoadSystemAppList) {
for (appInfo in App.SystemAppList) {
if (TextUtils.isEmpty(appInfo.packageName)) continue
appListSpinnerList.add(AppListAdapterItem(appInfo.name, appInfo.icon, appInfo.packageName))
}
}
//列表为空也不显示下拉框
if (appListSpinnerList.isEmpty()) return
appListSpinnerAdapter = AppListSpinnerAdapter(appListSpinnerList).setIsFilterKey(true).setFilterColor("#EF5362").setBackgroundSelector(R.drawable.selector_custom_spinner_bg)
binding!!.spApp.setAdapter(appListSpinnerAdapter)
binding!!.spApp.setOnItemClickListener { _: AdapterView<*>, _: View, position: Int, _: Long ->
try {
val appInfo = appListSpinnerAdapter.getItemSource(position) as AppListAdapterItem
CommonUtils.insertOrReplaceText2Cursor(binding!!.etValue, appInfo.packageName.toString())
} catch (e: Exception) {
XToastUtils.error(e.message.toString())
}
}
binding!!.layoutAppList.visibility = View.VISIBLE
}
//提交前检查表单
private fun checkForm(): Rule {
val filed = when (binding!!.rgFiled.checkedRadioButtonId) {
R.id.rb_content -> FILED_MSG_CONTENT
R.id.rb_phone -> FILED_PHONE_NUM
R.id.rb_call_type -> FILED_CALL_TYPE
R.id.rb_package_name -> FILED_PACKAGE_NAME
R.id.rb_uid -> FILED_UID
R.id.rb_inform_content -> FILED_INFORM_CONTENT
R.id.rb_multi_match -> FILED_MULTI_MATCH
else -> FILED_TRANSPOND_ALL
}
val check = when (kotlin.math.max(binding!!.rgCheck.checkedRadioButtonId, binding!!.rgCheck2.checkedRadioButtonId)) {
R.id.rb_contain -> CHECK_CONTAIN
R.id.rb_not_contain -> CHECK_NOT_CONTAIN
R.id.rb_start_with -> CHECK_START_WITH
R.id.rb_end_with -> CHECK_END_WITH
R.id.rb_regex -> CHECK_REGEX
else -> CHECK_IS
}
var value = binding!!.etValue.text.toString().trim()
if (FILED_CALL_TYPE == filed) {
value = callType.toString()
if (callType !in 1..6) {
throw Exception(getString(R.string.invalid_call_type))
}
} else if (FILED_TRANSPOND_ALL != filed && TextUtils.isEmpty(value)) {
throw Exception(getString(R.string.invalid_match_value))
}
if (FILED_MULTI_MATCH == filed) {
val lineError = checkMultiMatch(value)
if (lineError > 0) {
throw Exception(String.format(getString(R.string.invalid_multi_match), lineError))
}
}
val simSlot = when (binding!!.rgSimSlot.checkedRadioButtonId) {
R.id.rb_sim_slot_1 -> CHECK_SIM_SLOT_1
R.id.rb_sim_slot_2 -> CHECK_SIM_SLOT_2
else -> CHECK_SIM_SLOT_ALL
}
return Rule(0, ruleType, filed, check, value, 0, "", "", simSlot, STATUS_ON, Date(), listOf())
}
//检查多重匹配规则是否正确
private fun checkMultiMatch(ruleStr: String?): Int {
if (TextUtils.isEmpty(ruleStr)) return 0
//Log.d(TAG, getString(R.string.regex_multi_match))
val regex = Regex(pattern = getString(R.string.regex_multi_match))
var lineNum = 1
val lineArray = ruleStr?.split("\\n".toRegex())?.toTypedArray()
for (line in lineArray!!) {
Log.d(TAG, line)
if (!line.matches(regex)) return lineNum
lineNum++
}
return 0
}
private fun testRule(rule: Rule) {
val dialogTest = View.inflate(requireContext(), R.layout.dialog_rule_test, null)
val tvSimSlot = dialogTest.findViewById<TextView>(R.id.tv_sim_slot)
val rgSimSlot = dialogTest.findViewById<RadioGroup>(R.id.rg_sim_slot)
val tvFrom = dialogTest.findViewById<TextView>(R.id.tv_from)
val etFrom = dialogTest.findViewById<EditText>(R.id.et_from)
val tvTitle = dialogTest.findViewById<TextView>(R.id.tv_title)
val etTitle = dialogTest.findViewById<EditText>(R.id.et_title)
val tvContent = dialogTest.findViewById<TextView>(R.id.tv_content)
val etContent = dialogTest.findViewById<EditText>(R.id.et_content)
//通话类型
val tvCallType = dialogTest.findViewById<TextView>(R.id.tv_call_type)
val spCallType = dialogTest.findViewById<MaterialSpinner>(R.id.sp_call_type)
var callTypeTest = callType
var callTypeIndexTest = callTypeIndex
if ("app" == ruleType) {
tvSimSlot.visibility = View.GONE
rgSimSlot.visibility = View.GONE
tvTitle.visibility = View.VISIBLE
etTitle.visibility = View.VISIBLE
tvFrom.setText(R.string.test_package_name)
tvContent.setText(R.string.test_inform_content)
tvCallType.visibility = View.GONE
spCallType.visibility = View.GONE
} else if ("call" == ruleType) {
tvContent.visibility = View.GONE
etContent.visibility = View.GONE
tvCallType.visibility = View.VISIBLE
spCallType.visibility = View.VISIBLE
spCallType.setItems(CALL_TYPE_MAP.values.toList())
spCallType.setOnItemSelectedListener { _: MaterialSpinner?, _: Int, _: Long, item: Any ->
CALL_TYPE_MAP.forEach {
if (it.value == item) callTypeTest = it.key.toInt()
}
}
spCallType.setOnNothingSelectedListener {
callTypeTest = callType
callTypeIndexTest = callTypeIndex
spCallType.selectedIndex = callTypeIndexTest
}
spCallType.selectedIndex = callTypeIndexTest
}
MaterialDialog.Builder(requireContext()).iconRes(android.R.drawable.ic_dialog_email).title(R.string.rule_tester).customView(dialogTest, true).cancelable(false).autoDismiss(false).neutralText(R.string.action_back).neutralColor(getColors(R.color.darkGrey)).onNeutral { dialog: MaterialDialog?, _: DialogAction? ->
dialog?.dismiss()
}.positiveText(R.string.action_test).onPositive { _: MaterialDialog?, _: DialogAction? ->
try {
val simSlot = when (if (ruleType == "app") -1 else rgSimSlot.checkedRadioButtonId) {
R.id.rb_sim_slot_1 -> 0
R.id.rb_sim_slot_2 -> 1
else -> -1
}
val testSim = "SIM" + (simSlot + 1)
val ruleSim: String = rule.simSlot
if (ruleSim != "ALL" && ruleSim != testSim) {
throw Exception(getString(R.string.card_slot_does_not_match))
}
//获取卡槽信息
val simInfo = when (simSlot) {
0 -> "SIM1_" + SettingUtils.extraSim1
1 -> "SIM2_" + SettingUtils.extraSim2
else -> etTitle.text.toString()
}
val subId = when (simSlot) {
0 -> SettingUtils.subidSim1
1 -> SettingUtils.subidSim2
else -> 0
}
val msg = StringBuilder()
if (ruleType == "call") {
val phoneNumber = etFrom.text.toString()
val contacts = PhoneUtils.getContactByNumber(phoneNumber)
val contactName = if (contacts.isNotEmpty()) contacts[0].name else getString(R.string.unknown_number)
msg.append(getString(R.string.contact)).append(contactName).append("\n")
msg.append(getString(R.string.mandatory_type))
msg.append(CALL_TYPE_MAP[callType.toString()] ?: getString(R.string.unknown_call))
} else {
msg.append(etContent.text.toString())
}
val msgInfo = MsgInfo(ruleType, etFrom.text.toString(), msg.toString(), Date(), simInfo, simSlot, subId, callTypeTest)
if (!rule.checkMsg(msgInfo)) {
throw Exception(getString(R.string.unmatched_rule))
}
XToastUtils.success(getString(R.string.matched_rule))
} catch (e: Exception) {
XToastUtils.error(e.message.toString())
}
}.show()
}
}

@ -217,6 +217,7 @@ const val SP_CLIENT_SIGN_KEY = "client_sign_key"
const val MAX_SETTING_NUM = 5 //最大条件/动作设置条数
const val KEY_TEST_CONDITION = "key_test_condition"
const val KEY_EVENT_DATA_CONDITION = "event_data_condition"
const val KEY_EVENT_PARAMS_CONDITION = "event_params_condition"
const val KEY_BACK_CODE_CONDITION = 1000
const val KEY_BACK_DATA_CONDITION = "back_data_condition"
const val KEY_BACK_DESCRIPTION_CONDITION = "back_description_condition"
@ -235,6 +236,9 @@ const val TASK_CONDITION_SIM = 1004
const val TASK_CONDITION_BATTERY = 1005
const val TASK_CONDITION_CHARGE = 1006
const val TASK_CONDITION_LOCK_SCREEN = 1007
const val TASK_CONDITION_SMS = 1008
const val TASK_CONDITION_CALL = 1009
const val TASK_CONDITION_APP = 1010
//注意TASK_ACTION_XXX 枚举值 等于 TASK_ACTION_FRAGMENT_LIST 索引加上 KEY_BACK_CODE_ACTION不可改变
const val TASK_ACTION_SENDSMS = 2000

@ -61,7 +61,7 @@ class DingtalkInnerRobotUtils private constructor() {
Log.d(TAG, "proxyHost = ${setting.proxyHost}, proxyPort = ${setting.proxyPort}")
val proxyHost = if (NetworkUtils.isIP(setting.proxyHost)) setting.proxyHost else NetworkUtils.getDomainAddress(setting.proxyHost)
if (!NetworkUtils.isIP(proxyHost)) {
throw Exception("代理服务器主机名解析失败proxyHost=$proxyHost")
throw Exception(String.format(getString(R.string.invalid_proxy_host), proxyHost))
}
val proxyPort: Int = setting.proxyPort?.toInt() ?: 7890
@ -170,7 +170,7 @@ class DingtalkInnerRobotUtils private constructor() {
Log.d(TAG, "proxyHost = ${setting.proxyHost}, proxyPort = ${setting.proxyPort}")
val proxyHost = if (NetworkUtils.isIP(setting.proxyHost)) setting.proxyHost else NetworkUtils.getDomainAddress(setting.proxyHost)
if (!NetworkUtils.isIP(proxyHost)) {
throw Exception("代理服务器主机名解析失败proxyHost=$proxyHost")
throw Exception(String.format(getString(R.string.invalid_proxy_host), proxyHost))
}
val proxyPort: Int = setting.proxyPort?.toInt() ?: 7890

@ -2,6 +2,7 @@ package com.idormy.sms.forwarder.utils.sender
import android.text.TextUtils
import com.google.gson.Gson
import com.idormy.sms.forwarder.R
import com.idormy.sms.forwarder.database.entity.Rule
import com.idormy.sms.forwarder.entity.MsgInfo
import com.idormy.sms.forwarder.entity.result.TelegramResult
@ -13,6 +14,7 @@ import com.xuexiang.xhttp2.XHttp
import com.xuexiang.xhttp2.callback.SimpleCallBack
import com.xuexiang.xhttp2.exception.ApiException
import com.xuexiang.xutil.net.NetworkUtils
import com.xuexiang.xutil.resource.ResUtils.getString
import okhttp3.Credentials
import okhttp3.Response
import okhttp3.Route
@ -76,7 +78,7 @@ class TelegramUtils private constructor() {
Log.d(TAG, "proxyHost = ${setting.proxyHost}, proxyPort = ${setting.proxyPort}")
val proxyHost = if (NetworkUtils.isIP(setting.proxyHost)) setting.proxyHost else NetworkUtils.getDomainAddress(setting.proxyHost)
if (!NetworkUtils.isIP(proxyHost)) {
throw Exception("代理服务器主机名解析失败proxyHost=$proxyHost")
throw Exception(String.format(getString(R.string.invalid_proxy_host), proxyHost))
}
val proxyPort: Int = setting.proxyPort?.toInt() ?: 7890

@ -58,7 +58,7 @@ class WeworkAgentUtils private constructor() {
Log.d(TAG, "proxyHost = ${setting.proxyHost}, proxyPort = ${setting.proxyPort}")
val proxyHost = if (NetworkUtils.isIP(setting.proxyHost)) setting.proxyHost else NetworkUtils.getDomainAddress(setting.proxyHost)
if (!NetworkUtils.isIP(proxyHost)) {
throw Exception("代理服务器主机名解析失败proxyHost=$proxyHost")
throw Exception(String.format(getString(R.string.invalid_proxy_host), proxyHost))
}
val proxyPort: Int = setting.proxyPort?.toInt() ?: 7890
@ -158,7 +158,7 @@ class WeworkAgentUtils private constructor() {
Log.d(TAG, "proxyHost = ${setting.proxyHost}, proxyPort = ${setting.proxyPort}")
val proxyHost = if (NetworkUtils.isIP(setting.proxyHost)) setting.proxyHost else NetworkUtils.getDomainAddress(setting.proxyHost)
if (!NetworkUtils.isIP(proxyHost)) {
throw Exception("代理服务器主机名解析失败proxyHost=$proxyHost")
throw Exception(String.format(getString(R.string.invalid_proxy_host), proxyHost))
}
val proxyPort: Int = setting.proxyPort?.toInt() ?: 7890

@ -35,7 +35,7 @@ class WeworkRobotUtils private constructor() {
Log.i(TAG, "requestUrl:$requestUrl")
val msgMap: MutableMap<String, Any> = mutableMapOf()
msgMap["msgtype"] = setting.msgType
msgMap["msgtype"] = if (setting.msgType == "markdown") "markdown" else "text"
val contextMap = mutableMapOf<String, Any>()
contextMap["content"] = content

@ -2,6 +2,7 @@ package com.idormy.sms.forwarder.utils.task
import android.os.BatteryManager
import com.google.gson.Gson
import com.idormy.sms.forwarder.database.entity.Rule
import com.idormy.sms.forwarder.entity.TaskSetting
import com.idormy.sms.forwarder.entity.condition.BatterySetting
import com.idormy.sms.forwarder.entity.condition.ChargeSetting
@ -12,13 +13,16 @@ import com.idormy.sms.forwarder.entity.condition.NetworkSetting
import com.idormy.sms.forwarder.entity.condition.SimSetting
import com.idormy.sms.forwarder.utils.DELAY_TIME_AFTER_SIM_READY
import com.idormy.sms.forwarder.utils.Log
import com.idormy.sms.forwarder.utils.TASK_CONDITION_APP
import com.idormy.sms.forwarder.utils.TASK_CONDITION_BATTERY
import com.idormy.sms.forwarder.utils.TASK_CONDITION_CALL
import com.idormy.sms.forwarder.utils.TASK_CONDITION_CHARGE
import com.idormy.sms.forwarder.utils.TASK_CONDITION_CRON
import com.idormy.sms.forwarder.utils.TASK_CONDITION_LEAVE_ADDRESS
import com.idormy.sms.forwarder.utils.TASK_CONDITION_LOCK_SCREEN
import com.idormy.sms.forwarder.utils.TASK_CONDITION_NETWORK
import com.idormy.sms.forwarder.utils.TASK_CONDITION_SIM
import com.idormy.sms.forwarder.utils.TASK_CONDITION_SMS
import com.idormy.sms.forwarder.utils.TASK_CONDITION_TO_ADDRESS
import gatewayapps.crondroid.CronExpression
import java.util.Date
@ -214,6 +218,16 @@ class ConditionUtils private constructor() {
Log.d(TAG, "TASK-$taskIdlockScreenAction is match, lockScreenSetting = $lockScreenSetting")
}
TASK_CONDITION_SMS, TASK_CONDITION_CALL, TASK_CONDITION_APP -> {
val ruleSetting = Gson().fromJson(condition.setting, Rule::class.java)
if (ruleSetting == null) {
Log.d(TAG, "TASK-$taskIdruleSetting is null")
continue
}
//TODO: 判断消息是否满足条件
}
}
}

@ -28,13 +28,16 @@ import com.idormy.sms.forwarder.utils.TASK_ACTION_RULE
import com.idormy.sms.forwarder.utils.TASK_ACTION_SENDER
import com.idormy.sms.forwarder.utils.TASK_ACTION_SENDSMS
import com.idormy.sms.forwarder.utils.TASK_ACTION_SETTINGS
import com.idormy.sms.forwarder.utils.TASK_CONDITION_APP
import com.idormy.sms.forwarder.utils.TASK_CONDITION_BATTERY
import com.idormy.sms.forwarder.utils.TASK_CONDITION_CALL
import com.idormy.sms.forwarder.utils.TASK_CONDITION_CHARGE
import com.idormy.sms.forwarder.utils.TASK_CONDITION_CRON
import com.idormy.sms.forwarder.utils.TASK_CONDITION_LEAVE_ADDRESS
import com.idormy.sms.forwarder.utils.TASK_CONDITION_LOCK_SCREEN
import com.idormy.sms.forwarder.utils.TASK_CONDITION_NETWORK
import com.idormy.sms.forwarder.utils.TASK_CONDITION_SIM
import com.idormy.sms.forwarder.utils.TASK_CONDITION_SMS
import com.idormy.sms.forwarder.utils.TASK_CONDITION_TO_ADDRESS
/**
@ -55,6 +58,9 @@ class TaskUtils private constructor() {
TASK_CONDITION_BATTERY -> R.drawable.auto_task_icon_battery
TASK_CONDITION_CHARGE -> R.drawable.auto_task_icon_charge
TASK_CONDITION_LOCK_SCREEN -> R.drawable.auto_task_icon_lock_screen
TASK_CONDITION_SMS -> R.drawable.auto_task_icon_sms
TASK_CONDITION_CALL -> R.drawable.auto_task_icon_incall
TASK_CONDITION_APP -> R.drawable.auto_task_icon_start_activity
TASK_ACTION_SENDSMS -> R.drawable.auto_task_icon_sms
TASK_ACTION_NOTIFICATION -> R.drawable.auto_task_icon_notification
TASK_ACTION_CLEANER -> R.drawable.auto_task_icon_cleaner
@ -80,6 +86,9 @@ class TaskUtils private constructor() {
TASK_CONDITION_BATTERY -> R.drawable.auto_task_icon_battery_grey
TASK_CONDITION_CHARGE -> R.drawable.auto_task_icon_charge_grey
TASK_CONDITION_LOCK_SCREEN -> R.drawable.auto_task_icon_lock_screen_grey
TASK_CONDITION_SMS -> R.drawable.auto_task_icon_sms_grey
TASK_CONDITION_CALL -> R.drawable.auto_task_icon_incall_grey
TASK_CONDITION_APP -> R.drawable.auto_task_icon_start_activity_grey
TASK_ACTION_SENDSMS -> R.drawable.auto_task_icon_sms_grey
TASK_ACTION_NOTIFICATION -> R.drawable.auto_task_icon_notification_grey
TASK_ACTION_CLEANER -> R.drawable.auto_task_icon_cleaner_grey

@ -139,6 +139,9 @@ class ActionWorker(context: Context, params: WorkerParameters) : CoroutineWorker
TASK_ACTION_NOTIFICATION -> {
val ruleSetting = Gson().fromJson(action.setting, Rule::class.java)
//重新查询发送通道最新设置
val ids = ruleSetting.senderList.joinToString(",") { it.id.toString() }
ruleSetting.senderList = Core.sender.getByIds(ids.split(",").map { it.trim().toLong() }, ids)
//自动任务的不需要吐司或者更新日志,特殊处理 logId = -1msgId = -1
SendUtils.sendMsgSender(msgInfo, ruleSetting, 0, -1L, -1L)

@ -68,6 +68,10 @@ class BatteryWorker(context: Context, params: WorkerParameters) : CoroutineWorke
}
//TODO判断其他条件是否满足
if (!ConditionUtils.checkCondition(task.id, conditionList)) {
Log.d(TAG, "TASK-${task.id}other condition is not satisfied")
continue
}
//TODO: 组装消息体 && 执行具体任务
val msgInfo = MsgInfo("task", task.name, msg, Date(), task.name)

@ -12,10 +12,7 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
@Suppress("PrivatePropertyName")
class LoadAppListWorker(
context: Context,
workerParams: WorkerParameters,
) : CoroutineWorker(context, workerParams) {
class LoadAppListWorker(context: Context, params: WorkerParameters) : CoroutineWorker(context, params) {
private val TAG: String = LoadAppListWorker::class.java.simpleName

@ -14,10 +14,8 @@ import com.idormy.sms.forwarder.utils.Worker
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
class SendLogicWorker(
context: Context,
workerParams: WorkerParameters,
) : CoroutineWorker(context, workerParams) {
class SendLogicWorker(context: Context, params: WorkerParameters) : CoroutineWorker(context, params) {
override suspend fun doWork(): Result = withContext(Dispatchers.IO) {
val msgInfoJson = inputData.getString(Worker.SEND_MSG_INFO)

@ -3,6 +3,9 @@ package com.idormy.sms.forwarder.workers
import android.annotation.SuppressLint
import android.content.Context
import androidx.work.CoroutineWorker
import androidx.work.Data
import androidx.work.OneTimeWorkRequestBuilder
import androidx.work.WorkManager
import androidx.work.WorkerParameters
import androidx.work.workDataOf
import com.google.gson.Gson
@ -12,12 +15,19 @@ import com.idormy.sms.forwarder.database.entity.Logs
import com.idormy.sms.forwarder.database.entity.Msg
import com.idormy.sms.forwarder.database.entity.Rule
import com.idormy.sms.forwarder.entity.MsgInfo
import com.idormy.sms.forwarder.entity.TaskSetting
import com.idormy.sms.forwarder.utils.CHECK_SIM_SLOT_ALL
import com.idormy.sms.forwarder.utils.DataProvider
import com.idormy.sms.forwarder.utils.HistoryUtils
import com.idormy.sms.forwarder.utils.Log
import com.idormy.sms.forwarder.utils.SendUtils
import com.idormy.sms.forwarder.utils.SettingUtils
import com.idormy.sms.forwarder.utils.TASK_CONDITION_APP
import com.idormy.sms.forwarder.utils.TASK_CONDITION_CALL
import com.idormy.sms.forwarder.utils.TASK_CONDITION_SMS
import com.idormy.sms.forwarder.utils.TaskWorker
import com.idormy.sms.forwarder.utils.Worker
import com.idormy.sms.forwarder.utils.task.ConditionUtils
import com.xuexiang.xutil.resource.ResUtils
import com.xuexiang.xutil.security.CipherUtils
import kotlinx.coroutines.Dispatchers
@ -27,16 +37,28 @@ import java.text.SimpleDateFormat
import java.util.Calendar
import java.util.Date
class SendWorker(
context: Context,
workerParams: WorkerParameters,
) : CoroutineWorker(context, workerParams) {
@Suppress("PrivatePropertyName", "DEPRECATION")
class SendWorker(context: Context, params: WorkerParameters) : CoroutineWorker(context, params) {
private val TAG: String = SendWorker::class.java.simpleName
@SuppressLint("SimpleDateFormat")
override suspend fun doWork(): Result {
return withContext(Dispatchers.IO) {
try {
val msgInfoJson = inputData.getString(Worker.SEND_MSG_INFO)
if (msgInfoJson.isNullOrBlank()) {
return@withContext Result.failure(workDataOf("send" to "msgInfoJson is null"))
}
val msgInfo = Gson().fromJson(msgInfoJson, MsgInfo::class.java)
//【注意】卡槽id-1=获取失败、0=卡槽1、1=卡槽2但是 Rule 表里存的是 SIM1/SIM2
val simSlot = "SIM" + (msgInfo.simSlot + 1)
//自动任务处理逻辑
autoTaskProcess(msgInfo, msgInfoJson, simSlot)
// 免打扰(禁用转发)时间段
var isSilentPeriod = false
if (SettingUtils.silentPeriodStart != SettingUtils.silentPeriodEnd) {
@ -70,9 +92,6 @@ class SendWorker(
}
}
val msgInfoJson = inputData.getString(Worker.SEND_MSG_INFO)
val msgInfo = Gson().fromJson(msgInfoJson, MsgInfo::class.java)
// 过滤重复消息机制
val duplicateMessagesLimits = SettingUtils.duplicateMessagesLimits * 1000L
if (duplicateMessagesLimits > 0L) {
@ -88,8 +107,6 @@ class SendWorker(
timestampPrev = timestamp
}
//【注意】卡槽id-1=获取失败、0=卡槽1、1=卡槽2但是 Rule 表里存的是 SIM1/SIM2
val simSlot = "SIM" + (msgInfo.simSlot + 1)
val ruleList: List<Rule> = Core.rule.getRuleList(msgInfo.type, 1, simSlot)
if (ruleList.isEmpty()) {
return@withContext Result.failure(workDataOf("send" to "failed"))
@ -128,4 +145,60 @@ class SendWorker(
}
}
private fun autoTaskProcess(msgInfo: MsgInfo, msgInfoJson: String, simSlot: String) {
val conditionType = when (msgInfo.type) {
"app" -> TASK_CONDITION_APP
"call" -> TASK_CONDITION_CALL
else -> TASK_CONDITION_SMS
}
val taskList = Core.task.getByType(conditionType)
for (task in taskList) {
Log.d(TAG, "task = $task")
// 根据任务信息执行相应操作
val conditionList = Gson().fromJson(task.conditions, Array<TaskSetting>::class.java).toMutableList()
if (conditionList.isEmpty()) {
Log.d(TAG, "TASK-${task.id}conditionList is empty")
continue
}
val firstCondition = conditionList.firstOrNull()
if (firstCondition == null) {
Log.d(TAG, "TASK-${task.id}firstCondition is null")
continue
}
val ruleSetting = Gson().fromJson(firstCondition.setting, Rule::class.java)
if (ruleSetting == null) {
Log.d(TAG, "TASK-${task.id}ruleSetting is null")
continue
}
if (ruleSetting.simSlot != CHECK_SIM_SLOT_ALL && simSlot != ruleSetting.simSlot) {
Log.d(TAG, "TASK-${task.id}simSlot is not matched, simSlot = $simSlot, ruleSetting = $ruleSetting")
continue
}
if (!ruleSetting.checkMsg(msgInfo)) {
Log.d(TAG, "TASK-${task.id}ruleSetting is not matched, msgInfo = $msgInfo, ruleSetting = $ruleSetting")
continue
}
//TODO判断其他条件是否满足
if (!ConditionUtils.checkCondition(task.id, conditionList)) {
Log.d(TAG, "TASK-${task.id}other condition is not satisfied")
continue
}
//TODO: 组装消息体 && 执行具体任务
val actionData = Data.Builder()
.putLong(TaskWorker.TASK_ID, task.id)
.putString(TaskWorker.TASK_ACTIONS, task.actions)
.putString(TaskWorker.MSG_INFO, msgInfoJson)
.build()
val actionRequest = OneTimeWorkRequestBuilder<ActionWorker>().setInputData(actionData).build()
WorkManager.getInstance().enqueue(actionRequest)
}
}
}

@ -14,10 +14,8 @@ import kotlinx.coroutines.withContext
import java.text.SimpleDateFormat
import java.util.Locale
class UpdateLogsWorker(
context: Context,
workerParams: WorkerParameters,
) : CoroutineWorker(context, workerParams) {
class UpdateLogsWorker(context: Context, params: WorkerParameters) : CoroutineWorker(context, params) {
override suspend fun doWork(): Result = withContext(Dispatchers.IO) {
try {
val sendResponseJson = inputData.getString(Worker.UPDATE_LOGS)

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<vector android:height="25.0dip" android:width="25.0dip" android:autoMirrored="true" android:viewportWidth="25.0" android:viewportHeight="25.0"
xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@color/auto_task_icon_grey_bg" android:pathData="M6.66,0.66L18.66,0.66A6,6 0,0 1,24.66 6.66L24.66,18.66A6,6 0,0 1,18.66 24.66L6.66,24.66A6,6 0,0 1,0.66 18.66L0.66,6.66A6,6 0,0 1,6.66 0.66z" />
<path android:fillColor="@color/auto_task_icon_grey" android:pathData="M9.372,6.643C9.337,6.583 9.292,6.531 9.238,6.488C9.078,6.322 8.846,6.252 8.622,6.298L8.486,6.314C8.453,6.313 8.427,6.318 8.412,6.321C8.375,6.328 8.344,6.34 8.326,6.348C8.285,6.365 8.24,6.388 8.197,6.412C8.107,6.462 7.991,6.534 7.864,6.615C7.609,6.779 7.295,6.995 7.034,7.186L7.029,7.19L7.025,7.193C6.433,7.657 6.249,8.29 6.29,8.968C6.327,9.585 6.549,10.26 6.823,10.93L6.81,10.951L6.872,11.093C7.346,12.181 8.27,13.582 9.741,15.108L9.738,15.112L9.926,15.3L9.993,15.366L10.009,15.381L10.02,15.392L10.113,15.299L10.114,15.3L10.021,15.394C11.622,16.972 13.094,17.953 14.226,18.447L14.368,18.509L14.389,18.496C15.058,18.77 15.734,18.992 16.351,19.029C17.028,19.07 17.662,18.886 18.126,18.294L18.129,18.29L18.132,18.285C18.323,18.024 18.539,17.71 18.703,17.455C18.785,17.328 18.857,17.212 18.906,17.122C18.931,17.079 18.954,17.034 18.971,16.993C18.979,16.975 18.991,16.944 18.998,16.907C19.001,16.892 19.006,16.866 19.005,16.833L19.02,16.697C19.066,16.473 18.996,16.241 18.831,16.081C18.788,16.028 18.736,15.982 18.676,15.948L18.64,15.92L15.363,14.127L13.785,15.497C13.005,15.085 12.239,14.414 11.574,13.762C10.916,13.093 10.237,12.321 9.822,11.533L11.192,9.955L9.399,6.679L9.372,6.643ZM10.039,15.186L10.039,15.185L10.048,15.176L10.049,15.177C10.059,15.207 10.073,15.237 10.091,15.267L10.09,15.268C10.071,15.244 10.053,15.217 10.039,15.186ZM10.005,14.98L10.02,14.991C10.021,14.98 10.022,14.971 10.024,14.965C10.023,14.965 10.023,14.964 10.022,14.963C10.017,14.968 10.011,14.974 10.005,14.98ZM10.091,15.269L10.092,15.268L10.101,15.281L10.091,15.269ZM10.116,15.298L10.115,15.297L10.234,15.178L10.116,15.298ZM10.431,15.407L10.441,15.398L10.44,15.4L10.431,15.407ZM10.425,15.413L10.428,15.41L10.427,15.411L10.425,15.413ZM10.394,15.435L10.391,15.436L10.403,15.429L10.394,15.435ZM10.408,15.426L10.407,15.427L10.41,15.424L10.408,15.426Z" android:fillType="evenOdd" />
</vector>

@ -6,7 +6,7 @@
android:viewportWidth="25.0"
android:viewportHeight="25.0">
<path
android:fillColor="#ff2eafff"
android:fillColor="#ff8482ff"
android:pathData="M6.66,0.66L18.66,0.66A6,6 0,0 1,24.66 6.66L24.66,18.66A6,6 0,0 1,18.66 24.66L6.66,24.66A6,6 0,0 1,0.66 18.66L0.66,6.66A6,6 0,0 1,6.66 0.66z" />
<path
android:fillColor="#ffffffff"

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="25.0dip"
android:height="25.0dip"
android:autoMirrored="true"
android:viewportWidth="25.0"
android:viewportHeight="25.0">
<path
android:fillColor="#ffe6e6e6"
android:pathData="M6.66,0.66L18.66,0.66A6,6 0,0 1,24.66 6.66L24.66,18.66A6,6 0,0 1,18.66 24.66L6.66,24.66A6,6 0,0 1,0.66 18.66L0.66,6.66A6,6 0,0 1,6.66 0.66z" />
<path
android:fillColor="#ffffffff"
android:fillType="evenOdd"
android:pathData="M7.984,5.837C8.418,5.586 8.974,5.735 9.224,6.169L9.779,7.13C10.518,6.712 11.372,6.473 12.282,6.473C13.199,6.473 14.06,6.715 14.803,7.14L15.342,6.207C15.592,5.772 16.148,5.623 16.582,5.874C17.017,6.125 17.165,6.68 16.915,7.115L16.212,8.331C16.862,9.121 17.278,10.11 17.355,11.194H7.21C7.287,10.103 7.708,9.107 8.366,8.314L7.651,7.077C7.401,6.643 7.55,6.087 7.984,5.837ZM17.368,12.647H7.198V17.732C7.198,18.535 7.848,19.185 8.651,19.185H15.915C16.718,19.185 17.368,18.535 17.368,17.732V12.647Z" />
</vector>

@ -131,17 +131,17 @@
android:id="@+id/rb_sim_slot_all"
style="@style/rg_rb_style"
android:checked="true"
android:text="@string/all" />
android:text="@string/sim_any" />
<RadioButton
android:id="@+id/rb_sim_slot_1"
style="@style/rg_rb_style"
android:text="@string/sim1" />
android:text="@string/sim_1" />
<RadioButton
android:id="@+id/rb_sim_slot_2"
style="@style/rg_rb_style"
android:text="@string/sim2" />
android:text="@string/sim_2" />
</RadioGroup>

@ -0,0 +1,312 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?attr/xui_config_color_background"
android:orientation="vertical">
<androidx.core.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:overScrollMode="never">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="5dp"
android:gravity="center_horizontal"
android:orientation="vertical">
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/iv_task_sms"
android:layout_width="120dp"
android:layout_height="120dp"
android:layout_margin="10dp"
android:contentDescription="@string/task_sms"
android:visibility="gone"
app:srcCompat="@drawable/auto_task_icon_sms"
tools:ignore="ImageContrastCheck" />
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/iv_task_call"
android:layout_width="120dp"
android:layout_height="120dp"
android:layout_margin="10dp"
android:contentDescription="@string/task_call"
android:visibility="gone"
app:srcCompat="@drawable/auto_task_icon_incall"
tools:ignore="ImageContrastCheck" />
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/iv_task_app"
android:layout_width="120dp"
android:layout_height="120dp"
android:layout_margin="10dp"
android:contentDescription="@string/task_app"
android:visibility="gone"
app:srcCompat="@drawable/auto_task_icon_start_activity"
tools:ignore="ImageContrastCheck" />
<LinearLayout
android:id="@+id/layout_sim_slot"
style="@style/BarStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/match_sim_slot"
android:textStyle="bold" />
<RadioGroup
android:id="@+id/rg_sim_slot"
style="@style/rg_style"
android:orientation="horizontal">
<RadioButton
android:id="@+id/rb_sim_slot_all"
style="@style/rg_rb_style"
android:checked="true"
android:text="@string/sim_any" />
<RadioButton
android:id="@+id/rb_sim_slot_1"
style="@style/rg_rb_style"
android:text="@string/sim_1" />
<RadioButton
android:id="@+id/rb_sim_slot_2"
style="@style/rg_rb_style"
android:text="@string/sim_2" />
</RadioGroup>
</LinearLayout>
<LinearLayout
style="@style/BarStyle.Switch"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/match_field"
android:textStyle="bold" />
<RadioGroup
android:id="@+id/rg_filed"
style="@style/rg_style"
android:orientation="horizontal">
<RadioButton
android:id="@+id/rb_transpond_all"
style="@style/rg_rb_style_wrap"
android:checked="true"
android:text="@string/all" />
<RadioButton
android:id="@+id/rb_phone"
style="@style/rg_rb_style_wrap"
android:text="@string/phone_number" />
<RadioButton
android:id="@+id/rb_call_type"
style="@style/rg_rb_style_wrap"
android:text="@string/call_type" />
<RadioButton
android:id="@+id/rb_content"
style="@style/rg_rb_style_wrap"
android:text="@string/sms_content" />
<RadioButton
android:id="@+id/rb_package_name"
style="@style/rg_rb_style_wrap"
android:text="@string/package_name" />
<RadioButton
android:id="@+id/rb_uid"
style="@style/rg_rb_style_wrap"
android:text="@string/uid" />
<RadioButton
android:id="@+id/rb_inform_content"
style="@style/rg_rb_style_wrap"
android:text="@string/inform_content" />
<RadioButton
android:id="@+id/rb_multi_match"
style="@style/rg_rb_style_wrap"
android:text="@string/multiple_matches" />
</RadioGroup>
</LinearLayout>
<LinearLayout
android:id="@+id/layout_match_type"
style="@style/BarStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:visibility="gone">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="5dp"
android:text="@string/match_type"
android:textStyle="bold" />
<RadioGroup
android:id="@+id/rg_check"
style="@style/rg_style"
android:orientation="horizontal">
<RadioButton
android:id="@+id/rb_is"
style="@style/rg_rb_style"
android:checked="true"
android:text="@string/btn_is" />
<RadioButton
android:id="@+id/rb_contain"
style="@style/rg_rb_style"
android:text="@string/btn_contain" />
<RadioButton
android:id="@+id/rb_not_contain"
style="@style/rg_rb_style"
android:text="@string/btn_not_contain" />
</RadioGroup>
<RadioGroup
android:id="@+id/rg_check2"
style="@style/rg_style"
android:orientation="horizontal">
<RadioButton
android:id="@+id/rb_start_with"
style="@style/rg_rb_style"
android:text="@string/btn_start_with" />
<RadioButton
android:id="@+id/rb_end_with"
style="@style/rg_rb_style"
android:text="@string/btn_end_with" />
<RadioButton
android:id="@+id/rb_regex"
style="@style/rg_rb_style"
android:text="@string/btn_regex" />
</RadioGroup>
</LinearLayout>
<LinearLayout
android:id="@+id/layout_match_value"
style="@style/BarStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:visibility="gone">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/match_value"
android:textStyle="bold" />
<com.xuexiang.xui.widget.edittext.materialedittext.MaterialEditText
android:id="@+id/et_value"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/match_value_tips"
android:inputType="textMultiLine"
app:met_clearButton="true" />
<com.xuexiang.xui.widget.spinner.materialspinner.MaterialSpinner
android:id="@+id/sp_call_type"
style="@style/Material.SpinnerStyle"
android:layout_marginTop="@dimen/config_margin_4dp"
android:visibility="gone" />
<LinearLayout
android:id="@+id/layout_app_list"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:orientation="horizontal"
android:visibility="gone">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/choose_app"
android:textSize="@dimen/text_size_small"
android:textStyle="bold" />
<com.xuexiang.xui.widget.spinner.editspinner.EditSpinner
android:id="@+id/sp_app"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="5dp"
android:layout_weight="1"
app:es_hint="@string/choose_app_hint"
app:es_maxLength="20"
app:es_maxLine="1" />
</LinearLayout>
<TextView
android:id="@+id/tv_mu_rule_tips"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/mu_rule_sms_tips"
android:visibility="gone" />
</LinearLayout>
</LinearLayout>
</androidx.core.widget.NestedScrollView>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:orientation="horizontal"
android:padding="10dp">
<com.xuexiang.xui.widget.textview.supertextview.SuperButton
android:id="@+id/btn_del"
style="@style/SuperButton.Gray.Icon.Spacing"
android:drawableStart="@drawable/ic_delete"
android:text="@string/discard"
tools:ignore="RtlSymmetry,TextContrastCheck,TouchTargetSizeCheck" />
<com.xuexiang.xui.widget.textview.supertextview.SuperButton
android:id="@+id/btn_save"
style="@style/SuperButton.Blue.Icon.Spacing"
android:drawableStart="@drawable/ic_save"
android:text="@string/submit"
tools:ignore="RtlSymmetry,TextContrastCheck,TouchTargetSizeCheck" />
<com.xuexiang.xui.widget.textview.supertextview.SuperButton
android:id="@+id/btn_test"
style="@style/SuperButton.Green.Icon.Spacing"
android:drawableStart="@drawable/ic_test"
android:text="@string/test"
tools:ignore="RtlSymmetry,TextContrastCheck,TouchTargetSizeCheck" />
</LinearLayout>
</LinearLayout>

@ -169,6 +169,7 @@
<string name="test">Test</string>
<string name="confirm">Confirm</string>
<string name="all">All</string>
<string name="any">Any</string>
<string name="select">Select</string>
<string name="clone">Clone</string>
<string name="setting">Setting</string>
@ -640,6 +641,7 @@
<string name="rule_call">CALL</string>
<string name="rule_app">APP</string>
<string name="rule_all">ALL</string>
<string name="rule_any">ANY</string>
<string name="rule_transpond_all">Transpond All</string>
<string name="rule_phone_num">Phone Num</string>
<string name="rule_msg_content">Msg Content</string>
@ -997,6 +999,7 @@
<string name="contact_info" formatted="false">Name%s\nPhone%s</string>
<string name="card_slot_does_not_match">Card slot does not match the rule</string>
<string name="unmatched_rule">Unmatched rule</string>
<string name="matched_rule">Matched rule</string>
<string name="copied_to_clipboard">Copied to clipboard:\n%s</string>
<string name="search_keyword">Search Keyword: %s</string>
<string name="export_succeeded">Export configuration succeeded!</string>
@ -1194,6 +1197,15 @@
<string name="task_charge_tips">Trigger when charge status meets condition.</string>
<string name="task_lock_screen">Screen Off/On</string>
<string name="task_lock_screen_tips">Trigger upon screen lock/unlock instantly or after a set time.</string>
<string name="task_sms">SMS</string>
<string name="task_sms_tips">Triggered upon receiving SMS broadcast</string>
<string name="task_sms_when">received SMS broadcast from %s</string>
<string name="task_call">Call</string>
<string name="task_call_tips">Triggered upon receiving call broadcast</string>
<string name="task_call_when">received call broadcast from %s</string>
<string name="task_app">Notification</string>
<string name="task_app_tips">Triggered upon receiving app notification</string>
<string name="task_app_when">received app notification</string>
<string name="task_sendsms">Send Sms</string>
<string name="task_notification">Notify</string>
<string name="task_frpc">Frpc On/Off</string>
@ -1322,6 +1334,8 @@
<string name="condition_already_exists">This type of condition already exists.</string>
<string name="action_already_exists">This type of action already exists.</string>
<string name="only_one_location_condition">"To Address" vs "Leave Address": mutually exclusive.</string>
<string name="only_one_msg_condition">SMS/CALL/APP: mutually exclusive.</string>
<string name="msg_condition_must_be_trigger">SMS/CALL/APP: must be used as trigger.</string>
<string name="current_address">Current Address: %s</string>
<string name="location_failed">Location failed. Please try again later.</string>
<string name="current_distance_from_center">, %s meters from the center.</string>
@ -1385,4 +1399,9 @@
<string name="alarm_play_times">Play Times(0=Infinite)</string>
<string name="invalid_tag">%s tag is invalid: %s</string>
<string name="invalid_task_name">Please input task name.</string>
<string name="invalid_conditions">Please add trigger conditions.</string>
<string name="invalid_actions">Please add execution actions.</string>
<string name="invalid_cron">Please set the time for the scheduled task</string>
<string name="invalid_proxy_host">Proxy server hostname resolution failed: proxyHost=%s</string>
</resources>

@ -170,6 +170,7 @@
<string name="test">测试</string>
<string name="confirm">确认</string>
<string name="all">全部</string>
<string name="any">任意</string>
<string name="select">选择</string>
<string name="clone">一键克隆</string>
<string name="setting">通用设置</string>
@ -235,7 +236,7 @@
<string name="add_frpc_first">请先去设置FRPC页面添加</string>
<string name="add_task_first">请先去设置自动任务页面添加</string>
<string name="select_sender">发送通道</string>
<string name="rule_tester">转发规则测试</string>
<string name="rule_tester">规则匹配测试</string>
<string name="test_sim_slot">测试模拟的接收卡槽</string>
<string name="test_phone_number">测试模拟的来源号码</string>
<string name="test_msg_content">测试模拟的短信内容</string>
@ -641,6 +642,7 @@
<string name="rule_call">来电</string>
<string name="rule_app">应用</string>
<string name="rule_all">全部</string>
<string name="rule_any">任意</string>
<string name="rule_transpond_all">全部转发</string>
<string name="rule_phone_num">手机号</string>
<string name="rule_msg_content">内容</string>
@ -998,6 +1000,7 @@
<string name="contact_info" formatted="false">姓名:%s\n号码%s</string>
<string name="card_slot_does_not_match">卡槽未匹配中规则</string>
<string name="unmatched_rule">未匹配中规则</string>
<string name="matched_rule">匹配中规则</string>
<string name="copied_to_clipboard">已复制到剪贴板:\n%s</string>
<string name="search_keyword">搜索关键字: %s</string>
<string name="export_succeeded">导出配置成功!</string>
@ -1045,7 +1048,7 @@
<string name="enable_cactus">启用 Cactus 增强保活措施(会增加耗电)</string>
<string name="enabe_cactus_tips">双进程前台服务/JobScheduler/WorkManager/1像素/无声音乐</string>
<string name="load_app_list">启动时异步获取已安装App列表</string>
<string name="load_app_list_tips">用于加速进入应用列表/编辑转发规则下拉选择/替换{{APP名称}}</string>
<string name="load_app_list_tips">用于加速进入应用列表/编辑转发规则下拉选择/替换{{APP_NAME}}</string>
<string name="load_app_list_toast">开启异步获取已安装App列表时必选一个类型</string>
<string name="no_server_history">暂无历史记录,接口测试通过后自动加入</string>
<string name="select_time_period">时间段选择</string>
@ -1195,6 +1198,15 @@
<string name="task_charge_tips">当充电状态满足条件时触发</string>
<string name="task_lock_screen">锁屏解锁</string>
<string name="task_lock_screen_tips">在屏幕锁定或解锁后立即或指定时间触发</string>
<string name="task_sms">短信广播</string>
<string name="task_sms_tips">在接收到短信广播时触发</string>
<string name="task_sms_when">接收到%s短信广播</string>
<string name="task_call">通话广播</string>
<string name="task_call_tips">在接收到通话广播时触发</string>
<string name="task_call_when">接收到%s通话广播</string>
<string name="task_app">APP通知</string>
<string name="task_app_tips">在接收到APP通知时触发</string>
<string name="task_app_when">接收到APP通知</string>
<string name="task_sendsms">发送短信</string>
<string name="task_notification">推送通知</string>
<string name="task_frpc">启停Frpc</string>
@ -1323,6 +1335,8 @@
<string name="condition_already_exists">已添加过该类型条件</string>
<string name="action_already_exists">已添加过该类型动作</string>
<string name="only_one_location_condition">进入地点 与 离开地点 类型条件互斥</string>
<string name="only_one_msg_condition">短信广播/通话广播/APP通知 类型条件互斥</string>
<string name="msg_condition_must_be_trigger">短信广播/通话广播/APP通知 类型条件只能作为触发条件</string>
<string name="current_address">当前地址:%s</string>
<string name="location_failed">定位失败,请稍后重试</string>
<string name="current_distance_from_center">, 当前距离中心%s米</string>
@ -1386,4 +1400,9 @@
<string name="alarm_play_times">播放次数(0=无限)</string>
<string name="invalid_tag">%s 标签无效:%s</string>
<string name="invalid_task_name">请输入任务名称</string>
<string name="invalid_conditions">请添加触发条件</string>
<string name="invalid_actions">请添加执行动作</string>
<string name="invalid_cron">请设置定时任务的时间</string>
<string name="invalid_proxy_host">代理服务器主机名解析失败proxyHost=%s</string>
</resources>

@ -170,6 +170,7 @@
<string name="test">測試</string>
<string name="confirm">確認</string>
<string name="all">全部</string>
<string name="any">任意</string>
<string name="select">選擇</string>
<string name="clone">一鍵克隆</string>
<string name="setting">通用設置</string>
@ -235,7 +236,7 @@
<string name="add_frpc_first">請先去設置FRPC頁面添加</string>
<string name="add_task_first">請先去設置自動任務頁面添加</string>
<string name="select_sender">發送通道</string>
<string name="rule_tester">轉發規則測試</string>
<string name="rule_tester">規則匹配測試</string>
<string name="test_sim_slot">測試模擬的接收卡槽</string>
<string name="test_phone_number">測試模擬的來源號碼</string>
<string name="test_msg_content">測試模擬的簡訊內容</string>
@ -641,6 +642,7 @@
<string name="rule_call">來電</string>
<string name="rule_app">應用</string>
<string name="rule_all">全部</string>
<string name="rule_any">任意</string>
<string name="rule_transpond_all">全部轉發</string>
<string name="rule_phone_num">手機號</string>
<string name="rule_msg_content">內容</string>
@ -998,6 +1000,7 @@
<string name="contact_info" formatted="false">姓名:%s\n號碼%s</string>
<string name="card_slot_does_not_match">卡槽未匹配中規則</string>
<string name="unmatched_rule">未匹配中規則</string>
<string name="matched_rule">匹配中規則</string>
<string name="copied_to_clipboard">已複製到剪貼板:\n%s</string>
<string name="search_keyword">搜索關鍵字: %s</string>
<string name="export_succeeded">導出配置成功!</string>
@ -1045,7 +1048,7 @@
<string name="enable_cactus">啟用 Cactus 增強保活措施(會增加耗電)</string>
<string name="enabe_cactus_tips">雙進程前台服務/JobScheduler/WorkManager/1像素/無聲音樂</string>
<string name="load_app_list">啟動時異步獲取已安裝App列表</string>
<string name="load_app_list_tips">用於加速進入應用列表/編輯轉發規則下拉選擇/替換{{APP名稱}}</string>
<string name="load_app_list_tips">用於加速進入應用列表/編輯轉發規則下拉選擇/替換{{APP_NAME}}</string>
<string name="load_app_list_toast">開啟異步獲取已安裝App列表時必選一個類型</string>
<string name="no_server_history">暫無歷史記錄,接口測試通過後自動加入</string>
<string name="select_time_period">時間段選擇</string>
@ -1195,6 +1198,15 @@
<string name="task_charge_tips">當充電狀態滿足條件時觸發</string>
<string name="task_lock_screen">鎖屏解鎖</string>
<string name="task_lock_screen_tips">在屏幕鎖定或解鎖後立即或指定時間觸發</string>
<string name="task_sms">簡訊廣播</string>
<string name="task_sms_tips">在接收到簡訊廣播時觸發</string>
<string name="task_sms_when">接收到%s簡訊廣播</string>
<string name="task_call">通話廣播</string>
<string name="task_call_tips">在接收到通話廣播時觸發</string>
<string name="task_call_when">接收到%s通話廣播</string>
<string name="task_app">APP通知</string>
<string name="task_app_tips">在接收到APP通知時觸發</string>
<string name="task_app_when">接收到APP通知</string>
<string name="task_sendsms">發送簡訊</string>
<string name="task_notification">推送通知</string>
<string name="task_frpc">啟停Frpc</string>
@ -1322,7 +1334,9 @@
<string name="leave_address_keyword_description">離開GPS地址包含[%s]關鍵字區域</string>
<string name="condition_already_exists">已添加過該類型條件</string>
<string name="action_already_exists">已添加過該類型動作</string>
<string name="only_one_location_condition">進入地點與離開地點類型條件互斥</string>
<string name="only_one_location_condition">進入地點 與 離開地點 類型條件互斥</string>
<string name="only_one_msg_condition">簡訊廣播/通話廣播/APP通知 類型條件互斥</string>
<string name="msg_condition_must_be_trigger">簡訊廣播/通話廣播/APP通知 類型條件只能作為觸發條件</string>
<string name="current_address">當前地址:%s</string>
<string name="location_failed">定位失敗,請稍後重試</string>
<string name="current_distance_from_center">, 當前距離中心%s米</string>
@ -1386,4 +1400,9 @@
<string name="alarm_play_times">播放次數(0=無限)</string>
<string name="invalid_tag">%s 標籤無效:%s</string>
<string name="invalid_task_name">請輸入任務名稱</string>
<string name="invalid_conditions">請添加觸發條件</string>
<string name="invalid_actions">請添加執行動作</string>
<string name="invalid_cron">請設置定時任務的時間</string>
<string name="invalid_proxy_host">代理伺服器主機名解析失敗proxyHost=%s</string>
</resources>

@ -170,6 +170,7 @@
<string name="test">测试</string>
<string name="confirm">确认</string>
<string name="all">全部</string>
<string name="any">任意</string>
<string name="select">选择</string>
<string name="clone">一键克隆</string>
<string name="setting">通用设置</string>
@ -235,7 +236,7 @@
<string name="add_frpc_first">请先去设置Frpc页面添加</string>
<string name="add_task_first">请先去设置自动任务页面添加</string>
<string name="select_sender">发送通道</string>
<string name="rule_tester">转发规则测试</string>
<string name="rule_tester">规则匹配测试</string>
<string name="test_sim_slot">测试模拟的接收卡槽</string>
<string name="test_phone_number">测试模拟的来源号码</string>
<string name="test_msg_content">测试模拟的短信内容</string>
@ -641,6 +642,7 @@
<string name="rule_call">来电</string>
<string name="rule_app">应用</string>
<string name="rule_all">全部</string>
<string name="rule_any">任意</string>
<string name="rule_transpond_all">全部转发</string>
<string name="rule_phone_num">手机号</string>
<string name="rule_msg_content">内容</string>
@ -998,6 +1000,7 @@
<string name="contact_info" formatted="false">姓名:%s\n号码%s</string>
<string name="card_slot_does_not_match">卡槽未匹配中规则</string>
<string name="unmatched_rule">未匹配中规则</string>
<string name="matched_rule">匹配中规则</string>
<string name="copied_to_clipboard">已复制到剪贴板:\n%s</string>
<string name="search_keyword">搜索关键字: %s</string>
<string name="export_succeeded">导出配置成功!</string>
@ -1045,7 +1048,7 @@
<string name="enable_cactus">启用 Cactus 增强保活措施(会增加耗电)</string>
<string name="enabe_cactus_tips">双进程前台服务/JobScheduler/WorkManager/1像素/无声音乐</string>
<string name="load_app_list">启动时异步获取已安装App列表</string>
<string name="load_app_list_tips">用于加速进入应用列表/编辑转发规则下拉选择/替换{{APP名称}}</string>
<string name="load_app_list_tips">用于加速进入应用列表/编辑转发规则下拉选择/替换{{APP_NAME}}</string>
<string name="load_app_list_toast">开启异步获取已安装App列表时必选一个类型</string>
<string name="no_server_history">暂无历史记录,接口测试通过后自动加入</string>
<string name="select_time_period">时间段选择</string>
@ -1195,6 +1198,15 @@
<string name="task_charge_tips">当充电状态满足条件时触发</string>
<string name="task_lock_screen">锁屏解锁</string>
<string name="task_lock_screen_tips">在屏幕锁定或解锁后立即或指定时间触发</string>
<string name="task_sms">短信广播</string>
<string name="task_sms_tips">在接收到短信广播时触发</string>
<string name="task_sms_when">接收到%s短信广播</string>
<string name="task_call">通话广播</string>
<string name="task_call_tips">在接收到通话广播时触发</string>
<string name="task_call_when">接收到%s通话广播</string>
<string name="task_app">APP通知</string>
<string name="task_app_tips">在接收到APP通知时触发</string>
<string name="task_app_when">接收到APP通知</string>
<string name="task_sendsms">发送短信</string>
<string name="task_notification">推送通知</string>
<string name="task_frpc">启停Frpc</string>
@ -1323,6 +1335,8 @@
<string name="condition_already_exists">已添加过该类型条件</string>
<string name="action_already_exists">已添加过该类型动作</string>
<string name="only_one_location_condition">进入地点 与 离开地点 类型条件互斥</string>
<string name="only_one_msg_condition">短信广播/通话广播/APP通知 类型条件互斥</string>
<string name="msg_condition_must_be_trigger">短信广播/通话广播/APP通知 类型条件只能作为触发条件</string>
<string name="current_address">当前地址:%s</string>
<string name="location_failed">定位失败,请稍后重试</string>
<string name="current_distance_from_center">, 当前距离中心%s米</string>
@ -1386,4 +1400,9 @@
<string name="alarm_play_times">播放次数(0=无限)</string>
<string name="invalid_tag">%s 标签无效:%s</string>
<string name="invalid_task_name">请输入任务名称</string>
<string name="invalid_conditions">请添加触发条件</string>
<string name="invalid_actions">请添加执行动作</string>
<string name="invalid_cron">请设置定时任务的时间</string>
<string name="invalid_proxy_host">代理服务器主机名解析失败proxyHost=%s</string>
</resources>

Loading…
Cancel
Save