mirror of
https://github.com/pppscn/SmsForwarder
synced 2025-08-03 01:17:41 +08:00
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, "任务$taskId:smsSetting 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, "任务$taskId:send sms result: $msg")
|
||||
return Result.success()
|
||||
}
|
||||
|
||||
else -> {
|
||||
Log.d(TAG, "任务$taskId:action.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,9 +39,16 @@
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/layout_actions_icons"
|
||||
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:orientation="horizontal" />
|
||||
android:layout_marginStart="5dp" />
|
||||
|
||||
</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…
x
Reference in New Issue
Block a user