新增:自动任务·快捷指令 —— 定时任务(AlarmManager方案) #279

踩坑:间隔时间不准确,低于5秒,间隔成5秒
pull/408/head
pppscn 10 months ago
parent 0a651b2da2
commit 51149c95cd

@ -19,6 +19,7 @@
tools:ignore="ProtectedPermissions" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission
android:name="android.permission.MANAGE_EXTERNAL_STORAGE"

@ -0,0 +1,10 @@
package com.idormy.sms.forwarder.entity.task
import java.io.Serializable
data class SmsSetting(
var description: String = "",
var simSlot: Int = 1,
var phoneNumbers: String = "",
var msgContent: String = "",
) : Serializable

@ -26,6 +26,7 @@ import com.idormy.sms.forwarder.databinding.FragmentTasksEditBinding
import com.idormy.sms.forwarder.entity.task.CronSetting
import com.idormy.sms.forwarder.entity.task.TaskSetting
import com.idormy.sms.forwarder.utils.*
import com.idormy.sms.forwarder.utils.task.CronUtils
import com.xuexiang.xaop.annotation.SingleClick
import com.xuexiang.xpage.annotation.Page
import com.xuexiang.xpage.base.XPageFragment
@ -37,6 +38,7 @@ import com.xuexiang.xui.adapter.recyclerview.RecyclerViewHolder
import com.xuexiang.xui.utils.DensityUtils
import com.xuexiang.xui.utils.WidgetUtils
import com.xuexiang.xui.widget.actionbar.TitleBar
import gatewayapps.crondroid.CronExpression
import io.reactivex.SingleObserver
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.Disposable
@ -214,6 +216,9 @@ class TasksEditFragment : BaseFragment<FragmentTasksEditBinding?>(), View.OnClic
val taskNew = checkForm()
if (isClone) taskNew.id = 0
Log.d(TAG, taskNew.toString())
//应用任务
applyTask(taskNew)
//保存任务
viewModel.insertOrUpdate(taskNew)
XToastUtils.success(R.string.tipSaveSuccess)
popToBack()
@ -254,6 +259,7 @@ class TasksEditFragment : BaseFragment<FragmentTasksEditBinding?>(), View.OnClic
}
Log.d(TAG, "initForm: $itemListConditions")
conditionsAdapter.notifyDataSetChanged()
binding!!.layoutAddCondition.visibility = if (itemListConditions.size >= MAX_SETTING_NUM) View.GONE else View.VISIBLE
}
if (task.actions.isNotEmpty()) {
val actionList = Gson().fromJson(task.actions, Array<TaskSetting>::class.java).toMutableList()
@ -262,6 +268,7 @@ class TasksEditFragment : BaseFragment<FragmentTasksEditBinding?>(), View.OnClic
}
Log.d(TAG, "initForm: $itemListActions")
actionsAdapter.notifyDataSetChanged()
binding!!.layoutAddAction.visibility = if (itemListActions.size >= MAX_SETTING_NUM) View.GONE else View.VISIBLE
}
} catch (e: Exception) {
e.printStackTrace()
@ -283,7 +290,23 @@ class TasksEditFragment : BaseFragment<FragmentTasksEditBinding?>(), View.OnClic
if (itemListActions.size <= 0) {
throw Exception("请添加执行动作")
}
taskType = itemListConditions[0].type
val lastExecTime = Date()
var nextExecTime = Date()
val firstCondition = itemListConditions[0]
taskType = firstCondition.type
when (taskType) {
TASK_CONDITION_CRON -> {
//检查定时任务的时间设置
val cronSetting = Gson().fromJson(firstCondition.setting, CronSetting::class.java)
if (cronSetting.expression.isEmpty()) {
throw Exception("请设置定时任务的时间")
}
val cronExpression = CronExpression(cronSetting.expression)
nextExecTime = cronExpression.getNextValidTimeAfter(lastExecTime)
}
}
//拼接任务描述
val description = StringBuilder()
@ -293,12 +316,36 @@ class TasksEditFragment : BaseFragment<FragmentTasksEditBinding?>(), View.OnClic
description.append(itemListActions.map { it.description }.toTypedArray().joinToString(","))
val status = if (binding!!.sbStatus.isChecked) STATUS_ON else STATUS_OFF
return Task(taskId, taskType, taskName, description.toString(), Gson().toJson(itemListConditions), Gson().toJson(itemListActions), status)
return Task(
taskId,
taskType,
taskName,
description.toString(),
Gson().toJson(itemListConditions),
Gson().toJson(itemListActions),
status,
lastExecTime,
nextExecTime
)
}
//测试任务
private fun testTask(task: Task) {
}
//应用任务
private fun applyTask(task: Task) {
when (task.type) {
//定时任务
TASK_CONDITION_CRON -> {
//取消旧任务的定时器
CronUtils.cancelAlarm(task)
//设置新的定时器
CronUtils.scheduleAlarm(task)
}
}
}
@SingleClick
override fun onItemClick(itemView: View, widgetInfo: PageInfo, pos: Int) {
try {
@ -394,6 +441,7 @@ class TasksEditFragment : BaseFragment<FragmentTasksEditBinding?>(), View.OnClic
itemListConditions[requestCode - 1] = taskSetting
}
conditionsAdapter.notifyDataSetChanged()
binding!!.layoutAddCondition.visibility = if (itemListConditions.size >= MAX_SETTING_NUM) View.GONE else View.VISIBLE
} else if (resultCode in KEY_BACK_CODE_ACTION..KEY_BACK_CODE_ACTION + 999) {
setting = extras!!.getString(KEY_BACK_DATA_ACTION)
if (setting == null) return
@ -448,6 +496,7 @@ class TasksEditFragment : BaseFragment<FragmentTasksEditBinding?>(), View.OnClic
itemListActions[requestCode - 1] = taskSetting
}
actionsAdapter.notifyDataSetChanged()
binding!!.layoutAddAction.visibility = if (itemListActions.size >= MAX_SETTING_NUM) View.GONE else View.VISIBLE
}
Log.d(TAG, "requestCode:$requestCode resultCode:$resultCode setting:$setting")
}
@ -487,6 +536,7 @@ class TasksEditFragment : BaseFragment<FragmentTasksEditBinding?>(), View.OnClic
private fun removeCondition(position: Int) {
itemListConditions.removeAt(position)
conditionsAdapter.notifyItemRemoved(position)
binding!!.layoutAddCondition.visibility = if (itemListConditions.size >= MAX_SETTING_NUM) View.GONE else View.VISIBLE
}
private fun editAction(position: Int) {
@ -507,5 +557,6 @@ class TasksEditFragment : BaseFragment<FragmentTasksEditBinding?>(), View.OnClic
private fun removeAction(position: Int) {
itemListActions.removeAt(position)
actionsAdapter.notifyItemRemoved(position)
binding!!.layoutAddAction.visibility = if (itemListActions.size >= MAX_SETTING_NUM) View.GONE else View.VISIBLE
}
}

@ -9,7 +9,7 @@ import android.view.ViewGroup
import com.google.gson.Gson
import com.idormy.sms.forwarder.R
import com.idormy.sms.forwarder.core.BaseFragment
import com.idormy.sms.forwarder.databinding.FragmentTasksCronBinding
import com.idormy.sms.forwarder.databinding.FragmentTasksActionSendSmsBinding
import com.idormy.sms.forwarder.entity.task.CronSetting
import com.idormy.sms.forwarder.utils.KEY_BACK_DATA_ACTION
import com.idormy.sms.forwarder.utils.KEY_EVENT_DATA_ACTION
@ -26,7 +26,7 @@ import com.xuexiang.xui.widget.actionbar.TitleBar
@Page(name = "Frpc")
@Suppress("PrivatePropertyName")
class FrpcFragment : BaseFragment<FragmentTasksCronBinding?>(), View.OnClickListener {
class FrpcFragment : BaseFragment<FragmentTasksActionSendSmsBinding?>(), View.OnClickListener {
private val TAG: String = FrpcFragment::class.java.simpleName
var titleBar: TitleBar? = null
@ -46,12 +46,12 @@ class FrpcFragment : BaseFragment<FragmentTasksCronBinding?>(), View.OnClickList
override fun viewBindingInflate(
inflater: LayoutInflater,
container: ViewGroup,
): FragmentTasksCronBinding {
return FragmentTasksCronBinding.inflate(inflater, container, false)
): FragmentTasksActionSendSmsBinding {
return FragmentTasksActionSendSmsBinding.inflate(inflater, container, false)
}
override fun initTitle(): TitleBar? {
titleBar = super.initTitle()!!.setImmersive(false).setTitle(R.string.task_cron)
titleBar = super.initTitle()!!.setImmersive(false).setTitle(R.string.task_frpc)
return titleBar
}

@ -9,7 +9,7 @@ import android.view.ViewGroup
import com.google.gson.Gson
import com.idormy.sms.forwarder.R
import com.idormy.sms.forwarder.core.BaseFragment
import com.idormy.sms.forwarder.databinding.FragmentTasksCronBinding
import com.idormy.sms.forwarder.databinding.FragmentTasksActionSendSmsBinding
import com.idormy.sms.forwarder.entity.task.CronSetting
import com.idormy.sms.forwarder.utils.KEY_BACK_DATA_ACTION
import com.idormy.sms.forwarder.utils.KEY_EVENT_DATA_ACTION
@ -26,7 +26,7 @@ import com.xuexiang.xui.widget.actionbar.TitleBar
@Page(name = "HttpServer")
@Suppress("PrivatePropertyName")
class HttpServerFragment : BaseFragment<FragmentTasksCronBinding?>(), View.OnClickListener {
class HttpServerFragment : BaseFragment<FragmentTasksActionSendSmsBinding?>(), View.OnClickListener {
private val TAG: String = HttpServerFragment::class.java.simpleName
var titleBar: TitleBar? = null
@ -46,12 +46,12 @@ class HttpServerFragment : BaseFragment<FragmentTasksCronBinding?>(), View.OnCli
override fun viewBindingInflate(
inflater: LayoutInflater,
container: ViewGroup,
): FragmentTasksCronBinding {
return FragmentTasksCronBinding.inflate(inflater, container, false)
): FragmentTasksActionSendSmsBinding {
return FragmentTasksActionSendSmsBinding.inflate(inflater, container, false)
}
override fun initTitle(): TitleBar? {
titleBar = super.initTitle()!!.setImmersive(false).setTitle(R.string.task_cron)
titleBar = super.initTitle()!!.setImmersive(false).setTitle(R.string.task_server)
return titleBar
}

@ -107,7 +107,7 @@ class NotificationFragment : BaseFragment<FragmentRulesEditBinding?>(), View.OnC
override fun initTitle(): TitleBar? {
titleBar = super.initTitle()!!.setImmersive(false)
titleBar!!.setTitle(R.string.menu_rules)
titleBar!!.setTitle(R.string.task_notification)
return titleBar
}

@ -1,19 +1,32 @@
package com.idormy.sms.forwarder.fragment.action
import android.Manifest
import android.annotation.SuppressLint
import android.content.Intent
import android.content.pm.PackageManager
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.core.app.ActivityCompat
import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
import com.hjq.permissions.OnPermissionCallback
import com.hjq.permissions.Permission
import com.hjq.permissions.XXPermissions
import com.idormy.sms.forwarder.App
import com.idormy.sms.forwarder.R
import com.idormy.sms.forwarder.core.BaseFragment
import com.idormy.sms.forwarder.databinding.FragmentTasksCronBinding
import com.idormy.sms.forwarder.entity.task.CronSetting
import com.idormy.sms.forwarder.databinding.FragmentTasksActionSendSmsBinding
import com.idormy.sms.forwarder.entity.task.SmsSetting
import com.idormy.sms.forwarder.server.model.ConfigData
import com.idormy.sms.forwarder.utils.EVENT_KEY_PHONE_NUMBERS
import com.idormy.sms.forwarder.utils.EVENT_KEY_SIM_SLOT
import com.idormy.sms.forwarder.utils.HttpServerUtils
import com.idormy.sms.forwarder.utils.KEY_BACK_DATA_ACTION
import com.idormy.sms.forwarder.utils.KEY_EVENT_DATA_ACTION
import com.idormy.sms.forwarder.utils.KEY_TEST_ACTION
import com.idormy.sms.forwarder.utils.PhoneUtils
import com.idormy.sms.forwarder.utils.TASK_ACTION_SENDSMS
import com.idormy.sms.forwarder.utils.XToastUtils
import com.jeremyliao.liveeventbus.LiveEventBus
@ -21,12 +34,15 @@ 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.utils.CountDownButtonHelper
import com.xuexiang.xui.utils.ResUtils
import com.xuexiang.xui.widget.actionbar.TitleBar
import com.xuexiang.xutil.XUtil
@Page(name = "SendSms")
@Suppress("PrivatePropertyName")
class SendSmsFragment : BaseFragment<FragmentTasksCronBinding?>(), View.OnClickListener {
@Suppress("PrivatePropertyName", "DEPRECATION")
class SendSmsFragment : BaseFragment<FragmentTasksActionSendSmsBinding?>(), View.OnClickListener {
private val TAG: String = SendSmsFragment::class.java.simpleName
var titleBar: TitleBar? = null
@ -36,8 +52,10 @@ class SendSmsFragment : BaseFragment<FragmentTasksCronBinding?>(), View.OnClickL
@AutoWired(name = KEY_EVENT_DATA_ACTION)
var eventData: String? = null
private var expression = "* * * * * ? *"
private var description = "测试描述"
private var description = ""
private var simSlot = 1
private var phoneNumbers = ""
private var msgContent = ""
override fun initArgs() {
XRouter.getInstance().inject(this)
@ -46,18 +64,19 @@ class SendSmsFragment : BaseFragment<FragmentTasksCronBinding?>(), View.OnClickL
override fun viewBindingInflate(
inflater: LayoutInflater,
container: ViewGroup,
): FragmentTasksCronBinding {
return FragmentTasksCronBinding.inflate(inflater, container, false)
): FragmentTasksActionSendSmsBinding {
return FragmentTasksActionSendSmsBinding.inflate(inflater, container, false)
}
override fun initTitle(): TitleBar? {
titleBar = super.initTitle()!!.setImmersive(false).setTitle(R.string.task_cron)
titleBar = super.initTitle()!!.setImmersive(false).setTitle(R.string.task_sendsms)
return titleBar
}
/**
* 初始化控件
*/
@SuppressLint("SetTextI18n")
override fun initViews() {
//测试按钮增加倒计时,避免重复点击
mCountDownHelper = CountDownButtonHelper(binding!!.btnTest, 3)
@ -71,11 +90,27 @@ class SendSmsFragment : BaseFragment<FragmentTasksCronBinding?>(), View.OnClickL
}
})
//卡槽信息
val serverConfigStr = HttpServerUtils.serverConfig
if (!TextUtils.isEmpty(serverConfigStr)) {
val serverConfig: ConfigData = Gson().fromJson(serverConfigStr, object : TypeToken<ConfigData>() {}.type)
binding!!.rbSimSlot1.text = "SIM1" + serverConfig.extraSim1
binding!!.rbSimSlot2.text = "SIM2" + serverConfig.extraSim2
}
Log.d(TAG, "initViews eventData:$eventData")
if (eventData != null) {
val settingVo = Gson().fromJson(eventData, CronSetting::class.java)
val settingVo = Gson().fromJson(eventData, SmsSetting::class.java)
Log.d(TAG, "initViews settingVo:$settingVo")
simSlot = settingVo.simSlot
phoneNumbers = settingVo.phoneNumbers
msgContent = settingVo.msgContent
}
binding!!.rgSimSlot.check(if (simSlot == 1) R.id.rb_sim_slot_1 else R.id.rb_sim_slot_2)
binding!!.etPhoneNumbers.setText(phoneNumbers)
binding!!.etMsgContent.setText(msgContent)
}
@SuppressLint("SetTextI18n")
@ -83,6 +118,12 @@ class SendSmsFragment : BaseFragment<FragmentTasksCronBinding?>(), View.OnClickL
binding!!.btnTest.setOnClickListener(this)
binding!!.btnDel.setOnClickListener(this)
binding!!.btnSave.setOnClickListener(this)
LiveEventBus.get(EVENT_KEY_SIM_SLOT, Int::class.java).observeSticky(this) { value: Int ->
binding!!.rgSimSlot.check(if (value == 1) R.id.rb_sim_slot_2 else R.id.rb_sim_slot_1)
}
LiveEventBus.get(EVENT_KEY_PHONE_NUMBERS, String::class.java).observeSticky(this) { value: String ->
binding!!.etPhoneNumbers.setText(value)
}
LiveEventBus.get(KEY_TEST_ACTION, String::class.java).observe(this) {
mCountDownHelper?.finish()
@ -100,16 +141,45 @@ class SendSmsFragment : BaseFragment<FragmentTasksCronBinding?>(), View.OnClickL
when (v.id) {
R.id.btn_test -> {
mCountDownHelper?.start()
Thread {
try {
val settingVo = checkSetting()
Log.d(TAG, settingVo.toString())
LiveEventBus.get(KEY_TEST_ACTION, String::class.java).post("success")
} catch (e: Exception) {
LiveEventBus.get(KEY_TEST_ACTION, String::class.java).post(e.message.toString())
e.printStackTrace()
}
}.start()
//检查发送短信权限是否获取
XXPermissions.with(this)
.permission(Permission.SEND_SMS)
.request(object : OnPermissionCallback {
override fun onGranted(permissions: List<String>, all: Boolean) {
Thread {
try {
val settingVo = checkSetting()
Log.d(TAG, settingVo.toString())
//获取卡槽信息
if (App.SimInfoList.isEmpty()) {
App.SimInfoList = PhoneUtils.getSimMultiInfo()
}
Log.d(TAG, App.SimInfoList.toString())
//发送卡槽: 1=SIM1, 2=SIM2
val simSlotIndex = settingVo.simSlot - 1
//TODO取不到卡槽信息时采用默认卡槽发送
val mSubscriptionId: Int = App.SimInfoList[simSlotIndex]?.mSubscriptionId ?: -1
val msg = if (ActivityCompat.checkSelfPermission(XUtil.getContext(), Manifest.permission.SEND_SMS) != PackageManager.PERMISSION_GRANTED) {
ResUtils.getString(R.string.no_sms_sending_permission)
} else {
PhoneUtils.sendSms(mSubscriptionId, settingVo.phoneNumbers, settingVo.msgContent) ?: "success"
}
LiveEventBus.get(KEY_TEST_ACTION, String::class.java).post(msg)
} catch (e: Exception) {
LiveEventBus.get(KEY_TEST_ACTION, String::class.java).post(e.message.toString())
e.printStackTrace()
}
}.start()
}
override fun onDenied(permissions: List<String>, never: Boolean) {
LiveEventBus.get(KEY_TEST_ACTION, String::class.java).post(ResUtils.getString(R.string.no_sms_sending_permission))
}
})
return
}
@ -135,7 +205,21 @@ class SendSmsFragment : BaseFragment<FragmentTasksCronBinding?>(), View.OnClickL
//检查设置
@SuppressLint("SetTextI18n")
private fun checkSetting(): CronSetting {
return CronSetting(expression, description)
private fun checkSetting(): SmsSetting {
phoneNumbers = binding!!.etPhoneNumbers.text.toString().trim()
if (!getString(R.string.phone_numbers_regex).toRegex().matches(phoneNumbers)) {
throw Exception(getString(R.string.phone_numbers_error))
}
msgContent = binding!!.etMsgContent.text.toString().trim()
if (!getString(R.string.msg_content_regex).toRegex().matches(msgContent)) {
throw Exception(getString(R.string.msg_content_error))
}
simSlot = if (binding!!.rgSimSlot.checkedRadioButtonId == R.id.rb_sim_slot_2) 2 else 1
description = String.format(getString(R.string.send_sms_to), simSlot, phoneNumbers)
return SmsSetting(description, simSlot, phoneNumbers, msgContent)
}
}

@ -9,7 +9,7 @@ import android.view.ViewGroup
import com.google.gson.Gson
import com.idormy.sms.forwarder.R
import com.idormy.sms.forwarder.core.BaseFragment
import com.idormy.sms.forwarder.databinding.FragmentTasksCronBinding
import com.idormy.sms.forwarder.databinding.FragmentTasksActionSendSmsBinding
import com.idormy.sms.forwarder.entity.task.CronSetting
import com.idormy.sms.forwarder.utils.KEY_BACK_DATA_CONDITION
import com.idormy.sms.forwarder.utils.KEY_EVENT_DATA_CONDITION
@ -26,7 +26,7 @@ import com.xuexiang.xui.widget.actionbar.TitleBar
@Page(name = "Battery")
@Suppress("PrivatePropertyName")
class BatteryFragment : BaseFragment<FragmentTasksCronBinding?>(), View.OnClickListener {
class BatteryFragment : BaseFragment<FragmentTasksActionSendSmsBinding?>(), View.OnClickListener {
private val TAG: String = BatteryFragment::class.java.simpleName
var titleBar: TitleBar? = null
@ -46,8 +46,8 @@ class BatteryFragment : BaseFragment<FragmentTasksCronBinding?>(), View.OnClickL
override fun viewBindingInflate(
inflater: LayoutInflater,
container: ViewGroup,
): FragmentTasksCronBinding {
return FragmentTasksCronBinding.inflate(inflater, container, false)
): FragmentTasksActionSendSmsBinding {
return FragmentTasksActionSendSmsBinding.inflate(inflater, container, false)
}
override fun initTitle(): TitleBar? {

@ -9,7 +9,7 @@ import android.view.ViewGroup
import com.google.gson.Gson
import com.idormy.sms.forwarder.R
import com.idormy.sms.forwarder.core.BaseFragment
import com.idormy.sms.forwarder.databinding.FragmentTasksCronBinding
import com.idormy.sms.forwarder.databinding.FragmentTasksActionSendSmsBinding
import com.idormy.sms.forwarder.entity.task.CronSetting
import com.idormy.sms.forwarder.utils.KEY_BACK_DATA_CONDITION
import com.idormy.sms.forwarder.utils.KEY_EVENT_DATA_CONDITION
@ -26,7 +26,7 @@ import com.xuexiang.xui.widget.actionbar.TitleBar
@Page(name = "Charge")
@Suppress("PrivatePropertyName")
class ChargeFragment : BaseFragment<FragmentTasksCronBinding?>(), View.OnClickListener {
class ChargeFragment : BaseFragment<FragmentTasksActionSendSmsBinding?>(), View.OnClickListener {
private val TAG: String = ChargeFragment::class.java.simpleName
var titleBar: TitleBar? = null
@ -46,8 +46,8 @@ class ChargeFragment : BaseFragment<FragmentTasksCronBinding?>(), View.OnClickLi
override fun viewBindingInflate(
inflater: LayoutInflater,
container: ViewGroup,
): FragmentTasksCronBinding {
return FragmentTasksCronBinding.inflate(inflater, container, false)
): FragmentTasksActionSendSmsBinding {
return FragmentTasksActionSendSmsBinding.inflate(inflater, container, false)
}
override fun initTitle(): TitleBar? {

@ -13,7 +13,7 @@ import android.widget.RadioGroup
import com.google.gson.Gson
import com.idormy.sms.forwarder.R
import com.idormy.sms.forwarder.core.BaseFragment
import com.idormy.sms.forwarder.databinding.FragmentTasksCronBinding
import com.idormy.sms.forwarder.databinding.FragmentTasksConditionCronBinding
import com.idormy.sms.forwarder.entity.task.CronSetting
import com.idormy.sms.forwarder.utils.KEY_BACK_DATA_CONDITION
import com.idormy.sms.forwarder.utils.KEY_EVENT_DATA_CONDITION
@ -37,7 +37,7 @@ import java.util.Locale
@Page(name = "Cron")
@Suppress("PrivatePropertyName")
class CronFragment : BaseFragment<FragmentTasksCronBinding?>(), View.OnClickListener {
class CronFragment : BaseFragment<FragmentTasksConditionCronBinding?>(), View.OnClickListener {
private val TAG: String = CronFragment::class.java.simpleName
var titleBar: TitleBar? = null
@ -87,8 +87,8 @@ class CronFragment : BaseFragment<FragmentTasksCronBinding?>(), View.OnClickList
override fun viewBindingInflate(
inflater: LayoutInflater,
container: ViewGroup,
): FragmentTasksCronBinding {
return FragmentTasksCronBinding.inflate(inflater, container, false)
): FragmentTasksConditionCronBinding {
return FragmentTasksConditionCronBinding.inflate(inflater, container, false)
}
override fun initTitle(): TitleBar? {

@ -9,7 +9,7 @@ import android.view.ViewGroup
import com.google.gson.Gson
import com.idormy.sms.forwarder.R
import com.idormy.sms.forwarder.core.BaseFragment
import com.idormy.sms.forwarder.databinding.FragmentTasksCronBinding
import com.idormy.sms.forwarder.databinding.FragmentTasksActionSendSmsBinding
import com.idormy.sms.forwarder.entity.task.CronSetting
import com.idormy.sms.forwarder.utils.KEY_BACK_DATA_CONDITION
import com.idormy.sms.forwarder.utils.KEY_EVENT_DATA_CONDITION
@ -26,7 +26,7 @@ import com.xuexiang.xui.widget.actionbar.TitleBar
@Page(name = "Network")
@Suppress("PrivatePropertyName")
class NetworkFragment : BaseFragment<FragmentTasksCronBinding?>(), View.OnClickListener {
class NetworkFragment : BaseFragment<FragmentTasksActionSendSmsBinding?>(), View.OnClickListener {
private val TAG: String = NetworkFragment::class.java.simpleName
var titleBar: TitleBar? = null
@ -46,8 +46,8 @@ class NetworkFragment : BaseFragment<FragmentTasksCronBinding?>(), View.OnClickL
override fun viewBindingInflate(
inflater: LayoutInflater,
container: ViewGroup,
): FragmentTasksCronBinding {
return FragmentTasksCronBinding.inflate(inflater, container, false)
): FragmentTasksActionSendSmsBinding {
return FragmentTasksActionSendSmsBinding.inflate(inflater, container, false)
}
override fun initTitle(): TitleBar? {

@ -4,20 +4,70 @@ import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.util.Log
import com.google.gson.Gson
import com.idormy.sms.forwarder.App
import com.idormy.sms.forwarder.database.AppDatabase
import com.idormy.sms.forwarder.database.entity.Task
import com.idormy.sms.forwarder.entity.task.CronSetting
import com.idormy.sms.forwarder.entity.task.TaskSetting
import com.idormy.sms.forwarder.utils.task.CronUtils
import gatewayapps.crondroid.CronExpression
import java.util.Date
@Suppress("PropertyName")
@Suppress("PropertyName", "DEPRECATION")
class AlarmReceiver : BroadcastReceiver() {
val TAG: String = AlarmReceiver::class.java.simpleName
override fun onReceive(context: Context, intent: Intent) {
val task = intent.getParcelableExtra<Task>("task")
val task = intent.getParcelableExtra<Task>("TASK")
if (task == null) {
Log.d(TAG, "onReceive task is null")
return
}
Log.d(TAG, "onReceive task $task")
Log.d(TAG, "lastExecTime = ${task.lastExecTime}, nextExecTime = ${task.nextExecTime}")
try {
//取消旧任务的定时器
CronUtils.cancelAlarm(task)
// 根据任务信息执行相应操作
if (task != null) {
Log.d(TAG, "onReceive task $task")
// 处理任务逻辑,例如执行特定操作或者更新界面
// 根据任务信息执行相应操作
val conditionList = Gson().fromJson(task.conditions, Array<TaskSetting>::class.java).toMutableList()
if (conditionList.isEmpty()) {
Log.d(TAG, "onReceive conditionList is empty")
return
}
val firstCondition = conditionList.firstOrNull()
if (firstCondition == null) {
Log.d(TAG, "onReceive firstCondition is null")
return
}
val cronSetting = Gson().fromJson(firstCondition.setting, CronSetting::class.java)
if (cronSetting == null) {
Log.d(TAG, "onReceive cronSetting is null")
return
}
// 更新任务的上次执行时间和下次执行时间
val cronExpression = CronExpression(cronSetting.expression)
task.lastExecTime = Date()
task.nextExecTime = cronExpression.getNextValidTimeAfter(task.lastExecTime)
Log.d(TAG, "lastExecTime = ${task.lastExecTime}, nextExecTime = ${task.nextExecTime}")
// 自动禁用任务
if (task.nextExecTime <= task.lastExecTime) {
task.status = 0
}
// 更新任务信息
AppDatabase.getInstance(App.context).taskDao().update(task)
if (task.status == 0) {
Log.d(TAG, "onReceive task is disabled")
return
}
//设置新的定时器
CronUtils.scheduleAlarm(task)
} catch (e: Exception) {
Log.e(TAG, "onReceive error $e")
}
}
}

@ -44,7 +44,7 @@ import java.text.SimpleDateFormat
import java.util.Date
@SuppressLint("SimpleDateFormat")
@Suppress("PrivatePropertyName", "DeferredResultUnused", "OPT_IN_USAGE", "DEPRECATION")
@Suppress("PrivatePropertyName", "DeferredResultUnused", "OPT_IN_USAGE", "DEPRECATION", "LiftReturnOrAssignment")
class ForegroundService : Service() {
private val TAG: String = "ForegroundService"

@ -482,6 +482,7 @@ var CLIENT_FRAGMENT_LIST = listOf(
)
//自动任务
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_BACK_CODE_CONDITION = 1000

@ -5,10 +5,10 @@ import android.app.AlarmManager
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import android.os.Build
import com.idormy.sms.forwarder.database.entity.Task
import com.idormy.sms.forwarder.receiver.AlarmReceiver
@Suppress("unused")
class CronUtils {
companion object {
@ -19,48 +19,51 @@ class CronUtils {
this.context = context.applicationContext
}
fun updateTaskAndScheduleAlarm(task: Task) {
val oldTask = getOldTask(task.id) // 获取旧的任务信息
cancelAlarm(oldTask) // 取消旧任务的定时器
updateTaskInDatabase(task) // 更新任务信息(例如,更新数据库中的任务信息)
scheduleAlarm(task) // 设置新的定时器
}
private fun cancelAlarm(task: Task?) {
fun cancelAlarm(task: Task?) {
val alarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager
val alarmIntent = Intent(context, AlarmReceiver::class.java)
val requestCode = task?.id?.toInt() ?: -1
val pendingIntent = PendingIntent.getBroadcast(context, requestCode, alarmIntent, PendingIntent.FLAG_NO_CREATE or PendingIntent.FLAG_IMMUTABLE)
val pendingIntent = PendingIntent.getBroadcast(
context,
requestCode,
alarmIntent,
PendingIntent.FLAG_NO_CREATE or PendingIntent.FLAG_MUTABLE
)
pendingIntent?.let {
alarmManager.cancel(it)
it.cancel()
}
}
private fun scheduleAlarm(task: Task) {
@SuppressLint("ScheduleExactAlarm")
fun scheduleAlarm(task: Task) {
val alarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager
val alarmIntent = Intent(context, AlarmReceiver::class.java)
alarmIntent.putExtra("task", task)
val requestCode = task.id.toInt()
val pendingIntent = PendingIntent.getBroadcast(context, requestCode, alarmIntent, PendingIntent.FLAG_IMMUTABLE)
//val now = Calendar.getInstance()
val nextExecutionTime = task.nextExecTime.time
alarmManager.setExact(
AlarmManager.RTC_WAKEUP, nextExecutionTime, pendingIntent
alarmIntent.putExtra("TASK", task)
val pendingIntent = PendingIntent.getBroadcast(
context,
requestCode,
alarmIntent,
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_MUTABLE
)
}
private fun getOldTask(taskId: Long): Task {
// 实现获取旧任务信息的逻辑
// 返回旧任务信息Task对象
return Task()
// TODO设置闹钟低电量模式下无法设置精确闹钟
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
alarmManager.setExactAndAllowWhileIdle(
AlarmManager.RTC_WAKEUP,
task.nextExecTime.time,
pendingIntent
)
} else {
alarmManager.setExact(
AlarmManager.RTC_WAKEUP,
task.nextExecTime.time,
pendingIntent
)
}
}
private fun updateTaskInDatabase(task: Task) {
// 实现更新数据库中任务信息的逻辑
}
}
}

@ -25,7 +25,8 @@
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:contentDescription="@string/api_sms_send"
app:srcCompat="@drawable/icon_api_sms_send" />
app:srcCompat="@drawable/icon_api_sms_send"
tools:ignore="ImageContrastCheck" />
<LinearLayout
style="@style/senderBarStyle"
@ -41,18 +42,21 @@
<RadioGroup
android:id="@+id/rg_sim_slot"
style="@style/rg_style"
android:orientation="vertical">
android:orientation="vertical"
android:paddingBottom="@dimen/config_padding_5dp">
<RadioButton
android:id="@+id/rb_sim_slot_1"
style="@style/rg_rb_style_match"
android:checked="true"
android:text="@string/sim1" />
android:text="@string/sim1"
tools:ignore="TouchTargetSizeCheck" />
<RadioButton
android:id="@+id/rb_sim_slot_2"
style="@style/rg_rb_style_match"
android:text="@string/sim2" />
android:text="@string/sim2"
tools:ignore="TouchTargetSizeCheck" />
</RadioGroup>
@ -78,7 +82,8 @@
app:met_clearButton="true"
app:met_errorMessage="@string/phone_numbers_error"
app:met_regexp="@string/phone_numbers_regex"
app:met_validateOnFocusLost="true" />
app:met_validateOnFocusLost="true"
tools:ignore="TouchTargetSizeCheck,TextContrastCheck" />
</LinearLayout>
@ -105,7 +110,8 @@
app:met_maxCharacters="390"
app:met_regexp="@string/msg_content_regex"
app:met_validateOnFocusLost="true"
app:mlet_hintText="@string/msg_content_hint" />
app:mlet_hintText="@string/msg_content_hint"
tools:ignore="TextContrastCheck" />
</LinearLayout>
@ -126,7 +132,7 @@
android:drawableStart="@drawable/ic_send_white"
android:paddingStart="20dp"
android:text="@string/send"
tools:ignore="RtlSymmetry" />
tools:ignore="RtlSymmetry,TouchTargetSizeCheck,TextContrastCheck" />
</LinearLayout>

@ -0,0 +1,152 @@
<?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">
<LinearLayout
style="@style/senderBarStyle"
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/sim_slot" />
<RadioGroup
android:id="@+id/rg_sim_slot"
style="@style/rg_style"
android:orientation="vertical"
android:paddingBottom="@dimen/config_padding_5dp">
<RadioButton
android:id="@+id/rb_sim_slot_1"
style="@style/rg_rb_style_match"
android:checked="true"
android:text="@string/sim1"
tools:ignore="TouchTargetSizeCheck" />
<RadioButton
android:id="@+id/rb_sim_slot_2"
style="@style/rg_rb_style_match"
android:text="@string/sim2"
tools:ignore="TouchTargetSizeCheck" />
</RadioGroup>
</LinearLayout>
<LinearLayout
style="@style/senderBarStyle"
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/phone_numbers" />
<com.xuexiang.xui.widget.edittext.materialedittext.MaterialEditText
android:id="@+id/et_phone_numbers"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/phone_numbers_hint"
android:singleLine="true"
app:met_clearButton="true"
app:met_errorMessage="@string/phone_numbers_error"
app:met_regexp="@string/phone_numbers_regex"
app:met_validateOnFocusLost="true"
tools:ignore="TouchTargetSizeCheck,TextContrastCheck" />
</LinearLayout>
<LinearLayout
style="@style/senderBarStyle"
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/msg_content" />
<com.xuexiang.xui.widget.edittext.materialedittext.MaterialEditText
android:id="@+id/et_msg_content"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="start|top"
android:hint="@string/msg_content_hint"
android:inputType="textMultiLine"
app:met_clearButton="true"
app:met_errorMessage="@string/msg_content_error"
app:met_maxCharacters="390"
app:met_regexp="@string/msg_content_regex"
app:met_validateOnFocusLost="true"
app:mlet_hintText="@string/msg_content_hint"
tools:ignore="TextContrastCheck" />
</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"
android:drawableStart="@drawable/icon_delete"
android:paddingStart="15dp"
android:text="@string/discard"
android:textSize="11sp"
tools:ignore="RtlSymmetry,TextContrastCheck,TouchTargetSizeCheck" />
<com.xuexiang.xui.widget.textview.supertextview.SuperButton
android:id="@+id/btn_save"
style="@style/SuperButton.Blue.Icon"
android:layout_marginStart="10dp"
android:drawableStart="@drawable/icon_save"
android:paddingStart="15dp"
android:text="@string/submit"
android:textSize="11sp"
tools:ignore="RtlSymmetry,TextContrastCheck,TouchTargetSizeCheck" />
<com.xuexiang.xui.widget.textview.supertextview.SuperButton
android:id="@+id/btn_test"
style="@style/SuperButton.Green.Icon"
android:layout_marginStart="10dp"
android:drawableStart="@drawable/icon_test"
android:paddingStart="15dp"
android:text="@string/test"
android:textSize="11sp"
tools:ignore="RtlSymmetry,TextContrastCheck,TouchTargetSizeCheck" />
</LinearLayout>
</LinearLayout>

@ -262,6 +262,7 @@
android:text="@string/test"
android:textColor="#FFFFFF"
android:textSize="11sp"
android:visibility="gone"
tools:ignore="RtlSymmetry,TextContrastCheck,TouchTargetSizeCheck" />
</LinearLayout>

@ -1108,8 +1108,8 @@
<string name="task_network">Network</string>
<string name="task_sendsms">Send Sms</string>
<string name="task_notification">Notification</string>
<string name="task_frpc">Frpc</string>
<string name="task_server">HttpServer</string>
<string name="task_frpc">Frpc Setting</string>
<string name="task_server">Server Setting</string>
<string name="second">Second</string>
<string name="minute">Minute</string>
@ -1166,4 +1166,6 @@
<string name="cron_expression_check">Cron Expression Test Result</string>
<string name="invalid_cron_expression">Cron expression is invalid:\n%s</string>
<string name="next_execution_times">The next %s execution times:\n%s</string>
<string name="send_sms_to">Use SIM-%s to send sms\n%s</string>
</resources>

@ -1109,8 +1109,8 @@
<string name="task_network">网络状态</string>
<string name="task_sendsms">发送短信</string>
<string name="task_notification">通道推送</string>
<string name="task_frpc">Frpc</string>
<string name="task_server">HttpServer</string>
<string name="task_frpc">设置 Frpc</string>
<string name="task_server">设置 HttpServer</string>
<string name="second"></string>
<string name="minute"></string>
@ -1167,4 +1167,6 @@
<string name="cron_expression_check">Cron表达式测试结果</string>
<string name="invalid_cron_expression">Cron表达式无效\n%s</string>
<string name="next_execution_times">最近 %s 次运行时间:\n%s</string>
<string name="send_sms_to">通过卡槽 SIM-%s 发送短信到:\n%s</string>
</resources>

@ -225,6 +225,7 @@
</style>
<style name="rg_rb_style_match">
<item name="android:layout_marginTop">5dp</item>
<item name="android:layout_width">match_parent</item>
<item name="android:layout_height">wrap_content</item>
<item name="android:singleLine">true</item>

Loading…
Cancel
Save