From 0d77eac6ce03a8c9f90b19a3689138b185fa695a Mon Sep 17 00:00:00 2001 From: pppscn <35696959@qq.com> Date: Fri, 15 Dec 2023 18:53:20 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9E=EF=BC=9A=E8=87=AA=E5=8A=A8?= =?UTF-8?q?=E4=BB=BB=E5=8A=A1=C2=B7=E5=BF=AB=E6=8D=B7=E6=8C=87=E4=BB=A4=20?= =?UTF-8?q?=E2=80=94=E2=80=94=20=E5=88=B0=E8=BE=BE=E5=9C=B0=E7=82=B9&?= =?UTF-8?q?=E7=A6=BB=E5=BC=80=E5=9C=B0=E7=82=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/src/main/AndroidManifest.xml | 5 +- .../main/java/com/idormy/sms/forwarder/App.kt | 40 +-- .../forwarder/entity/task/LocationSetting.kt | 38 +++ .../sms/forwarder/fragment/ServerFragment.kt | 17 +- .../forwarder/fragment/SettingsFragment.kt | 87 ++++-- .../forwarder/fragment/TasksEditFragment.kt | 14 +- .../condition/LeaveAddressFragment.kt | 175 ++++++++++++ .../fragment/condition/ToAddressFragment.kt | 175 ++++++++++++ .../forwarder/service/ForegroundService.kt | 84 ------ .../sms/forwarder/service/LocationService.kt | 170 ++++++++++++ .../idormy/sms/forwarder/utils/Constants.kt | 10 +- .../sms/forwarder/utils/SettingUtils.kt | 17 +- .../sms/forwarder/workers/LocationWorker.kt | 149 ++++++++++ app/src/main/res/layout/fragment_settings.xml | 194 +++++++++++-- ...fragment_tasks_condition_leave_address.xml | 256 ++++++++++++++++++ .../fragment_tasks_condition_to_address.xml | 256 ++++++++++++++++++ app/src/main/res/values-en/strings.xml | 43 ++- app/src/main/res/values/strings.xml | 43 ++- 18 files changed, 1608 insertions(+), 165 deletions(-) create mode 100644 app/src/main/java/com/idormy/sms/forwarder/entity/task/LocationSetting.kt create mode 100644 app/src/main/java/com/idormy/sms/forwarder/fragment/condition/LeaveAddressFragment.kt create mode 100644 app/src/main/java/com/idormy/sms/forwarder/fragment/condition/ToAddressFragment.kt create mode 100644 app/src/main/java/com/idormy/sms/forwarder/service/LocationService.kt create mode 100644 app/src/main/java/com/idormy/sms/forwarder/workers/LocationWorker.kt create mode 100644 app/src/main/res/layout/fragment_tasks_condition_leave_address.xml create mode 100644 app/src/main/res/layout/fragment_tasks_condition_to_address.xml 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 关键字区域 + 已经添加过该类型条件 + 已经添加过该类型动作 + 只能添加一个 进入地点 或 离开地址 类型条件