diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index eff8f18c..8c65937d 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -222,11 +222,14 @@
android:exported="true"
android:value="640" />
+
()
-
- //Cactus上次存活时间
- val mLastTimer = MutableLiveData()
-
- //Cactus存活时间
- val mTimer = MutableLiveData()
-
- //Cactus运行状态
- val mStatus = MutableLiveData().apply { value = true }
-
+ //Cactus相关
+ val mEndDate = MutableLiveData() //结束时间
+ val mLastTimer = MutableLiveData() //上次存活时间
+ val mTimer = MutableLiveData() //存活时间
+ val mStatus = MutableLiveData().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)
diff --git a/app/src/main/java/com/idormy/sms/forwarder/entity/task/LocationSetting.kt b/app/src/main/java/com/idormy/sms/forwarder/entity/task/LocationSetting.kt
new file mode 100644
index 00000000..e1f33af0
--- /dev/null
+++ b/app/src/main/java/com/idormy/sms/forwarder/entity/task/LocationSetting.kt
@@ -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
+ }
+}
diff --git a/app/src/main/java/com/idormy/sms/forwarder/fragment/ServerFragment.kt b/app/src/main/java/com/idormy/sms/forwarder/fragment/ServerFragment.kt
index 002b9a0e..94b18db3 100644
--- a/app/src/main/java/com/idormy/sms/forwarder/fragment/ServerFragment.kt
+++ b/app/src/main/java/com/idormy/sms/forwarder/fragment/ServerFragment.kt
@@ -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(), 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(), 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)
}
}
diff --git a/app/src/main/java/com/idormy/sms/forwarder/fragment/SettingsFragment.kt b/app/src/main/java/com/idormy/sms/forwarder/fragment/SettingsFragment.kt
index e8d06b7c..c644b944 100644
--- a/app/src/main/java/com/idormy/sms/forwarder/fragment/SettingsFragment.kt
+++ b/app/src/main/java/com/idormy/sms/forwarder/fragment/SettingsFragment.kt
@@ -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(), 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(), 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, all: Boolean) {
- restartForegroundService()
+ restartLocationService()
}
override fun onDenied(permissions: List, never: Boolean) {
@@ -904,26 +907,72 @@ class SettingsFragment : BaseFragment(), 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)
}
//获取当前手机品牌
diff --git a/app/src/main/java/com/idormy/sms/forwarder/fragment/TasksEditFragment.kt b/app/src/main/java/com/idormy/sms/forwarder/fragment/TasksEditFragment.kt
index e93acbbb..b12ec106 100644
--- a/app/src/main/java/com/idormy/sms/forwarder/fragment/TasksEditFragment.kt
+++ b/app/src/main/java/com/idormy/sms/forwarder/fragment/TasksEditFragment.kt
@@ -355,20 +355,26 @@ class TasksEditFragment : BaseFragment(), 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
}
}
diff --git a/app/src/main/java/com/idormy/sms/forwarder/fragment/condition/LeaveAddressFragment.kt b/app/src/main/java/com/idormy/sms/forwarder/fragment/condition/LeaveAddressFragment.kt
new file mode 100644
index 00000000..a1d471a0
--- /dev/null
+++ b/app/src/main/java/com/idormy/sms/forwarder/fragment/condition/LeaveAddressFragment.kt
@@ -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(), 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)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/idormy/sms/forwarder/fragment/condition/ToAddressFragment.kt b/app/src/main/java/com/idormy/sms/forwarder/fragment/condition/ToAddressFragment.kt
new file mode 100644
index 00000000..98691348
--- /dev/null
+++ b/app/src/main/java/com/idormy/sms/forwarder/fragment/condition/ToAddressFragment.kt
@@ -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(), 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)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/idormy/sms/forwarder/service/ForegroundService.kt b/app/src/main/java/com/idormy/sms/forwarder/service/ForegroundService.kt
index d12bb923..1e539e94 100644
--- a/app/src/main/java/com/idormy/sms/forwarder/service/ForegroundService.kt
+++ b/app/src/main/java/com/idormy/sms/forwarder/service/ForegroundService.kt
@@ -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()
diff --git a/app/src/main/java/com/idormy/sms/forwarder/service/LocationService.kt b/app/src/main/java/com/idormy/sms/forwarder/service/LocationService.kt
new file mode 100644
index 00000000..c85183f3
--- /dev/null
+++ b/app/src/main/java/com/idormy/sms/forwarder/service/LocationService.kt
@@ -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
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/idormy/sms/forwarder/utils/Constants.kt b/app/src/main/java/com/idormy/sms/forwarder/utils/Constants.kt
index 8b7edba3..e31f5eac 100644
--- a/app/src/main/java/com/idormy/sms/forwarder/utils/Constants.kt
+++ b/app/src/main/java/com/idormy/sms/forwarder/utils/Constants.kt
@@ -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,
diff --git a/app/src/main/java/com/idormy/sms/forwarder/utils/SettingUtils.kt b/app/src/main/java/com/idormy/sms/forwarder/utils/SettingUtils.kt
index df57f851..68e0c2e4 100644
--- a/app/src/main/java/com/idormy/sms/forwarder/utils/SettingUtils.kt
+++ b/app/src/main/java/com/idormy/sms/forwarder/utils/SettingUtils.kt
@@ -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)
}
diff --git a/app/src/main/java/com/idormy/sms/forwarder/workers/LocationWorker.kt b/app/src/main/java/com/idormy/sms/forwarder/workers/LocationWorker.kt
new file mode 100644
index 00000000..f3a1a3b5
--- /dev/null
+++ b/app/src/main/java/com/idormy/sms/forwarder/workers/LocationWorker.kt
@@ -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::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().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::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().setInputData(actionData).build()
+ WorkManager.getInstance().enqueue(actionRequest)
+ }
+
+ return Result.success()
+ }
+
+ else -> {
+ Log.d(TAG, "conditionType is $conditionType")
+ return Result.failure()
+ }
+ }
+
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_settings.xml b/app/src/main/res/layout/fragment_settings.xml
index 83469959..1e4a6db4 100644
--- a/app/src/main/res/layout/fragment_settings.xml
+++ b/app/src/main/res/layout/fragment_settings.xml
@@ -347,34 +347,188 @@
+ android:layout_height="wrap_content"
+ android:orientation="vertical">
+ android:orientation="horizontal">
-
+ android:layout_weight="1"
+ android:orientation="vertical">
+
+
+
+
+
+
+
+
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/fragment_tasks_condition_leave_address.xml b/app/src/main/res/layout/fragment_tasks_condition_leave_address.xml
new file mode 100644
index 00000000..63610496
--- /dev/null
+++ b/app/src/main/res/layout/fragment_tasks_condition_leave_address.xml
@@ -0,0 +1,256 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_tasks_condition_to_address.xml b/app/src/main/res/layout/fragment_tasks_condition_to_address.xml
new file mode 100644
index 00000000..542c09a6
--- /dev/null
+++ b/app/src/main/res/layout/fragment_tasks_condition_to_address.xml
@@ -0,0 +1,256 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values-en/strings.xml b/app/src/main/res/values-en/strings.xml
index 4c4f8271..5eaf7eb6 100644
--- a/app/src/main/res/values-en/strings.xml
+++ b/app/src/main/res/values-en/strings.xml
@@ -895,6 +895,7 @@
Turn on your Wake-On-LAN enabled devices remotely
Location
Remote query mobile phone location
+ \'Enable Location Function\' in the \'Settings\' first.
Longitude:%s
Latitude:%s
Address:%s
@@ -1085,8 +1086,19 @@
IPv4
IPv6
- Enable {{LOCATION}} Tag
- Insert location info into forwarded msg.
+ Enable Location Function
+ Used for locating the phone, {{LOCATION}} tag.
+ Accuracy
+ Fine
+ Coarse
+ No Requirement
+ Power Requirement
+ Low
+ Medium
+ High
+ Min Interval For Update
+ Min Distance
+ m
UID
Name/Status
@@ -1116,8 +1128,8 @@
Please select action
Close
Cron
- To Address
- Leave Address
+ To Address
+ Leave Address
Network
SIM Status
Battery
@@ -1206,4 +1218,27 @@
Time After Screen On (Minutes)
%sAfter Screen On
%s minutes
+
+ Calculate distance based on GPS coordinates.
+ Determine based on address keywords.
+ Longitude
+ Latitude
+ Distance
+ Create an e-fence:
+ m radius
+ Current
+ Keyword
+ GPS address contains
+ = arrived
+ GPS address NOT contains
+ = leaved
+ Latitude and longitude or distance cannot be empty.
+ Address keyword cannot be empty.
+ Around longitude %s, latitude %s, within a %s-meter radius.
+ GPS address contains %s means arrival.
+ Leave around longitude %s, latitude %s, within a %s-meter radius.
+ GPS address NOT contains %s means leaved.
+ This type of condition already exists.
+ This type of action already exists.
+ Only one condition, either "To Address" or "Leave Address" can be added.
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index c58606ec..1bd0e78a 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -896,6 +896,7 @@
远程打开启用LAN唤醒功能(Wake-On-LAN)的设备
远程找手机
远程查询手机定位,方便找回手机/防止老少走丢
+ 请先在【通用设置】中【启用GPS定位功能】
经度:%s
维度:%s
地址:%s
@@ -1086,8 +1087,19 @@
IPv4
IPv6
- 启用 {{定位信息}} 标签
- 在转发信息中插入手机的当前定位信息
+ 启用GPS定位功能
+ 用于支持 查找手机、{{定位信息}}标签 功能
+ 位置精度
+ 精确位置
+ 模糊位置
+ 不要求
+ 电量消耗
+ 低
+ 中
+ 高
+ 位置更新最小间隔
+ 最小距离
+ 米
UID
任务名称/状态
@@ -1117,8 +1129,8 @@
请选择动作
关 闭
定时任务
- 到达地点
- 离开地点
+ 到达地点
+ 离开地点
网络状态
SIM卡状态
电量使用
@@ -1207,4 +1219,27 @@
屏幕解锁后多长时间(分钟)
屏幕解锁%s后
%s分钟
+
+ 根据GPS坐标计算距离
+ 根据地址关键字判断
+ 经度
+ 维度
+ 距离
+ 以经纬度为中心,
+ 米半径建立电子围栏
+ 当前坐标
+ 关键字
+ 当前GPS地址包含
+ 则表示到达
+ 当前GPS地址不包含
+ 则表示离开
+ 经纬度或距离都不能为空
+ 地址关键字不能为空
+ 进入以经度:%s,维度:%s 为中心,%s 米半径的区域
+ 进入GPS地址包含 %s 关键字区域
+ 离开以经度:%s,维度:%s 为中心,%s 米半径的区域
+ 离开GPS地址包含 %s 关键字区域
+ 已经添加过该类型条件
+ 已经添加过该类型动作
+ 只能添加一个 进入地点 或 离开地址 类型条件