新增:自动任务·快捷指令 —— Cron定时发送短信 #279 #344

pull/408/head
pppscn 10 months ago
parent af63302df6
commit 5fb9f1b75c

@ -28,7 +28,7 @@ import com.idormy.sms.forwarder.utils.*
import com.idormy.sms.forwarder.utils.sdkinit.UMengInit
import com.idormy.sms.forwarder.utils.sdkinit.XBasicLibInit
import com.idormy.sms.forwarder.utils.sdkinit.XUpdateInit
import com.idormy.sms.forwarder.utils.task.CronUtils
import com.idormy.sms.forwarder.utils.task.AlarmUtils
import com.idormy.sms.forwarder.utils.tinker.TinkerLoadLibrary
import io.reactivex.Observable
import io.reactivex.android.schedulers.AndroidSchedulers
@ -208,7 +208,7 @@ class App : Application(), CactusCallback, Configuration.Provider by Core {
// 运营统计数据
UMengInit.init(this)
// 定时任务初始化
CronUtils.initialize(this)
AlarmUtils.initialize(this)
// 三方时间库初始化
//AndroidThreeTen.init(this)
}

@ -16,6 +16,7 @@ import com.idormy.sms.forwarder.adapter.TaskPagingAdapter.MyViewHolder
import com.idormy.sms.forwarder.database.entity.Task
import com.idormy.sms.forwarder.databinding.AdapterTasksCardViewListItemBinding
import com.idormy.sms.forwarder.entity.task.TaskSetting
import com.xuexiang.xutil.data.DateUtils
class TaskPagingAdapter(private val itemClickListener: OnItemClickListener) : PagingDataAdapter<Task, MyViewHolder>(diffCallback) {
@ -31,6 +32,8 @@ class TaskPagingAdapter(private val itemClickListener: OnItemClickListener) : Pa
if (item.type >= 1000) {
holder.binding.layoutImage.visibility = View.GONE
holder.binding.tvTime.text = DateUtils.getFriendlyTimeSpanByNow(item.lastExecTime.time)
//遍历conditions显示图标
holder.binding.layoutConditionsIcons.removeAllViews()
if (item.conditions.isNotEmpty()) {

@ -175,6 +175,9 @@ class SettingsFragment : BaseFragment<FragmentSettingsBinding?>(), View.OnClickL
//纯客户端模式
switchDirectlyToClient(binding!!.sbDirectlyToClient)
//纯自动任务模式
switchDirectlyToTask(binding!!.sbDirectlyToTask)
//启用 {{定位信息}} 标签
switchEnableLocationTag(binding!!.sbEnableLocationTag)
}
@ -996,6 +999,19 @@ class SettingsFragment : BaseFragment<FragmentSettingsBinding?>(), View.OnClickL
}
}
//纯自动任务模式
private fun switchDirectlyToTask(@SuppressLint("UseSwitchCompatOrMaterialCode") switchDirectlyToTask: SwitchButton) {
switchDirectlyToTask.isChecked = SettingUtils.enablePureTaskMode
switchDirectlyToTask.setOnCheckedChangeListener { _: CompoundButton?, isChecked: Boolean ->
SettingUtils.enablePureTaskMode = isChecked
if (isChecked) {
MaterialDialog.Builder(requireContext()).content(getString(R.string.enabling_pure_client_mode)).positiveText(R.string.lab_yes).onPositive { _: MaterialDialog?, _: DialogAction? ->
XUtil.exitApp()
}.negativeText(R.string.lab_no).show()
}
}
}
//启用 {{定位信息}} 标签
private fun switchEnableLocationTag(@SuppressLint("UseSwitchCompatOrMaterialCode") switchEnableLocationTag: SwitchButton) {
switchEnableLocationTag.isChecked = SettingUtils.enableLocationTag

@ -257,7 +257,7 @@ class TasksEditFragment : BaseFragment<FragmentTasksEditBinding?>(), View.OnClic
for (condition in conditionList) {
itemListConditions.add(condition)
}
Log.d(TAG, "initForm: $itemListConditions")
Log.d(TAG, "itemListConditions: $itemListConditions")
conditionsAdapter.notifyDataSetChanged()
binding!!.layoutAddCondition.visibility = if (itemListConditions.size >= MAX_SETTING_NUM) View.GONE else View.VISIBLE
}
@ -266,7 +266,7 @@ class TasksEditFragment : BaseFragment<FragmentTasksEditBinding?>(), View.OnClic
for (action in actionList) {
itemListActions.add(action)
}
Log.d(TAG, "initForm: $itemListActions")
Log.d(TAG, "itemListActions: $itemListActions")
actionsAdapter.notifyDataSetChanged()
binding!!.layoutAddAction.visibility = if (itemListActions.size >= MAX_SETTING_NUM) View.GONE else View.VISIBLE
}
@ -292,7 +292,9 @@ class TasksEditFragment : BaseFragment<FragmentTasksEditBinding?>(), View.OnClic
}
val lastExecTime = Date()
var nextExecTime = Date()
// 将毫秒部分设置为 0避免因为毫秒部分不同导致的任务重复执行
lastExecTime.time = lastExecTime.time / 1000 * 1000
var nextExecTime = lastExecTime
val firstCondition = itemListConditions[0]
taskType = firstCondition.type
@ -317,15 +319,7 @@ class TasksEditFragment : BaseFragment<FragmentTasksEditBinding?>(), View.OnClic
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,
lastExecTime,
nextExecTime
taskId, taskType, taskName, description.toString(), Gson().toJson(itemListConditions), Gson().toJson(itemListActions), status, lastExecTime, nextExecTime
)
}
@ -339,11 +333,9 @@ class TasksEditFragment : BaseFragment<FragmentTasksEditBinding?>(), View.OnClic
//定时任务
TASK_CONDITION_CRON -> {
//取消旧任务的定时器 & 设置新的定时器
//CronUtils.cancelAlarm(task)
//CronUtils.scheduleAlarm(task)
//AlarmUtils.cancelAlarm(task)
//AlarmUtils.scheduleAlarm(task)
//val uuid = App.TaskIdToWorkerIdMap[task.id]
//uuid?.let { CronJobScheduler.cancelTask(it) }
CronJobScheduler.cancelTask(task.id)
CronJobScheduler.scheduleTask(task)
}
@ -355,6 +347,10 @@ class TasksEditFragment : BaseFragment<FragmentTasksEditBinding?>(), View.OnClic
try {
dialog.dismiss()
Log.d(TAG, "onItemClick: $widgetInfo")
if (pos > 0) {
XToastUtils.info("暂不支持,敬请期待……")
return
}
//判断点击的是条件还是动作
if (widgetInfo.classPath.contains(".condition.")) {
//判断是否已经添加过该类型条件

@ -10,7 +10,7 @@ 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 com.idormy.sms.forwarder.utils.task.AlarmUtils
import gatewayapps.crondroid.CronExpression
import java.util.Date
@ -30,7 +30,7 @@ class AlarmReceiver : BroadcastReceiver() {
Log.d(TAG, "lastExecTime = ${task.lastExecTime}, nextExecTime = ${task.nextExecTime}")
try {
//取消旧任务的定时器
CronUtils.cancelAlarm(task)
AlarmUtils.cancelAlarm(task)
// 根据任务信息执行相应操作
val conditionList = Gson().fromJson(task.conditions, Array<TaskSetting>::class.java).toMutableList()
@ -64,7 +64,7 @@ class AlarmReceiver : BroadcastReceiver() {
return
}
//设置新的定时器
CronUtils.scheduleAlarm(task)
AlarmUtils.scheduleAlarm(task)
} catch (e: Exception) {
Log.e(TAG, "onReceive error $e")
}

@ -159,7 +159,7 @@ class SettingUtils private constructor() {
var enablePureClientMode: Boolean by SharedPreference(SP_PURE_CLIENT_MODE, false)
//是否纯任务模式
var enablePureTaskMode: Boolean by SharedPreference(SP_PURE_TASK_MODE, true)
var enablePureTaskMode: Boolean by SharedPreference(SP_PURE_TASK_MODE, false)
//是否启用定位标签
var enableLocationTag: Boolean by SharedPreference(SP_LOCATION_TAG, false)

@ -9,7 +9,7 @@ import android.os.Build
import com.idormy.sms.forwarder.database.entity.Task
import com.idormy.sms.forwarder.receiver.AlarmReceiver
class CronUtils {
class AlarmUtils {
companion object {
@SuppressLint("StaticFieldLeak")

@ -6,7 +6,7 @@ import androidx.work.ExistingWorkPolicy
import androidx.work.OneTimeWorkRequestBuilder
import androidx.work.WorkManager
import com.idormy.sms.forwarder.database.entity.Task
import com.idormy.sms.forwarder.workers.TaskWorker
import com.idormy.sms.forwarder.workers.CronWorker
import java.util.concurrent.TimeUnit
@Suppress("DEPRECATION")
@ -21,13 +21,13 @@ class CronJobScheduler {
val delayInMillis = task.nextExecTime.time / 1000 * 1000 - currentTimeMillis
val inputData = Data.Builder().putLong("taskId", task.id).build()
val taskRequest = if (delayInMillis <= 0L) {
Log.d(TAG, "立即执行任务${task.id}delayInMillis = $delayInMillis")
OneTimeWorkRequestBuilder<TaskWorker>()
Log.d(TAG, "任务${task.id}:立即执行delayInMillis = $delayInMillis")
OneTimeWorkRequestBuilder<CronWorker>()
.setInputData(inputData)
.build()
} else {
Log.d(TAG, "延迟 $delayInMillis 毫秒执行任务${task.id}")
OneTimeWorkRequestBuilder<TaskWorker>()
Log.d(TAG, "任务${task.id}延迟 $delayInMillis 毫秒执行")
OneTimeWorkRequestBuilder<CronWorker>()
.setInitialDelay(delayInMillis, TimeUnit.MILLISECONDS)
.setInputData(inputData)
.build()

@ -0,0 +1,69 @@
package com.idormy.sms.forwarder.workers
import android.Manifest
import android.content.Context
import android.content.pm.PackageManager
import android.util.Log
import androidx.core.app.ActivityCompat
import androidx.work.CoroutineWorker
import androidx.work.WorkerParameters
import com.google.gson.Gson
import com.idormy.sms.forwarder.App
import com.idormy.sms.forwarder.R
import com.idormy.sms.forwarder.entity.task.SmsSetting
import com.idormy.sms.forwarder.utils.PhoneUtils
import com.idormy.sms.forwarder.utils.TASK_ACTION_SENDSMS
import com.xuexiang.xui.utils.ResUtils
import com.xuexiang.xutil.XUtil
@Suppress("PrivatePropertyName", "DEPRECATION")
class ActionWorker(context: Context, params: WorkerParameters) : CoroutineWorker(context, params) {
private val TAG: String = ActionWorker::class.java.simpleName
override suspend fun doWork(): Result {
val taskId = inputData.getLong("taskId", -1L)
val actionType = inputData.getInt("actionType", -1)
val actionSetting = inputData.getString("actionSetting")
Log.d(TAG, "taskId: $taskId, actionSetting: $actionSetting")
if (taskId == -1L || actionSetting == null) {
Log.d(TAG, "taskId is -1L or actionSetting is null")
return Result.failure()
}
when (actionType) {
TASK_ACTION_SENDSMS -> {
val smsSetting = Gson().fromJson(actionSetting, SmsSetting::class.java)
if (smsSetting == null) {
Log.d(TAG, "任务$taskIdsmsSetting is null")
return Result.failure()
}
//获取卡槽信息
if (App.SimInfoList.isEmpty()) {
App.SimInfoList = PhoneUtils.getSimMultiInfo()
}
Log.d(TAG, App.SimInfoList.toString())
//发送卡槽: 1=SIM1, 2=SIM2
val simSlotIndex = smsSetting.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, smsSetting.phoneNumbers, smsSetting.msgContent) ?: "success"
}
Log.d(TAG, "任务$taskIdsend sms result: $msg")
return Result.success()
}
else -> {
Log.d(TAG, "任务$taskIdaction.type is $actionType")
return Result.failure()
}
}
}
}

@ -3,6 +3,9 @@ package com.idormy.sms.forwarder.workers
import android.content.Context
import android.util.Log
import androidx.work.CoroutineWorker
import androidx.work.Data
import androidx.work.OneTimeWorkRequestBuilder
import androidx.work.WorkManager
import androidx.work.WorkerParameters
import com.google.gson.Gson
import com.idormy.sms.forwarder.App
@ -11,13 +14,12 @@ import com.idormy.sms.forwarder.entity.task.CronSetting
import com.idormy.sms.forwarder.entity.task.TaskSetting
import com.idormy.sms.forwarder.utils.task.CronJobScheduler
import gatewayapps.crondroid.CronExpression
import kotlinx.coroutines.delay
import java.util.Date
@Suppress("PrivatePropertyName")
class TaskWorker(context: Context, params: WorkerParameters) : CoroutineWorker(context, params) {
@Suppress("PrivatePropertyName", "DEPRECATION")
class CronWorker(context: Context, params: WorkerParameters) : CoroutineWorker(context, params) {
private val TAG: String = TaskWorker::class.java.simpleName
private val TAG: String = CronWorker::class.java.simpleName
override suspend fun doWork(): Result {
val taskId = inputData.getLong("taskId", -1L)
@ -27,20 +29,31 @@ class TaskWorker(context: Context, params: WorkerParameters) : CoroutineWorker(c
}
val task = AppDatabase.getInstance(App.context).taskDao().getOne(taskId)
if (task.status == 0) {
Log.d(TAG, "任务${task.id}task is disabled")
return Result.success()
}
// 根据任务信息执行相应操作
val conditionList = Gson().fromJson(task.conditions, Array<TaskSetting>::class.java).toMutableList()
if (conditionList.isEmpty()) {
Log.d(TAG, "onReceive conditionList is empty")
Log.d(TAG, "任务${task.id}conditionList is empty")
return Result.failure()
}
val firstCondition = conditionList.firstOrNull()
if (firstCondition == null) {
Log.d(TAG, "onReceive firstCondition is null")
Log.d(TAG, "任务${task.id}firstCondition is null")
return Result.failure()
}
val cronSetting = Gson().fromJson(firstCondition.setting, CronSetting::class.java)
if (cronSetting == null) {
Log.d(TAG, "onReceive cronSetting is null")
Log.d(TAG, "任务${task.id}cronSetting is null")
return Result.failure()
}
// TODO: 判断其他条件是否满足
if (false) {
Log.d(TAG, "任务${task.id}:其他条件不满足")
return Result.failure()
}
@ -52,7 +65,7 @@ class TaskWorker(context: Context, params: WorkerParameters) : CoroutineWorker(c
// 将 nextExecTime 的毫秒部分设置为 0避免因为毫秒部分不同导致的任务重复执行
nextExecTime.time = nextExecTime.time / 1000 * 1000
task.nextExecTime = nextExecTime
Log.d(TAG, "lastExecTime = ${task.lastExecTime}, nextExecTime = ${task.nextExecTime}")
Log.d(TAG, "任务${task.id}lastExecTime = ${task.lastExecTime}, nextExecTime = ${task.nextExecTime}")
// 自动禁用任务
if (task.nextExecTime.time / 1000 < now.time / 1000) {
@ -63,13 +76,25 @@ class TaskWorker(context: Context, params: WorkerParameters) : CoroutineWorker(c
AppDatabase.getInstance(App.context).taskDao().updateExecTime(task.id, task.lastExecTime, task.nextExecTime, task.status)
if (task.status == 0) {
Log.d(TAG, "onReceive task is disabled")
Log.d(TAG, "任务${task.id}task is disabled")
return Result.success()
}
// TODO: 执行具体任务
Log.d(TAG, "执行具体任务,耗时 200 毫秒")
delay(200L)
val actionList = Gson().fromJson(task.actions, Array<TaskSetting>::class.java).toMutableList()
if (actionList.isEmpty()) {
Log.d(TAG, "任务${task.id}actionsList is empty")
return Result.failure()
}
for (action in actionList) {
val actionData = Data.Builder()
.putLong("taskId", task.id)
.putInt("actionType", action.type)
.putString("actionSetting", action.setting)
.build()
val actionRequest = OneTimeWorkRequestBuilder<ActionWorker>().setInputData(actionData).build()
WorkManager.getInstance().enqueue(actionRequest)
}
// 为新的 nextExecTime 调度下一次任务执行
CronJobScheduler.cancelTask(task.id)

@ -39,10 +39,17 @@
<LinearLayout
android:id="@+id/layout_actions_icons"
android:layout_width="wrap_content"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="horizontal" />
<TextView
android:id="@+id/tv_time"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="5dp" />
</LinearLayout>
<LinearLayout

@ -1748,6 +1748,40 @@
</LinearLayout>
<LinearLayout
style="@style/settingBarStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/pure_task_mode"
android:textStyle="bold"
tools:ignore="RelativeOverlap" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/pure_task_mode_tips"
android:textSize="9sp"
tools:ignore="SmallSp" />
</LinearLayout>
<com.xuexiang.xui.widget.button.switchbutton.SwitchButton
android:id="@+id/sb_directly_to_task"
style="@style/SwitchButtonStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>
</LinearLayout>
</androidx.core.widget.NestedScrollView>

@ -989,6 +989,10 @@
<string name="pure_client_mode_tips">When starting the APP, it will directly enter the active control client</string>
<string name="exit_pure_client_mode">Exit pure client mode</string>
<string name="enabling_pure_client_mode">Do you want to quit the app immediately and start it manually to take effect in pure client mode?</string>
<string name="pure_task_mode">Directly To Task</string>
<string name="pure_task_mode_tips">When starting the APP, it will directly enter the task center</string>
<string name="exit_pure_task_mode">Exit pure task mode</string>
<string name="enabling_pure_task_mode">Do you want to quit the app immediately and start it manually to take effect in pure task mode?</string>
<string name="optional_components">Optional:</string>
<string name="enable_cactus">Enable Cactus Keep Alive</string>
<string name="enabe_cactus_tips">Dual process foreground service/JobScheduler/WorkManager/1px/silent music</string>

@ -990,6 +990,10 @@
<string name="pure_client_mode_tips">启动APP时直接进入主动控制·客户端</string>
<string name="exit_pure_client_mode">退出纯客户端模式</string>
<string name="enabling_pure_client_mode">是否立即退出App并手动启动以生效纯客户端模式</string>
<string name="pure_task_mode">纯自动任务模式</string>
<string name="pure_task_mode_tips">启动APP时直接进入自动任务</string>
<string name="exit_pure_task_mode">退出纯自动任务模式</string>
<string name="enabling_pure_task_mode">是否立即退出App并手动启动以生效纯自动任务模式</string>
<string name="optional_components">可选组件:</string>
<string name="enable_cactus">启用 Cactus 增强保活措施(会增加耗电)</string>
<string name="enabe_cactus_tips">双进程前台服务/JobScheduler/WorkManager/1像素/无声音乐</string>

Loading…
Cancel
Save