新增:自动任务·快捷指令 —— 电池电量&充电状态改变(废弃:`88888888`)

pull/408/head
pppscn 7 months ago
parent e03a9b8198
commit eb20d8ea05

@ -79,6 +79,7 @@
android:configChanges="screenSize|keyboardHidden|orientation|keyboard"
android:defaultToDeviceProtectedStorage="true"
android:directBootAware="true"
android:enableOnBackInvokedCallback="false"
android:fullBackupContent="@xml/backup_descriptor"
android:hardwareAccelerated="true"
android:icon="@mipmap/ic_launcher"
@ -222,19 +223,13 @@
android:value="640" />
<service
android:name=".service.HttpService"
android:enabled="true" />
<service
android:name=".service.NetworkStateService"
android:enabled="true" />
<service
android:name=".service.BatteryService"
android:name=".service.HttpServerService"
android:enabled="true" />
<service
android:name=".service.ForegroundService"
android:enabled="true" />
<service
android:name=".service.NotifyService"
android:name=".service.NotificationService"
android:enabled="true"
android:exported="false"
android:label="@string/app_name"
@ -248,9 +243,15 @@
android:exported="false"
android:permission="android.permission.BIND_JOB_SERVICE" />
<receiver
android:name=".receiver.BootReceiver"
android:name=".receiver.BatteryReceiver"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.BATTERY_CHANGED" />
</intent-filter>
</receiver>
<receiver
android:name=".receiver.BootCompletedReceiver"
android:defaultToDeviceProtectedStorage="true"
android:directBootAware="true"
android:exported="true"
@ -299,7 +300,7 @@
<action android:name="android.intent.action.SIM_STATE_CHANGED" />
</intent-filter>
</receiver>
<receiver android:name=".receiver.AlarmReceiver" />
</application>
</manifest>

@ -19,16 +19,14 @@ import com.idormy.sms.forwarder.core.Core
import com.idormy.sms.forwarder.database.AppDatabase
import com.idormy.sms.forwarder.database.repository.*
import com.idormy.sms.forwarder.entity.SimInfo
import com.idormy.sms.forwarder.receiver.BatteryReceiver
import com.idormy.sms.forwarder.receiver.CactusReceiver
import com.idormy.sms.forwarder.service.BatteryService
import com.idormy.sms.forwarder.service.ForegroundService
import com.idormy.sms.forwarder.service.HttpService
import com.idormy.sms.forwarder.service.NetworkStateService
import com.idormy.sms.forwarder.service.HttpServerService
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.AlarmUtils
import com.idormy.sms.forwarder.utils.tinker.TinkerLoadLibrary
import io.reactivex.Observable
import io.reactivex.android.schedulers.AndroidSchedulers
@ -121,23 +119,18 @@ class App : Application(), CactusCallback, Configuration.Provider by Core {
startService(serviceIntent)
}
//网络状态监听
Intent(this, NetworkStateService::class.java).also {
startService(it)
}
//电池状态监听
Intent(this, BatteryService::class.java).also {
startService(it)
}
//启动HttpServer
if (HttpServerUtils.enableServerAutorun) {
Intent(this, HttpService::class.java).also {
Intent(this, HttpServerService::class.java).also {
startService(it)
}
}
//监听电量&充电状态变化
val batteryReceiver = BatteryReceiver()
val filter = IntentFilter(Intent.ACTION_BATTERY_CHANGED)
registerReceiver(batteryReceiver, filter)
//Cactus 集成双进程前台服务JobScheduleronePix(一像素)WorkManager无声音乐
if (SettingUtils.enableCactus) {
//注册广播监听器
@ -207,10 +200,6 @@ class App : Application(), CactusCallback, Configuration.Provider by Core {
XUpdateInit.init(this)
// 运营统计数据
UMengInit.init(this)
// 定时任务初始化
AlarmUtils.initialize(this)
// 三方时间库初始化
//AndroidThreeTen.init(this)
}
@SuppressLint("CheckResult")

@ -37,7 +37,7 @@ interface TaskDao {
@RawQuery(observedEntities = [Task::class])
fun getAllRaw(query: SupportSQLiteQuery): List<Task>
@Query("SELECT * FROM Task WHERE type = :taskType")
@Query("SELECT * FROM Task WHERE status = 1 AND type = :taskType")
fun getByType(taskType: Int): List<Task>
//TODO:根据条件查询,不推荐使用

@ -4,6 +4,7 @@ import com.idormy.sms.forwarder.R
import com.xuexiang.xui.utils.ResUtils
import java.io.Serializable
@Suppress("DEPRECATION")
data class BatteryInfo(
var level: String = "",
var scale: String = "",

@ -5,12 +5,14 @@ import android.text.TextUtils
import android.util.Log
import com.idormy.sms.forwarder.App
import com.idormy.sms.forwarder.R
import com.idormy.sms.forwarder.utils.BatteryUtils
import com.idormy.sms.forwarder.utils.CALL_TYPE_MAP
import com.idormy.sms.forwarder.utils.HttpServerUtils
import com.idormy.sms.forwarder.utils.SettingUtils
import com.idormy.sms.forwarder.utils.SettingUtils.Companion.enableSmsTemplate
import com.idormy.sms.forwarder.utils.SettingUtils.Companion.extraDeviceMark
import com.idormy.sms.forwarder.utils.SettingUtils.Companion.smsTemplate
import com.idormy.sms.forwarder.utils.task.TaskUtils
import com.xuexiang.xui.utils.ResUtils.getString
import com.xuexiang.xutil.app.AppUtils
import java.io.Serializable
@ -109,6 +111,10 @@ data class MsgInfo(
.replace(getString(R.string.tag_device_name), deviceMark)
.replace(getString(R.string.tag_app_version), versionName)
.replace(getString(R.string.tag_call_type), CALL_TYPE_MAP[callType.toString()] ?: getString(R.string.unknown_call))
.replace(getString(R.string.tag_battery_pct), TaskUtils.batteryPct.toString())
.replace(getString(R.string.tag_battery_status), BatteryUtils.getStatus(TaskUtils.batteryStatus))
.replace(getString(R.string.tag_battery_plugged), BatteryUtils.getPlugged(TaskUtils.batteryPlugged))
.replace(getString(R.string.tag_battery_info), TaskUtils.batteryInfo)
.trim()
return replaceLocationTag(replaceAppName(regexReplace(smsVoForSend, regexReplace), from))
}

@ -2,6 +2,7 @@ package com.idormy.sms.forwarder.entity.task
import android.os.BatteryManager
import com.idormy.sms.forwarder.R
import com.xuexiang.xutil.resource.ResUtils.getString
import java.io.Serializable
data class BatterySetting(
@ -19,4 +20,30 @@ data class BatterySetting(
else -> R.id.rb_battery_charging
}
}
fun getMsg(statusNew: Int, levelNew: Int, levelOld: Int, batteryInfo: String): String {
when (statusNew) {
BatteryManager.BATTERY_STATUS_CHARGING, BatteryManager.BATTERY_STATUS_FULL -> { //充电中
if (status != BatteryManager.BATTERY_STATUS_CHARGING) return ""
if (keepReminding && levelOld < levelNew && levelNew >= levelMax) {
return String.format(getString(R.string.over_level_max), batteryInfo)
} else if (!keepReminding && levelOld < levelNew && levelNew == levelMax) {
return String.format(getString(R.string.reach_level_max), batteryInfo)
}
}
BatteryManager.BATTERY_STATUS_DISCHARGING, BatteryManager.BATTERY_STATUS_NOT_CHARGING -> { //放电中
if (status != BatteryManager.BATTERY_STATUS_DISCHARGING) return ""
if (keepReminding && levelOld > levelNew && levelNew <= levelMin) {
return String.format(getString(R.string.below_level_min), batteryInfo)
} else if (!keepReminding && levelOld > levelNew && levelNew == levelMin) {
return String.format(getString(R.string.reach_level_min), batteryInfo)
}
}
}
return ""
}
}

@ -71,4 +71,11 @@ data class ChargeSetting(
else -> R.id.rb_plugged_unknown
}
}
fun getMsg(statusNew: Int, statusOld: Int, pluggedNew: Int, pluggedOld: Int, batteryInfo: String): String {
if (statusNew != status || pluggedNew != plugged) return ""
return getString(R.string.battery_status_changed) + getStatusStr(statusOld) + "(" + getPluggedStr(pluggedOld) + ") → " + getStatusStr(statusNew) + "(" + getPluggedStr(pluggedNew) + ")" + batteryInfo
}
}

@ -22,7 +22,7 @@ import com.idormy.sms.forwarder.R
import com.idormy.sms.forwarder.core.BaseFragment
import com.idormy.sms.forwarder.databinding.FragmentServerBinding
import com.idormy.sms.forwarder.service.ForegroundService
import com.idormy.sms.forwarder.service.HttpService
import com.idormy.sms.forwarder.service.HttpServerService
import com.idormy.sms.forwarder.utils.*
import com.xuexiang.xaop.annotation.SingleClick
import com.xuexiang.xpage.annotation.Page
@ -241,9 +241,9 @@ class ServerFragment : BaseFragment<FragmentServerBinding?>(), View.OnClickListe
binding!!.sbApiLocation.isChecked = HttpServerUtils.enableApiLocation
binding!!.sbApiLocation.setOnCheckedChangeListener { _: CompoundButton?, isChecked: Boolean ->
HttpServerUtils.enableApiLocation = isChecked
if (ServiceUtils.isServiceRunning("com.idormy.sms.forwarder.service.HttpService")) {
if (ServiceUtils.isServiceRunning("com.idormy.sms.forwarder.service.HttpServerService")) {
Log.d("ServerFragment", "onClick: 重启服务")
Intent(appContext, HttpService::class.java).also {
Intent(appContext, HttpServerService::class.java).also {
appContext?.stopService(it)
Thread.sleep(500)
appContext?.startService(it)
@ -272,8 +272,8 @@ class ServerFragment : BaseFragment<FragmentServerBinding?>(), View.OnClickListe
checkCallPermission()
checkContactsPermission()
checkLocationPermission()
Intent(appContext, HttpService::class.java).also {
if (ServiceUtils.isServiceRunning("com.idormy.sms.forwarder.service.HttpService")) {
Intent(appContext, HttpServerService::class.java).also {
if (ServiceUtils.isServiceRunning("com.idormy.sms.forwarder.service.HttpServerService")) {
appContext?.stopService(it)
} else {
appContext?.startService(it)
@ -348,8 +348,8 @@ class ServerFragment : BaseFragment<FragmentServerBinding?>(), View.OnClickListe
HttpServerUtils.serverWebPath = webPath
XToastUtils.info(getString(R.string.restarting_httpserver))
Intent(appContext, HttpService::class.java).also {
if (ServiceUtils.isServiceRunning("com.idormy.sms.forwarder.service.HttpService")) {
Intent(appContext, HttpServerService::class.java).also {
if (ServiceUtils.isServiceRunning("com.idormy.sms.forwarder.service.HttpServerService")) {
appContext?.stopService(it)
Thread.sleep(500)
appContext?.startService(it)
@ -381,7 +381,7 @@ class ServerFragment : BaseFragment<FragmentServerBinding?>(), View.OnClickListe
//刷新按钮
private fun refreshButtonText() {
if (ServiceUtils.isServiceRunning("com.idormy.sms.forwarder.service.HttpService")) {
if (ServiceUtils.isServiceRunning("com.idormy.sms.forwarder.service.HttpServerService")) {
binding!!.btnToggleServer.text = resources.getText(R.string.stop_server)
binding!!.ivCopy.visibility = View.VISIBLE
try {

@ -32,7 +32,7 @@ import com.idormy.sms.forwarder.adapter.spinner.AppListSpinnerAdapter
import com.idormy.sms.forwarder.core.BaseFragment
import com.idormy.sms.forwarder.databinding.FragmentSettingsBinding
import com.idormy.sms.forwarder.entity.SimInfo
import com.idormy.sms.forwarder.receiver.BootReceiver
import com.idormy.sms.forwarder.receiver.BootCompletedReceiver
import com.idormy.sms.forwarder.service.ForegroundService
import com.idormy.sms.forwarder.utils.*
import com.idormy.sms.forwarder.workers.LoadAppListWorker
@ -44,16 +44,12 @@ import com.xuexiang.xui.widget.button.SmoothCheckBox
import com.xuexiang.xui.widget.button.switchbutton.SwitchButton
import com.xuexiang.xui.widget.dialog.materialdialog.DialogAction
import com.xuexiang.xui.widget.dialog.materialdialog.MaterialDialog
import com.xuexiang.xui.widget.picker.XRangeSlider
import com.xuexiang.xui.widget.picker.XRangeSlider.OnRangeSliderListener
import com.xuexiang.xui.widget.picker.XSeekBar
import com.xuexiang.xui.widget.picker.widget.builder.OptionsPickerBuilder
import com.xuexiang.xui.widget.picker.widget.builder.TimePickerBuilder
import com.xuexiang.xui.widget.picker.widget.listener.OnOptionsSelectListener
import com.xuexiang.xutil.XUtil
import com.xuexiang.xutil.XUtil.getPackageManager
import com.xuexiang.xutil.app.AppUtils.getAppPackageName
import com.xuexiang.xutil.data.DateUtils
import kotlinx.coroutines.*
import java.util.*
@ -125,15 +121,6 @@ class SettingsFragment : BaseFragment<FragmentSettingsBinding?>(), View.OnClickL
//监听网络状态变化
switchNetworkStateReceiver(binding!!.sbNetworkStateReceiver)
//监听电池状态变化
switchBatteryReceiver(binding!!.sbBatteryReceiver)
//电量预警
editBatteryLevelAlarm(binding!!.xrsBatteryLevelAlarm, binding!!.scbBatteryLevelAlarmOnce)
//定时推送电池状态
switchBatteryCron(binding!!.sbBatteryCron)
//设置推送电池状态时机
editBatteryCronTiming(binding!!.etBatteryCronStartTime, binding!!.etBatteryCronInterval)
//开机启动
checkWithReboot(binding!!.sbWithReboot, binding!!.tvAutoStartup)
//忽略电池优化设置
@ -629,85 +616,6 @@ class SettingsFragment : BaseFragment<FragmentSettingsBinding?>(), View.OnClickL
}
}
//监听电池状态变化
@SuppressLint("UseSwitchCompatOrMaterialCode")
fun switchBatteryReceiver(sbBatteryReceiver: SwitchButton) {
sbBatteryReceiver.isChecked = SettingUtils.enableBatteryReceiver
sbBatteryReceiver.setOnCheckedChangeListener { _: CompoundButton?, isChecked: Boolean ->
SettingUtils.enableBatteryReceiver = isChecked
}
}
//设置低电量报警
private fun editBatteryLevelAlarm(
xrsBatteryLevelAlarm: XRangeSlider, scbBatteryLevelAlarmOnce: SmoothCheckBox
) {
xrsBatteryLevelAlarm.setStartingMinMax(
SettingUtils.batteryLevelMin, SettingUtils.batteryLevelMax
)
xrsBatteryLevelAlarm.setOnRangeSliderListener(object : OnRangeSliderListener {
override fun onMaxChanged(slider: XRangeSlider, maxValue: Int) {
//SettingUtils.batteryLevelMin = slider.selectedMin
SettingUtils.batteryLevelMax = slider.selectedMax
}
override fun onMinChanged(slider: XRangeSlider, minValue: Int) {
SettingUtils.batteryLevelMin = slider.selectedMin
//SettingUtils.batteryLevelMax = slider.selectedMax
}
})
scbBatteryLevelAlarmOnce.isChecked = SettingUtils.batteryLevelOnce
scbBatteryLevelAlarmOnce.setOnCheckedChangeListener { _: SmoothCheckBox, isChecked: Boolean ->
SettingUtils.batteryLevelOnce = isChecked
if (isChecked && 0 == SettingUtils.batteryLevelMin && 0 == SettingUtils.batteryLevelMax) {
XToastUtils.warning(R.string.tips_battery_level_alarm_once)
}
}
}
//定时推送电池状态
@SuppressLint("UseSwitchCompatOrMaterialCode")
fun switchBatteryCron(sbBatteryCron: SwitchButton) {
sbBatteryCron.isChecked = SettingUtils.enableBatteryCron
binding!!.layoutBatteryCron.visibility = if (SettingUtils.enableBatteryCron) View.VISIBLE else View.GONE
sbBatteryCron.setOnCheckedChangeListener { _: CompoundButton?, isChecked: Boolean ->
binding!!.layoutBatteryCron.visibility = if (isChecked) View.VISIBLE else View.GONE
SettingUtils.enableBatteryCron = isChecked
}
}
//设置推送电池状态时机
private fun editBatteryCronTiming(
etBatteryCronStartTime: EditText, etBatteryCronInterval: EditText
) {
etBatteryCronStartTime.setText(SettingUtils.batteryCronStartTime)
etBatteryCronStartTime.setOnClickListener {
val calendar = Calendar.getInstance()
calendar.time = DateUtils.getNowDate()
val mTimePicker = TimePickerBuilder(context) { date: Date?, _: View? ->
etBatteryCronStartTime.setText(DateUtils.date2String(date, DateUtils.HHmm.get()))
}.setType(false, false, false, true, true, false).setTitleText(getString(R.string.time_picker)).setSubmitText(getString(R.string.ok)).setCancelText(getString(R.string.cancel)).setDate(calendar).build()
mTimePicker.show()
}
etBatteryCronInterval.setText(SettingUtils.batteryCronInterval.toString())
etBatteryCronInterval.addTextChangedListener(object : TextWatcher {
override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {}
override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {}
override fun afterTextChanged(s: Editable) {
val interval = etBatteryCronInterval.text.toString().trim()
if (interval.isNotEmpty() && interval.toInt() > 0) {
SettingUtils.batteryCronInterval = interval.toInt()
//TODO:BatteryReportCronTask
//BatteryReportCronTask.getSingleton().updateTimer()
} else {
SettingUtils.batteryCronInterval = 60
}
}
})
}
//开机启动
private fun checkWithReboot(
@SuppressLint("UseSwitchCompatOrMaterialCode") sbWithReboot: SwitchButton, tvAutoStartup: TextView
@ -715,7 +623,7 @@ class SettingsFragment : BaseFragment<FragmentSettingsBinding?>(), View.OnClickL
tvAutoStartup.text = getAutoStartTips()
//获取组件
val cm = ComponentName(getAppPackageName(), BootReceiver::class.java.name)
val cm = ComponentName(getAppPackageName(), BootCompletedReceiver::class.java.name)
val pm: PackageManager = getPackageManager()
val state = pm.getComponentEnabledSetting(cm)
sbWithReboot.isChecked = !(state == PackageManager.COMPONENT_ENABLED_STATE_DISABLED || state == PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER)

@ -335,9 +335,6 @@ class TasksEditFragment : BaseFragment<FragmentTasksEditBinding?>(), View.OnClic
//定时任务
TASK_CONDITION_CRON -> {
//取消旧任务的定时器 & 设置新的定时器
//AlarmUtils.cancelAlarm(task)
//AlarmUtils.scheduleAlarm(task)
CronJobScheduler.cancelTask(task.id)
CronJobScheduler.scheduleTask(task)
}

@ -1,73 +0,0 @@
package com.idormy.sms.forwarder.receiver
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.AlarmUtils
import gatewayapps.crondroid.CronExpression
import java.util.Date
@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")
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 {
//取消旧任务的定时器
AlarmUtils.cancelAlarm(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
}
//设置新的定时器
AlarmUtils.scheduleAlarm(task)
} catch (e: Exception) {
Log.e(TAG, "onReceive error $e")
}
}
}

@ -0,0 +1,79 @@
package com.idormy.sms.forwarder.receiver
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.os.BatteryManager
import android.util.Log
import androidx.work.OneTimeWorkRequestBuilder
import androidx.work.WorkManager
import androidx.work.workDataOf
import com.idormy.sms.forwarder.utils.BatteryUtils
import com.idormy.sms.forwarder.utils.TASK_CONDITION_BATTERY
import com.idormy.sms.forwarder.utils.TASK_CONDITION_CHARGE
import com.idormy.sms.forwarder.utils.TaskWorker
import com.idormy.sms.forwarder.utils.task.TaskUtils
import com.idormy.sms.forwarder.workers.BatteryWorker
@Suppress("PropertyName")
class BatteryReceiver : BroadcastReceiver() {
val TAG: String = BatteryReceiver::class.java.simpleName
override fun onReceive(context: Context?, intent: Intent?) {
if (context == null || intent?.action != Intent.ACTION_BATTERY_CHANGED) return
val batteryInfo = BatteryUtils.getBatteryInfo(intent).toString()
TaskUtils.batteryInfo = batteryInfo
val levelNew = intent.getIntExtra(BatteryManager.EXTRA_LEVEL, 0)
val levelOld = TaskUtils.batteryLevel
val isLevelChanged = levelNew != levelOld
TaskUtils.batteryLevel = levelNew
val scale: Int = intent.getIntExtra(BatteryManager.EXTRA_SCALE, 100)
TaskUtils.batteryPct = levelNew.toFloat() / scale.toFloat() * 100
val pluggedNew: Int = intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, -1)
val pluggedOld = TaskUtils.batteryPlugged
val isPluggedChanged = pluggedNew != pluggedOld
TaskUtils.batteryPlugged = pluggedNew
val statusNew: Int = intent.getIntExtra(BatteryManager.EXTRA_STATUS, -1)
val statusOld = TaskUtils.batteryStatus
val isStatusChanged = statusNew != statusOld
TaskUtils.batteryStatus = statusNew
//电量改变
if (isLevelChanged) {
Log.d(TAG, "电量改变")
val request = OneTimeWorkRequestBuilder<BatteryWorker>().setInputData(
workDataOf(
TaskWorker.conditionType to TASK_CONDITION_BATTERY,
"status" to statusNew,
"level_new" to levelNew,
"level_old" to levelOld,
)
).build()
WorkManager.getInstance(context).enqueue(request)
}
//充电状态改变
if (isPluggedChanged || isStatusChanged) {
Log.d(TAG, "充电状态改变")
val request = OneTimeWorkRequestBuilder<BatteryWorker>().setInputData(
workDataOf(
TaskWorker.conditionType to TASK_CONDITION_CHARGE,
"status_new" to statusNew,
"status_old" to statusOld,
"plugged_new" to pluggedNew,
"plugged_old" to pluggedOld,
)
).build()
WorkManager.getInstance(context).enqueue(request)
}
}
}

@ -0,0 +1,28 @@
package com.idormy.sms.forwarder.receiver
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.util.Log
import com.idormy.sms.forwarder.activity.SplashActivity
@Suppress("PropertyName")
class BootCompletedReceiver : BroadcastReceiver() {
val TAG: String = BootCompletedReceiver::class.java.simpleName
override fun onReceive(context: Context, intent: Intent?) {
if (intent?.action != Intent.ACTION_BOOT_COMPLETED && intent?.action != Intent.ACTION_LOCKED_BOOT_COMPLETED) return
try {
Log.d(TAG, "强制重启APP一次")
val intent1 = Intent(context, SplashActivity::class.java)
intent1.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK)
context.startActivity(intent1)
} catch (e: Exception) {
e.printStackTrace()
}
}
}

@ -1,28 +0,0 @@
package com.idormy.sms.forwarder.receiver
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.util.Log
import com.idormy.sms.forwarder.activity.SplashActivity
@Suppress("PropertyName")
class BootReceiver : BroadcastReceiver() {
val TAG: String = BootReceiver::class.java.simpleName
override fun onReceive(context: Context, intent: Intent?) {
val receiveAction: String? = intent?.action
Log.d(TAG, "onReceive intent $receiveAction")
if (receiveAction == Intent.ACTION_BOOT_COMPLETED || receiveAction == Intent.ACTION_LOCKED_BOOT_COMPLETED) {
try {
Log.d(TAG, "强制重启APP一次")
val intent1 = Intent(context, SplashActivity::class.java)
intent1.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK)
context.startActivity(intent1)
} catch (e: Exception) {
e.printStackTrace()
}
}
}
}

@ -22,7 +22,7 @@ import java.util.Date
@Suppress("PrivatePropertyName", "UNUSED_PARAMETER")
class SimStateReceiver : BroadcastReceiver() {
private var TAG = "SimStateReceiver"
private var TAG = SimStateReceiver::class.java.simpleName
override fun onReceive(context: Context, intent: Intent) {
//纯客户端模式

@ -23,7 +23,7 @@ import java.util.Date
@Suppress("PrivatePropertyName")
class SmsReceiver : BroadcastReceiver() {
private var TAG = "SmsReceiver"
private var TAG = SmsReceiver::class.java.simpleName
private var from = ""
private var msg = ""

@ -1,172 +0,0 @@
package com.idormy.sms.forwarder.service
import android.annotation.SuppressLint
import android.app.Service
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.os.IBinder
import android.text.TextUtils
import android.util.Log
import androidx.work.OneTimeWorkRequestBuilder
import androidx.work.WorkManager
import androidx.work.workDataOf
import com.google.gson.Gson
import com.idormy.sms.forwarder.App
import com.idormy.sms.forwarder.R
import com.idormy.sms.forwarder.core.Core
import com.idormy.sms.forwarder.database.AppDatabase
import com.idormy.sms.forwarder.entity.MsgInfo
import com.idormy.sms.forwarder.utils.BatteryUtils
import com.idormy.sms.forwarder.utils.CacheUtils
import com.idormy.sms.forwarder.utils.HistoryUtils
import com.idormy.sms.forwarder.utils.SettingUtils
import com.idormy.sms.forwarder.utils.Worker
import com.idormy.sms.forwarder.workers.SendWorker
import com.xuexiang.xutil.file.FileUtils
import frpclib.Frpclib
import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.async
import java.util.*
@OptIn(DelicateCoroutinesApi::class)
@Suppress("DeferredResultUnused")
class BatteryService : Service() {
override fun onBind(intent: Intent): IBinder? {
return null
}
override fun onCreate() {
super.onCreate()
Log.i(TAG, "onCreate--------------")
//纯客户端模式
//if (SettingUtils.enablePureClientMode) return
val batteryFilter = IntentFilter()
batteryFilter.addAction(Intent.ACTION_BATTERY_CHANGED)
registerReceiver(batteryReceiver, batteryFilter)
}
override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
Log.i(TAG, "onStartCommand--------------")
return START_STICKY
}
override fun onDestroy() {
Log.i(TAG, "onDestroy--------------")
super.onDestroy()
//纯客户端模式
//if (SettingUtils.enablePureClientMode) return
unregisterReceiver(batteryReceiver)
}
// 接收电池信息更新的广播
private val batteryReceiver: BroadcastReceiver = object : BroadcastReceiver() {
@SuppressLint("DefaultLocale")
override fun onReceive(context: Context, intent: Intent) {
if (intent.action != Intent.ACTION_BATTERY_CHANGED) return
//自动删除N天前的转发记录
if (SettingUtils.autoCleanLogsDays > 0) {
Log.d(TAG, "自动删除N天前的转发记录")
val cal = Calendar.getInstance()
cal.add(Calendar.DAY_OF_MONTH, 0 - SettingUtils.autoCleanLogsDays)
Core.msg.deleteTimeAgo(cal.timeInMillis)
//清理缓存
HistoryUtils.clearPreference()
CacheUtils.clearAllCache(context)
}
//守护自启动的Frpc
if (FileUtils.isFileExists(filesDir.absolutePath + "/libs/libgojni.so")) {
GlobalScope.async(Dispatchers.IO) {
val frpcList = AppDatabase.getInstance(App.context).frpcDao().getAutorun()
if (frpcList.isEmpty()) {
Log.d(TAG, "没有自启动的Frpc")
return@async
}
for (frpc in frpcList) {
if (!Frpclib.isRunning(frpc.uid)) {
val error = Frpclib.runContent(frpc.uid, frpc.config)
if (!TextUtils.isEmpty(error)) {
Log.e(TAG, error)
}
}
}
}
}
//电量发生变化
val levelCur: Int = intent.getIntExtra("level", 0)
val levelPre: Int = SettingUtils.batteryLevelCurrent
if (levelCur != levelPre) {
var msg: String = BatteryUtils.getBatteryInfo(intent).toString()
SettingUtils.batteryLevelCurrent = levelCur
val levelMin: Int = SettingUtils.batteryLevelMin
val levelMax: Int = SettingUtils.batteryLevelMax
if (SettingUtils.batteryLevelOnce && levelMin > 0 && levelPre > levelCur && levelCur <= levelMin) { //电量下降到下限
msg = String.format(getString(R.string.below_level_min), msg)
sendMessage(context, msg)
return
} else if (SettingUtils.batteryLevelOnce && levelMax > 0 && levelPre < levelCur && levelCur >= levelMax) { //电量上升到上限
msg = String.format(getString(R.string.over_level_max), msg)
sendMessage(context, msg)
return
} else if (!SettingUtils.batteryLevelOnce && levelMin > 0 && levelPre > levelCur && levelCur == levelMin) { //电量下降到下限
msg = String.format(getString(R.string.reach_level_min), msg)
sendMessage(context, msg)
return
} else if (!SettingUtils.batteryLevelOnce && levelMax > 0 && levelPre < levelCur && levelCur == levelMax) { //电量上升到上限
msg = String.format(getString(R.string.reach_level_max), msg)
sendMessage(context, msg)
return
}
}
//充电状态改变
val status: Int = intent.getIntExtra("status", 0)
if (SettingUtils.enableBatteryReceiver) {
val oldStatus: Int = SettingUtils.batteryStatus
if (status != oldStatus) {
var msg: String = BatteryUtils.getBatteryInfo(intent).toString()
SettingUtils.batteryStatus = status
msg = getString(R.string.battery_status_changed) + BatteryUtils.getStatus(
oldStatus
) + "" + BatteryUtils.getStatus(status) + msg
sendMessage(context, msg)
}
}
}
}
//发送信息
private fun sendMessage(context: Context, msg: String) {
Log.i(TAG, msg)
try {
val msgInfo = MsgInfo("app", "88888888", msg, Date(), getString(R.string.battery_status_monitor), -1)
val request = OneTimeWorkRequestBuilder<SendWorker>().setInputData(
workDataOf(
Worker.sendMsgInfo to Gson().toJson(msgInfo),
)
).build()
WorkManager.getInstance(context).enqueue(request)
} catch (e: Exception) {
Log.e(TAG, "getLog e:" + e.message)
}
}
companion object {
private const val TAG = "BatteryReceiver"
}
}

@ -49,7 +49,7 @@ import java.util.Date
@Suppress("PrivatePropertyName", "DeferredResultUnused", "OPT_IN_USAGE", "DEPRECATION", "LiftReturnOrAssignment")
class ForegroundService : Service() {
private val TAG: String = "ForegroundService"
private val TAG: String = ForegroundService::class.java.simpleName
private var notificationManager: NotificationManager? = null
private val compositeDisposable = CompositeDisposable()

@ -9,13 +9,12 @@ import com.idormy.sms.forwarder.utils.HTTP_SERVER_TIME_OUT
import com.idormy.sms.forwarder.utils.SettingUtils
import com.yanzhenjie.andserver.AndServer
import com.yanzhenjie.andserver.Server
import java.util.*
import java.util.concurrent.TimeUnit
@Suppress("PrivatePropertyName")
class HttpService : Service(), Server.ServerListener {
class HttpServerService : Service(), Server.ServerListener {
private val TAG: String = "HttpService"
private val TAG: String = HttpServerService::class.java.simpleName
private val server by lazy {
AndServer.webServer(this).port(HTTP_SERVER_PORT).listener(this).timeout(HTTP_SERVER_TIME_OUT, TimeUnit.SECONDS).build()
}

@ -1,265 +0,0 @@
package com.idormy.sms.forwarder.service
import android.annotation.SuppressLint
import android.app.Service
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.net.ConnectivityManager
import android.net.wifi.WifiInfo
import android.net.wifi.WifiManager
import android.os.IBinder
import android.util.Log
import androidx.work.OneTimeWorkRequestBuilder
import androidx.work.WorkManager
import androidx.work.workDataOf
import com.google.gson.Gson
import com.idormy.sms.forwarder.R
import com.idormy.sms.forwarder.entity.MsgInfo
import com.idormy.sms.forwarder.utils.CommonUtils
import com.idormy.sms.forwarder.utils.SettingUtils
import com.idormy.sms.forwarder.utils.Worker
import com.idormy.sms.forwarder.workers.SendWorker
import com.xuexiang.xutil.app.ServiceUtils
import com.xuexiang.xutil.net.NetworkUtils
import java.util.*
import android.os.Build
import android.telephony.SubscriptionInfo
import android.telephony.SubscriptionManager
import androidx.annotation.RequiresApi
import com.idormy.sms.forwarder.App
import com.idormy.sms.forwarder.utils.PhoneUtils
import java.lang.reflect.Method
@Suppress("DEPRECATION")
class NetworkStateService : Service() {
override fun onBind(intent: Intent): IBinder? {
return null
}
override fun onCreate() {
super.onCreate()
Log.i(TAG, "onCreate--------------")
//纯客户端模式
//if (SettingUtils.enablePureClientMode) return
val networkStateFilter = IntentFilter()
networkStateFilter.addAction(ConnectivityManager.CONNECTIVITY_ACTION)
registerReceiver(networkStateReceiver, networkStateFilter)
}
override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
Log.i(TAG, "onStartCommand--------------")
return START_STICKY
}
override fun onDestroy() {
Log.i(TAG, "onDestroy--------------")
super.onDestroy()
//纯客户端模式
//if (SettingUtils.enablePureClientMode) return
unregisterReceiver(networkStateReceiver)
}
// 接收网络状态更新的广播
private val networkStateReceiver: BroadcastReceiver = object : BroadcastReceiver() {
@RequiresApi(Build.VERSION_CODES.Q)
@SuppressLint("DefaultLocale")
override fun onReceive(context: Context, intent: Intent) {
if (intent.action != ConnectivityManager.CONNECTIVITY_ACTION) return
if (!SettingUtils.enableNetworkStateReceiver) return
Log.i(TAG, "网络状态已经改变延时2秒后获取信息")
Thread.sleep(2000)
val msg = StringBuilder()
//枚举网络状态 NET_NO没有网络 , NET_2G:2g网络 , NET_3G3g网络, NET_4G4g网络, NET_5G5g网络, NET_WIFIwifi, NET_ETHERNET有线网络, NET_UNKNOWN未知网络
val netStateType = NetworkUtils.getNetStateType()
Log.d(TAG, "netStateType: $netStateType")
val netStateTypeName = when (netStateType) {
NetworkUtils.NetState.NET_NO -> getString(R.string.no_network)
NetworkUtils.NetState.NET_2G -> getString(R.string.net_2g)
NetworkUtils.NetState.NET_3G -> getString(R.string.net_3g)
NetworkUtils.NetState.NET_4G -> getString(R.string.net_4g)
NetworkUtils.NetState.NET_5G -> getString(R.string.net_5g)
NetworkUtils.NetState.NET_WIFI -> getString(R.string.net_wifi)
NetworkUtils.NetState.NET_ETHERNET -> getString(R.string.net_ethernet)
NetworkUtils.NetState.NET_UNKNOWN -> getString(R.string.net_unknown)
else -> getString(R.string.net_unknown)
}
msg.append(getString(R.string.network_type)).append(": ").append(netStateTypeName).append("\n")
if (netStateType == NetworkUtils.NetState.NET_2G || netStateType == NetworkUtils.NetState.NET_3G || netStateType == NetworkUtils.NetState.NET_4G || netStateType == NetworkUtils.NetState.NET_5G) {
// 获取当前使用的 SIM index
val simIndex = getSlotId(context)
if (simIndex != -1) {
// 获取 SIM 卡信息
App.SimInfoList = PhoneUtils.getSimMultiInfo()
Log.d(TAG, App.SimInfoList.toString())
if (App.SimInfoList[simIndex]?.mCarrierName != null) {
//获取网络运营商名称:中国移动、中国联通、中国电信
msg.append(getString(R.string.carrier_name)).append(": ").append(App.SimInfoList[simIndex]?.mCarrierName).append("\n")
}
msg.append(getString(R.string.data_sim_index)).append(": SIM-").append(simIndex + 1).append("\n")
}
} else if (netStateType == NetworkUtils.NetState.NET_WIFI) {
//获取当前连接的WiFi名称
val wifiSSID = getWifiSSID(context)
msg.append(getString(R.string.wifi_ssid)).append(": ").append(wifiSSID).append("\n")
}
//获取IP地址
val ipList = CommonUtils.getIPAddresses().filter { !isLocalAddress(it) }
if (ServiceUtils.isServiceRunning("com.idormy.sms.forwarder.service.HttpService")) {
ipList.forEach {
msg.append(getString(R.string.host_address)).append(": ").append(it).append("\n")
val hostAddress = if (it.indexOf(':', 0, false) > 0) "[${CommonUtils.removeInterfaceFromIP(it)}]" else it
msg.append(getString(R.string.http_server)).append(": ").append("http://${hostAddress}:5000").append("\n")
}
} else {
ipList.forEach {
msg.append(getString(R.string.host_address)).append(": ").append(it).append("\n")
}
}
sendMessage(context, msg.toString().trimEnd())
}
}
// 判断手机数据流量是否打开
@Suppress("rawtypes", "unchecked")
private fun isMobileDataOpen(context: Context): Boolean {
return try {
val mConnectivityManager = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
val ownerClass = mConnectivityManager.javaClass
val method = ownerClass.getMethod("getMobileDataEnabled")
method.invoke(mConnectivityManager) as Boolean
} catch (e: Exception) {
false
}
}
// 获取当前数据连接的卡槽ID
@RequiresApi(Build.VERSION_CODES.Q)
private fun getSlotId(context: Context): Int {
if (!isMobileDataOpen(context)) {
return -1
}
var dataSubId = 0
try {
dataSubId = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
SubscriptionManager.getDefaultDataSubscriptionId()
} else {
getDataSubId(context)
}
} catch (e: Exception) {
e.printStackTrace()
}
return SubscriptionManager.getSlotIndex(dataSubId)
}
// 获取数据连接的订阅ID
@SuppressLint("DiscouragedPrivateApi")
private fun getDataSubId(context: Context): Int {
val defaultDataSlotId = getDefaultDataSlotId(context)
try {
val obj = Class.forName("android.telephony.SubscriptionManager")
.getDeclaredMethod("getSubId", Int::class.javaPrimitiveType)
.invoke(null, defaultDataSlotId)
if (obj != null) {
if (Build.VERSION.SDK_INT == Build.VERSION_CODES.LOLLIPOP) {
return (obj as LongArray)[0].toInt()
}
return (obj as IntArray)[0]
}
} catch (e: Exception) {
e.printStackTrace()
}
return defaultDataSlotId
}
// 获取默认数据卡的卡槽ID
private fun getDefaultDataSlotId(context: Context): Int {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1) {
val subscriptionManager = SubscriptionManager.from(context.applicationContext)
if (subscriptionManager != null) {
try {
val subClass = Class.forName(subscriptionManager.javaClass.name)
val getSubID = subClass.getMethod("getDefaultDataSubscriptionInfo")
val subInfo = getSubID.invoke(subscriptionManager) as SubscriptionInfo
return subInfo.simSlotIndex
} catch (e: Exception) {
e.printStackTrace()
}
}
} else {
try {
val cls = Class.forName("android.telephony.SubscriptionManager")
val getSubId: Method = try {
cls.getDeclaredMethod("getDefaultDataSubId")
} catch (e: NoSuchMethodException) {
cls.getDeclaredMethod("getDefaultDataSubscriptionId")
}
val subId = getSubId.invoke(null) as Int
val slotId: Int = if (Build.VERSION.SDK_INT == Build.VERSION_CODES.LOLLIPOP) {
val getSlotId = cls.getDeclaredMethod("getSlotId", Long::class.javaPrimitiveType)
getSlotId.invoke(null, subId.toLong()) as Int
} else {
val getSlotId = cls.getDeclaredMethod("getSlotId", Int::class.javaPrimitiveType)
getSlotId.invoke(null, subId) as Int
}
return slotId
} catch (e: Exception) {
e.printStackTrace()
}
}
return -1
}
//发送信息
private fun sendMessage(context: Context, msg: String) {
Log.i(TAG, msg)
try {
val msgInfo = MsgInfo("app", "77777777", msg, Date(), getString(R.string.network_state_monitor), -1)
val request = OneTimeWorkRequestBuilder<SendWorker>().setInputData(
workDataOf(
Worker.sendMsgInfo to Gson().toJson(msgInfo),
)
).build()
WorkManager.getInstance(context).enqueue(request)
} catch (e: Exception) {
Log.e(TAG, "getLog e:" + e.message)
}
}
//获取当前连接的WiFi名称
@SuppressLint("WifiManagerPotentialLeak")
private fun getWifiSSID(context: Context): String {
val wifiManager = context.getSystemService(Context.WIFI_SERVICE) as WifiManager
val wifiInfo: WifiInfo? = wifiManager.connectionInfo
if (wifiInfo != null && wifiInfo.networkId != -1) {
return wifiInfo.ssid.replace("\"", "")
}
return "Not connected to WiFi"
}
//检查IP地址是否为本地地址
private fun isLocalAddress(ip: String): Boolean {
return ip == "127.0.0.1" || ip == "::1" || ip.startsWith("fe80:") || ip.startsWith("fec0:")
}
companion object {
private const val TAG = "NetworkStateReceiver"
}
}

@ -24,9 +24,9 @@ import java.util.*
@Suppress("PrivatePropertyName", "DEPRECATION")
class NotifyService : NotificationListenerService() {
class NotificationService : NotificationListenerService() {
private val TAG: String = "NotifyService"
private val TAG: String = NotificationService::class.java.simpleName
override fun onListenerConnected() {
Log.d(TAG, "onListenerConnected")

@ -7,6 +7,7 @@ import com.idormy.sms.forwarder.R
import com.idormy.sms.forwarder.entity.BatteryInfo
import com.xuexiang.xui.utils.ResUtils.getString
@Suppress("DEPRECATION", "MemberVisibilityCanBePrivate")
object BatteryUtils {
private const val TAG = "BatteryUtils"
@ -27,7 +28,25 @@ object BatteryUtils {
if (voltage > 0) batteryInfo.voltage = "${String.format("%.2f", voltage / 1000f)}V"
if (temperature > 0) batteryInfo.temperature = "${String.format("%.2f", temperature / 10f)}"
batteryInfo.status = getStatus(status)
batteryInfo.health = when (health) {
batteryInfo.health = getHealth(health)
batteryInfo.plugged = getPlugged(plugged)
Log.i(TAG, batteryInfo.toString())
return batteryInfo
}
fun getStatus(status: Int): String {
return when (status) {
BatteryManager.BATTERY_STATUS_CHARGING -> getString(R.string.battery_charging)
BatteryManager.BATTERY_STATUS_DISCHARGING -> getString(R.string.battery_discharging)
BatteryManager.BATTERY_STATUS_NOT_CHARGING -> getString(R.string.battery_not_charging)
BatteryManager.BATTERY_STATUS_FULL -> getString(R.string.battery_full)
BatteryManager.BATTERY_STATUS_UNKNOWN -> getString(R.string.battery_unknown)
else -> getString(R.string.battery_unknown)
}
}
fun getHealth(health: Int): String {
return when (health) {
BatteryManager.BATTERY_HEALTH_UNKNOWN -> getString(R.string.battery_unknown)
BatteryManager.BATTERY_HEALTH_GOOD -> getString(R.string.battery_good)
BatteryManager.BATTERY_HEALTH_OVERHEAT -> getString(R.string.battery_overheat)
@ -37,25 +56,15 @@ object BatteryUtils {
BatteryManager.BATTERY_HEALTH_COLD -> getString(R.string.battery_cold)
else -> getString(R.string.battery_unknown)
}
batteryInfo.plugged = when (plugged) {
}
fun getPlugged(plugged: Int): String {
return when (plugged) {
BatteryManager.BATTERY_PLUGGED_AC -> getString(R.string.battery_ac)
BatteryManager.BATTERY_PLUGGED_USB -> getString(R.string.battery_usb)
BatteryManager.BATTERY_PLUGGED_WIRELESS -> getString(R.string.battery_wireless)
else -> getString(R.string.battery_unknown)
}
Log.i(TAG, batteryInfo.toString())
return batteryInfo
}
fun getStatus(status: Int): String {
return when (status) {
BatteryManager.BATTERY_STATUS_CHARGING -> getString(R.string.battery_charging)
BatteryManager.BATTERY_STATUS_DISCHARGING -> getString(R.string.battery_discharging)
BatteryManager.BATTERY_STATUS_NOT_CHARGING -> getString(R.string.battery_not_charging)
BatteryManager.BATTERY_STATUS_FULL -> getString(R.string.battery_full)
BatteryManager.BATTERY_STATUS_UNKNOWN -> getString(R.string.battery_unknown)
else -> getString(R.string.battery_unknown)
}
}
}

@ -23,7 +23,7 @@ import com.idormy.sms.forwarder.core.webview.AgentWebFragment
import com.idormy.sms.forwarder.entity.ImageInfo
import com.idormy.sms.forwarder.fragment.MarkdownFragment
import com.idormy.sms.forwarder.fragment.ServiceProtocolFragment
import com.idormy.sms.forwarder.service.NotifyService
import com.idormy.sms.forwarder.service.NotificationService
import com.xuexiang.xpage.base.XPageFragment
import com.xuexiang.xpage.core.PageOption
import com.xuexiang.xui.utils.ColorUtils
@ -273,10 +273,10 @@ class CommonUtils private constructor() {
fun toggleNotificationListenerService(context: Context) {
val pm = context.packageManager
pm.setComponentEnabledSetting(
ComponentName(context.applicationContext, NotifyService::class.java), PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager.DONT_KILL_APP
ComponentName(context.applicationContext, NotificationService::class.java), PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager.DONT_KILL_APP
)
pm.setComponentEnabledSetting(
ComponentName(context.applicationContext, NotifyService::class.java), PackageManager.COMPONENT_ENABLED_STATE_ENABLED, PackageManager.DONT_KILL_APP
ComponentName(context.applicationContext, NotificationService::class.java), PackageManager.COMPONENT_ENABLED_STATE_ENABLED, PackageManager.DONT_KILL_APP
)
}

@ -16,9 +16,14 @@ object Worker {
const val rule = "rule"
const val senderIndex = "sender_index"
const val msgId = "msg_id"
}
object TaskWorker {
const val taskId = "task_id"
const val actionType = "action_type"
const val actionSetting = "action_setting"
const val task = "task"
const val taskActions = "task_actions"
const val conditionType = "condition_type"
const val msgInfo = "msg_info"
}
//初始化相关
@ -59,17 +64,6 @@ const val SP_AUTO_CLEAN_LOGS_DAYS = "auto_clean_logs_days"
const val SP_NET_STATE_RECEIVER = "enable_network_state_receiver"
const val SP_BATTERY_RECEIVER = "enable_battery_receiver"
const val SP_BATTERY_STATUS = "battery_status"
const val SP_BATTERY_LEVEL_MIN = "battery_level_min"
const val SP_BATTERY_LEVEL_MAX = "battery_level_max"
const val SP_BATTERY_LEVEL_ONCE = "battery_level_once"
const val SP_BATTERY_LEVEL_CURRENT = "battery_level_current"
const val SP_BATTERY_CRON = "enable_battery_cron"
const val SP_BATTERY_CRON_START_TIME = "battery_cron_start_time"
const val SP_BATTERY_CRON_INTERVAL = "battery_cron_interval"
const val SP_ENABLE_EXCLUDE_FROM_RECENTS = "enable_exclude_from_recents"
const val SP_ENABLE_PLAY_SILENCE_MUSIC = "enable_play_silence_music"
const val SP_ENABLE_ONE_PIXEL_ACTIVITY = "enable_one_pixel_activity"
@ -581,4 +575,9 @@ var TASK_ACTION_FRAGMENT_LIST = listOf(
CoreAnim.slide,
R.drawable.auto_task_icon_http_server
),
)
)
const val SP_BATTERY_STATUS = "battery_status"
const val SP_BATTERY_LEVEL = "battery_level"
const val SP_BATTERY_PCT = "battery_pct"
const val SP_BATTERY_PLUGGED = "battery_plugged"

@ -77,33 +77,6 @@ class SettingUtils private constructor() {
//是否监听网络状态变化
var enableNetworkStateReceiver: Boolean by SharedPreference(SP_NET_STATE_RECEIVER, false)
//是否监听电池状态变化
var enableBatteryReceiver: Boolean by SharedPreference(SP_BATTERY_RECEIVER, false)
//电量预警当前状态
var batteryStatus: Int by SharedPreference(SP_BATTERY_STATUS, 0)
//电量预警当前值
var batteryLevelCurrent: Int by SharedPreference(SP_BATTERY_LEVEL_CURRENT, 0)
//电量预警最低值
var batteryLevelMin: Int by SharedPreference(SP_BATTERY_LEVEL_MIN, 0)
//电量预警最高值
var batteryLevelMax: Int by SharedPreference(SP_BATTERY_LEVEL_MAX, 100)
//是否持续电量预警
var batteryLevelOnce: Boolean by SharedPreference(SP_BATTERY_LEVEL_ONCE, false)
//是否定时推送电池状态
var enableBatteryCron: Boolean by SharedPreference(SP_BATTERY_CRON, false)
//是否定时推送电池状态——开始时间
var batteryCronStartTime: String by SharedPreference(SP_BATTERY_CRON_START_TIME, "00:00")
//是否定时推送电池状态——间隔时间(分钟)
var batteryCronInterval: Int by SharedPreference(SP_BATTERY_CRON_INTERVAL, 60)
//是否不在最近任务列表中显示
var enableExcludeFromRecents: Boolean by SharedPreference(SP_ENABLE_EXCLUDE_FROM_RECENTS, false)

@ -9,12 +9,10 @@ import android.util.Log
import androidx.core.app.ActivityCompat
import com.google.gson.Gson
import com.idormy.sms.forwarder.App
import com.idormy.sms.forwarder.R
import com.idormy.sms.forwarder.database.AppDatabase
import com.idormy.sms.forwarder.server.model.SmsSendData
import com.idormy.sms.forwarder.service.HttpService
import com.idormy.sms.forwarder.service.HttpServerService
import com.xuexiang.xrouter.utils.TextUtils
import com.xuexiang.xui.utils.ResUtils
import com.xuexiang.xutil.XUtil
import com.xuexiang.xutil.file.FileUtils
import com.xuexiang.xutil.system.DeviceUtils
@ -101,7 +99,7 @@ class SmsCommandUtils {
}
"httpserver" -> {
Intent(context, HttpService::class.java).also {
Intent(context, HttpServerService::class.java).also {
if (action == "start") {
context.startService(it)
} else if (action == "stop") {

@ -1,69 +0,0 @@
package com.idormy.sms.forwarder.utils.task
import android.annotation.SuppressLint
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
class AlarmUtils {
companion object {
@SuppressLint("StaticFieldLeak")
private lateinit var context: Context
fun initialize(context: Context) {
this.context = context.applicationContext
}
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_MUTABLE
)
pendingIntent?.let {
alarmManager.cancel(it)
it.cancel()
}
}
@SuppressLint("ScheduleExactAlarm")
fun scheduleAlarm(task: Task) {
val alarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager
val alarmIntent = Intent(context, AlarmReceiver::class.java)
val requestCode = task.id.toInt()
alarmIntent.putExtra("TASK", task)
val pendingIntent = PendingIntent.getBroadcast(
context,
requestCode,
alarmIntent,
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_MUTABLE
)
// 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
)
}
}
}
}

@ -6,6 +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.utils.TaskWorker
import com.idormy.sms.forwarder.workers.CronWorker
import java.util.concurrent.TimeUnit
@ -19,7 +20,7 @@ class CronJobScheduler {
fun scheduleTask(task: Task) {
val currentTimeMillis = System.currentTimeMillis()
val delayInMillis = task.nextExecTime.time / 1000 * 1000 - currentTimeMillis
val inputData = Data.Builder().putLong("taskId", task.id).build()
val inputData = Data.Builder().putLong(TaskWorker.taskId, task.id).build()
val taskRequest = if (delayInMillis <= 0L) {
Log.d(TAG, "任务${task.id}立即执行delayInMillis = $delayInMillis")
OneTimeWorkRequestBuilder<CronWorker>()

@ -1,6 +1,10 @@
package com.idormy.sms.forwarder.utils.task
import com.idormy.sms.forwarder.utils.SP_ENABLE_SERVER_AUTORUN
import android.os.BatteryManager
import com.idormy.sms.forwarder.utils.SP_BATTERY_LEVEL
import com.idormy.sms.forwarder.utils.SP_BATTERY_PCT
import com.idormy.sms.forwarder.utils.SP_BATTERY_PLUGGED
import com.idormy.sms.forwarder.utils.SP_BATTERY_STATUS
import com.idormy.sms.forwarder.utils.SharedPreference
/**
@ -10,7 +14,19 @@ class TaskUtils private constructor() {
companion object {
//是否启用HttpServer开机自启
var enableServerAutorun: Boolean by SharedPreference(SP_ENABLE_SERVER_AUTORUN, false)
//电池信息
var batteryInfo: String by SharedPreference("batteryInfo", "")
//当前电量
var batteryLevel: Int by SharedPreference(SP_BATTERY_LEVEL, 0)
//当前电量百分比level/scale
var batteryPct: Float by SharedPreference(SP_BATTERY_PCT, 0.00F)
//电池状态
var batteryStatus: Int by SharedPreference(SP_BATTERY_STATUS, BatteryManager.BATTERY_STATUS_UNKNOWN)
//充电方式
var batteryPlugged: Int by SharedPreference(SP_BATTERY_PLUGGED, BatteryManager.BATTERY_PLUGGED_AC)
}
}

@ -13,76 +13,94 @@ 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.task.SmsSetting
import com.idormy.sms.forwarder.entity.task.TaskSetting
import com.idormy.sms.forwarder.utils.PhoneUtils
import com.idormy.sms.forwarder.utils.SendUtils
import com.idormy.sms.forwarder.utils.TASK_ACTION_NOTIFICATION
import com.idormy.sms.forwarder.utils.TASK_ACTION_SENDSMS
import com.idormy.sms.forwarder.utils.Worker
import com.idormy.sms.forwarder.utils.TaskWorker
import com.xuexiang.xui.utils.ResUtils
import com.xuexiang.xutil.XUtil
//执行每个task具体动作任务
@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(Worker.taskId, -1L)
val actionType = inputData.getInt(Worker.actionType, -1)
val actionSetting = inputData.getString(Worker.actionSetting)
val msgInfoJson = inputData.getString(Worker.sendMsgInfo)
Log.d(TAG, "taskId: $taskId, actionType: $actionType, actionSetting: $actionSetting, msgInfoJson: $msgInfoJson")
if (taskId == -1L || actionSetting == null) {
val taskId = inputData.getLong(TaskWorker.taskId, -1L)
val taskActionsJson = inputData.getString(TaskWorker.taskActions)
val msgInfoJson = inputData.getString(TaskWorker.msgInfo)
Log.d(TAG, "taskId: $taskId, taskActionsJson: $taskActionsJson, msgInfoJson: $msgInfoJson")
if (taskId == -1L || taskActionsJson.isNullOrEmpty() || msgInfoJson.isNullOrEmpty()) {
Log.d(TAG, "taskId is -1L or actionSetting is null")
return Result.failure()
}
val actionList = Gson().fromJson(taskActionsJson, Array<TaskSetting>::class.java).toMutableList()
if (actionList.isEmpty()) {
Log.d(TAG, "任务$taskIdactionList is empty")
return Result.failure()
}
val msgInfo = Gson().fromJson(msgInfoJson, MsgInfo::class.java)
if (msgInfo == null) {
Log.d(TAG, "任务$taskIdmsgInfo 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())
var successNum = 0
for (action in actionList) {
when (action.type) {
TASK_ACTION_SENDSMS -> {
val smsSetting = Gson().fromJson(action.setting, SmsSetting::class.java)
if (smsSetting == null) {
Log.d(TAG, "任务$taskIdsmsSetting is null")
continue
}
//获取卡槽信息
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
//发送卡槽: 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"
}
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)
successNum++
}
Log.d(TAG, "任务$taskIdsend sms result: $msg")
return Result.success()
}
Log.d(TAG, "任务$taskIdsend sms result: $msg")
continue
}
TASK_ACTION_NOTIFICATION -> {
return try {
val settingVo = Gson().fromJson(actionSetting, Rule::class.java)
//自动任务的不需要吐司或者更新日志,特殊处理 logId = -1msgId = -1
SendUtils.sendMsgSender(msgInfo, settingVo, 0, -1L, -1L)
Result.success()
} catch (e: Exception) {
e.printStackTrace()
Result.failure()
TASK_ACTION_NOTIFICATION -> {
try {
val settingVo = Gson().fromJson(action.setting, Rule::class.java)
//自动任务的不需要吐司或者更新日志,特殊处理 logId = -1msgId = -1
SendUtils.sendMsgSender(msgInfo, settingVo, 0, -1L, -1L)
successNum++
} catch (e: Exception) {
e.printStackTrace()
}
continue
}
}
else -> {
Log.d(TAG, "任务$taskIdaction.type is $actionType")
return Result.failure()
else -> {
Log.d(TAG, "任务$taskIdaction.type is ${action.type}")
continue
}
}
}
return if (successNum == actionList.size) Result.success() else Result.failure()
}
}

@ -0,0 +1,149 @@
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
import com.idormy.sms.forwarder.database.AppDatabase
import com.idormy.sms.forwarder.entity.MsgInfo
import com.idormy.sms.forwarder.entity.task.BatterySetting
import com.idormy.sms.forwarder.entity.task.ChargeSetting
import com.idormy.sms.forwarder.entity.task.TaskSetting
import com.idormy.sms.forwarder.utils.TASK_CONDITION_BATTERY
import com.idormy.sms.forwarder.utils.TASK_CONDITION_CHARGE
import com.idormy.sms.forwarder.utils.TaskWorker
import com.idormy.sms.forwarder.utils.task.TaskUtils
import java.util.Date
@Suppress("PrivatePropertyName", "DEPRECATION")
class BatteryWorker(context: Context, params: WorkerParameters) : CoroutineWorker(context, params) {
private val TAG: String = BatteryWorker::class.java.simpleName
override suspend fun doWork(): Result {
when (val conditionType = inputData.getInt(TaskWorker.conditionType, -1)) {
TASK_CONDITION_BATTERY -> {
val status = inputData.getInt("status", -1)
val levelNew = inputData.getInt("level_new", -1)
val levelOld = inputData.getInt("level_old", -1)
Log.d(TAG, "levelNew: $levelNew, levelOld: $levelOld")
if (levelNew == -1 || levelOld == -1) {
Log.d(TAG, "levelNew or levelOld is -1")
return Result.failure()
}
val taskList = AppDatabase.getInstance(App.context).taskDao().getByType(TASK_CONDITION_BATTERY)
for (task in taskList) {
Log.d(TAG, "task = $task")
// 根据任务信息执行相应操作
val conditionList = Gson().fromJson(task.conditions, Array<TaskSetting>::class.java).toMutableList()
if (conditionList.isEmpty()) {
Log.d(TAG, "任务${task.id}conditionList is empty")
continue
}
val firstCondition = conditionList.firstOrNull()
if (firstCondition == null) {
Log.d(TAG, "任务${task.id}firstCondition is null")
continue
}
val batterySetting = Gson().fromJson(firstCondition.setting, BatterySetting::class.java)
if (batterySetting == null) {
Log.d(TAG, "任务${task.id}batterySetting is null")
continue
}
val msg = batterySetting.getMsg(status, levelNew, levelOld, TaskUtils.batteryInfo)
if (msg.isEmpty()) {
Log.d(TAG, "任务${task.id}msg is empty, batterySetting = $batterySetting, status = $status, levelNew = $levelNew, levelOld = $levelOld")
continue
}
//TODO判断其他条件是否满足
//TODO: 组装消息体 && 执行具体任务
val msgInfo = MsgInfo("task", task.name, msg, Date(), task.name)
val actionData = Data.Builder()
.putLong(TaskWorker.taskId, task.id)
.putString(TaskWorker.taskActions, task.actions)
.putString(TaskWorker.msgInfo, Gson().toJson(msgInfo))
.build()
val actionRequest = OneTimeWorkRequestBuilder<ActionWorker>().setInputData(actionData).build()
WorkManager.getInstance().enqueue(actionRequest)
}
return Result.success()
}
TASK_CONDITION_CHARGE -> {
val statusNew = inputData.getInt("status_new", -1)
val statusOld = inputData.getInt("status_old", -1)
val pluggedNew = inputData.getInt("plugged_new", -1)
val pluggedOld = inputData.getInt("plugged_old", -1)
Log.d(TAG, "statusNew: $statusNew, statusOld: $statusOld, pluggedNew: $pluggedNew, pluggedOld: $pluggedOld")
if (statusNew == -1 || statusOld == -1 || pluggedNew == -1 || pluggedOld == -1) {
Log.d(TAG, "statusNew or statusOld or pluggedNew or pluggedOld is -1")
return Result.failure()
}
val taskList = AppDatabase.getInstance(App.context).taskDao().getByType(TASK_CONDITION_CHARGE)
for (task in taskList) {
Log.d(TAG, "task = $task")
// 根据任务信息执行相应操作
val conditionList = Gson().fromJson(task.conditions, Array<TaskSetting>::class.java).toMutableList()
if (conditionList.isEmpty()) {
Log.d(TAG, "任务${task.id}conditionList is empty")
continue
}
val firstCondition = conditionList.firstOrNull()
if (firstCondition == null) {
Log.d(TAG, "任务${task.id}firstCondition is null")
continue
}
val chargeSetting = Gson().fromJson(firstCondition.setting, ChargeSetting::class.java)
if (chargeSetting == null) {
Log.d(TAG, "任务${task.id}chargeSetting is null")
continue
}
val msg = chargeSetting.getMsg(statusNew, statusOld, pluggedNew, pluggedOld, TaskUtils.batteryInfo)
if (msg.isEmpty()) {
Log.d(TAG, "任务${task.id}msg is empty, chargeSetting = $chargeSetting, statusNew = $statusNew, statusOld = $statusOld, pluggedNew = $pluggedNew, pluggedOld = $pluggedOld")
continue
}
//TODO判断其他条件是否满足
//TODO: 组装消息体 && 执行具体任务
val msgInfo = MsgInfo("task", task.name, msg, Date(), task.name)
val actionData = Data.Builder()
.putLong(TaskWorker.taskId, task.id)
.putString(TaskWorker.taskActions, task.actions)
.putString(TaskWorker.msgInfo, Gson().toJson(msgInfo))
.build()
val actionRequest = OneTimeWorkRequestBuilder<ActionWorker>().setInputData(actionData).build()
WorkManager.getInstance().enqueue(actionRequest)
}
return Result.success()
}
else -> {
Log.d(TAG, "conditionType is $conditionType")
return Result.failure()
}
}
}
}

@ -13,7 +13,7 @@ import com.idormy.sms.forwarder.database.AppDatabase
import com.idormy.sms.forwarder.entity.MsgInfo
import com.idormy.sms.forwarder.entity.task.CronSetting
import com.idormy.sms.forwarder.entity.task.TaskSetting
import com.idormy.sms.forwarder.utils.Worker
import com.idormy.sms.forwarder.utils.TaskWorker
import com.idormy.sms.forwarder.utils.task.CronJobScheduler
import gatewayapps.crondroid.CronExpression
import java.util.Date
@ -24,7 +24,7 @@ class CronWorker(context: Context, params: WorkerParameters) : CoroutineWorker(c
private val TAG: String = CronWorker::class.java.simpleName
override suspend fun doWork(): Result {
val taskId = inputData.getLong("taskId", -1L)
val taskId = inputData.getLong(TaskWorker.taskId, -1L)
if (taskId == -1L) {
Log.d(TAG, "taskId is -1L")
return Result.failure()
@ -82,25 +82,15 @@ class CronWorker(context: Context, params: WorkerParameters) : CoroutineWorker(c
return Result.success()
}
//组装消息体
//TODO: 组装消息体 && 执行具体任务
val msgInfo = MsgInfo("task", task.name, task.description, Date(), task.name)
// TODO: 执行具体任务
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(Worker.taskId, task.id)
.putInt(Worker.actionType, action.type)
.putString(Worker.actionSetting, action.setting)
.putString(Worker.sendMsgInfo, Gson().toJson(msgInfo))
.build()
val actionRequest = OneTimeWorkRequestBuilder<ActionWorker>().setInputData(actionData).build()
WorkManager.getInstance().enqueue(actionRequest)
}
val actionData = Data.Builder()
.putLong(TaskWorker.taskId, task.id)
.putString(TaskWorker.taskActions, task.actions)
.putString(TaskWorker.msgInfo, Gson().toJson(msgInfo))
.build()
val actionRequest = OneTimeWorkRequestBuilder<ActionWorker>().setInputData(actionData).build()
WorkManager.getInstance().enqueue(actionRequest)
// 为新的 nextExecTime 调度下一次任务执行
CronJobScheduler.cancelTask(task.id)

@ -758,206 +758,6 @@
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="5dp"
android:layout_marginTop="10dp"
android:layout_marginEnd="5dp"
android:gravity="center_vertical"
android:orientation="horizontal">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/battery_monitor"
android:textStyle="bold" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="10dp"
android:text="@string/battery_monitor_tips"
android:textSize="10sp"
tools:ignore="SmallSp" />
</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/monitor_battery_status_changes"
android:textStyle="bold"
tools:ignore="RelativeOverlap" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/battery_status_changes_tips"
android:textSize="9sp"
tools:ignore="SmallSp" />
</LinearLayout>
<com.xuexiang.xui.widget.button.switchbutton.SwitchButton
android:id="@+id/sb_battery_receiver"
style="@style/SwitchButtonStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>
<LinearLayout
style="@style/settingBarStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingEnd="10dp"
tools:ignore="RtlSymmetry">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/low_power_alarm_threshold"
android:textStyle="bold" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/low_power_alarm_threshold_tips"
android:textSize="9sp"
tools:ignore="SmallSp" />
</LinearLayout>
<com.xuexiang.xui.widget.picker.XRangeSlider
android:id="@+id/xrs_battery_level_alarm"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="5dp"
android:layout_weight="1"
app:xrs_insideRangeLineColor="#0bd97f"
app:xrs_max="100"
app:xrs_min="0"
app:xrs_verticalPadding="0dp" />
<com.xuexiang.xui.widget.button.SmoothCheckBox
android:id="@+id/scb_battery_level_alarm_once"
android:layout_width="15dp"
android:layout_height="15dp"
android:layout_marginStart="5dp"
app:scb_color_checked="@color/colorPrimary" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/keep_reminding"
android:textSize="10sp"
tools:ignore="SmallSp" />
</LinearLayout>
<LinearLayout
style="@style/settingBarStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:visibility="gone">
<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/timing_report_battery_status"
android:textStyle="bold" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/timing_report_battery_status_tips"
android:textSize="9sp"
tools:ignore="SmallSp" />
<LinearLayout
android:id="@+id/layout_battery_cron"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:orientation="horizontal"
android:visibility="gone">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/first_send_time"
android:textSize="12sp"
android:textStyle="bold" />
<EditText
android:id="@+id/et_battery_cron_start_time"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:autofillHints=""
android:ems="5"
android:focusable="false"
android:maxLines="1"
android:text=""
android:textAlignment="center"
android:textSize="12sp"
tools:ignore="LabelFor,TextFields" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="10dp"
android:text="@string/interval_minutes"
android:textSize="12sp"
android:textStyle="bold" />
<EditText
android:id="@+id/et_battery_cron_interval"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:autofillHints=""
android:digits="0123456789"
android:ems="4"
android:inputType="number"
android:maxLength="4"
android:maxLines="1"
android:text=""
android:textAlignment="center"
android:textSize="12sp"
tools:ignore="LabelFor" />
</LinearLayout>
</LinearLayout>
<com.xuexiang.xui.widget.button.switchbutton.SwitchButton
android:id="@+id/sb_battery_cron"
style="@style/SwitchButtonStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"

@ -44,14 +44,14 @@
android:paddingBottom="@dimen/config_padding_5dp">
<RadioButton
android:id="@+id/rb_battery_discharging"
android:id="@+id/rb_battery_charging"
style="@style/rg_rb_style_match"
android:checked="true"
android:text="@string/battery_charging"
tools:ignore="TouchTargetSizeCheck" />
<RadioButton
android:id="@+id/rb_battery_charging"
android:id="@+id/rb_battery_discharging"
style="@style/rg_rb_style_match"
android:text="@string/battery_discharging"
tools:ignore="TouchTargetSizeCheck" />

@ -353,8 +353,6 @@
<string name="carrier_mobile" tools:ignore="Typos">Label of SIM,\neg. AT&amp;T_88888888</string>
<string name="tip_number_only_error_message">Number must be greater than 0!</string>
<string name="regexp_number_only" tools:ignore="TypographyDashes,Typos">^[1-9]?\\d+$</string>
<string name="low_power_alarm_threshold">Low Power Alarm</string>
<string name="low_power_alarm_threshold_tips">Value range: 099.\nLeft blank or 0 is disabled</string>
<string name="retry_interval">Retry Interval</string>
<string name="retry_interval_tips">Disabled when times = 0,\nthe interval is incremented</string>
<string name="filtering_duplicate_messages">Filter Duplicate Messages</string>
@ -476,10 +474,6 @@
<string name="percent">%</string>
<string name="above"><![CDATA[≥]]></string>
<string name="zero">0</string>
<string name="monitor_battery_status_changes">Monitor Battery Status Changes</string>
<string name="battery_status_changes_tips">Notify when charge status changes (charging/discharging/uncharged/full)</string>
<string name="timing_report_battery_status">Push Battery Status Regularly</string>
<string name="timing_report_battery_status_tips">Please set the daily first sending time and re-sending interval</string>
<string name="first_send_time">First time: </string>
<string name="interval_minutes">Interval(minutes): </string>
<string name="proxy_settings">Proxy Settings</string>
@ -566,8 +560,6 @@
<string name="network_state_monitor_tips">[Note] You need to manually create APP forwarding rules, package name: 77777777</string>
<string name="network_state_change_remind">Network State Change Remind</string>
<string name="network_state_change_remind_tips">Send a notification when the network status changes (connection mode/IP change)</string>
<string name="battery_monitor">Battery Monitor</string>
<string name="battery_monitor_tips">[Note] You need to manually create APP forwarding rules, package name: 88888888</string>
<string name="keep_alive">Keep Alive</string>
<string name="keep_alive_tips">It is recommended to open the first three switch, do not disable the notification bar, to avoid APP being killed</string>
<string name="custom_settings">Custom Settings</string>
@ -611,6 +603,10 @@
<string name="tag_scheme">{{SCHEME}}</string>
<string name="tag_call_type">{{CALL_TYPE}}</string>
<string name="tag_location">{{LOCATION}}</string>
<string name="tag_battery_pct">{{BATTERY_PCT}}</string>
<string name="tag_battery_status">{{BATTERY_STATUS}}</string>
<string name="tag_battery_plugged">{{BATTERY_PLUGGED}}</string>
<string name="tag_battery_info">{{BATTERY_INFO}}</string>
<string name="tag_uid">{{UID}}</string>
<string name="rule_sms">SMS</string>
<string name="rule_call">CALL</string>

@ -354,8 +354,6 @@
<string name="carrier_mobile">序号/运营商_手机号</string>
<string name="tip_number_only_error_message">数字必须大于0!</string>
<string name="regexp_number_only" tools:ignore="TypographyDashes,Typos">^[1-9]?\\d+$</string>
<string name="low_power_alarm_threshold">安全电量范围(%)</string>
<string name="low_power_alarm_threshold_tips">超出安全范围将发出预警</string>
<string name="retry_interval">请求重试机制</string>
<string name="retry_interval_tips">次数=0禁用,逐次递增</string>
<string name="filtering_duplicate_messages">自动过滤多久内重复消息</string>
@ -477,10 +475,6 @@
<string name="percent">%</string>
<string name="above">高于</string>
<string name="zero">0</string>
<string name="monitor_battery_status_changes">监听电池状态变化</string>
<string name="battery_status_changes_tips">充电状态改变(充电中/放电中/未充电/已充满)时发出通知</string>
<string name="timing_report_battery_status">定时推送电池状态</string>
<string name="timing_report_battery_status_tips">请设置每日首次发送时间与再次发送间隔时间</string>
<string name="first_send_time">首次发送时间:</string>
<string name="interval_minutes">间隔(分钟)</string>
<string name="proxy_settings">代理设置</string>
@ -567,8 +561,6 @@
<string name="network_state_monitor_tips">需要手动创建APP转发规则包名77777777</string>
<string name="network_state_change_remind">网络状态改变提醒</string>
<string name="network_state_change_remind_tips">网络状态改变(连接方式/IP变化)时发出通知</string>
<string name="battery_monitor">电池监控</string>
<string name="battery_monitor_tips">需要手动创建APP转发规则包名88888888</string>
<string name="keep_alive">保活措施</string>
<string name="keep_alive_tips">建议开启前三项授权或设置不要禁用通知栏避免APP被杀</string>
<string name="custom_settings">个性设置</string>
@ -612,6 +604,10 @@
<string name="tag_scheme">{{通知Scheme}}</string>
<string name="tag_call_type">{{通话类型}}</string>
<string name="tag_location">{{定位信息}}</string>
<string name="tag_battery_pct">{{电池电量}}</string>
<string name="tag_battery_status">{{电池状态}}</string>
<string name="tag_battery_plugged">{{充电方式}}</string>
<string name="tag_battery_info">{{电池信息}}</string>
<string name="tag_uid">{{UID}}</string>
<string name="rule_sms">短信</string>
<string name="rule_call">来电</string>
@ -974,8 +970,8 @@
<string name="battery_status_monitor">电池状态监听</string>
<string name="below_level_min">【电量预警】已低于电量预警下限,请及时充电!%s</string>
<string name="over_level_max">【电量预警】已高于电量预警上限,请拔掉充电器!%s</string>
<string name="reach_level_min">【电量预警】已低于电量预警下限,请及时充电!%s</string>
<string name="reach_level_max">【电量预警】已高于电量预警上限,请拔掉充电器!%s</string>
<string name="reach_level_min">【电量预警】已达到电量预警下限,请及时充电!%s</string>
<string name="reach_level_max">【电量预警】已达到电量预警上限,请拔掉充电器!%s</string>
<string name="battery_status_changed">【充电状态】发生变化:</string>
<string name="no_indentation_allowed_on_the_first_line">第一行不允许缩进</string>
<string name="sign_required">服务端启用签名密钥sign节点必传</string>

Loading…
Cancel
Save