From d3899d9404e3cbbdc6b9f0bf1f209c71917614b6 Mon Sep 17 00:00:00 2001 From: pppscn <35696959@qq.com> Date: Thu, 14 Mar 2024 01:09:12 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9E=EF=BC=9A=E8=87=AA=E5=8A=A8?= =?UTF-8?q?=E4=BB=BB=E5=8A=A1=C2=B7=E5=BF=AB=E6=8D=B7=E6=8C=87=E4=BB=A4=20?= =?UTF-8?q?=E2=80=94=E2=80=94=20=E8=A7=A6=E5=8F=91=E6=9D=A1=E4=BB=B6?= =?UTF-8?q?=EF=BC=9A=E7=9F=AD=E4=BF=A1=E5=B9=BF=E6=92=AD=E3=80=81=E9=80=9A?= =?UTF-8?q?=E8=AF=9D=E5=B9=BF=E6=92=AD=E3=80=81APP=E9=80=9A=E7=9F=A5=20#38?= =?UTF-8?q?5=20#389?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../sms/forwarder/database/entity/Rule.kt | 19 +- .../idormy/sms/forwarder/entity/MsgInfo.kt | 6 +- .../forwarder/fragment/TasksEditFragment.kt | 50 +- .../fragment/condition/MsgFragment.kt | 494 ++++++++++++++++++ .../idormy/sms/forwarder/utils/Constants.kt | 4 + .../utils/sender/DingtalkInnerRobotUtils.kt | 4 +- .../forwarder/utils/sender/TelegramUtils.kt | 4 +- .../utils/sender/WeworkAgentUtils.kt | 4 +- .../utils/sender/WeworkRobotUtils.kt | 2 +- .../forwarder/utils/task/ConditionUtils.kt | 14 + .../sms/forwarder/utils/task/TaskUtils.kt | 9 + .../sms/forwarder/workers/ActionWorker.kt | 3 + .../sms/forwarder/workers/BatteryWorker.kt | 4 + .../forwarder/workers/LoadAppListWorker.kt | 5 +- .../sms/forwarder/workers/SendLogicWorker.kt | 6 +- .../sms/forwarder/workers/SendWorker.kt | 91 +++- .../sms/forwarder/workers/UpdateLogsWorker.kt | 6 +- .../drawable/auto_task_icon_incall_grey.xml | 6 + .../auto_task_icon_start_activity.xml | 2 +- .../auto_task_icon_start_activity_grey.xml | 15 + .../main/res/layout/fragment_rules_edit.xml | 6 +- .../layout/fragment_tasks_condition_msg.xml | 312 +++++++++++ app/src/main/res/values-en/strings.xml | 19 + app/src/main/res/values-zh-rCN/strings.xml | 23 +- app/src/main/res/values-zh-rTW/strings.xml | 25 +- app/src/main/res/values/strings.xml | 23 +- 26 files changed, 1112 insertions(+), 44 deletions(-) create mode 100644 app/src/main/java/com/idormy/sms/forwarder/fragment/condition/MsgFragment.kt create mode 100644 app/src/main/res/drawable/auto_task_icon_incall_grey.xml create mode 100644 app/src/main/res/drawable/auto_task_icon_start_activity_grey.xml create mode 100644 app/src/main/res/layout/fragment_tasks_condition_msg.xml diff --git a/app/src/main/java/com/idormy/sms/forwarder/database/entity/Rule.kt b/app/src/main/java/com/idormy/sms/forwarder/database/entity/Rule.kt index 8707069f..d2d0dfd7 100644 --- a/app/src/main/java/com/idormy/sms/forwarder/database/entity/Rule.kt +++ b/app/src/main/java/com/idormy/sms/forwarder/database/entity/Rule.kt @@ -89,7 +89,7 @@ data class Rule( } val SIM_SLOT_MAP = object : HashMap() { 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) diff --git a/app/src/main/java/com/idormy/sms/forwarder/entity/MsgInfo.kt b/app/src/main/java/com/idormy/sms/forwarder/entity/MsgInfo.kt index 537602d0..a9072e55 100644 --- a/app/src/main/java/com/idormy/sms/forwarder/entity/MsgInfo.kt +++ b/app/src/main/java/com/idormy/sms/forwarder/entity/MsgInfo.kt @@ -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) diff --git a/app/src/main/java/com/idormy/sms/forwarder/fragment/TasksEditFragment.kt b/app/src/main/java/com/idormy/sms/forwarder/fragment/TasksEditFragment.kt index bbe34b58..2ec2f39f 100644 --- a/app/src/main/java/com/idormy/sms/forwarder/fragment/TasksEditFragment.kt +++ b/app/src/main/java/com/idormy/sms/forwarder/fragment/TasksEditFragment.kt @@ -135,6 +135,27 @@ class TasksEditFragment : BaseFragment(), 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(), 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(), 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(), 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(), 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(), View.OnClic } } } + @Suppress("UNCHECKED_CAST") PageOption.to(Class.forName(widgetInfo.classPath) as Class) //跳转的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(), View.OnClic PageOption.to(Class.forName(widgetInfo.classPath) as Class) //跳转的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) } diff --git a/app/src/main/java/com/idormy/sms/forwarder/fragment/condition/MsgFragment.kt b/app/src/main/java/com/idormy/sms/forwarder/fragment/condition/MsgFragment.kt new file mode 100644 index 00000000..be56e0a1 --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/fragment/condition/MsgFragment.kt @@ -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(), 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() + 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().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(R.id.tv_sim_slot) + val rgSimSlot = dialogTest.findViewById(R.id.rg_sim_slot) + val tvFrom = dialogTest.findViewById(R.id.tv_from) + val etFrom = dialogTest.findViewById(R.id.et_from) + val tvTitle = dialogTest.findViewById(R.id.tv_title) + val etTitle = dialogTest.findViewById(R.id.et_title) + val tvContent = dialogTest.findViewById(R.id.tv_content) + val etContent = dialogTest.findViewById(R.id.et_content) + //通话类型 + val tvCallType = dialogTest.findViewById(R.id.tv_call_type) + val spCallType = dialogTest.findViewById(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() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/utils/Constants.kt b/app/src/main/java/com/idormy/sms/forwarder/utils/Constants.kt index 1ef2957c..ddf779a4 100644 --- a/app/src/main/java/com/idormy/sms/forwarder/utils/Constants.kt +++ b/app/src/main/java/com/idormy/sms/forwarder/utils/Constants.kt @@ -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 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 001be75c..1d07c63e 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 @@ -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 diff --git a/app/src/main/java/com/idormy/sms/forwarder/utils/sender/TelegramUtils.kt b/app/src/main/java/com/idormy/sms/forwarder/utils/sender/TelegramUtils.kt index 96b3069c..aa1949e4 100644 --- a/app/src/main/java/com/idormy/sms/forwarder/utils/sender/TelegramUtils.kt +++ b/app/src/main/java/com/idormy/sms/forwarder/utils/sender/TelegramUtils.kt @@ -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 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 3ec04d17..8573aeb5 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 @@ -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 diff --git a/app/src/main/java/com/idormy/sms/forwarder/utils/sender/WeworkRobotUtils.kt b/app/src/main/java/com/idormy/sms/forwarder/utils/sender/WeworkRobotUtils.kt index ef37f782..15cbffc9 100644 --- a/app/src/main/java/com/idormy/sms/forwarder/utils/sender/WeworkRobotUtils.kt +++ b/app/src/main/java/com/idormy/sms/forwarder/utils/sender/WeworkRobotUtils.kt @@ -35,7 +35,7 @@ class WeworkRobotUtils private constructor() { Log.i(TAG, "requestUrl:$requestUrl") val msgMap: MutableMap = mutableMapOf() - msgMap["msgtype"] = setting.msgType + msgMap["msgtype"] = if (setting.msgType == "markdown") "markdown" else "text" val contextMap = mutableMapOf() contextMap["content"] = content diff --git a/app/src/main/java/com/idormy/sms/forwarder/utils/task/ConditionUtils.kt b/app/src/main/java/com/idormy/sms/forwarder/utils/task/ConditionUtils.kt index dcd80816..e4b9d368 100644 --- a/app/src/main/java/com/idormy/sms/forwarder/utils/task/ConditionUtils.kt +++ b/app/src/main/java/com/idormy/sms/forwarder/utils/task/ConditionUtils.kt @@ -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-$taskId:lockScreenAction 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-$taskId:ruleSetting is null") + continue + } + //TODO: 判断消息是否满足条件 + } + } } diff --git a/app/src/main/java/com/idormy/sms/forwarder/utils/task/TaskUtils.kt b/app/src/main/java/com/idormy/sms/forwarder/utils/task/TaskUtils.kt index d72424d6..acc40802 100644 --- a/app/src/main/java/com/idormy/sms/forwarder/utils/task/TaskUtils.kt +++ b/app/src/main/java/com/idormy/sms/forwarder/utils/task/TaskUtils.kt @@ -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 diff --git a/app/src/main/java/com/idormy/sms/forwarder/workers/ActionWorker.kt b/app/src/main/java/com/idormy/sms/forwarder/workers/ActionWorker.kt index 0726ba91..fca3ffd8 100644 --- a/app/src/main/java/com/idormy/sms/forwarder/workers/ActionWorker.kt +++ b/app/src/main/java/com/idormy/sms/forwarder/workers/ActionWorker.kt @@ -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 = -1,msgId = -1 SendUtils.sendMsgSender(msgInfo, ruleSetting, 0, -1L, -1L) diff --git a/app/src/main/java/com/idormy/sms/forwarder/workers/BatteryWorker.kt b/app/src/main/java/com/idormy/sms/forwarder/workers/BatteryWorker.kt index 6d3b213b..cd9925c8 100644 --- a/app/src/main/java/com/idormy/sms/forwarder/workers/BatteryWorker.kt +++ b/app/src/main/java/com/idormy/sms/forwarder/workers/BatteryWorker.kt @@ -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) diff --git a/app/src/main/java/com/idormy/sms/forwarder/workers/LoadAppListWorker.kt b/app/src/main/java/com/idormy/sms/forwarder/workers/LoadAppListWorker.kt index 786dac83..6ef9c079 100644 --- a/app/src/main/java/com/idormy/sms/forwarder/workers/LoadAppListWorker.kt +++ b/app/src/main/java/com/idormy/sms/forwarder/workers/LoadAppListWorker.kt @@ -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 diff --git a/app/src/main/java/com/idormy/sms/forwarder/workers/SendLogicWorker.kt b/app/src/main/java/com/idormy/sms/forwarder/workers/SendLogicWorker.kt index 1852546c..bb8afc9c 100644 --- a/app/src/main/java/com/idormy/sms/forwarder/workers/SendLogicWorker.kt +++ b/app/src/main/java/com/idormy/sms/forwarder/workers/SendLogicWorker.kt @@ -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) 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 6dd0451f..b6cf2096 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 @@ -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 = 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::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().setInputData(actionData).build() + WorkManager.getInstance().enqueue(actionRequest) + } + } + } \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/workers/UpdateLogsWorker.kt b/app/src/main/java/com/idormy/sms/forwarder/workers/UpdateLogsWorker.kt index e0c75327..44b7a55b 100644 --- a/app/src/main/java/com/idormy/sms/forwarder/workers/UpdateLogsWorker.kt +++ b/app/src/main/java/com/idormy/sms/forwarder/workers/UpdateLogsWorker.kt @@ -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) diff --git a/app/src/main/res/drawable/auto_task_icon_incall_grey.xml b/app/src/main/res/drawable/auto_task_icon_incall_grey.xml new file mode 100644 index 00000000..0ec39622 --- /dev/null +++ b/app/src/main/res/drawable/auto_task_icon_incall_grey.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/auto_task_icon_start_activity.xml b/app/src/main/res/drawable/auto_task_icon_start_activity.xml index 8ca7b9f6..097bdb42 100644 --- a/app/src/main/res/drawable/auto_task_icon_start_activity.xml +++ b/app/src/main/res/drawable/auto_task_icon_start_activity.xml @@ -6,7 +6,7 @@ android:viewportWidth="25.0" android:viewportHeight="25.0"> + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_rules_edit.xml b/app/src/main/res/layout/fragment_rules_edit.xml index 3adb984f..a5c9924d 100644 --- a/app/src/main/res/layout/fragment_rules_edit.xml +++ b/app/src/main/res/layout/fragment_rules_edit.xml @@ -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" /> + android:text="@string/sim_1" /> + android:text="@string/sim_2" /> diff --git a/app/src/main/res/layout/fragment_tasks_condition_msg.xml b/app/src/main/res/layout/fragment_tasks_condition_msg.xml new file mode 100644 index 00000000..95cd7652 --- /dev/null +++ b/app/src/main/res/layout/fragment_tasks_condition_msg.xml @@ -0,0 +1,312 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values-en/strings.xml b/app/src/main/res/values-en/strings.xml index e71e7eab..7f659064 100644 --- a/app/src/main/res/values-en/strings.xml +++ b/app/src/main/res/values-en/strings.xml @@ -169,6 +169,7 @@ Test Confirm All + Any Select Clone Setting @@ -640,6 +641,7 @@ CALL APP ALL + ANY Transpond All Phone Num Msg Content @@ -997,6 +999,7 @@ Name:%s\nPhone:%s Card slot does not match the rule Unmatched rule + Matched rule Copied to clipboard:\n%s Search Keyword: %s Export configuration succeeded! @@ -1194,6 +1197,15 @@ Trigger when charge status meets condition. Screen Off/On Trigger upon screen lock/unlock instantly or after a set time. + SMS + Triggered upon receiving SMS broadcast + received SMS broadcast from %s + Call + Triggered upon receiving call broadcast + received call broadcast from %s + Notification + Triggered upon receiving app notification + received app notification Send Sms Notify Frpc On/Off @@ -1322,6 +1334,8 @@ This type of condition already exists. This type of action already exists. "To Address" vs "Leave Address": mutually exclusive. + SMS/CALL/APP: mutually exclusive. + SMS/CALL/APP: must be used as trigger. Current Address: %s Location failed. Please try again later. , %s meters from the center. @@ -1385,4 +1399,9 @@ Play Times(0=Infinite) %s tag is invalid: %s + Please input task name. + Please add trigger conditions. + Please add execution actions. + Please set the time for the scheduled task + Proxy server hostname resolution failed: proxyHost=%s diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index 8c31ab7d..33279199 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -170,6 +170,7 @@ 测试 确认 全部 + 任意 选择 一键克隆 通用设置 @@ -235,7 +236,7 @@ 请先去设置FRPC页面添加 请先去设置自动任务页面添加 发送通道 - 转发规则测试 + 规则匹配测试 测试模拟的接收卡槽 测试模拟的来源号码 测试模拟的短信内容 @@ -641,6 +642,7 @@ 来电 应用 全部 + 任意 全部转发 手机号 内容 @@ -998,6 +1000,7 @@ 姓名:%s\n号码:%s 卡槽未匹配中规则 未匹配中规则 + 匹配中规则 已复制到剪贴板:\n%s 搜索关键字: %s 导出配置成功! @@ -1045,7 +1048,7 @@ 启用 Cactus 增强保活措施(会增加耗电) 双进程前台服务/JobScheduler/WorkManager/1像素/无声音乐 启动时异步获取已安装App列表 - 用于加速进入应用列表/编辑转发规则下拉选择/替换{{APP名称}} + 用于加速进入应用列表/编辑转发规则下拉选择/替换{{APP_NAME}} 开启异步获取已安装App列表时必选一个类型 暂无历史记录,接口测试通过后自动加入 时间段选择 @@ -1195,6 +1198,15 @@ 当充电状态满足条件时触发 锁屏解锁 在屏幕锁定或解锁后立即或指定时间触发 + 短信广播 + 在接收到短信广播时触发 + 接收到%s短信广播 + 通话广播 + 在接收到通话广播时触发 + 接收到%s通话广播 + APP通知 + 在接收到APP通知时触发 + 接收到APP通知 发送短信 推送通知 启停Frpc @@ -1323,6 +1335,8 @@ 已添加过该类型条件 已添加过该类型动作 进入地点 与 离开地点 类型条件互斥 + 短信广播/通话广播/APP通知 类型条件互斥 + 短信广播/通话广播/APP通知 类型条件只能作为触发条件 当前地址:%s 定位失败,请稍后重试 , 当前距离中心%s米 @@ -1386,4 +1400,9 @@ 播放次数(0=无限) %s 标签无效:%s + 请输入任务名称 + 请添加触发条件 + 请添加执行动作 + 请设置定时任务的时间 + 代理服务器主机名解析失败:proxyHost=%s diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index 0ef20f1a..3f9590b5 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -170,6 +170,7 @@ 測試 確認 全部 + 任意 選擇 一鍵克隆 通用設置 @@ -235,7 +236,7 @@ 請先去設置FRPC頁面添加 請先去設置自動任務頁面添加 發送通道 - 轉發規則測試 + 規則匹配測試 測試模擬的接收卡槽 測試模擬的來源號碼 測試模擬的簡訊內容 @@ -641,6 +642,7 @@ 來電 應用 全部 + 任意 全部轉發 手機號 內容 @@ -998,6 +1000,7 @@ 姓名:%s\n號碼:%s 卡槽未匹配中規則 未匹配中規則 + 匹配中規則 已複製到剪貼板:\n%s 搜索關鍵字: %s 導出配置成功! @@ -1045,7 +1048,7 @@ 啟用 Cactus 增強保活措施(會增加耗電) 雙進程前台服務/JobScheduler/WorkManager/1像素/無聲音樂 啟動時異步獲取已安裝App列表 - 用於加速進入應用列表/編輯轉發規則下拉選擇/替換{{APP名稱}} + 用於加速進入應用列表/編輯轉發規則下拉選擇/替換{{APP_NAME}} 開啟異步獲取已安裝App列表時必選一個類型 暫無歷史記錄,接口測試通過後自動加入 時間段選擇 @@ -1195,6 +1198,15 @@ 當充電狀態滿足條件時觸發 鎖屏解鎖 在屏幕鎖定或解鎖後立即或指定時間觸發 + 簡訊廣播 + 在接收到簡訊廣播時觸發 + 接收到%s簡訊廣播 + 通話廣播 + 在接收到通話廣播時觸發 + 接收到%s通話廣播 + APP通知 + 在接收到APP通知時觸發 + 接收到APP通知 發送簡訊 推送通知 啟停Frpc @@ -1322,7 +1334,9 @@ 離開GPS地址包含[%s]關鍵字區域 已添加過該類型條件 已添加過該類型動作 - 進入地點與離開地點類型條件互斥 + 進入地點 與 離開地點 類型條件互斥 + 簡訊廣播/通話廣播/APP通知 類型條件互斥 + 簡訊廣播/通話廣播/APP通知 類型條件只能作為觸發條件 當前地址:%s 定位失敗,請稍後重試 , 當前距離中心%s米 @@ -1386,4 +1400,9 @@ 播放次數(0=無限) %s 標籤無效:%s + 請輸入任務名稱 + 請添加觸發條件 + 請添加執行動作 + 請設置定時任務的時間 + 代理伺服器主機名解析失敗:proxyHost=%s diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 32cfd98c..321500f1 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -170,6 +170,7 @@ 测试 确认 全部 + 任意 选择 一键克隆 通用设置 @@ -235,7 +236,7 @@ 请先去设置Frpc页面添加 请先去设置自动任务页面添加 发送通道 - 转发规则测试 + 规则匹配测试 测试模拟的接收卡槽 测试模拟的来源号码 测试模拟的短信内容 @@ -641,6 +642,7 @@ 来电 应用 全部 + 任意 全部转发 手机号 内容 @@ -998,6 +1000,7 @@ 姓名:%s\n号码:%s 卡槽未匹配中规则 未匹配中规则 + 匹配中规则 已复制到剪贴板:\n%s 搜索关键字: %s 导出配置成功! @@ -1045,7 +1048,7 @@ 启用 Cactus 增强保活措施(会增加耗电) 双进程前台服务/JobScheduler/WorkManager/1像素/无声音乐 启动时异步获取已安装App列表 - 用于加速进入应用列表/编辑转发规则下拉选择/替换{{APP名称}} + 用于加速进入应用列表/编辑转发规则下拉选择/替换{{APP_NAME}} 开启异步获取已安装App列表时必选一个类型 暂无历史记录,接口测试通过后自动加入 时间段选择 @@ -1195,6 +1198,15 @@ 当充电状态满足条件时触发 锁屏解锁 在屏幕锁定或解锁后立即或指定时间触发 + 短信广播 + 在接收到短信广播时触发 + 接收到%s短信广播 + 通话广播 + 在接收到通话广播时触发 + 接收到%s通话广播 + APP通知 + 在接收到APP通知时触发 + 接收到APP通知 发送短信 推送通知 启停Frpc @@ -1323,6 +1335,8 @@ 已添加过该类型条件 已添加过该类型动作 进入地点 与 离开地点 类型条件互斥 + 短信广播/通话广播/APP通知 类型条件互斥 + 短信广播/通话广播/APP通知 类型条件只能作为触发条件 当前地址:%s 定位失败,请稍后重试 , 当前距离中心%s米 @@ -1386,4 +1400,9 @@ 播放次数(0=无限) %s 标签无效:%s + 请输入任务名称 + 请添加触发条件 + 请添加执行动作 + 请设置定时任务的时间 + 代理服务器主机名解析失败:proxyHost=%s