From dd798e42cc104078cafc93dd598b7d9b48695bf5 Mon Sep 17 00:00:00 2001
From: pppscn <35696959@qq.com>
Date: Tue, 14 Feb 2023 10:04:18 +0800
Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9E=EF=BC=9A=E7=9B=91=E5=90=AC?=
=?UTF-8?q?=E7=BD=91=E7=BB=9C=E7=8A=B6=E6=80=81=E5=8F=98=E5=8C=96=E6=8F=90?=
=?UTF-8?q?=E9=86=92=EF=BC=88APP=E9=80=9A=E7=9F=A5=E8=BD=AC=E5=8F=91?=
=?UTF-8?q?=EF=BC=8C=E5=8C=85=E5=90=8D=EF=BC=9A77777777=EF=BC=89=20#259=20?=
=?UTF-8?q?=E4=BC=98=E5=8C=96=EF=BC=9A=E5=B7=B2=E5=AE=89=E8=A3=85App?=
=?UTF-8?q?=E4=BF=A1=E6=81=AF=E5=88=97=E8=A1=A8=E5=BC=82=E6=AD=A5=E5=8A=A0?=
=?UTF-8?q?=E8=BD=BD=E6=9C=BA=E5=88=B6?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
app/src/main/AndroidManifest.xml | 3 +
.../main/java/com/idormy/sms/forwarder/App.kt | 34 +--
.../idormy/sms/forwarder/entity/CloneInfo.kt | 2 +
.../sms/forwarder/fragment/AppListFragment.kt | 253 +++++++++---------
.../forwarder/fragment/RulesEditFragment.kt | 101 ++++---
.../forwarder/fragment/SettingsFragment.kt | 166 ++++++------
.../sms/forwarder/service/BatteryService.kt | 22 +-
.../forwarder/service/ForegroundService.kt | 10 +
.../forwarder/service/NetworkStateService.kt | 126 +++++++++
.../idormy/sms/forwarder/utils/CommonUtils.kt | 18 +-
.../idormy/sms/forwarder/utils/Constants.kt | 4 +
.../sms/forwarder/utils/HttpServerUtils.kt | 2 +
.../sms/forwarder/utils/SettingUtils.kt | 3 +
.../forwarder/workers/LoadAppListWorker.kt | 47 ++++
app/src/main/res/layout/fragment_settings.xml | 97 +++++--
app/src/main/res/values-en/strings.xml | 8 +
app/src/main/res/values/strings.xml | 8 +
17 files changed, 586 insertions(+), 318 deletions(-)
create mode 100644 app/src/main/java/com/idormy/sms/forwarder/service/NetworkStateService.kt
create mode 100644 app/src/main/java/com/idormy/sms/forwarder/workers/LoadAppListWorker.kt
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index dea96fcf..cb804787 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -202,6 +202,9 @@
+
diff --git a/app/src/main/java/com/idormy/sms/forwarder/App.kt b/app/src/main/java/com/idormy/sms/forwarder/App.kt
index 281cdcd6..74e71ee4 100644
--- a/app/src/main/java/com/idormy/sms/forwarder/App.kt
+++ b/app/src/main/java/com/idormy/sms/forwarder/App.kt
@@ -23,6 +23,7 @@ import com.idormy.sms.forwarder.receiver.CactusReceiver
import com.idormy.sms.forwarder.service.BatteryService
import com.idormy.sms.forwarder.service.ForegroundService
import com.idormy.sms.forwarder.service.HttpService
+import com.idormy.sms.forwarder.service.NetworkStateService
import com.idormy.sms.forwarder.utils.*
import com.idormy.sms.forwarder.utils.sdkinit.UMengInit
import com.idormy.sms.forwarder.utils.sdkinit.XBasicLibInit
@@ -60,6 +61,7 @@ class App : Application(), CactusCallback, Configuration.Provider by Core {
var SimInfoList: MutableMap = mutableMapOf()
//已安装App信息
+ var LoadingAppList = false
var UserAppList: MutableList = mutableListOf()
var SystemAppList: MutableList = mutableListOf()
@@ -118,38 +120,14 @@ class App : Application(), CactusCallback, Configuration.Provider by Core {
startService(intent)
}
+ //网络状态监听
+ val networkStateServiceIntent = Intent(this, NetworkStateService::class.java)
+ startService(networkStateServiceIntent)
+
//电池状态监听
val batteryServiceIntent = Intent(this, BatteryService::class.java)
startService(batteryServiceIntent)
- //异步获取所有已安装 App 信息
- if (SettingUtils.enableLoadAppList) {
- val enableLoadUserAppList = SettingUtils.enableLoadUserAppList
- val enableLoadSystemAppList = SettingUtils.enableLoadSystemAppList
- val get = GlobalScope.async(Dispatchers.IO) {
- val appInfoList = AppUtils.getAppsInfo()
- for (appInfo in appInfoList) {
- if (appInfo.isSystem && enableLoadSystemAppList) {
- SystemAppList.add(appInfo)
- } else if (enableLoadUserAppList) {
- UserAppList.add(appInfo)
- }
- }
- UserAppList.sortBy { appInfo -> appInfo.name }
- SystemAppList.sortBy { appInfo -> appInfo.name }
- }
- GlobalScope.launch(Dispatchers.Main) {
- runCatching {
- get.await()
- Log.d("GlobalScope", "AppUtils.getAppsInfo() Done")
- //Log.d("GlobalScope", "UserAppList = $UserAppList")
- //Log.d("GlobalScope", "SystemAppList = $SystemAppList")
- }.onFailure {
- //Log.e("GlobalScope", it.message.toString())
- }
- }
- }
-
//启动HttpServer
if (HttpServerUtils.enableServerAutorun) {
startService(Intent(this, HttpService::class.java))
diff --git a/app/src/main/java/com/idormy/sms/forwarder/entity/CloneInfo.kt b/app/src/main/java/com/idormy/sms/forwarder/entity/CloneInfo.kt
index e77ff87c..39cc854f 100644
--- a/app/src/main/java/com/idormy/sms/forwarder/entity/CloneInfo.kt
+++ b/app/src/main/java/com/idormy/sms/forwarder/entity/CloneInfo.kt
@@ -43,6 +43,8 @@ data class CloneInfo(
var enableLoadSystemAppList: Boolean = false,
@SerializedName("duplicate_messages_limits")
var duplicateMessagesLimits: Int = 0,
+ @SerializedName("enable_network_state_receiver")
+ var enableNetworkStateReceiver: Boolean = false,
@SerializedName("enable_battery_receiver")
var enableBatteryReceiver: Boolean = false,
@SerializedName("battery_level_min")
diff --git a/app/src/main/java/com/idormy/sms/forwarder/fragment/AppListFragment.kt b/app/src/main/java/com/idormy/sms/forwarder/fragment/AppListFragment.kt
index 8b334b09..95313e65 100644
--- a/app/src/main/java/com/idormy/sms/forwarder/fragment/AppListFragment.kt
+++ b/app/src/main/java/com/idormy/sms/forwarder/fragment/AppListFragment.kt
@@ -1,124 +1,131 @@
-package com.idormy.sms.forwarder.fragment
-
-import android.content.ClipData
-import android.content.ClipboardManager
-import android.view.LayoutInflater
-import android.view.View
-import android.view.ViewGroup
-import androidx.appcompat.app.AppCompatActivity
-import com.idormy.sms.forwarder.App
-import com.idormy.sms.forwarder.R
-import com.idormy.sms.forwarder.adapter.AppListAdapter
-import com.idormy.sms.forwarder.core.BaseFragment
-import com.idormy.sms.forwarder.databinding.FragmentAppListBinding
-import com.idormy.sms.forwarder.utils.XToastUtils
-import com.scwang.smartrefresh.layout.api.RefreshLayout
-import com.scwang.smartrefresh.layout.listener.OnRefreshLoadMoreListener
-import com.xuexiang.xaop.annotation.SingleClick
-import com.xuexiang.xpage.annotation.Page
-import com.xuexiang.xui.utils.DensityUtils
-import com.xuexiang.xui.utils.ResUtils
-import com.xuexiang.xui.utils.ThemeUtils
-import com.xuexiang.xui.utils.WidgetUtils
-import com.xuexiang.xui.widget.actionbar.TitleBar
-import com.xuexiang.xutil.app.AppUtils
-
-@Page(name = "应用列表")
-class AppListFragment : BaseFragment() {
-
- var appListAdapter: AppListAdapter? = null
- private var currentType: String = "user"
-
- override fun viewBindingInflate(
- inflater: LayoutInflater,
- container: ViewGroup,
- ): FragmentAppListBinding {
- return FragmentAppListBinding.inflate(inflater, container, false)
- }
-
- override fun initTitle(): TitleBar {
- val titleBar = super.initTitle()!!.setImmersive(false)
- titleBar.setTitle(R.string.menu_apps)
- titleBar!!.addAction(object : TitleBar.ImageAction(R.drawable.ic_refresh) {
- @SingleClick
- override fun performAction(view: View) {
- binding!!.refreshLayout.autoRefresh()
- }
- })
- return titleBar
- }
-
- /**
- * 初始化控件
- */
- override fun initViews() {
- WidgetUtils.initRecyclerView(binding!!.recyclerView, DensityUtils.dp2px(5f), ThemeUtils.resolveColor(context, R.attr.xui_config_color_background))
- binding!!.recyclerView.adapter = AppListAdapter(true).also { appListAdapter = it }
-
- binding!!.tabBar.setTabTitles(ResUtils.getStringArray(R.array.app_type_option))
- binding!!.tabBar.setOnTabClickListener { _, position ->
- //XToastUtils.toast("点击了$title--$position")
- currentType = when (position) {
- 1 -> "system"
- else -> "user"
- }
- appListAdapter?.refresh(getAppsList(false))
- binding!!.refreshLayout.finishRefresh()
- binding!!.recyclerView.scrollToPosition(0)
- }
- }
-
- override fun initListeners() {
- binding!!.refreshLayout.setOnRefreshLoadMoreListener(object : OnRefreshLoadMoreListener {
- override fun onLoadMore(refreshLayout: RefreshLayout) {
- refreshLayout.layout.postDelayed({
- refreshLayout.finishLoadMore()
- }, 1000)
- }
-
- override fun onRefresh(refreshLayout: RefreshLayout) {
- refreshLayout.layout.postDelayed({
- appListAdapter?.refresh(getAppsList(true))
- refreshLayout.finishRefresh()
- }, 3000)
- }
- })
- appListAdapter?.setOnItemClickListener { _, item, _ ->
- val cm = requireContext().getSystemService(AppCompatActivity.CLIPBOARD_SERVICE) as ClipboardManager
- val mClipData = ClipData.newPlainText("pkgName", item?.packageName)
- cm.setPrimaryClip(mClipData)
- XToastUtils.toast(ResUtils.getString(R.string.package_name_copied) + item?.packageName, 2000)
- }
-
- //设置刷新加载时禁止所有列表操作
- binding!!.refreshLayout.setDisableContentWhenRefresh(true)
- binding!!.refreshLayout.setDisableContentWhenLoading(true)
- appListAdapter?.refresh(getAppsList(false))
- binding!!.refreshLayout.finishRefresh()
- }
-
- override fun onDestroyView() {
- appListAdapter?.recycle()
- super.onDestroyView()
- }
-
- private fun getAppsList(refresh: Boolean): MutableList {
- if (refresh || (currentType == "user" && App.UserAppList.isEmpty()) || (currentType == "system" && App.SystemAppList.isEmpty())) {
- App.UserAppList.clear()
- App.SystemAppList.clear()
- val appInfoList = AppUtils.getAppsInfo()
- for (appInfo in appInfoList) {
- if (appInfo.isSystem) {
- App.SystemAppList.add(appInfo)
- } else {
- App.UserAppList.add(appInfo)
- }
- }
- App.UserAppList.sortBy { appInfo -> appInfo.name }
- App.SystemAppList.sortBy { appInfo -> appInfo.name }
- }
-
- return if (currentType == "system") App.SystemAppList else App.UserAppList
- }
-
+package com.idormy.sms.forwarder.fragment
+
+import android.content.ClipData
+import android.content.ClipboardManager
+import android.util.Log
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.appcompat.app.AppCompatActivity
+import androidx.lifecycle.Observer
+import androidx.work.OneTimeWorkRequestBuilder
+import androidx.work.WorkManager
+import com.idormy.sms.forwarder.App
+import com.idormy.sms.forwarder.R
+import com.idormy.sms.forwarder.adapter.AppListAdapter
+import com.idormy.sms.forwarder.core.BaseFragment
+import com.idormy.sms.forwarder.databinding.FragmentAppListBinding
+import com.idormy.sms.forwarder.utils.EVENT_LOAD_APP_LIST
+import com.idormy.sms.forwarder.utils.XToastUtils
+import com.idormy.sms.forwarder.workers.LoadAppListWorker
+import com.jeremyliao.liveeventbus.LiveEventBus
+import com.scwang.smartrefresh.layout.api.RefreshLayout
+import com.scwang.smartrefresh.layout.listener.OnRefreshLoadMoreListener
+import com.xuexiang.xaop.annotation.SingleClick
+import com.xuexiang.xpage.annotation.Page
+import com.xuexiang.xui.utils.DensityUtils
+import com.xuexiang.xui.utils.ResUtils
+import com.xuexiang.xui.utils.ThemeUtils
+import com.xuexiang.xui.utils.WidgetUtils
+import com.xuexiang.xui.widget.actionbar.TitleBar
+import com.xuexiang.xutil.XUtil
+import com.xuexiang.xutil.app.AppUtils
+
+@Suppress("PrivatePropertyName")
+@Page(name = "应用列表")
+class AppListFragment : BaseFragment() {
+
+ private val TAG: String = AppListFragment::class.java.simpleName
+ var appListAdapter: AppListAdapter? = null
+ private val appListObserver = Observer { it: String ->
+ Log.d(TAG, "EVENT_LOAD_APP_LIST: $it")
+ appListAdapter?.refresh(getAppsList(false))
+ }
+ private var currentType: String = "user"
+
+ override fun viewBindingInflate(
+ inflater: LayoutInflater,
+ container: ViewGroup,
+ ): FragmentAppListBinding {
+ return FragmentAppListBinding.inflate(inflater, container, false)
+ }
+
+ override fun initTitle(): TitleBar {
+ val titleBar = super.initTitle()!!.setImmersive(false)
+ titleBar.setTitle(R.string.menu_apps)
+ titleBar!!.addAction(object : TitleBar.ImageAction(R.drawable.ic_refresh) {
+ @SingleClick
+ override fun performAction(view: View) {
+ binding!!.refreshLayout.autoRefresh()
+ }
+ })
+ return titleBar
+ }
+
+ /**
+ * 初始化控件
+ */
+ override fun initViews() {
+ WidgetUtils.initRecyclerView(binding!!.recyclerView, DensityUtils.dp2px(5f), ThemeUtils.resolveColor(context, R.attr.xui_config_color_background))
+ binding!!.recyclerView.adapter = AppListAdapter(true).also { appListAdapter = it }
+
+ binding!!.tabBar.setTabTitles(ResUtils.getStringArray(R.array.app_type_option))
+ binding!!.tabBar.setOnTabClickListener { _, position ->
+ //XToastUtils.toast("点击了$title--$position")
+ currentType = when (position) {
+ 1 -> "system"
+ else -> "user"
+ }
+ appListAdapter?.refresh(getAppsList(false))
+ binding!!.refreshLayout.finishRefresh()
+ binding!!.recyclerView.scrollToPosition(0)
+ }
+ }
+
+ override fun initListeners() {
+ binding!!.refreshLayout.setOnRefreshLoadMoreListener(object : OnRefreshLoadMoreListener {
+ override fun onLoadMore(refreshLayout: RefreshLayout) {
+ refreshLayout.layout.postDelayed({
+ refreshLayout.finishLoadMore()
+ }, 1000)
+ }
+
+ override fun onRefresh(refreshLayout: RefreshLayout) {
+ refreshLayout.layout.postDelayed({
+ appListAdapter?.refresh(getAppsList(true))
+ refreshLayout.finishRefresh()
+ }, 3000)
+ }
+ })
+ appListAdapter?.setOnItemClickListener { _, item, _ ->
+ val cm = requireContext().getSystemService(AppCompatActivity.CLIPBOARD_SERVICE) as ClipboardManager
+ val mClipData = ClipData.newPlainText("pkgName", item?.packageName)
+ cm.setPrimaryClip(mClipData)
+ XToastUtils.toast(ResUtils.getString(R.string.package_name_copied) + item?.packageName, 2000)
+ }
+
+ //设置刷新加载时禁止所有列表操作
+ binding!!.refreshLayout.setDisableContentWhenRefresh(true)
+ binding!!.refreshLayout.setDisableContentWhenLoading(true)
+ appListAdapter?.refresh(getAppsList(false))
+ binding!!.refreshLayout.finishRefresh()
+ //监听已安装App信息列表加载完成事件
+ LiveEventBus.get(EVENT_LOAD_APP_LIST, String::class.java).observeStickyForever(appListObserver)
+ }
+
+ override fun onDestroyView() {
+ appListAdapter?.recycle()
+ super.onDestroyView()
+ }
+
+ private fun getAppsList(refresh: Boolean): MutableList {
+ if (refresh || (currentType == "user" && App.UserAppList.isEmpty()) || (currentType == "system" && App.SystemAppList.isEmpty())) {
+ XToastUtils.info(getString(R.string.loading_app_list))
+ val request = OneTimeWorkRequestBuilder().build()
+ WorkManager.getInstance(XUtil.getContext()).enqueue(request)
+ }
+
+ return if (currentType == "system") App.SystemAppList else App.UserAppList
+ }
+
}
\ No newline at end of file
diff --git a/app/src/main/java/com/idormy/sms/forwarder/fragment/RulesEditFragment.kt b/app/src/main/java/com/idormy/sms/forwarder/fragment/RulesEditFragment.kt
index fc932202..a567fd22 100644
--- a/app/src/main/java/com/idormy/sms/forwarder/fragment/RulesEditFragment.kt
+++ b/app/src/main/java/com/idormy/sms/forwarder/fragment/RulesEditFragment.kt
@@ -8,6 +8,9 @@ import android.view.View
import android.view.ViewGroup
import android.widget.*
import androidx.fragment.app.viewModels
+import androidx.lifecycle.Observer
+import androidx.work.OneTimeWorkRequestBuilder
+import androidx.work.WorkManager
import com.idormy.sms.forwarder.App
import com.idormy.sms.forwarder.R
import com.idormy.sms.forwarder.adapter.spinner.AppListAdapterItem
@@ -23,6 +26,8 @@ import com.idormy.sms.forwarder.database.viewmodel.RuleViewModel
import com.idormy.sms.forwarder.databinding.FragmentRulesEditBinding
import com.idormy.sms.forwarder.entity.MsgInfo
import com.idormy.sms.forwarder.utils.*
+import com.idormy.sms.forwarder.workers.LoadAppListWorker
+import com.jeremyliao.liveeventbus.LiveEventBus
import com.xuexiang.xaop.annotation.SingleClick
import com.xuexiang.xpage.annotation.Page
import com.xuexiang.xrouter.annotation.AutoWired
@@ -32,7 +37,7 @@ import com.xuexiang.xui.utils.ResUtils
import com.xuexiang.xui.widget.actionbar.TitleBar
import com.xuexiang.xui.widget.dialog.materialdialog.DialogAction
import com.xuexiang.xui.widget.dialog.materialdialog.MaterialDialog
-import com.xuexiang.xutil.app.AppUtils
+import com.xuexiang.xutil.XUtil
import io.reactivex.SingleObserver
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.Disposable
@@ -61,6 +66,10 @@ class RulesEditFragment : BaseFragment(), View.OnClic
//已安装App信息列表
private val appListSpinnerList = ArrayList()
private lateinit var appListSpinnerAdapter: AppListSpinnerAdapter<*>
+ private val appListObserver = Observer { it: String ->
+ Log.d(TAG, "EVENT_LOAD_APP_LIST: $it")
+ initAppSpinner()
+ }
@JvmField
@AutoWired(name = KEY_RULE_ID)
@@ -105,6 +114,10 @@ class RulesEditFragment : BaseFragment(), View.OnClic
binding!!.btInsertExtra.visibility = View.GONE
binding!!.btInsertSender.visibility = View.GONE
binding!!.btInsertContent.visibility = View.GONE
+ //初始化APP下拉列表
+ initAppSpinner()
+ //监听已安装App信息列表加载完成事件
+ LiveEventBus.get(EVENT_LOAD_APP_LIST, String::class.java).observeStickyForever(appListObserver)
}
"call" -> {
titleBar?.setTitle(R.string.call_rule)
@@ -136,8 +149,6 @@ class RulesEditFragment : BaseFragment(), View.OnClic
initForm()
}
- //初始化APP下拉列表
- initAppSpinner()
}
override fun initListeners() {
@@ -408,68 +419,48 @@ class RulesEditFragment : BaseFragment(), View.OnClic
}
//初始化APP下拉列表
- @OptIn(DelicateCoroutinesApi::class)
private fun initAppSpinner() {
if (ruleType != "app") return
//未开启异步获取已安装App信息开关时,规则编辑不显示已安装APP下拉框
if (!SettingUtils.enableLoadUserAppList && !SettingUtils.enableLoadSystemAppList) return
- val get = GlobalScope.async(Dispatchers.IO) {
- if ((SettingUtils.enableLoadUserAppList && App.UserAppList.isEmpty()) || (SettingUtils.enableLoadSystemAppList && App.SystemAppList.isEmpty())) {
- App.UserAppList.clear()
- App.SystemAppList.clear()
- val appInfoList = AppUtils.getAppsInfo()
- for (appInfo in appInfoList) {
- if (appInfo.isSystem) {
- App.SystemAppList.add(appInfo)
- } else {
- App.UserAppList.add(appInfo)
- }
- }
- App.UserAppList.sortBy { appInfo -> appInfo.name }
- App.SystemAppList.sortBy { appInfo -> appInfo.name }
+ if (App.UserAppList.isEmpty() && App.SystemAppList.isEmpty()) {
+ XToastUtils.info(getString(R.string.loading_app_list))
+ val request = OneTimeWorkRequestBuilder().build()
+ WorkManager.getInstance(XUtil.getContext()).enqueue(request)
+ return
+ }
+
+ appListSpinnerList.clear()
+ if (SettingUtils.enableLoadUserAppList) {
+ for (appInfo in App.UserAppList) {
+ if (TextUtils.isEmpty(appInfo.packageName)) continue
+ appListSpinnerList.add(AppListAdapterItem(appInfo.name, appInfo.icon, appInfo.packageName))
}
}
- GlobalScope.launch(Dispatchers.Main) {
- runCatching {
- get.await()
- if (App.UserAppList.isEmpty() && App.SystemAppList.isEmpty()) return@runCatching
-
- appListSpinnerList.clear()
- if (SettingUtils.enableLoadUserAppList) {
- for (appInfo in App.UserAppList) {
- appListSpinnerList.add(AppListAdapterItem(appInfo.name, appInfo.icon, appInfo.packageName))
- }
- }
- if (SettingUtils.enableLoadSystemAppList) {
- for (appInfo in App.SystemAppList) {
- appListSpinnerList.add(AppListAdapterItem(appInfo.name, appInfo.icon, appInfo.packageName))
- }
- }
-
- //列表为空也不显示下拉框
- if (appListSpinnerList.isEmpty()) return@runCatching
-
- appListSpinnerAdapter = AppListSpinnerAdapter(appListSpinnerList)
- //.setTextColor(ResUtils.getColor(R.color.green))
- //.setTextSize(12F)
- .setIsFilterKey(true).setFilterColor("#EF5362").setBackgroundSelector(R.drawable.selector_custom_spinner_bg)
- binding!!.spApp.setAdapter(appListSpinnerAdapter)
- binding!!.spApp.setOnItemClickListener { _: AdapterView<*>, _: View, position: Int, _: Long ->
- try {
- //val appInfo = appListSpinnerList[position]
- val appInfo = appListSpinnerAdapter.getItemSource(position) as AppListAdapterItem
- CommonUtils.insertOrReplaceText2Cursor(binding!!.etValue, appInfo.packageName.toString())
- } catch (e: Exception) {
- XToastUtils.error(e.message.toString())
- }
- }
- binding!!.layoutAppList.visibility = View.VISIBLE
- }.onFailure {
- Log.e("GlobalScope", it.message.toString())
+ if (SettingUtils.enableLoadSystemAppList) {
+ for (appInfo in App.SystemAppList) {
+ if (TextUtils.isEmpty(appInfo.packageName)) continue
+ appListSpinnerList.add(AppListAdapterItem(appInfo.name, appInfo.icon, appInfo.packageName))
}
}
+
+ //列表为空也不显示下拉框
+ if (appListSpinnerList.isEmpty()) return
+
+ appListSpinnerAdapter = AppListSpinnerAdapter(appListSpinnerList).setIsFilterKey(true).setFilterColor("#EF5362").setBackgroundSelector(R.drawable.selector_custom_spinner_bg)
+ binding!!.spApp.setAdapter(appListSpinnerAdapter)
+ binding!!.spApp.setOnItemClickListener { _: AdapterView<*>, _: View, position: Int, _: Long ->
+ try {
+ val appInfo = appListSpinnerAdapter.getItemSource(position) as AppListAdapterItem
+ CommonUtils.insertOrReplaceText2Cursor(binding!!.etValue, appInfo.packageName.toString())
+ } catch (e: Exception) {
+ XToastUtils.error(e.message.toString())
+ }
+ }
+ binding!!.layoutAppList.visibility = View.VISIBLE
+
}
//初始化表单
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 324533bf..9b451b64 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
@@ -18,6 +18,9 @@ import android.view.View
import android.view.ViewGroup
import android.widget.*
import androidx.annotation.RequiresApi
+import androidx.lifecycle.Observer
+import androidx.work.OneTimeWorkRequestBuilder
+import androidx.work.WorkManager
import com.hjq.permissions.OnPermissionCallback
import com.hjq.permissions.Permission
import com.hjq.permissions.XXPermissions
@@ -30,6 +33,7 @@ import com.idormy.sms.forwarder.databinding.FragmentSettingsBinding
import com.idormy.sms.forwarder.entity.SimInfo
import com.idormy.sms.forwarder.receiver.BootReceiver
import com.idormy.sms.forwarder.utils.*
+import com.idormy.sms.forwarder.workers.LoadAppListWorker
import com.jeremyliao.liveeventbus.LiveEventBus
import com.xuexiang.xaop.annotation.SingleClick
import com.xuexiang.xpage.annotation.Page
@@ -46,13 +50,11 @@ import com.xuexiang.xui.widget.picker.widget.builder.TimePickerBuilder
import com.xuexiang.xui.widget.picker.widget.listener.OnOptionsSelectListener
import com.xuexiang.xutil.XUtil
import com.xuexiang.xutil.XUtil.getPackageManager
-import com.xuexiang.xutil.app.AppUtils
import com.xuexiang.xutil.app.AppUtils.getAppPackageName
import com.xuexiang.xutil.data.DateUtils
import kotlinx.coroutines.*
import java.util.*
-
@Suppress("PropertyName", "SpellCheckingInspection")
@Page(name = "通用设置")
class SettingsFragment : BaseFragment(), View.OnClickListener {
@@ -63,6 +65,10 @@ class SettingsFragment : BaseFragment(), View.OnClickL
//已安装App信息列表
private val appListSpinnerList = ArrayList()
private lateinit var appListSpinnerAdapter: AppListSpinnerAdapter<*>
+ private val appListObserver = Observer { it: String ->
+ Log.d(TAG, "EVENT_LOAD_APP_LIST: $it")
+ initAppSpinner()
+ }
override fun viewBindingInflate(
inflater: LayoutInflater,
@@ -110,6 +116,9 @@ class SettingsFragment : BaseFragment(), View.OnClickL
SettingUtils.autoCleanLogsDays = newValue
}
+ //监听网络状态变化
+ switchNetworkStateReceiver(binding!!.sbNetworkStateReceiver)
+
//监听电池状态变化
switchBatteryReceiver(binding!!.sbBatteryReceiver)
//电量预警
@@ -157,7 +166,10 @@ class SettingsFragment : BaseFragment(), View.OnClickL
//纯客户端模式
switchDirectlyToClient(binding!!.sbDirectlyToClient)
+ }
+ override fun onResume() {
+ super.onResume()
//初始化APP下拉列表
initAppSpinner()
}
@@ -172,6 +184,10 @@ class SettingsFragment : BaseFragment(), View.OnClickL
binding!!.btInsertExtra.setOnClickListener(this)
binding!!.btInsertTime.setOnClickListener(this)
binding!!.btInsertDeviceName.setOnClickListener(this)
+
+ //监听已安装App信息列表加载完成事件
+ LiveEventBus.get(EVENT_LOAD_APP_LIST, String::class.java).observeStickyForever(appListObserver)
+
}
@SuppressLint("SetTextI18n")
@@ -433,20 +449,18 @@ class SettingsFragment : BaseFragment(), View.OnClickL
SettingUtils.enableAppNotify = isChecked
if (isChecked) {
//检查权限是否获取
- XXPermissions.with(this)
- .permission(Permission.BIND_NOTIFICATION_LISTENER_SERVICE)
- .request(OnPermissionCallback { _, allGranted ->
- if (!allGranted) {
- SettingUtils.enableAppNotify = false
- sbEnableAppNotify.isChecked = false
- XToastUtils.error(R.string.tips_notification_listener)
- return@OnPermissionCallback
- }
+ XXPermissions.with(this).permission(Permission.BIND_NOTIFICATION_LISTENER_SERVICE).request(OnPermissionCallback { _, allGranted ->
+ if (!allGranted) {
+ SettingUtils.enableAppNotify = false
+ sbEnableAppNotify.isChecked = false
+ XToastUtils.error(R.string.tips_notification_listener)
+ return@OnPermissionCallback
+ }
- SettingUtils.enableAppNotify = true
- sbEnableAppNotify.isChecked = true
- CommonUtils.toggleNotificationListenerService(requireContext())
- })
+ SettingUtils.enableAppNotify = true
+ sbEnableAppNotify.isChecked = true
+ CommonUtils.toggleNotificationListenerService(requireContext())
+ })
}
}
scbCancelAppNotify.isChecked = SettingUtils.enableCancelAppNotify
@@ -487,6 +501,10 @@ class SettingsFragment : BaseFragment(), View.OnClickL
return@setOnCheckedChangeListener
}
SettingUtils.enableLoadAppList = isChecked
+
+ XToastUtils.info(getString(R.string.loading_app_list))
+ val request = OneTimeWorkRequestBuilder().build()
+ WorkManager.getInstance(XUtil.getContext()).enqueue(request)
}
scbLoadUserApp.isChecked = SettingUtils.enableLoadUserAppList
scbLoadUserApp.setOnCheckedChangeListener { _: SmoothCheckBox, isChecked: Boolean ->
@@ -496,6 +514,13 @@ class SettingsFragment : BaseFragment(), View.OnClickL
SettingUtils.enableLoadAppList = false
XToastUtils.error(getString(R.string.load_app_list_toast))
}
+ if (isChecked && SettingUtils.enableLoadAppList && App.UserAppList.isEmpty()) {
+ XToastUtils.info(getString(R.string.loading_app_list))
+ val request = OneTimeWorkRequestBuilder().build()
+ WorkManager.getInstance(XUtil.getContext()).enqueue(request)
+ } else {
+ initAppSpinner()
+ }
}
scbLoadSystemApp.isChecked = SettingUtils.enableLoadSystemAppList
scbLoadSystemApp.setOnCheckedChangeListener { _: SmoothCheckBox, isChecked: Boolean ->
@@ -505,6 +530,22 @@ class SettingsFragment : BaseFragment(), View.OnClickL
SettingUtils.enableLoadAppList = false
XToastUtils.error(getString(R.string.load_app_list_toast))
}
+ if (isChecked && SettingUtils.enableLoadAppList && App.SystemAppList.isEmpty()) {
+ XToastUtils.info(getString(R.string.loading_app_list))
+ val request = OneTimeWorkRequestBuilder().build()
+ WorkManager.getInstance(XUtil.getContext()).enqueue(request)
+ } else {
+ initAppSpinner()
+ }
+ }
+ }
+
+ //监听网络状态变化
+ @SuppressLint("UseSwitchCompatOrMaterialCode")
+ fun switchNetworkStateReceiver(sbNetworkStateReceiver: SwitchButton) {
+ sbNetworkStateReceiver.isChecked = SettingUtils.enableNetworkStateReceiver
+ sbNetworkStateReceiver.setOnCheckedChangeListener { _: CompoundButton?, isChecked: Boolean ->
+ SettingUtils.enableNetworkStateReceiver = isChecked
}
}
@@ -553,8 +594,6 @@ class SettingsFragment : BaseFragment(), View.OnClickL
sbBatteryCron.setOnCheckedChangeListener { _: CompoundButton?, isChecked: Boolean ->
binding!!.layoutBatteryCron.visibility = if (isChecked) View.VISIBLE else View.GONE
SettingUtils.enableBatteryCron = isChecked
- //TODO:BatteryReportCronTask
- //BatteryReportCronTask.getSingleton().updateTimer()
}
}
@@ -568,11 +607,7 @@ class SettingsFragment : BaseFragment(), View.OnClickL
calendar.time = DateUtils.getNowDate()
val mTimePicker = TimePickerBuilder(context) { date: Date?, _: View? ->
etBatteryCronStartTime.setText(DateUtils.date2String(date, DateUtils.HHmm.get()))
- //TODO:BatteryReportCronTask
- //BatteryReportCronTask.getSingleton().updateTimer()
- }
- //.setTimeSelectChangeListener { date: Date? -> etBatteryCronStartTime.setText(DateUtils.date2String(date, DateUtils.HHmm.get())) }
- .setType(false, false, false, true, true, false).setTitleText(getString(R.string.time_picker)).setSubmitText(getString(R.string.ok)).setCancelText(getString(R.string.cancel)).setDate(calendar).build()
+ }.setType(false, false, false, true, true, false).setTitleText(getString(R.string.time_picker)).setSubmitText(getString(R.string.ok)).setCancelText(getString(R.string.cancel)).setDate(calendar).build()
mTimePicker.show()
}
@@ -1077,68 +1112,47 @@ class SettingsFragment : BaseFragment(), View.OnClickL
}
//初始化APP下拉列表
- @OptIn(DelicateCoroutinesApi::class)
private fun initAppSpinner() {
- if (!SettingUtils.enableAppNotify) return
- //未开启异步获取已安装App信息开关时,规则编辑不显示已安装APP下拉框
+ //未开启异步获取已安装App信息开关时,不显示已安装APP下拉框
if (!SettingUtils.enableLoadUserAppList && !SettingUtils.enableLoadSystemAppList) return
- val get = GlobalScope.async(Dispatchers.IO) {
- if ((SettingUtils.enableLoadUserAppList && App.UserAppList.isEmpty()) || (SettingUtils.enableLoadSystemAppList && App.SystemAppList.isEmpty())) {
- App.UserAppList.clear()
- App.SystemAppList.clear()
- val appInfoList = AppUtils.getAppsInfo()
- for (appInfo in appInfoList) {
- if (appInfo.isSystem) {
- App.SystemAppList.add(appInfo)
- } else {
- App.UserAppList.add(appInfo)
- }
- }
- App.UserAppList.sortBy { appInfo -> appInfo.name }
- App.SystemAppList.sortBy { appInfo -> appInfo.name }
+ if (App.UserAppList.isEmpty() && App.SystemAppList.isEmpty()) {
+ //XToastUtils.info(getString(R.string.loading_app_list))
+ val request = OneTimeWorkRequestBuilder().build()
+ WorkManager.getInstance(XUtil.getContext()).enqueue(request)
+ return
+ }
+
+ appListSpinnerList.clear()
+ if (SettingUtils.enableLoadUserAppList) {
+ for (appInfo in App.UserAppList) {
+ if (TextUtils.isEmpty(appInfo.packageName)) continue
+ appListSpinnerList.add(AppListAdapterItem(appInfo.name, appInfo.icon, appInfo.packageName))
}
}
- GlobalScope.launch(Dispatchers.Main) {
- runCatching {
- get.await()
- if (App.UserAppList.isEmpty() && App.SystemAppList.isEmpty()) return@runCatching
-
- appListSpinnerList.clear()
- if (SettingUtils.enableLoadUserAppList) {
- for (appInfo in App.UserAppList) {
- appListSpinnerList.add(AppListAdapterItem(appInfo.name, appInfo.icon, appInfo.packageName))
- }
- }
- if (SettingUtils.enableLoadSystemAppList) {
- for (appInfo in App.SystemAppList) {
- appListSpinnerList.add(AppListAdapterItem(appInfo.name, appInfo.icon, appInfo.packageName))
- }
- }
-
- //列表为空也不显示下拉框
- if (appListSpinnerList.isEmpty()) return@runCatching
-
- appListSpinnerAdapter = AppListSpinnerAdapter(appListSpinnerList)
- //.setTextColor(ResUtils.getColor(R.color.green))
- //.setTextSize(12F)
- .setIsFilterKey(true).setFilterColor("#EF5362").setBackgroundSelector(R.drawable.selector_custom_spinner_bg)
- binding!!.spApp.setAdapter(appListSpinnerAdapter)
- binding!!.spApp.setOnItemClickListener { _: AdapterView<*>, _: View, position: Int, _: Long ->
- try {
- //val appInfo = appListSpinnerList[position]
- val appInfo = appListSpinnerAdapter.getItemSource(position) as AppListAdapterItem
- CommonUtils.insertOrReplaceText2Cursor(binding!!.etAppList, appInfo.packageName.toString() + "\n")
- } catch (e: Exception) {
- XToastUtils.error(e.message.toString())
- }
- }
- binding!!.spApp.visibility = View.VISIBLE
- }.onFailure {
- Log.e("GlobalScope", it.message.toString())
+ if (SettingUtils.enableLoadSystemAppList) {
+ for (appInfo in App.SystemAppList) {
+ if (TextUtils.isEmpty(appInfo.packageName)) continue
+ appListSpinnerList.add(AppListAdapterItem(appInfo.name, appInfo.icon, appInfo.packageName))
}
}
+
+ //列表为空也不显示下拉框
+ if (appListSpinnerList.isEmpty()) return
+
+ appListSpinnerAdapter = AppListSpinnerAdapter(appListSpinnerList).setIsFilterKey(true).setFilterColor("#EF5362").setBackgroundSelector(R.drawable.selector_custom_spinner_bg)
+ binding!!.spApp.setAdapter(appListSpinnerAdapter)
+ binding!!.spApp.setOnItemClickListener { _: AdapterView<*>, _: View, position: Int, _: Long ->
+ try {
+ val appInfo = appListSpinnerAdapter.getItemSource(position) as AppListAdapterItem
+ CommonUtils.insertOrReplaceText2Cursor(binding!!.etAppList, appInfo.packageName.toString() + "\n")
+ } catch (e: Exception) {
+ XToastUtils.error(e.message.toString())
+ }
+ }
+ binding!!.layoutSpApp.visibility = View.VISIBLE
+
}
}
\ No newline at end of file
diff --git a/app/src/main/java/com/idormy/sms/forwarder/service/BatteryService.kt b/app/src/main/java/com/idormy/sms/forwarder/service/BatteryService.kt
index 441f8066..325f6686 100644
--- a/app/src/main/java/com/idormy/sms/forwarder/service/BatteryService.kt
+++ b/app/src/main/java/com/idormy/sms/forwarder/service/BatteryService.kt
@@ -69,6 +69,9 @@ class BatteryService : Service() {
private val batteryReceiver: BroadcastReceiver = object : BroadcastReceiver() {
@SuppressLint("DefaultLocale")
override fun onReceive(context: Context, intent: Intent) {
+
+ if (intent.action != Intent.ACTION_BATTERY_CHANGED) return
+
//自动删除N天前的转发记录
if (SettingUtils.autoCleanLogsDays > 0) {
Log.d(TAG, "自动删除N天前的转发记录")
@@ -145,21 +148,12 @@ class BatteryService : Service() {
private fun sendMessage(context: Context, msg: String) {
Log.i(TAG, msg)
try {
- val msgInfo = MsgInfo(
- "app",
- "88888888",
- msg,
- Date(),
- getString(R.string.battery_status_monitor),
- -1
- )
- val request = OneTimeWorkRequestBuilder()
- .setInputData(
- workDataOf(
- Worker.sendMsgInfo to Gson().toJson(msgInfo),
- )
+ val msgInfo = MsgInfo("app", "88888888", msg, Date(), getString(R.string.battery_status_monitor), -1)
+ val request = OneTimeWorkRequestBuilder().setInputData(
+ workDataOf(
+ Worker.sendMsgInfo to Gson().toJson(msgInfo),
)
- .build()
+ ).build()
WorkManager.getInstance(context).enqueue(request)
} catch (e: Exception) {
Log.e(TAG, "getLog e:" + e.message)
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 dcf3d708..e4598de3 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
@@ -10,12 +10,16 @@ import android.text.TextUtils
import android.util.Log
import androidx.core.app.NotificationCompat
import androidx.lifecycle.Observer
+import androidx.work.OneTimeWorkRequestBuilder
+import androidx.work.WorkManager
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.utils.*
+import com.idormy.sms.forwarder.workers.LoadAppListWorker
import com.jeremyliao.liveeventbus.LiveEventBus
+import com.xuexiang.xutil.XUtil
import com.xuexiang.xutil.file.FileUtils
import frpclib.Frpclib
import io.reactivex.Single
@@ -86,6 +90,12 @@ class ForegroundService : Service() {
CommonUtils.toggleNotificationListenerService(this)
}
+ //异步获取所有已安装 App 信息
+ if (SettingUtils.enableLoadAppList) {
+ val request = OneTimeWorkRequestBuilder().build()
+ WorkManager.getInstance(XUtil.getContext()).enqueue(request)
+ }
+
if (FileUtils.isFileExists(filesDir.absolutePath + "/libs/libgojni.so")) {
//监听Frpc启动指令
LiveEventBus.get(INTENT_FRPC_APPLY_FILE, String::class.java).observeStickyForever(frpcObserver)
diff --git a/app/src/main/java/com/idormy/sms/forwarder/service/NetworkStateService.kt b/app/src/main/java/com/idormy/sms/forwarder/service/NetworkStateService.kt
new file mode 100644
index 00000000..e362a2dc
--- /dev/null
+++ b/app/src/main/java/com/idormy/sms/forwarder/service/NetworkStateService.kt
@@ -0,0 +1,126 @@
+package com.idormy.sms.forwarder.service
+
+import android.annotation.SuppressLint
+import android.app.Service
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import android.content.IntentFilter
+import android.net.ConnectivityManager
+import android.os.IBinder
+import android.util.Log
+import androidx.work.OneTimeWorkRequestBuilder
+import androidx.work.WorkManager
+import androidx.work.workDataOf
+import com.google.gson.Gson
+import com.idormy.sms.forwarder.R
+import com.idormy.sms.forwarder.entity.MsgInfo
+import com.idormy.sms.forwarder.utils.SettingUtils
+import com.idormy.sms.forwarder.utils.Worker
+import com.idormy.sms.forwarder.workers.SendWorker
+import com.xuexiang.xutil.app.ServiceUtils
+import com.xuexiang.xutil.net.NetworkUtils
+import java.util.*
+
+@Suppress("DEPRECATION", "DeferredResultUnused")
+class NetworkStateService : Service() {
+
+ override fun onBind(intent: Intent): IBinder? {
+ return null
+ }
+
+ override fun onCreate() {
+ super.onCreate()
+ Log.i(TAG, "onCreate--------------")
+
+ //纯客户端模式
+ //if (SettingUtils.enablePureClientMode) return
+
+ val networkStateFilter = IntentFilter()
+ networkStateFilter.addAction(ConnectivityManager.CONNECTIVITY_ACTION)
+ registerReceiver(networkStateReceiver, networkStateFilter)
+ }
+
+ override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
+ Log.i(TAG, "onStartCommand--------------")
+ return START_STICKY
+ }
+
+ override fun onDestroy() {
+ Log.i(TAG, "onDestroy--------------")
+ super.onDestroy()
+
+ //纯客户端模式
+ //if (SettingUtils.enablePureClientMode) return
+
+ unregisterReceiver(networkStateReceiver)
+ }
+
+ // 接收电池信息更新的广播
+ private val networkStateReceiver: BroadcastReceiver = object : BroadcastReceiver() {
+ @SuppressLint("DefaultLocale")
+ override fun onReceive(context: Context, intent: Intent) {
+
+ if (intent.action != ConnectivityManager.CONNECTIVITY_ACTION) return
+
+ if (!SettingUtils.enableNetworkStateReceiver) return
+
+ Log.i(TAG, "网络状态已经改变")
+
+ val msg = StringBuilder()
+
+ //枚举网络状态 NET_NO:没有网络 , NET_2G:2g网络 , NET_3G:3g网络, NET_4G:4g网络, NET_5G:5g网络, NET_WIFI:wifi, NET_ETHERNET:有线网络, NET_UNKNOWN:未知网络
+ val netStateType = NetworkUtils.getNetStateType()
+ Log.d(TAG, "netStateType: $netStateType")
+ val netStateTypeName = when (netStateType) {
+ NetworkUtils.NetState.NET_NO -> "没有网络"
+ NetworkUtils.NetState.NET_2G -> "2G网络"
+ NetworkUtils.NetState.NET_3G -> "3G网络"
+ NetworkUtils.NetState.NET_4G -> "4G网络"
+ NetworkUtils.NetState.NET_5G -> "5G网络"
+ NetworkUtils.NetState.NET_WIFI -> "Wifi"
+ NetworkUtils.NetState.NET_ETHERNET -> "有线网络"
+ NetworkUtils.NetState.NET_UNKNOWN -> "未知网络"
+ else -> "未知网络"
+ }
+ msg.append(getString(R.string.network_type)).append(": ").append(netStateTypeName).append("\n")
+
+ //获取网络运营商名称:中国移动、中国联通、中国电信
+ if (netStateType == NetworkUtils.NetState.NET_2G || netStateType == NetworkUtils.NetState.NET_3G || netStateType == NetworkUtils.NetState.NET_4G || netStateType == NetworkUtils.NetState.NET_5G) {
+ val operatorName = NetworkUtils.getNetworkOperatorName()
+ msg.append(getString(R.string.operator_name)).append(": ").append(operatorName).append("\n")
+ }
+
+ val inetAddress = NetworkUtils.getLocalInetAddress()
+ var hostAddress: String = inetAddress?.hostAddress?.toString() ?: "127.0.0.1"
+ msg.append(getString(R.string.host_address)).append(": ").append(hostAddress).append("\n")
+
+ if (ServiceUtils.isServiceRunning("com.idormy.sms.forwarder.service.HttpService")) {
+ hostAddress = if (hostAddress.indexOf(':', 0, false) > 0) "[${hostAddress}]" else hostAddress
+ msg.append(getString(R.string.http_server)).append(": ").append("http://${hostAddress}:5000")
+ }
+
+ sendMessage(context, msg.toString())
+ }
+ }
+
+ //发送信息
+ private fun sendMessage(context: Context, msg: String) {
+ Log.i(TAG, msg)
+ try {
+ val msgInfo = MsgInfo("app", "77777777", msg, Date(), getString(R.string.network_state_monitor), -1)
+ val request = OneTimeWorkRequestBuilder().setInputData(
+ workDataOf(
+ Worker.sendMsgInfo to Gson().toJson(msgInfo),
+ )
+ ).build()
+ WorkManager.getInstance(context).enqueue(request)
+ } catch (e: Exception) {
+ Log.e(TAG, "getLog e:" + e.message)
+ }
+ }
+
+ companion object {
+ private const val TAG = "NetworkStateReceiver"
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/idormy/sms/forwarder/utils/CommonUtils.kt b/app/src/main/java/com/idormy/sms/forwarder/utils/CommonUtils.kt
index 9735d376..57356615 100644
--- a/app/src/main/java/com/idormy/sms/forwarder/utils/CommonUtils.kt
+++ b/app/src/main/java/com/idormy/sms/forwarder/utils/CommonUtils.kt
@@ -36,13 +36,11 @@ import com.xuexiang.xui.widget.imageview.preview.PreviewBuilder
import com.xuexiang.xutil.XUtil
import com.xuexiang.xutil.common.StringUtils
import java.util.regex.Pattern
-import kotlin.math.max
-import kotlin.math.min
/**
* 常用工具类
*/
-@Suppress("RegExpRedundantEscape", "unused")
+@Suppress("RegExpRedundantEscape", "unused", "RegExpUnnecessaryNonCapturingGroup")
class CommonUtils private constructor() {
companion object {
/**
@@ -161,11 +159,19 @@ class CommonUtils private constructor() {
//焦点位置插入文本
fun insertOrReplaceText2Cursor(editText: EditText, str: String) {
+ if (TextUtils.isEmpty(str)) return
+
+ //避免出错:java.lang.IndexOutOfBoundsException: setSpan (36 ... 36) ends beyond length 20
+ if (str.length > 20) {
+ editText.text.append(str)
+ return
+ }
+
editText.isFocusable = true
editText.requestFocus()
- val start = max(editText.selectionStart, 0)
- val end = max(editText.selectionEnd, 0)
- editText.text.replace(min(start, end), max(start, end), str, 0, str.length)
+ val nSection: Int = editText.selectionStart
+ editText.text.insert(nSection, str)
+ editText.setSelection(nSection + str.length)
}
//==========图片预览===========//
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 6f2a6af3..c9238c1c 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
@@ -50,6 +50,8 @@ const val SP_SILENT_PERIOD_START = "silent_period_start"
const val SP_SILENT_PERIOD_END = "silent_period_end"
const val SP_AUTO_CLEAN_LOGS_DAYS = "auto_clean_logs_days"
+const val SP_NET_STATE_RECEIVER = "enable_network_state_receiver"
+
const val SP_BATTERY_RECEIVER = "enable_battery_receiver"
const val SP_BATTERY_STATUS = "battery_status"
const val SP_BATTERY_LEVEL_MIN = "battery_level_min"
@@ -333,6 +335,8 @@ const val KEY_RULE_ID = "key_rule_id"
const val KEY_RULE_TYPE = "key_rule_type"
const val KEY_RULE_CLONE = "key_rule_clone"
+const val EVENT_LOAD_APP_LIST = "EVENT_LOAD_APP_LIST"
+
const val EVENT_KEY_SIM_SLOT = "EVENT_KEY_SIM_SLOT"
const val EVENT_KEY_PHONE_NUMBERS = "EVENT_KEY_PHONE_NUMBERS"
diff --git a/app/src/main/java/com/idormy/sms/forwarder/utils/HttpServerUtils.kt b/app/src/main/java/com/idormy/sms/forwarder/utils/HttpServerUtils.kt
index 813962a1..70faa39d 100644
--- a/app/src/main/java/com/idormy/sms/forwarder/utils/HttpServerUtils.kt
+++ b/app/src/main/java/com/idormy/sms/forwarder/utils/HttpServerUtils.kt
@@ -160,6 +160,7 @@ class HttpServerUtils private constructor() {
cloneInfo.enableLoadUserAppList = SettingUtils.enableLoadUserAppList
cloneInfo.enableLoadSystemAppList = SettingUtils.enableLoadSystemAppList
cloneInfo.duplicateMessagesLimits = SettingUtils.duplicateMessagesLimits
+ cloneInfo.enableNetworkStateReceiver = SettingUtils.enableNetworkStateReceiver
cloneInfo.enableBatteryReceiver = SettingUtils.enableBatteryReceiver
cloneInfo.batteryLevelMin = SettingUtils.batteryLevelMin
cloneInfo.batteryLevelMax = SettingUtils.batteryLevelMax
@@ -206,6 +207,7 @@ class HttpServerUtils private constructor() {
SettingUtils.enableLoadUserAppList = cloneInfo.enableLoadUserAppList
SettingUtils.enableLoadSystemAppList = cloneInfo.enableLoadSystemAppList
SettingUtils.duplicateMessagesLimits = cloneInfo.duplicateMessagesLimits
+ SettingUtils.enableNetworkStateReceiver = cloneInfo.enableNetworkStateReceiver
SettingUtils.enableBatteryReceiver = cloneInfo.enableBatteryReceiver
SettingUtils.batteryLevelMin = cloneInfo.batteryLevelMin
SettingUtils.batteryLevelMax = cloneInfo.batteryLevelMax
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 148010b6..05c930e3 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
@@ -69,6 +69,9 @@ class SettingUtils private constructor() {
//自动删除N天前的转发记录
var autoCleanLogsDays: Int by SharedPreference(SP_AUTO_CLEAN_LOGS_DAYS, 0)
+ //是否监听网络状态变化
+ var enableNetworkStateReceiver: Boolean by SharedPreference(SP_NET_STATE_RECEIVER, false)
+
//是否监听电池状态变化
var enableBatteryReceiver: Boolean by SharedPreference(SP_BATTERY_RECEIVER, false)
diff --git a/app/src/main/java/com/idormy/sms/forwarder/workers/LoadAppListWorker.kt b/app/src/main/java/com/idormy/sms/forwarder/workers/LoadAppListWorker.kt
new file mode 100644
index 00000000..9be13a4d
--- /dev/null
+++ b/app/src/main/java/com/idormy/sms/forwarder/workers/LoadAppListWorker.kt
@@ -0,0 +1,47 @@
+package com.idormy.sms.forwarder.workers
+
+import android.content.Context
+import android.util.Log
+import androidx.work.CoroutineWorker
+import androidx.work.WorkerParameters
+import com.idormy.sms.forwarder.App
+import com.idormy.sms.forwarder.utils.EVENT_LOAD_APP_LIST
+import com.jeremyliao.liveeventbus.LiveEventBus
+import com.xuexiang.xutil.app.AppUtils
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.withContext
+
+class LoadAppListWorker(
+ context: Context,
+ workerParams: WorkerParameters,
+) : CoroutineWorker(context, workerParams) {
+
+ override suspend fun doWork(): Result = withContext(Dispatchers.IO) {
+ if (App.LoadingAppList) {
+ Log.d("LoadAppListWorker", "LoadingAppList is true, return")
+ return@withContext Result.success()
+ }
+
+ App.LoadingAppList = true
+ App.UserAppList.clear()
+ App.SystemAppList.clear()
+ val appInfoList = AppUtils.getAppsInfo()
+ for (appInfo in appInfoList) {
+ if (appInfo.isSystem) {
+ App.SystemAppList.add(appInfo)
+ } else {
+ App.UserAppList.add(appInfo)
+ }
+ }
+ App.UserAppList.sortBy { appInfo -> appInfo.name }
+ App.SystemAppList.sortBy { appInfo -> appInfo.name }
+
+
+ LiveEventBus.get(EVENT_LOAD_APP_LIST, String::class.java).post("finish")
+ App.LoadingAppList = false
+ Log.d("LoadAppListWorker", "LoadAppListWorker finish, App.LoadingAppList=${App.LoadingAppList}")
+
+ return@withContext Result.success()
+ }
+
+}
\ 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 59e57325..dc0b5b46 100644
--- a/app/src/main/res/layout/fragment_settings.xml
+++ b/app/src/main/res/layout/fragment_settings.xml
@@ -329,40 +329,47 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
- android:orientation="horizontal"
+ android:orientation="vertical"
android:paddingEnd="10dp"
android:visibility="gone"
tools:ignore="RtlSymmetry">
-
+ android:hint="@string/extra_app_hint"
+ android:inputType="textMultiLine"
+ app:met_clearButton="true" />
-
+
+
+ android:text="@string/choose_app"
+ android:textSize="12sp"
+ android:textStyle="bold" />
@@ -600,6 +607,64 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Obtain instructions through passive reception or active polling to operate the machine
Local HttpServer
Available under WiFi network, after startup, other machines in the LAN can directly call the local interface
+ Network State Monitor
+ [Note] You need to manually create APP forwarding rules, package name: 77777777
+ Network State Change Remind
+ Send a notification when the network status changes (connection mode/IP change)
Battery Monitor
[Note] You need to manually create APP forwarding rules, package name: 88888888
Keep Alive
@@ -995,4 +999,8 @@
Del Sender
Sender is disabled
Unknown sender
+ Network Type
+ Operator Name
+ Host Address
+ Loading app list async...
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 0db4a1d3..f49ca825 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -543,6 +543,10 @@
通过 被动接收 或者 主动轮询 获取指令,从而操作本机
被动接收本地 HttpServer
WiFi网络下可用,启动后局域网内其他机器可直接调用本机接口
+ 网络状态监控
+ 【注意】需要手动创建APP转发规则,包名:77777777
+ 网络状态改变提醒
+ 网络状态改变(连接方式/IP变化)时发出通知
电池监控
【注意】需要手动创建APP转发规则,包名:88888888
保活措施
@@ -996,4 +1000,8 @@
删除发送通道
发送通道已禁用
未知发送通道
+ 网络类型
+ 运营商
+ 本地IP
+ 正在异步加载应用列表...