新增:自动任务·快捷指令 —— 到达地点&离开地点

pull/408/head
pppscn 10 months ago
parent d8553ef793
commit 0d77eac6ce

@ -222,11 +222,14 @@
android:exported="true"
android:value="640" />
<service
android:name=".service.ForegroundService"
android:enabled="true" />
<service
android:name=".service.HttpServerService"
android:enabled="true" />
<service
android:name=".service.ForegroundService"
android:name=".service.LocationService"
android:enabled="true" />
<service
android:name=".service.NotificationService"

@ -6,6 +6,7 @@ import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.location.Geocoder
import android.net.ConnectivityManager
import android.net.wifi.WifiManager
import android.os.Build
@ -27,11 +28,13 @@ import com.idormy.sms.forwarder.receiver.LockScreenReceiver
import com.idormy.sms.forwarder.receiver.NetworkChangeReceiver
import com.idormy.sms.forwarder.service.ForegroundService
import com.idormy.sms.forwarder.service.HttpServerService
import com.idormy.sms.forwarder.service.LocationService
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.tinker.TinkerLoadLibrary
import com.king.location.LocationClient
import io.reactivex.Observable
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.Disposable
@ -74,19 +77,17 @@ class App : Application(), CactusCallback, Configuration.Provider by Core {
val isDebug: Boolean
get() = BuildConfig.DEBUG
//Cactus结束时间
val mEndDate = MutableLiveData<String>()
//Cactus上次存活时间
val mLastTimer = MutableLiveData<String>()
//Cactus存活时间
val mTimer = MutableLiveData<String>()
//Cactus运行状态
val mStatus = MutableLiveData<Boolean>().apply { value = true }
//Cactus相关
val mEndDate = MutableLiveData<String>() //结束时间
val mLastTimer = MutableLiveData<String>() //上次存活时间
val mTimer = MutableLiveData<String>() //存活时间
val mStatus = MutableLiveData<Boolean>().apply { value = true } //运行状态
var mDisposable: Disposable? = null
//Location相关
val LocationClient by lazy { LocationClient(context) }
val Geocoder by lazy { Geocoder(context) }
val DateFormat by lazy { SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault()) }
}
override fun attachBaseContext(base: Context) {
@ -116,12 +117,12 @@ class App : Application(), CactusCallback, Configuration.Provider by Core {
}
//启动前台服务
val serviceIntent = Intent(this, ForegroundService::class.java)
serviceIntent.action = "START"
val foregroundServiceIntent = Intent(this, ForegroundService::class.java)
foregroundServiceIntent.action = "START"
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
startForegroundService(serviceIntent)
startForegroundService(foregroundServiceIntent)
} else {
startService(serviceIntent)
startService(foregroundServiceIntent)
}
//启动HttpServer
@ -131,6 +132,13 @@ class App : Application(), CactusCallback, Configuration.Provider by Core {
}
}
//启动LocationService
if (SettingUtils.enableLocation) {
val locationServiceIntent = Intent(this, LocationService::class.java)
locationServiceIntent.action = "START"
startService(locationServiceIntent)
}
//监听电量&充电状态变化
val batteryReceiver = BatteryReceiver()
val batteryFilter = IntentFilter(Intent.ACTION_BATTERY_CHANGED)

@ -0,0 +1,38 @@
package com.idormy.sms.forwarder.entity.task
import com.idormy.sms.forwarder.R
import java.io.Serializable
import kotlin.math.atan2
import kotlin.math.cos
import kotlin.math.sin
import kotlin.math.sqrt
data class LocationSetting(
var description: String = "", //描述
var type: String = "to", //监控类型:"to":到达地点;"leave":离开地点
var calcType: String = "distance", //计算方式:"distance":计算距离;"address":地址匹配
var longitude: Double = 0.0, //经度
var latitude: Double = 0.0, //纬度
var distance: Double = 0.0, //距离
var address: String = "", //地址
) : Serializable {
fun getCalcTypeCheckId(): Int {
return when (calcType) {
"distance" -> R.id.rb_calc_type_distance
"address" -> R.id.rb_calc_type_address
else -> R.id.rb_calc_type_distance
}
}
fun calculateDistance(
lat1: Double, lon1: Double, lat2: Double, lon2: Double
): Double {
val earthRadius = 6371000.0 // 地球平均半径,单位:米
val latDistance = Math.toRadians(lat2 - lat1)
val lonDistance = Math.toRadians(lon2 - lon1)
val a = sin(latDistance / 2) * sin(latDistance / 2) + cos(Math.toRadians(lat1)) * cos(Math.toRadians(lat2)) * sin(lonDistance / 2) * sin(lonDistance / 2)
val c = 2 * atan2(sqrt(a), sqrt(1 - a))
return earthRadius * c
}
}

@ -2,7 +2,6 @@ package com.idormy.sms.forwarder.fragment
import android.annotation.SuppressLint
import android.content.Intent
import android.os.Build
import android.os.Environment
import android.os.Handler
import android.os.Looper
@ -21,8 +20,8 @@ import com.idormy.sms.forwarder.App
import com.idormy.sms.forwarder.R
import com.idormy.sms.forwarder.core.BaseFragment
import com.idormy.sms.forwarder.databinding.FragmentServerBinding
import com.idormy.sms.forwarder.service.ForegroundService
import com.idormy.sms.forwarder.service.HttpServerService
import com.idormy.sms.forwarder.service.LocationService
import com.idormy.sms.forwarder.utils.*
import com.xuexiang.xaop.annotation.SingleClick
import com.xuexiang.xpage.annotation.Page
@ -240,6 +239,12 @@ class ServerFragment : BaseFragment<FragmentServerBinding?>(), View.OnClickListe
binding!!.sbApiLocation.isChecked = HttpServerUtils.enableApiLocation
binding!!.sbApiLocation.setOnCheckedChangeListener { _: CompoundButton?, isChecked: Boolean ->
if (isChecked && !SettingUtils.enableLocation) {
XToastUtils.error(getString(R.string.api_location_permission_tips))
binding!!.sbApiLocation.isChecked = false
return@setOnCheckedChangeListener
}
HttpServerUtils.enableApiLocation = isChecked
if (ServiceUtils.isServiceRunning("com.idormy.sms.forwarder.service.HttpServerService")) {
Log.d("ServerFragment", "onClick: 重启服务")
@ -251,13 +256,9 @@ class ServerFragment : BaseFragment<FragmentServerBinding?>(), View.OnClickListe
refreshButtonText()
}
//重启前台服务,启动/停止定位服务
val serviceIntent = Intent(requireContext(), ForegroundService::class.java)
val serviceIntent = Intent(requireContext(), LocationService::class.java)
serviceIntent.action = "START"
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
requireContext().startForegroundService(serviceIntent)
} else {
requireContext().startService(serviceIntent)
}
requireContext().startService(serviceIntent)
}
}

@ -6,6 +6,7 @@ import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.location.Criteria
import android.net.Uri
import android.os.Build
import android.provider.Settings
@ -34,6 +35,7 @@ import com.idormy.sms.forwarder.databinding.FragmentSettingsBinding
import com.idormy.sms.forwarder.entity.SimInfo
import com.idormy.sms.forwarder.receiver.BootCompletedReceiver
import com.idormy.sms.forwarder.service.ForegroundService
import com.idormy.sms.forwarder.service.LocationService
import com.idormy.sms.forwarder.utils.*
import com.idormy.sms.forwarder.workers.LoadAppListWorker
import com.jeremyliao.liveeventbus.LiveEventBus
@ -162,7 +164,7 @@ class SettingsFragment : BaseFragment<FragmentSettingsBinding?>(), View.OnClickL
switchDirectlyToTask(binding!!.sbDirectlyToTask)
//启用 {{定位信息}} 标签
switchEnableLocationTag(binding!!.sbEnableLocationTag)
switchEnableLocation(binding!!.sbEnableLocation, binding!!.rgAccuracy, binding!!.rgPowerRequirement, binding!!.xsbMinInterval, binding!!.xsbMinDistance)
}
override fun onResume() {
@ -885,15 +887,16 @@ class SettingsFragment : BaseFragment<FragmentSettingsBinding?>(), View.OnClickL
}
}
//启用 {{定位信息}} 标签
private fun switchEnableLocationTag(@SuppressLint("UseSwitchCompatOrMaterialCode") switchEnableLocationTag: SwitchButton) {
switchEnableLocationTag.isChecked = SettingUtils.enableLocationTag
switchEnableLocationTag.setOnCheckedChangeListener { _: CompoundButton?, isChecked: Boolean ->
SettingUtils.enableLocationTag = isChecked
//启用定位功能
private fun switchEnableLocation(@SuppressLint("UseSwitchCompatOrMaterialCode") switchEnableLocation: SwitchButton, rgAccuracy: RadioGroup, rgPowerRequirement: RadioGroup, xsbMinInterval: XSeekBar, xsbMinDistance: XSeekBar) {
//是否启用定位功能
switchEnableLocation.isChecked = SettingUtils.enableLocation
switchEnableLocation.setOnCheckedChangeListener { _: CompoundButton?, isChecked: Boolean ->
SettingUtils.enableLocation = isChecked
if (isChecked) {
XXPermissions.with(this).permission(Permission.ACCESS_COARSE_LOCATION).permission(Permission.ACCESS_FINE_LOCATION).permission(Permission.ACCESS_BACKGROUND_LOCATION).request(object : OnPermissionCallback {
override fun onGranted(permissions: List<String>, all: Boolean) {
restartForegroundService()
restartLocationService()
}
override fun onDenied(permissions: List<String>, never: Boolean) {
@ -904,26 +907,72 @@ class SettingsFragment : BaseFragment<FragmentSettingsBinding?>(), View.OnClickL
} else {
XToastUtils.error(R.string.toast_denied)
}
SettingUtils.enableLocationTag = false
switchEnableLocationTag.isChecked = false
restartForegroundService()
SettingUtils.enableLocation = false
switchEnableLocation.isChecked = false
restartLocationService()
}
})
} else {
restartForegroundService()
restartLocationService()
}
}
//设置位置精度:高精度(默认)
rgAccuracy.check(
when (SettingUtils.locationAccuracy) {
Criteria.ACCURACY_FINE -> R.id.rb_accuracy_fine
Criteria.ACCURACY_COARSE -> R.id.rb_accuracy_coarse
Criteria.NO_REQUIREMENT -> R.id.rb_accuracy_no_requirement
else -> R.id.rb_accuracy_fine
}
)
rgAccuracy.setOnCheckedChangeListener { _: RadioGroup?, checkedId: Int ->
SettingUtils.locationAccuracy = when (checkedId) {
R.id.rb_accuracy_fine -> Criteria.ACCURACY_FINE
R.id.rb_accuracy_coarse -> Criteria.ACCURACY_COARSE
R.id.rb_accuracy_no_requirement -> Criteria.NO_REQUIREMENT
else -> Criteria.ACCURACY_FINE
}
restartLocationService()
}
//设置电量消耗:低电耗(默认)
rgPowerRequirement.check(
when (SettingUtils.locationPowerRequirement) {
Criteria.POWER_HIGH -> R.id.rb_power_requirement_high
Criteria.POWER_MEDIUM -> R.id.rb_power_requirement_medium
Criteria.POWER_LOW -> R.id.rb_power_requirement_low
Criteria.NO_REQUIREMENT -> R.id.rb_power_requirement_no_requirement
else -> R.id.rb_power_requirement_low
}
)
rgPowerRequirement.setOnCheckedChangeListener { _: RadioGroup?, checkedId: Int ->
SettingUtils.locationPowerRequirement = when (checkedId) {
R.id.rb_power_requirement_high -> Criteria.POWER_HIGH
R.id.rb_power_requirement_medium -> Criteria.POWER_MEDIUM
R.id.rb_power_requirement_low -> Criteria.POWER_LOW
R.id.rb_power_requirement_no_requirement -> Criteria.NO_REQUIREMENT
else -> Criteria.POWER_LOW
}
restartLocationService()
}
//设置位置更新最小时间间隔(单位:毫秒); 默认间隔10000毫秒最小间隔1000毫秒
xsbMinInterval.setDefaultValue((SettingUtils.locationMinInterval / 1000).toInt())
xsbMinInterval.setOnSeekBarListener { _: XSeekBar?, newValue: Int ->
SettingUtils.locationMinInterval = newValue * 1000L
restartLocationService()
}
//设置位置更新最小距离单位默认距离0米
xsbMinDistance.setDefaultValue(SettingUtils.locationMinDistance)
xsbMinDistance.setOnSeekBarListener { _: XSeekBar?, newValue: Int ->
SettingUtils.locationMinDistance = newValue
restartLocationService()
}
}
//重启前台服务
private fun restartForegroundService() {
val serviceIntent = Intent(requireContext(), ForegroundService::class.java)
//重启定位服务
private fun restartLocationService() {
val serviceIntent = Intent(requireContext(), LocationService::class.java)
serviceIntent.action = "START"
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
requireContext().startForegroundService(serviceIntent)
} else {
requireContext().startService(serviceIntent)
}
requireContext().startService(serviceIntent)
}
//获取当前手机品牌

@ -355,20 +355,26 @@ class TasksEditFragment : BaseFragment<FragmentTasksEditBinding?>(), View.OnClic
Log.d(TAG, "onItemClick: $widgetInfo")
//判断点击的是条件还是动作
if (widgetInfo.classPath.contains(".condition.")) {
val typeCondition = pos + KEY_BACK_CODE_CONDITION
//判断是否已经添加过该类型条件
for (item in itemListConditions) {
//注意TASK_CONDITION_XXX 枚举值 等于 TASK_CONDITION_FRAGMENT_LIST 索引加上 KEY_BACK_CODE_CONDITION不可改变
if (item.type == pos + KEY_BACK_CODE_CONDITION) {
XToastUtils.error("已经添加过该类型条件")
if (item.type == typeCondition) {
XToastUtils.error(getString(R.string.condition_already_exists))
return
}
if ((typeCondition == TASK_CONDITION_TO_ADDRESS || typeCondition == TASK_CONDITION_LEAVE_ADDRESS) && (item.type == TASK_CONDITION_TO_ADDRESS || item.type == TASK_CONDITION_LEAVE_ADDRESS)) {
XToastUtils.error(getString(R.string.only_one_location_condition))
return
}
}
} else {
val typeAction = pos + KEY_BACK_CODE_ACTION
//判断是否已经添加过该类型动作
for (item in itemListActions) {
//注意TASK_ACTION_XXX 枚举值 等于 TASK_ACTION_FRAGMENT_LIST 索引加上 KEY_BACK_CODE_ACTION不可改变
if (item.type == pos + KEY_BACK_CODE_ACTION) {
XToastUtils.error("已经添加过该类型动作")
if (item.type == typeAction) {
XToastUtils.error(getString(R.string.action_already_exists))
return
}
}

@ -0,0 +1,175 @@
package com.idormy.sms.forwarder.fragment.condition
import android.annotation.SuppressLint
import android.content.Intent
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import com.google.gson.Gson
import com.idormy.sms.forwarder.R
import com.idormy.sms.forwarder.core.BaseFragment
import com.idormy.sms.forwarder.databinding.FragmentTasksConditionLeaveAddressBinding
import com.idormy.sms.forwarder.entity.task.LocationSetting
import com.idormy.sms.forwarder.utils.KEY_BACK_DATA_CONDITION
import com.idormy.sms.forwarder.utils.KEY_BACK_DESCRIPTION_CONDITION
import com.idormy.sms.forwarder.utils.KEY_EVENT_DATA_CONDITION
import com.idormy.sms.forwarder.utils.KEY_TEST_CONDITION
import com.idormy.sms.forwarder.utils.TASK_CONDITION_LEAVE_ADDRESS
import com.idormy.sms.forwarder.utils.XToastUtils
import com.jeremyliao.liveeventbus.LiveEventBus
import com.xuexiang.xaop.annotation.SingleClick
import com.xuexiang.xpage.annotation.Page
import com.xuexiang.xrouter.annotation.AutoWired
import com.xuexiang.xrouter.launcher.XRouter
import com.xuexiang.xui.utils.CountDownButtonHelper
import com.xuexiang.xui.widget.actionbar.TitleBar
@Page(name = "LeaveAddress")
@Suppress("PrivatePropertyName")
class LeaveAddressFragment : BaseFragment<FragmentTasksConditionLeaveAddressBinding?>(), View.OnClickListener {
private val TAG: String = LeaveAddressFragment::class.java.simpleName
var titleBar: TitleBar? = null
private var mCountDownHelper: CountDownButtonHelper? = null
@JvmField
@AutoWired(name = KEY_EVENT_DATA_CONDITION)
var eventData: String? = null
private var description = ""
override fun initArgs() {
XRouter.getInstance().inject(this)
}
override fun viewBindingInflate(
inflater: LayoutInflater,
container: ViewGroup,
): FragmentTasksConditionLeaveAddressBinding {
return FragmentTasksConditionLeaveAddressBinding.inflate(inflater, container, false)
}
override fun initTitle(): TitleBar? {
titleBar = super.initTitle()!!.setImmersive(false).setTitle(R.string.task_leave_address)
return titleBar
}
/**
* 初始化控件
*/
override fun initViews() {
//测试按钮增加倒计时,避免重复点击
mCountDownHelper = CountDownButtonHelper(binding!!.btnTest, 3)
mCountDownHelper!!.setOnCountDownListener(object : CountDownButtonHelper.OnCountDownListener {
override fun onCountDown(time: Int) {
binding!!.btnTest.text = String.format(getString(R.string.seconds_n), time)
}
override fun onFinished() {
binding!!.btnTest.text = getString(R.string.test)
}
})
binding!!.rgCalcType.setOnCheckedChangeListener { _, checkedId ->
if (checkedId == R.id.rb_calc_type_distance) {
binding!!.layoutCalcTypeDistance.visibility = View.VISIBLE
binding!!.layoutCalcTypeAddress.visibility = View.GONE
} else {
binding!!.layoutCalcTypeDistance.visibility = View.GONE
binding!!.layoutCalcTypeAddress.visibility = View.VISIBLE
}
}
Log.d(TAG, "initViews eventData:$eventData")
if (eventData != null) {
val settingVo = Gson().fromJson(eventData, LocationSetting::class.java)
Log.d(TAG, "initViews settingVo:$settingVo")
binding!!.rgCalcType.check(settingVo.getCalcTypeCheckId())
binding!!.etLongitude.setText(settingVo.longitude.toString())
binding!!.etLatitude.setText(settingVo.latitude.toString())
binding!!.etDistance.setText(settingVo.distance.toString())
binding!!.etAddress.setText(settingVo.address)
}
}
@SuppressLint("SetTextI18n")
override fun initListeners() {
binding!!.btnTest.setOnClickListener(this)
binding!!.btnDel.setOnClickListener(this)
binding!!.btnSave.setOnClickListener(this)
LiveEventBus.get(KEY_TEST_CONDITION, String::class.java).observe(this) {
mCountDownHelper?.finish()
if (it == "success") {
XToastUtils.success("测试通过", 30000)
} else {
XToastUtils.error(it, 30000)
}
}
}
@SingleClick
override fun onClick(v: View) {
try {
when (v.id) {
R.id.btn_test -> {
mCountDownHelper?.start()
Thread {
try {
val settingVo = checkSetting()
Log.d(TAG, settingVo.toString())
LiveEventBus.get(KEY_TEST_CONDITION, String::class.java).post("success")
} catch (e: Exception) {
LiveEventBus.get(KEY_TEST_CONDITION, String::class.java).post(e.message.toString())
e.printStackTrace()
}
}.start()
return
}
R.id.btn_del -> {
popToBack()
return
}
R.id.btn_save -> {
val settingVo = checkSetting()
val intent = Intent()
intent.putExtra(KEY_BACK_DESCRIPTION_CONDITION, description)
intent.putExtra(KEY_BACK_DATA_CONDITION, Gson().toJson(settingVo))
setFragmentResult(TASK_CONDITION_LEAVE_ADDRESS, intent)
popToBack()
return
}
}
} catch (e: Exception) {
XToastUtils.error(e.message.toString(), 30000)
e.printStackTrace()
}
}
//检查设置
@SuppressLint("SetTextI18n")
private fun checkSetting(): LocationSetting {
val longitude = binding!!.etLongitude.text.toString().toDouble()
val latitude = binding!!.etLatitude.text.toString().toDouble()
val distance = binding!!.etDistance.text.toString().toDouble()
val address = binding!!.etAddress.text.toString()
var calcType = "distance"
if (binding!!.rbCalcTypeDistance.isChecked) {
if (latitude.isNaN() || longitude.isNaN() || distance.isNaN()) {
throw Exception(getString(R.string.calc_type_address_error))
}
description = String.format(getString(R.string.leave_address_distance_description), longitude, latitude, distance)
} else if (binding!!.rbCalcTypeAddress.isChecked) {
if (address.isEmpty()) {
throw Exception(getString(R.string.calc_type_address_error))
}
description = String.format(getString(R.string.leave_address_keyword_description), address)
calcType = "address"
}
return LocationSetting(description, "leave", calcType, longitude, latitude, distance, address)
}
}

@ -0,0 +1,175 @@
package com.idormy.sms.forwarder.fragment.condition
import android.annotation.SuppressLint
import android.content.Intent
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import com.google.gson.Gson
import com.idormy.sms.forwarder.R
import com.idormy.sms.forwarder.core.BaseFragment
import com.idormy.sms.forwarder.databinding.FragmentTasksConditionToAddressBinding
import com.idormy.sms.forwarder.entity.task.LocationSetting
import com.idormy.sms.forwarder.utils.KEY_BACK_DATA_CONDITION
import com.idormy.sms.forwarder.utils.KEY_BACK_DESCRIPTION_CONDITION
import com.idormy.sms.forwarder.utils.KEY_EVENT_DATA_CONDITION
import com.idormy.sms.forwarder.utils.KEY_TEST_CONDITION
import com.idormy.sms.forwarder.utils.TASK_CONDITION_TO_ADDRESS
import com.idormy.sms.forwarder.utils.XToastUtils
import com.jeremyliao.liveeventbus.LiveEventBus
import com.xuexiang.xaop.annotation.SingleClick
import com.xuexiang.xpage.annotation.Page
import com.xuexiang.xrouter.annotation.AutoWired
import com.xuexiang.xrouter.launcher.XRouter
import com.xuexiang.xui.utils.CountDownButtonHelper
import com.xuexiang.xui.widget.actionbar.TitleBar
@Page(name = "ToAddress")
@Suppress("PrivatePropertyName")
class ToAddressFragment : BaseFragment<FragmentTasksConditionToAddressBinding?>(), View.OnClickListener {
private val TAG: String = ToAddressFragment::class.java.simpleName
var titleBar: TitleBar? = null
private var mCountDownHelper: CountDownButtonHelper? = null
@JvmField
@AutoWired(name = KEY_EVENT_DATA_CONDITION)
var eventData: String? = null
private var description = ""
override fun initArgs() {
XRouter.getInstance().inject(this)
}
override fun viewBindingInflate(
inflater: LayoutInflater,
container: ViewGroup,
): FragmentTasksConditionToAddressBinding {
return FragmentTasksConditionToAddressBinding.inflate(inflater, container, false)
}
override fun initTitle(): TitleBar? {
titleBar = super.initTitle()!!.setImmersive(false).setTitle(R.string.task_to_address)
return titleBar
}
/**
* 初始化控件
*/
override fun initViews() {
//测试按钮增加倒计时,避免重复点击
mCountDownHelper = CountDownButtonHelper(binding!!.btnTest, 3)
mCountDownHelper!!.setOnCountDownListener(object : CountDownButtonHelper.OnCountDownListener {
override fun onCountDown(time: Int) {
binding!!.btnTest.text = String.format(getString(R.string.seconds_n), time)
}
override fun onFinished() {
binding!!.btnTest.text = getString(R.string.test)
}
})
binding!!.rgCalcType.setOnCheckedChangeListener { _, checkedId ->
if (checkedId == R.id.rb_calc_type_distance) {
binding!!.layoutCalcTypeDistance.visibility = View.VISIBLE
binding!!.layoutCalcTypeAddress.visibility = View.GONE
} else {
binding!!.layoutCalcTypeDistance.visibility = View.GONE
binding!!.layoutCalcTypeAddress.visibility = View.VISIBLE
}
}
Log.d(TAG, "initViews eventData:$eventData")
if (eventData != null) {
val settingVo = Gson().fromJson(eventData, LocationSetting::class.java)
Log.d(TAG, "initViews settingVo:$settingVo")
binding!!.rgCalcType.check(settingVo.getCalcTypeCheckId())
binding!!.etLongitude.setText(settingVo.longitude.toString())
binding!!.etLatitude.setText(settingVo.latitude.toString())
binding!!.etDistance.setText(settingVo.distance.toString())
binding!!.etAddress.setText(settingVo.address)
}
}
@SuppressLint("SetTextI18n")
override fun initListeners() {
binding!!.btnTest.setOnClickListener(this)
binding!!.btnDel.setOnClickListener(this)
binding!!.btnSave.setOnClickListener(this)
LiveEventBus.get(KEY_TEST_CONDITION, String::class.java).observe(this) {
mCountDownHelper?.finish()
if (it == "success") {
XToastUtils.success("测试通过", 30000)
} else {
XToastUtils.error(it, 30000)
}
}
}
@SingleClick
override fun onClick(v: View) {
try {
when (v.id) {
R.id.btn_test -> {
mCountDownHelper?.start()
Thread {
try {
val settingVo = checkSetting()
Log.d(TAG, settingVo.toString())
LiveEventBus.get(KEY_TEST_CONDITION, String::class.java).post("success")
} catch (e: Exception) {
LiveEventBus.get(KEY_TEST_CONDITION, String::class.java).post(e.message.toString())
e.printStackTrace()
}
}.start()
return
}
R.id.btn_del -> {
popToBack()
return
}
R.id.btn_save -> {
val settingVo = checkSetting()
val intent = Intent()
intent.putExtra(KEY_BACK_DESCRIPTION_CONDITION, description)
intent.putExtra(KEY_BACK_DATA_CONDITION, Gson().toJson(settingVo))
setFragmentResult(TASK_CONDITION_TO_ADDRESS, intent)
popToBack()
return
}
}
} catch (e: Exception) {
XToastUtils.error(e.message.toString(), 30000)
e.printStackTrace()
}
}
//检查设置
@SuppressLint("SetTextI18n")
private fun checkSetting(): LocationSetting {
val longitude = binding!!.etLongitude.text.toString().toDouble()
val latitude = binding!!.etLatitude.text.toString().toDouble()
val distance = binding!!.etDistance.text.toString().toDouble()
val address = binding!!.etAddress.text.toString()
var calcType = "distance"
if (binding!!.rbCalcTypeDistance.isChecked) {
if (latitude.isNaN() || longitude.isNaN() || distance.isNaN()) {
throw Exception(getString(R.string.calc_type_address_error))
}
description = String.format(getString(R.string.to_address_distance_description), longitude, latitude, distance)
} else if (binding!!.rbCalcTypeAddress.isChecked) {
if (address.isEmpty()) {
throw Exception(getString(R.string.calc_type_address_error))
}
description = String.format(getString(R.string.to_address_keyword_description), address)
calcType = "address"
}
return LocationSetting(description, "to", calcType, longitude, latitude, distance, address)
}
}

@ -5,9 +5,6 @@ import android.app.*
import android.content.Intent
import android.graphics.BitmapFactory
import android.graphics.Color
import android.location.Criteria
import android.location.Geocoder
import android.location.Location
import android.os.Build
import android.os.IBinder
import android.text.TextUtils
@ -20,16 +17,10 @@ import com.idormy.sms.forwarder.App
import com.idormy.sms.forwarder.R
import com.idormy.sms.forwarder.activity.MainActivity
import com.idormy.sms.forwarder.database.AppDatabase
import com.idormy.sms.forwarder.entity.LocationInfo
import com.idormy.sms.forwarder.utils.*
import com.idormy.sms.forwarder.utils.task.CronJobScheduler
import com.idormy.sms.forwarder.workers.LoadAppListWorker
import com.jeremyliao.liveeventbus.LiveEventBus
import com.king.location.LocationClient
import com.king.location.LocationErrorCode
import com.king.location.OnExceptionListener
import com.king.location.OnLocationListener
import com.xuexiang.xaop.util.PermissionUtils
import com.xuexiang.xutil.XUtil
import com.xuexiang.xutil.file.FileUtils
import frpclib.Frpclib
@ -42,8 +33,6 @@ import io.reactivex.schedulers.Schedulers
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.async
import java.text.SimpleDateFormat
import java.util.Date
@SuppressLint("SimpleDateFormat")
@Suppress("PrivatePropertyName", "DeferredResultUnused", "OPT_IN_USAGE", "DEPRECATION", "LiftReturnOrAssignment")
@ -81,10 +70,6 @@ class ForegroundService : Service() {
})
}
private val locationClient by lazy { LocationClient(App.context) }
private val geocoder by lazy { Geocoder(App.context) }
private val simpleDateFormat by lazy { SimpleDateFormat("yyyy-MM-dd HH:mm:ss") }
companion object {
var isRunning = false
}
@ -181,70 +166,6 @@ class ForegroundService : Service() {
}
}
//远程找手机 TODO: 判断权限 ACCESS_COARSE_LOCATION ACCESS_FINE_LOCATION
if ((SettingUtils.enableLocationTag || HttpServerUtils.enableApiLocation)
&& PermissionUtils.isGranted(android.Manifest.permission.ACCESS_COARSE_LOCATION, android.Manifest.permission.ACCESS_FINE_LOCATION)
) {
//可根据具体需求设置定位配置参数(这里只列出一些主要的参数)
val locationOption = locationClient.getLocationOption().setAccuracy(Criteria.ACCURACY_FINE)//设置位置精度:高精度
.setPowerRequirement(Criteria.POWER_LOW) //设置电量消耗:低电耗
.setMinTime(10000)//设置位置更新最小时间间隔(单位:毫秒); 默认间隔10000毫秒最小间隔1000毫秒
.setMinDistance(0)//设置位置更新最小距离单位默认距离0米
.setOnceLocation(false)//设置是否只定位一次,默认为 false当设置为 true 时,则只定位一次后,会自动停止定位
.setLastKnownLocation(false)//设置是否获取最后一次缓存的已知位置,默认为 true
//设置定位配置参数
locationClient.setLocationOption(locationOption)
locationClient.startLocation()
//设置定位监听
locationClient.setOnLocationListener(object : OnLocationListener() {
override fun onLocationChanged(location: Location) {
//位置信息
Log.d(TAG, "onLocationChanged(location = ${location})")
val locationInfo = LocationInfo(
location.longitude, location.latitude, "", simpleDateFormat.format(Date(location.time)), location.provider.toString()
)
//根据坐标经纬度获取位置地址信息WGS-84坐标系
val list = geocoder.getFromLocation(location.latitude, location.longitude, 1)
if (list?.isNotEmpty() == true) {
locationInfo.address = list[0].getAddressLine(0)
}
Log.d(TAG, "locationInfo = $locationInfo")
HttpServerUtils.apiLocationCache = locationInfo
}
override fun onProviderEnabled(provider: String) {
super.onProviderEnabled(provider)
Log.d(TAG, "onProviderEnabled(provider = ${provider})")
}
override fun onProviderDisabled(provider: String) {
super.onProviderDisabled(provider)
Log.d(TAG, "onProviderDisabled(provider = ${provider})")
}
})
//设置异常监听
locationClient.setOnExceptionListener(object : OnExceptionListener {
override fun onException(@LocationErrorCode errorCode: Int, e: Exception) {
//定位出现异常
Log.w(TAG, "onException(errorCode = ${errorCode}, e = ${e})")
}
})
if (locationClient.isStarted()) {//如果已经开始定位,则先停止定位
locationClient.stopLocation()
}
locationClient.startLocation()
} else if ((!SettingUtils.enableLocationTag && !HttpServerUtils.enableApiLocation) && locationClient.isStarted()) {
Log.d(TAG, "stopLocation")
locationClient.stopLocation()
}
isRunning = true
} catch (e: Exception) {
e.printStackTrace()
@ -255,11 +176,6 @@ class ForegroundService : Service() {
private fun stopForegroundService() {
try {
//如果已经开始定位,则先停止定位
if ((SettingUtils.enableLocationTag || HttpServerUtils.enableApiLocation) && locationClient.isStarted()) {
locationClient.stopLocation()
}
stopForeground(true)
stopSelf()
compositeDisposable.dispose()

@ -0,0 +1,170 @@
package com.idormy.sms.forwarder.service
import android.annotation.SuppressLint
import android.app.Service
import android.content.Intent
import android.location.Location
import android.os.IBinder
import android.util.Log
import com.idormy.sms.forwarder.App
import com.idormy.sms.forwarder.entity.LocationInfo
import com.idormy.sms.forwarder.utils.HttpServerUtils
import com.idormy.sms.forwarder.utils.SettingUtils
import com.king.location.LocationErrorCode
import com.king.location.OnExceptionListener
import com.king.location.OnLocationListener
import com.xuexiang.xaop.util.PermissionUtils
import com.yanzhenjie.andserver.Server
import java.util.Date
@SuppressLint("SimpleDateFormat")
@Suppress("PrivatePropertyName", "DEPRECATION")
class LocationService : Service(), Server.ServerListener {
private val TAG: String = LocationService::class.java.simpleName
companion object {
var isRunning = false
}
override fun onBind(p0: Intent?): IBinder? {
return null
}
override fun onCreate() {
Log.i(TAG, "onCreate: ")
super.onCreate()
if (!SettingUtils.enableLocation) return
startForegroundService()
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
Log.i(TAG, "onStartCommand: ")
if (intent != null) {
when (intent.action) {
"START" -> {
startForegroundService()
}
"STOP" -> {
stopForegroundService()
}
}
}
return super.onStartCommand(intent, flags, startId)
}
override fun onDestroy() {
Log.i(TAG, "onDestroy: ")
super.onDestroy()
if (!SettingUtils.enableLocation) return
stopForegroundService()
}
override fun onException(e: Exception?) {
Log.i(TAG, "onException: ")
}
override fun onStarted() {
Log.i(TAG, "onStarted: ")
}
override fun onStopped() {
Log.i(TAG, "onStopped: ")
}
private fun startForegroundService() {
try {
//远程找手机 TODO: 判断权限 ACCESS_COARSE_LOCATION ACCESS_FINE_LOCATION
if (SettingUtils.enableLocation && PermissionUtils.isGranted(android.Manifest.permission.ACCESS_COARSE_LOCATION, android.Manifest.permission.ACCESS_FINE_LOCATION)) {
//可根据具体需求设置定位配置参数(这里只列出一些主要的参数)
val locationOption = App.LocationClient.getLocationOption().setAccuracy(SettingUtils.locationAccuracy)//设置位置精度:高精度
.setPowerRequirement(SettingUtils.locationPowerRequirement) //设置电量消耗:低电耗
.setMinTime(SettingUtils.locationMinInterval)//设置位置更新最小时间间隔(单位:毫秒); 默认间隔10000毫秒最小间隔1000毫秒
.setMinDistance(SettingUtils.locationMinDistance)//设置位置更新最小距离单位默认距离0米
.setOnceLocation(false)//设置是否只定位一次,默认为 false当设置为 true 时,则只定位一次后,会自动停止定位
.setLastKnownLocation(false)//设置是否获取最后一次缓存的已知位置,默认为 true
//设置定位配置参数
App.LocationClient.setLocationOption(locationOption)
App.LocationClient.startLocation()
//设置定位监听
App.LocationClient.setOnLocationListener(object : OnLocationListener() {
override fun onLocationChanged(location: Location) {
//位置信息
Log.d(TAG, "onLocationChanged(location = ${location})")
val locationInfo = LocationInfo(
location.longitude, location.latitude, "", App.DateFormat.format(Date(location.time)), location.provider.toString()
)
//根据坐标经纬度获取位置地址信息WGS-84坐标系
val list = App.Geocoder.getFromLocation(location.latitude, location.longitude, 1)
if (list?.isNotEmpty() == true) {
locationInfo.address = list[0].getAddressLine(0)
}
Log.d(TAG, "locationInfo = $locationInfo")
HttpServerUtils.apiLocationCache = locationInfo
//TODO: 触发自动任务
}
override fun onProviderEnabled(provider: String) {
super.onProviderEnabled(provider)
Log.d(TAG, "onProviderEnabled(provider = ${provider})")
}
override fun onProviderDisabled(provider: String) {
super.onProviderDisabled(provider)
Log.d(TAG, "onProviderDisabled(provider = ${provider})")
}
})
//设置异常监听
App.LocationClient.setOnExceptionListener(object : OnExceptionListener {
override fun onException(@LocationErrorCode errorCode: Int, e: Exception) {
//定位出现异常
Log.w(TAG, "onException(errorCode = ${errorCode}, e = ${e})")
//TODO: 重启定位
App.LocationClient.startLocation()
}
})
if (App.LocationClient.isStarted()) {//如果已经开始定位,则先停止定位
App.LocationClient.stopLocation()
}
App.LocationClient.startLocation()
isRunning = true
} else if (!SettingUtils.enableLocation && App.LocationClient.isStarted()) {
Log.d(TAG, "stopLocation")
App.LocationClient.stopLocation()
isRunning = false
}
} catch (e: Exception) {
e.printStackTrace()
isRunning = false
}
}
private fun stopForegroundService() {
isRunning = try {
//如果已经开始定位,则先停止定位
if (SettingUtils.enableLocation && App.LocationClient.isStarted()) {
App.LocationClient.stopLocation()
}
stopForeground(true)
stopSelf()
false
} catch (e: Exception) {
e.printStackTrace()
true
}
}
}

@ -85,7 +85,11 @@ const val SP_SMS_TEMPLATE = "sms_template"
const val SP_ENABLE_HELP_TIP = "enable_help_tip"
const val SP_PURE_CLIENT_MODE = "enable_pure_client_mode"
const val SP_PURE_TASK_MODE = "enable_pure_task_mode"
const val SP_LOCATION_TAG = "enable_location_tag"
const val SP_LOCATION = "enable_location"
const val SP_LOCATION_ACCURACY = "location_accuracy"
const val SP_LOCATION_POWER_REQUIREMENT = "location_power_requirement"
const val SP_LOCATION_MIN_INTERVAL = "location_min_interval_time"
const val SP_LOCATION_MIN_DISTANCE = "location_min_distance"
const val SP_ENABLE_CACTUS = "enable_cactus"
const val CACTUS_TIMER = "cactus_timer"
@ -515,14 +519,14 @@ var TASK_CONDITION_FRAGMENT_LIST = listOf(
R.drawable.auto_task_icon_custom_time,
),
PageInfo(
getString(R.string.to_address),
getString(R.string.task_to_address),
"com.idormy.sms.forwarder.fragment.condition.ToAddressFragment",
"{\"\":\"\"}",
CoreAnim.slide,
R.drawable.auto_task_icon_to_address,
),
PageInfo(
getString(R.string.leave_address),
getString(R.string.task_leave_address),
"com.idormy.sms.forwarder.fragment.condition.LeaveAddressFragment",
"{\"\":\"\"}",
CoreAnim.slide,

@ -1,5 +1,6 @@
package com.idormy.sms.forwarder.utils
import android.location.Criteria
import com.idormy.sms.forwarder.R
import com.xuexiang.xui.utils.ResUtils.getString
@ -128,8 +129,20 @@ class SettingUtils private constructor() {
//是否纯任务模式
var enablePureTaskMode: Boolean by SharedPreference(SP_PURE_TASK_MODE, false)
//是否启用定位标签
var enableLocationTag: Boolean by SharedPreference(SP_LOCATION_TAG, false)
//是否启用定位功能
var enableLocation: Boolean by SharedPreference(SP_LOCATION, false)
//设置位置精度:高精度
var locationAccuracy: Int by SharedPreference(SP_LOCATION_ACCURACY, Criteria.ACCURACY_FINE)
//设置电量消耗:低电耗
var locationPowerRequirement: Int by SharedPreference(SP_LOCATION_POWER_REQUIREMENT, Criteria.POWER_LOW)
//设置位置更新最小时间间隔(单位:毫秒); 默认间隔10000毫秒最小间隔1000毫秒
var locationMinInterval: Long by SharedPreference(SP_LOCATION_MIN_INTERVAL, 10000L)
//设置位置更新最小距离单位默认距离0米
var locationMinDistance: Int by SharedPreference(SP_LOCATION_MIN_DISTANCE, 0)
}

@ -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 LocationWorker(context: Context, params: WorkerParameters) : CoroutineWorker(context, params) {
private val TAG: String = LocationWorker::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(conditionType)
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(conditionType)
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.description)
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()
}
}
}
}

@ -347,34 +347,188 @@
<LinearLayout
style="@style/settingBarStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content">
android:layout_height="wrap_content"
android:orientation="vertical">
<LinearLayout
android:layout_width="0dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical">
android:orientation="horizontal">
<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/enable_location"
android:textStyle="bold"
tools:ignore="RelativeOverlap" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/enable_location_tips"
android:textSize="9sp"
tools:ignore="SmallSp" />
</LinearLayout>
<com.xuexiang.xui.widget.button.switchbutton.SwitchButton
android:id="@+id/sb_enable_location"
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"
android:gravity="center_vertical"
android:orientation="horizontal">
<TextView
android:layout_width="match_parent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/enable_location_tag"
android:text="@string/accuracy"
android:textSize="10sp"
android:textStyle="bold"
tools:ignore="RelativeOverlap" />
tools:ignore="SmallSp" />
<RadioGroup
android:id="@+id/rg_accuracy"
style="@style/rg_style"
android:layout_marginStart="5dp"
android:orientation="horizontal">
<RadioButton
android:id="@+id/rb_accuracy_fine"
style="@style/rg_rb_style_wrap"
android:checked="true"
android:text="@string/accuracy_fine" />
<RadioButton
android:id="@+id/rb_accuracy_coarse"
style="@style/rg_rb_style_wrap"
android:text="@string/accuracy_coarse" />
<RadioButton
android:id="@+id/rb_accuracy_no_requirement"
style="@style/rg_rb_style_wrap"
android:text="@string/no_requirement" />
</RadioGroup>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:orientation="horizontal">
<TextView
android:layout_width="match_parent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/enable_location_tag_tips"
android:textSize="9sp"
android:text="@string/power_requirement"
android:textSize="10sp"
android:textStyle="bold"
tools:ignore="SmallSp" />
<RadioGroup
android:id="@+id/rg_power_requirement"
style="@style/rg_style"
android:layout_marginStart="5dp"
android:orientation="horizontal">
<RadioButton
android:id="@+id/rb_power_requirement_low"
style="@style/rg_rb_style_wrap"
android:checked="true"
android:text="@string/power_requirement_low" />
<RadioButton
android:id="@+id/rb_power_requirement_medium"
style="@style/rg_rb_style_wrap"
android:text="@string/power_requirement_medium" />
<RadioButton
android:id="@+id/rb_power_requirement_high"
style="@style/rg_rb_style_wrap"
android:text="@string/power_requirement_high" />
<RadioButton
android:id="@+id/rb_power_requirement_no_requirement"
style="@style/rg_rb_style_wrap"
android:text="@string/no_requirement" />
</RadioGroup>
</LinearLayout>
<com.xuexiang.xui.widget.button.switchbutton.SwitchButton
android:id="@+id/sb_enable_location_tag"
style="@style/SwitchButtonStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginEnd="@dimen/config_margin_10dp"
android:gravity="center_vertical"
android:orientation="horizontal">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/min_interval"
android:textSize="10sp"
android:textStyle="bold"
tools:ignore="SmallSp" />
<com.xuexiang.xui.widget.picker.XSeekBar
android:id="@+id/xsb_min_interval"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="5dp"
android:layout_weight="1"
app:xsb_max="120"
app:xsb_min="1" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="5dp"
android:text="@string/seconds"
android:textSize="12sp"
android:textStyle="bold" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/config_margin_10dp"
android:text="@string/min_distance"
android:textSize="10sp"
android:textStyle="bold"
tools:ignore="SmallSp" />
<com.xuexiang.xui.widget.picker.XSeekBar
android:id="@+id/xsb_min_distance"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="5dp"
android:layout_weight="1"
app:xsb_max="120"
app:xsb_min="0" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="5dp"
android:text="@string/seconds"
android:textSize="12sp"
android:textStyle="bold" />
</LinearLayout>
</LinearLayout>

@ -0,0 +1,256 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?attr/xui_config_color_background"
android:orientation="vertical">
<androidx.core.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:overScrollMode="never">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="5dp"
android:gravity="center_horizontal"
android:orientation="vertical">
<androidx.appcompat.widget.AppCompatImageView
android:layout_width="120dp"
android:layout_height="120dp"
android:layout_margin="10dp"
android:contentDescription="@string/task_leave_address"
app:srcCompat="@drawable/auto_task_icon_leave_address"
tools:ignore="ImageContrastCheck" />
<LinearLayout
style="@style/taskBarStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/task_leave_address"
android:textStyle="bold" />
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginTop="5dp"
android:layout_marginBottom="5dp"
android:background="?attr/xui_config_color_separator_light" />
<RadioGroup
android:id="@+id/rg_calc_type"
style="@style/rg_style"
android:orientation="vertical"
android:paddingBottom="@dimen/config_padding_5dp">
<RadioButton
android:id="@+id/rb_calc_type_distance"
style="@style/rg_rb_style_match"
android:checked="true"
android:text="@string/calc_type_distance"
tools:ignore="TouchTargetSizeCheck" />
<LinearLayout
android:id="@+id/layout_calc_type_distance"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/config_margin_10dp"
android:layout_marginEnd="@dimen/config_margin_10dp"
android:gravity="center_vertical"
android:orientation="horizontal">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:text="@string/longitude" />
<EditText
android:id="@+id/et_longitude"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginStart="5dp"
android:layout_weight="1"
android:importantForAutofill="no"
android:inputType="numberDecimal"
android:textAlignment="center"
tools:ignore="LabelFor,RtlCompat,SpeakableTextPresentCheck,TextContrastCheck,TextFields,TouchTargetSizeCheck" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginStart="@dimen/config_margin_10dp"
android:text="@string/latitude" />
<EditText
android:id="@+id/et_latitude"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginStart="5dp"
android:layout_weight="1"
android:importantForAutofill="no"
android:inputType="numberDecimal"
android:textAlignment="center"
tools:ignore="LabelFor,RtlCompat,SpeakableTextPresentCheck,TextContrastCheck,TextFields,TouchTargetSizeCheck" />
<com.xuexiang.xui.widget.button.shadowbutton.RippleShadowShadowButton
android:id="@+id/btn_current_coordinates"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="5dp"
android:gravity="center"
android:padding="5dp"
android:text="@string/current_coordinates"
android:textColor="@color/white"
android:textSize="10sp"
app:sb_color_unpressed="@color/colorPrimary"
app:sb_ripple_color="@color/white"
app:sb_ripple_duration="500"
app:sb_shape_type="rectangle"
tools:ignore="SmallSp,TextContrastCheck,TouchTargetSizeCheck" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/config_margin_10dp"
android:layout_marginEnd="@dimen/config_margin_10dp"
android:gravity="center_vertical"
android:orientation="horizontal">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:text="@string/distance1" />
<EditText
android:id="@+id/et_distance"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginStart="5dp"
android:layout_weight="1"
android:importantForAutofill="no"
android:inputType="numberDecimal"
android:textAlignment="center"
tools:ignore="LabelFor,RtlCompat,SpeakableTextPresentCheck,TextContrastCheck,TextFields,TouchTargetSizeCheck" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:text="@string/distance2" />
</LinearLayout>
</LinearLayout>
<RadioButton
android:id="@+id/rb_calc_type_address"
style="@style/rg_rb_style_match"
android:text="@string/calc_type_address"
tools:ignore="TouchTargetSizeCheck" />
<LinearLayout
android:id="@+id/layout_calc_type_address"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/config_margin_10dp"
android:layout_marginEnd="@dimen/config_margin_10dp"
android:gravity="center_vertical"
android:orientation="horizontal">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:text="@string/keyword_leave_address_1" />
<EditText
android:id="@+id/et_address"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginStart="5dp"
android:layout_weight="1"
android:hint="@string/keyword"
android:importantForAutofill="no"
android:textAlignment="center"
tools:ignore="RtlCompat,TextContrastCheck,TextFields,TouchTargetSizeCheck" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:text="@string/keyword_leave_address_2" />
</LinearLayout>
</RadioGroup>
</LinearLayout>
</LinearLayout>
</androidx.core.widget.NestedScrollView>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:orientation="horizontal"
android:padding="10dp">
<com.xuexiang.xui.widget.textview.supertextview.SuperButton
android:id="@+id/btn_del"
style="@style/SuperButton.Gray.Icon"
android:drawableStart="@drawable/icon_delete"
android:paddingStart="15dp"
android:text="@string/discard"
android:textSize="11sp"
tools:ignore="RtlSymmetry,TextContrastCheck,TouchTargetSizeCheck" />
<com.xuexiang.xui.widget.textview.supertextview.SuperButton
android:id="@+id/btn_save"
style="@style/SuperButton.Blue.Icon"
android:layout_marginStart="10dp"
android:drawableStart="@drawable/icon_save"
android:paddingStart="15dp"
android:text="@string/submit"
android:textSize="11sp"
tools:ignore="RtlSymmetry,TextContrastCheck,TouchTargetSizeCheck" />
<com.xuexiang.xui.widget.textview.supertextview.SuperButton
android:id="@+id/btn_test"
style="@style/SuperButton.Green.Icon"
android:layout_marginStart="10dp"
android:drawableStart="@drawable/icon_test"
android:paddingStart="15dp"
android:text="@string/test"
android:textSize="11sp"
android:visibility="gone"
tools:ignore="RtlSymmetry" />
</LinearLayout>
</LinearLayout>

@ -0,0 +1,256 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?attr/xui_config_color_background"
android:orientation="vertical">
<androidx.core.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:overScrollMode="never">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="5dp"
android:gravity="center_horizontal"
android:orientation="vertical">
<androidx.appcompat.widget.AppCompatImageView
android:layout_width="120dp"
android:layout_height="120dp"
android:layout_margin="10dp"
android:contentDescription="@string/task_to_address"
app:srcCompat="@drawable/auto_task_icon_to_address"
tools:ignore="ImageContrastCheck" />
<LinearLayout
style="@style/taskBarStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/task_to_address"
android:textStyle="bold" />
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginTop="5dp"
android:layout_marginBottom="5dp"
android:background="?attr/xui_config_color_separator_light" />
<RadioGroup
android:id="@+id/rg_calc_type"
style="@style/rg_style"
android:orientation="vertical"
android:paddingBottom="@dimen/config_padding_5dp">
<RadioButton
android:id="@+id/rb_calc_type_distance"
style="@style/rg_rb_style_match"
android:checked="true"
android:text="@string/calc_type_distance"
tools:ignore="TouchTargetSizeCheck" />
<LinearLayout
android:id="@+id/layout_calc_type_distance"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/config_margin_10dp"
android:layout_marginEnd="@dimen/config_margin_10dp"
android:gravity="center_vertical"
android:orientation="horizontal">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:text="@string/longitude" />
<EditText
android:id="@+id/et_longitude"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginStart="5dp"
android:layout_weight="1"
android:importantForAutofill="no"
android:inputType="numberDecimal"
android:textAlignment="center"
tools:ignore="LabelFor,RtlCompat,SpeakableTextPresentCheck,TextContrastCheck,TextFields,TouchTargetSizeCheck" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginStart="@dimen/config_margin_10dp"
android:text="@string/latitude" />
<EditText
android:id="@+id/et_latitude"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginStart="5dp"
android:layout_weight="1"
android:importantForAutofill="no"
android:inputType="numberDecimal"
android:textAlignment="center"
tools:ignore="LabelFor,RtlCompat,SpeakableTextPresentCheck,TextContrastCheck,TextFields,TouchTargetSizeCheck" />
<com.xuexiang.xui.widget.button.shadowbutton.RippleShadowShadowButton
android:id="@+id/btn_current_coordinates"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="5dp"
android:gravity="center"
android:padding="5dp"
android:text="@string/current_coordinates"
android:textColor="@color/white"
android:textSize="10sp"
app:sb_color_unpressed="@color/colorPrimary"
app:sb_ripple_color="@color/white"
app:sb_ripple_duration="500"
app:sb_shape_type="rectangle"
tools:ignore="SmallSp,TextContrastCheck,TouchTargetSizeCheck" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/config_margin_10dp"
android:layout_marginEnd="@dimen/config_margin_10dp"
android:gravity="center_vertical"
android:orientation="horizontal">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:text="@string/distance1" />
<EditText
android:id="@+id/et_distance"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginStart="5dp"
android:layout_weight="1"
android:importantForAutofill="no"
android:inputType="numberDecimal"
android:textAlignment="center"
tools:ignore="LabelFor,RtlCompat,SpeakableTextPresentCheck,TextContrastCheck,TextFields,TouchTargetSizeCheck" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:text="@string/distance2" />
</LinearLayout>
</LinearLayout>
<RadioButton
android:id="@+id/rb_calc_type_address"
style="@style/rg_rb_style_match"
android:text="@string/calc_type_address"
tools:ignore="TouchTargetSizeCheck" />
<LinearLayout
android:id="@+id/layout_calc_type_address"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/config_margin_10dp"
android:layout_marginEnd="@dimen/config_margin_10dp"
android:gravity="center_vertical"
android:orientation="horizontal">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:text="@string/keyword_to_address_1" />
<EditText
android:id="@+id/et_address"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginStart="5dp"
android:layout_weight="1"
android:hint="@string/keyword"
android:importantForAutofill="no"
android:textAlignment="center"
tools:ignore="RtlCompat,TextContrastCheck,TextFields,TouchTargetSizeCheck" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:text="@string/keyword_to_address_2" />
</LinearLayout>
</RadioGroup>
</LinearLayout>
</LinearLayout>
</androidx.core.widget.NestedScrollView>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:orientation="horizontal"
android:padding="10dp">
<com.xuexiang.xui.widget.textview.supertextview.SuperButton
android:id="@+id/btn_del"
style="@style/SuperButton.Gray.Icon"
android:drawableStart="@drawable/icon_delete"
android:paddingStart="15dp"
android:text="@string/discard"
android:textSize="11sp"
tools:ignore="RtlSymmetry,TextContrastCheck,TouchTargetSizeCheck" />
<com.xuexiang.xui.widget.textview.supertextview.SuperButton
android:id="@+id/btn_save"
style="@style/SuperButton.Blue.Icon"
android:layout_marginStart="10dp"
android:drawableStart="@drawable/icon_save"
android:paddingStart="15dp"
android:text="@string/submit"
android:textSize="11sp"
tools:ignore="RtlSymmetry,TextContrastCheck,TouchTargetSizeCheck" />
<com.xuexiang.xui.widget.textview.supertextview.SuperButton
android:id="@+id/btn_test"
style="@style/SuperButton.Green.Icon"
android:layout_marginStart="10dp"
android:drawableStart="@drawable/icon_test"
android:paddingStart="15dp"
android:text="@string/test"
android:textSize="11sp"
android:visibility="gone"
tools:ignore="RtlSymmetry" />
</LinearLayout>
</LinearLayout>

@ -895,6 +895,7 @@
<string name="api_wol_tips">Turn on your Wake-On-LAN enabled devices remotely</string>
<string name="api_location">Location</string>
<string name="api_location_tips">Remote query mobile phone location</string>
<string name="api_location_permission_tips">\'Enable Location Function\' in the \'Settings\' first.</string>
<string name="location_longitude">Longitude%s</string>
<string name="location_latitude">Latitude%s</string>
<string name="location_address">Address%s</string>
@ -1085,8 +1086,19 @@
<string name="ipv4">IPv4</string>
<string name="ipv6">IPv6</string>
<string name="enable_location_tag">Enable {{LOCATION}} Tag</string>
<string name="enable_location_tag_tips">Insert location info into forwarded msg.</string>
<string name="enable_location">Enable Location Function</string>
<string name="enable_location_tips">Used for locating the phone, {{LOCATION}} tag.</string>
<string name="accuracy">Accuracy</string>
<string name="accuracy_fine">Fine</string>
<string name="accuracy_coarse">Coarse</string>
<string name="no_requirement">No Requirement</string>
<string name="power_requirement">Power Requirement</string>
<string name="power_requirement_low">Low</string>
<string name="power_requirement_medium">Medium</string>
<string name="power_requirement_high">High</string>
<string name="min_interval">Min Interval For Update</string>
<string name="min_distance">Min Distance</string>
<string name="meter">m</string>
<string name="uid">UID</string>
<string name="task_name_status">Name/Status</string>
@ -1116,8 +1128,8 @@
<string name="select_task_action">Please select action</string>
<string name="bottom_sheet_close">Close</string>
<string name="task_cron">Cron</string>
<string name="to_address">To Address</string>
<string name="leave_address">Leave Address</string>
<string name="task_to_address">To Address</string>
<string name="task_leave_address">Leave Address</string>
<string name="task_network">Network</string>
<string name="task_sim">SIM Status</string>
<string name="task_battery">Battery</string>
@ -1206,4 +1218,27 @@
<string name="time_after_screen_on">Time After Screen On (Minutes)</string>
<string name="time_after_screen_on_description">%sAfter Screen On</string>
<string name="duration_minute">%s minutes </string>
<string name="calc_type_distance">Calculate distance based on GPS coordinates.</string>
<string name="calc_type_address">Determine based on address keywords.</string>
<string name="longitude">Longitude</string>
<string name="latitude">Latitude</string>
<string name="distance">Distance</string>
<string name="distance1">Create an e-fence:</string>
<string name="distance2">m radius</string>
<string name="current_coordinates">Current</string>
<string name="keyword">Keyword</string>
<string name="keyword_to_address_1">GPS address contains</string>
<string name="keyword_to_address_2"> = arrived</string>
<string name="keyword_leave_address_1">GPS address NOT contains</string>
<string name="keyword_leave_address_2"> = leaved</string>
<string name="calc_type_distance_error">Latitude and longitude or distance cannot be empty.</string>
<string name="calc_type_address_error">Address keyword cannot be empty.</string>
<string name="to_address_distance_description">Around longitude %s, latitude %s, within a %s-meter radius.</string>
<string name="to_address_keyword_description">GPS address contains %s means arrival.</string>
<string name="leave_address_distance_description">Leave around longitude %s, latitude %s, within a %s-meter radius.</string>
<string name="leave_address_keyword_description">GPS address NOT contains %s means leaved.</string>
<string name="condition_already_exists">This type of condition already exists.</string>
<string name="action_already_exists">This type of action already exists.</string>
<string name="only_one_location_condition">Only one condition, either "To Address" or "Leave Address" can be added.</string>
</resources>

@ -896,6 +896,7 @@
<string name="api_wol_tips">远程打开启用LAN唤醒功能(Wake-On-LAN)的设备</string>
<string name="api_location">远程找手机</string>
<string name="api_location_tips">远程查询手机定位,方便找回手机/防止老少走丢</string>
<string name="api_location_permission_tips">请先在【通用设置】中【启用GPS定位功能】</string>
<string name="location_longitude">经度:%s</string>
<string name="location_latitude">维度:%s</string>
<string name="location_address">地址:%s</string>
@ -1086,8 +1087,19 @@
<string name="ipv4">IPv4</string>
<string name="ipv6">IPv6</string>
<string name="enable_location_tag">启用 {{定位信息}} 标签</string>
<string name="enable_location_tag_tips">在转发信息中插入手机的当前定位信息</string>
<string name="enable_location">启用GPS定位功能</string>
<string name="enable_location_tips">用于支持 查找手机、{{定位信息}}标签 功能</string>
<string name="accuracy">位置精度</string>
<string name="accuracy_fine">精确位置</string>
<string name="accuracy_coarse">模糊位置</string>
<string name="no_requirement">不要求</string>
<string name="power_requirement">电量消耗</string>
<string name="power_requirement_low"></string>
<string name="power_requirement_medium"></string>
<string name="power_requirement_high"></string>
<string name="min_interval">位置更新最小间隔</string>
<string name="min_distance">最小距离</string>
<string name="meter"></string>
<string name="uid">UID</string>
<string name="task_name_status">任务名称/状态</string>
@ -1117,8 +1129,8 @@
<string name="select_task_action">请选择动作</string>
<string name="bottom_sheet_close">关 闭</string>
<string name="task_cron">定时任务</string>
<string name="to_address">到达地点</string>
<string name="leave_address">离开地点</string>
<string name="task_to_address">到达地点</string>
<string name="task_leave_address">离开地点</string>
<string name="task_network">网络状态</string>
<string name="task_sim">SIM卡状态</string>
<string name="task_battery">电量使用</string>
@ -1207,4 +1219,27 @@
<string name="time_after_screen_on">屏幕解锁后多长时间(分钟)</string>
<string name="time_after_screen_on_description">屏幕解锁%s后</string>
<string name="duration_minute">%s分钟</string>
<string name="calc_type_distance">根据GPS坐标计算距离</string>
<string name="calc_type_address">根据地址关键字判断</string>
<string name="longitude">经度</string>
<string name="latitude">维度</string>
<string name="distance">距离</string>
<string name="distance1">以经纬度为中心,</string>
<string name="distance2">米半径建立电子围栏</string>
<string name="current_coordinates">当前坐标</string>
<string name="keyword">关键字</string>
<string name="keyword_to_address_1">当前GPS地址包含</string>
<string name="keyword_to_address_2">则表示到达</string>
<string name="keyword_leave_address_1">当前GPS地址不包含</string>
<string name="keyword_leave_address_2">则表示离开</string>
<string name="calc_type_distance_error">经纬度或距离都不能为空</string>
<string name="calc_type_address_error">地址关键字不能为空</string>
<string name="to_address_distance_description">进入以经度:%s维度%s 为中心,%s 米半径的区域</string>
<string name="to_address_keyword_description">进入GPS地址包含 %s 关键字区域</string>
<string name="leave_address_distance_description">离开以经度:%s维度%s 为中心,%s 米半径的区域</string>
<string name="leave_address_keyword_description">离开GPS地址包含 %s 关键字区域</string>
<string name="condition_already_exists">已经添加过该类型条件</string>
<string name="action_already_exists">已经添加过该类型动作</string>
<string name="only_one_location_condition">只能添加一个 进入地点 或 离开地址 类型条件</string>
</resources>

Loading…
Cancel
Save