新增:监听网络状态变化提醒(APP通知转发,包名:77777777) #259

优化:已安装App信息列表异步加载机制
This commit is contained in:
pppscn 2023-02-14 10:04:18 +08:00
parent 032837615f
commit dd798e42cc
17 changed files with 586 additions and 318 deletions

View File

@ -202,6 +202,9 @@
<service <service
android:name=".service.HttpService" android:name=".service.HttpService"
android:enabled="true" /> android:enabled="true" />
<service
android:name=".service.NetworkStateService"
android:enabled="true" />
<service <service
android:name=".service.BatteryService" android:name=".service.BatteryService"
android:enabled="true" /> android:enabled="true" />

View File

@ -23,6 +23,7 @@ import com.idormy.sms.forwarder.receiver.CactusReceiver
import com.idormy.sms.forwarder.service.BatteryService import com.idormy.sms.forwarder.service.BatteryService
import com.idormy.sms.forwarder.service.ForegroundService import com.idormy.sms.forwarder.service.ForegroundService
import com.idormy.sms.forwarder.service.HttpService 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.*
import com.idormy.sms.forwarder.utils.sdkinit.UMengInit import com.idormy.sms.forwarder.utils.sdkinit.UMengInit
import com.idormy.sms.forwarder.utils.sdkinit.XBasicLibInit import com.idormy.sms.forwarder.utils.sdkinit.XBasicLibInit
@ -60,6 +61,7 @@ class App : Application(), CactusCallback, Configuration.Provider by Core {
var SimInfoList: MutableMap<Int, SimInfo> = mutableMapOf() var SimInfoList: MutableMap<Int, SimInfo> = mutableMapOf()
//已安装App信息 //已安装App信息
var LoadingAppList = false
var UserAppList: MutableList<AppUtils.AppInfo> = mutableListOf() var UserAppList: MutableList<AppUtils.AppInfo> = mutableListOf()
var SystemAppList: MutableList<AppUtils.AppInfo> = mutableListOf() var SystemAppList: MutableList<AppUtils.AppInfo> = mutableListOf()
@ -118,38 +120,14 @@ class App : Application(), CactusCallback, Configuration.Provider by Core {
startService(intent) startService(intent)
} }
//网络状态监听
val networkStateServiceIntent = Intent(this, NetworkStateService::class.java)
startService(networkStateServiceIntent)
//电池状态监听 //电池状态监听
val batteryServiceIntent = Intent(this, BatteryService::class.java) val batteryServiceIntent = Intent(this, BatteryService::class.java)
startService(batteryServiceIntent) 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 //启动HttpServer
if (HttpServerUtils.enableServerAutorun) { if (HttpServerUtils.enableServerAutorun) {
startService(Intent(this, HttpService::class.java)) startService(Intent(this, HttpService::class.java))

View File

@ -43,6 +43,8 @@ data class CloneInfo(
var enableLoadSystemAppList: Boolean = false, var enableLoadSystemAppList: Boolean = false,
@SerializedName("duplicate_messages_limits") @SerializedName("duplicate_messages_limits")
var duplicateMessagesLimits: Int = 0, var duplicateMessagesLimits: Int = 0,
@SerializedName("enable_network_state_receiver")
var enableNetworkStateReceiver: Boolean = false,
@SerializedName("enable_battery_receiver") @SerializedName("enable_battery_receiver")
var enableBatteryReceiver: Boolean = false, var enableBatteryReceiver: Boolean = false,
@SerializedName("battery_level_min") @SerializedName("battery_level_min")

View File

@ -1,124 +1,131 @@
package com.idormy.sms.forwarder.fragment package com.idormy.sms.forwarder.fragment
import android.content.ClipData import android.content.ClipData
import android.content.ClipboardManager import android.content.ClipboardManager
import android.view.LayoutInflater import android.util.Log
import android.view.View import android.view.LayoutInflater
import android.view.ViewGroup import android.view.View
import androidx.appcompat.app.AppCompatActivity import android.view.ViewGroup
import com.idormy.sms.forwarder.App import androidx.appcompat.app.AppCompatActivity
import com.idormy.sms.forwarder.R import androidx.lifecycle.Observer
import com.idormy.sms.forwarder.adapter.AppListAdapter import androidx.work.OneTimeWorkRequestBuilder
import com.idormy.sms.forwarder.core.BaseFragment import androidx.work.WorkManager
import com.idormy.sms.forwarder.databinding.FragmentAppListBinding import com.idormy.sms.forwarder.App
import com.idormy.sms.forwarder.utils.XToastUtils import com.idormy.sms.forwarder.R
import com.scwang.smartrefresh.layout.api.RefreshLayout import com.idormy.sms.forwarder.adapter.AppListAdapter
import com.scwang.smartrefresh.layout.listener.OnRefreshLoadMoreListener import com.idormy.sms.forwarder.core.BaseFragment
import com.xuexiang.xaop.annotation.SingleClick import com.idormy.sms.forwarder.databinding.FragmentAppListBinding
import com.xuexiang.xpage.annotation.Page import com.idormy.sms.forwarder.utils.EVENT_LOAD_APP_LIST
import com.xuexiang.xui.utils.DensityUtils import com.idormy.sms.forwarder.utils.XToastUtils
import com.xuexiang.xui.utils.ResUtils import com.idormy.sms.forwarder.workers.LoadAppListWorker
import com.xuexiang.xui.utils.ThemeUtils import com.jeremyliao.liveeventbus.LiveEventBus
import com.xuexiang.xui.utils.WidgetUtils import com.scwang.smartrefresh.layout.api.RefreshLayout
import com.xuexiang.xui.widget.actionbar.TitleBar import com.scwang.smartrefresh.layout.listener.OnRefreshLoadMoreListener
import com.xuexiang.xutil.app.AppUtils import com.xuexiang.xaop.annotation.SingleClick
import com.xuexiang.xpage.annotation.Page
@Page(name = "应用列表") import com.xuexiang.xui.utils.DensityUtils
class AppListFragment : BaseFragment<FragmentAppListBinding?>() { import com.xuexiang.xui.utils.ResUtils
import com.xuexiang.xui.utils.ThemeUtils
var appListAdapter: AppListAdapter? = null import com.xuexiang.xui.utils.WidgetUtils
private var currentType: String = "user" import com.xuexiang.xui.widget.actionbar.TitleBar
import com.xuexiang.xutil.XUtil
override fun viewBindingInflate( import com.xuexiang.xutil.app.AppUtils
inflater: LayoutInflater,
container: ViewGroup, @Suppress("PrivatePropertyName")
): FragmentAppListBinding { @Page(name = "应用列表")
return FragmentAppListBinding.inflate(inflater, container, false) class AppListFragment : BaseFragment<FragmentAppListBinding?>() {
}
private val TAG: String = AppListFragment::class.java.simpleName
override fun initTitle(): TitleBar { var appListAdapter: AppListAdapter? = null
val titleBar = super.initTitle()!!.setImmersive(false) private val appListObserver = Observer { it: String ->
titleBar.setTitle(R.string.menu_apps) Log.d(TAG, "EVENT_LOAD_APP_LIST: $it")
titleBar!!.addAction(object : TitleBar.ImageAction(R.drawable.ic_refresh) { appListAdapter?.refresh(getAppsList(false))
@SingleClick }
override fun performAction(view: View) { private var currentType: String = "user"
binding!!.refreshLayout.autoRefresh()
} override fun viewBindingInflate(
}) inflater: LayoutInflater,
return titleBar container: ViewGroup,
} ): FragmentAppListBinding {
return FragmentAppListBinding.inflate(inflater, container, false)
/** }
* 初始化控件
*/ override fun initTitle(): TitleBar {
override fun initViews() { val titleBar = super.initTitle()!!.setImmersive(false)
WidgetUtils.initRecyclerView(binding!!.recyclerView, DensityUtils.dp2px(5f), ThemeUtils.resolveColor(context, R.attr.xui_config_color_background)) titleBar.setTitle(R.string.menu_apps)
binding!!.recyclerView.adapter = AppListAdapter(true).also { appListAdapter = it } titleBar!!.addAction(object : TitleBar.ImageAction(R.drawable.ic_refresh) {
@SingleClick
binding!!.tabBar.setTabTitles(ResUtils.getStringArray(R.array.app_type_option)) override fun performAction(view: View) {
binding!!.tabBar.setOnTabClickListener { _, position -> binding!!.refreshLayout.autoRefresh()
//XToastUtils.toast("点击了$title--$position") }
currentType = when (position) { })
1 -> "system" return titleBar
else -> "user" }
}
appListAdapter?.refresh(getAppsList(false)) /**
binding!!.refreshLayout.finishRefresh() * 初始化控件
binding!!.recyclerView.scrollToPosition(0) */
} 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 }
override fun initListeners() {
binding!!.refreshLayout.setOnRefreshLoadMoreListener(object : OnRefreshLoadMoreListener { binding!!.tabBar.setTabTitles(ResUtils.getStringArray(R.array.app_type_option))
override fun onLoadMore(refreshLayout: RefreshLayout) { binding!!.tabBar.setOnTabClickListener { _, position ->
refreshLayout.layout.postDelayed({ //XToastUtils.toast("点击了$title--$position")
refreshLayout.finishLoadMore() currentType = when (position) {
}, 1000) 1 -> "system"
} else -> "user"
}
override fun onRefresh(refreshLayout: RefreshLayout) { appListAdapter?.refresh(getAppsList(false))
refreshLayout.layout.postDelayed({ binding!!.refreshLayout.finishRefresh()
appListAdapter?.refresh(getAppsList(true)) binding!!.recyclerView.scrollToPosition(0)
refreshLayout.finishRefresh() }
}, 3000) }
}
}) override fun initListeners() {
appListAdapter?.setOnItemClickListener { _, item, _ -> binding!!.refreshLayout.setOnRefreshLoadMoreListener(object : OnRefreshLoadMoreListener {
val cm = requireContext().getSystemService(AppCompatActivity.CLIPBOARD_SERVICE) as ClipboardManager override fun onLoadMore(refreshLayout: RefreshLayout) {
val mClipData = ClipData.newPlainText("pkgName", item?.packageName) refreshLayout.layout.postDelayed({
cm.setPrimaryClip(mClipData) refreshLayout.finishLoadMore()
XToastUtils.toast(ResUtils.getString(R.string.package_name_copied) + item?.packageName, 2000) }, 1000)
} }
//设置刷新加载时禁止所有列表操作 override fun onRefresh(refreshLayout: RefreshLayout) {
binding!!.refreshLayout.setDisableContentWhenRefresh(true) refreshLayout.layout.postDelayed({
binding!!.refreshLayout.setDisableContentWhenLoading(true) appListAdapter?.refresh(getAppsList(true))
appListAdapter?.refresh(getAppsList(false)) refreshLayout.finishRefresh()
binding!!.refreshLayout.finishRefresh() }, 3000)
} }
})
override fun onDestroyView() { appListAdapter?.setOnItemClickListener { _, item, _ ->
appListAdapter?.recycle() val cm = requireContext().getSystemService(AppCompatActivity.CLIPBOARD_SERVICE) as ClipboardManager
super.onDestroyView() val mClipData = ClipData.newPlainText("pkgName", item?.packageName)
} cm.setPrimaryClip(mClipData)
XToastUtils.toast(ResUtils.getString(R.string.package_name_copied) + item?.packageName, 2000)
private fun getAppsList(refresh: Boolean): MutableList<AppUtils.AppInfo> { }
if (refresh || (currentType == "user" && App.UserAppList.isEmpty()) || (currentType == "system" && App.SystemAppList.isEmpty())) {
App.UserAppList.clear() //设置刷新加载时禁止所有列表操作
App.SystemAppList.clear() binding!!.refreshLayout.setDisableContentWhenRefresh(true)
val appInfoList = AppUtils.getAppsInfo() binding!!.refreshLayout.setDisableContentWhenLoading(true)
for (appInfo in appInfoList) { appListAdapter?.refresh(getAppsList(false))
if (appInfo.isSystem) { binding!!.refreshLayout.finishRefresh()
App.SystemAppList.add(appInfo) //监听已安装App信息列表加载完成事件
} else { LiveEventBus.get(EVENT_LOAD_APP_LIST, String::class.java).observeStickyForever(appListObserver)
App.UserAppList.add(appInfo) }
}
} override fun onDestroyView() {
App.UserAppList.sortBy { appInfo -> appInfo.name } appListAdapter?.recycle()
App.SystemAppList.sortBy { appInfo -> appInfo.name } super.onDestroyView()
} }
return if (currentType == "system") App.SystemAppList else App.UserAppList private fun getAppsList(refresh: Boolean): MutableList<AppUtils.AppInfo> {
} if (refresh || (currentType == "user" && App.UserAppList.isEmpty()) || (currentType == "system" && App.SystemAppList.isEmpty())) {
XToastUtils.info(getString(R.string.loading_app_list))
val request = OneTimeWorkRequestBuilder<LoadAppListWorker>().build()
WorkManager.getInstance(XUtil.getContext()).enqueue(request)
}
return if (currentType == "system") App.SystemAppList else App.UserAppList
}
} }

View File

@ -8,6 +8,9 @@ import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.* import android.widget.*
import androidx.fragment.app.viewModels 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.App
import com.idormy.sms.forwarder.R import com.idormy.sms.forwarder.R
import com.idormy.sms.forwarder.adapter.spinner.AppListAdapterItem 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.databinding.FragmentRulesEditBinding
import com.idormy.sms.forwarder.entity.MsgInfo import com.idormy.sms.forwarder.entity.MsgInfo
import com.idormy.sms.forwarder.utils.* 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.xaop.annotation.SingleClick
import com.xuexiang.xpage.annotation.Page import com.xuexiang.xpage.annotation.Page
import com.xuexiang.xrouter.annotation.AutoWired 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.actionbar.TitleBar
import com.xuexiang.xui.widget.dialog.materialdialog.DialogAction import com.xuexiang.xui.widget.dialog.materialdialog.DialogAction
import com.xuexiang.xui.widget.dialog.materialdialog.MaterialDialog 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.SingleObserver
import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.Disposable import io.reactivex.disposables.Disposable
@ -61,6 +66,10 @@ class RulesEditFragment : BaseFragment<FragmentRulesEditBinding?>(), View.OnClic
//已安装App信息列表 //已安装App信息列表
private val appListSpinnerList = ArrayList<AppListAdapterItem>() private val appListSpinnerList = ArrayList<AppListAdapterItem>()
private lateinit var appListSpinnerAdapter: AppListSpinnerAdapter<*> private lateinit var appListSpinnerAdapter: AppListSpinnerAdapter<*>
private val appListObserver = Observer { it: String ->
Log.d(TAG, "EVENT_LOAD_APP_LIST: $it")
initAppSpinner()
}
@JvmField @JvmField
@AutoWired(name = KEY_RULE_ID) @AutoWired(name = KEY_RULE_ID)
@ -105,6 +114,10 @@ class RulesEditFragment : BaseFragment<FragmentRulesEditBinding?>(), View.OnClic
binding!!.btInsertExtra.visibility = View.GONE binding!!.btInsertExtra.visibility = View.GONE
binding!!.btInsertSender.visibility = View.GONE binding!!.btInsertSender.visibility = View.GONE
binding!!.btInsertContent.visibility = View.GONE binding!!.btInsertContent.visibility = View.GONE
//初始化APP下拉列表
initAppSpinner()
//监听已安装App信息列表加载完成事件
LiveEventBus.get(EVENT_LOAD_APP_LIST, String::class.java).observeStickyForever(appListObserver)
} }
"call" -> { "call" -> {
titleBar?.setTitle(R.string.call_rule) titleBar?.setTitle(R.string.call_rule)
@ -136,8 +149,6 @@ class RulesEditFragment : BaseFragment<FragmentRulesEditBinding?>(), View.OnClic
initForm() initForm()
} }
//初始化APP下拉列表
initAppSpinner()
} }
override fun initListeners() { override fun initListeners() {
@ -408,68 +419,48 @@ class RulesEditFragment : BaseFragment<FragmentRulesEditBinding?>(), View.OnClic
} }
//初始化APP下拉列表 //初始化APP下拉列表
@OptIn(DelicateCoroutinesApi::class)
private fun initAppSpinner() { private fun initAppSpinner() {
if (ruleType != "app") return if (ruleType != "app") return
//未开启异步获取已安装App信息开关时规则编辑不显示已安装APP下拉框 //未开启异步获取已安装App信息开关时规则编辑不显示已安装APP下拉框
if (!SettingUtils.enableLoadUserAppList && !SettingUtils.enableLoadSystemAppList) return if (!SettingUtils.enableLoadUserAppList && !SettingUtils.enableLoadSystemAppList) return
val get = GlobalScope.async(Dispatchers.IO) { if (App.UserAppList.isEmpty() && App.SystemAppList.isEmpty()) {
if ((SettingUtils.enableLoadUserAppList && App.UserAppList.isEmpty()) || (SettingUtils.enableLoadSystemAppList && App.SystemAppList.isEmpty())) { XToastUtils.info(getString(R.string.loading_app_list))
App.UserAppList.clear() val request = OneTimeWorkRequestBuilder<LoadAppListWorker>().build()
App.SystemAppList.clear() WorkManager.getInstance(XUtil.getContext()).enqueue(request)
val appInfoList = AppUtils.getAppsInfo() return
for (appInfo in appInfoList) { }
if (appInfo.isSystem) {
App.SystemAppList.add(appInfo) appListSpinnerList.clear()
} else { if (SettingUtils.enableLoadUserAppList) {
App.UserAppList.add(appInfo) for (appInfo in App.UserAppList) {
} if (TextUtils.isEmpty(appInfo.packageName)) continue
} appListSpinnerList.add(AppListAdapterItem(appInfo.name, appInfo.icon, appInfo.packageName))
App.UserAppList.sortBy { appInfo -> appInfo.name }
App.SystemAppList.sortBy { appInfo -> appInfo.name }
} }
} }
GlobalScope.launch(Dispatchers.Main) { if (SettingUtils.enableLoadSystemAppList) {
runCatching { for (appInfo in App.SystemAppList) {
get.await() if (TextUtils.isEmpty(appInfo.packageName)) continue
if (App.UserAppList.isEmpty() && App.SystemAppList.isEmpty()) return@runCatching appListSpinnerList.add(AppListAdapterItem(appInfo.name, appInfo.icon, appInfo.packageName))
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 (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
} }
//初始化表单 //初始化表单

View File

@ -18,6 +18,9 @@ import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.* import android.widget.*
import androidx.annotation.RequiresApi 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.OnPermissionCallback
import com.hjq.permissions.Permission import com.hjq.permissions.Permission
import com.hjq.permissions.XXPermissions 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.entity.SimInfo
import com.idormy.sms.forwarder.receiver.BootReceiver import com.idormy.sms.forwarder.receiver.BootReceiver
import com.idormy.sms.forwarder.utils.* import com.idormy.sms.forwarder.utils.*
import com.idormy.sms.forwarder.workers.LoadAppListWorker
import com.jeremyliao.liveeventbus.LiveEventBus import com.jeremyliao.liveeventbus.LiveEventBus
import com.xuexiang.xaop.annotation.SingleClick import com.xuexiang.xaop.annotation.SingleClick
import com.xuexiang.xpage.annotation.Page 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.xui.widget.picker.widget.listener.OnOptionsSelectListener
import com.xuexiang.xutil.XUtil import com.xuexiang.xutil.XUtil
import com.xuexiang.xutil.XUtil.getPackageManager import com.xuexiang.xutil.XUtil.getPackageManager
import com.xuexiang.xutil.app.AppUtils
import com.xuexiang.xutil.app.AppUtils.getAppPackageName import com.xuexiang.xutil.app.AppUtils.getAppPackageName
import com.xuexiang.xutil.data.DateUtils import com.xuexiang.xutil.data.DateUtils
import kotlinx.coroutines.* import kotlinx.coroutines.*
import java.util.* import java.util.*
@Suppress("PropertyName", "SpellCheckingInspection") @Suppress("PropertyName", "SpellCheckingInspection")
@Page(name = "通用设置") @Page(name = "通用设置")
class SettingsFragment : BaseFragment<FragmentSettingsBinding?>(), View.OnClickListener { class SettingsFragment : BaseFragment<FragmentSettingsBinding?>(), View.OnClickListener {
@ -63,6 +65,10 @@ class SettingsFragment : BaseFragment<FragmentSettingsBinding?>(), View.OnClickL
//已安装App信息列表 //已安装App信息列表
private val appListSpinnerList = ArrayList<AppListAdapterItem>() private val appListSpinnerList = ArrayList<AppListAdapterItem>()
private lateinit var appListSpinnerAdapter: AppListSpinnerAdapter<*> private lateinit var appListSpinnerAdapter: AppListSpinnerAdapter<*>
private val appListObserver = Observer { it: String ->
Log.d(TAG, "EVENT_LOAD_APP_LIST: $it")
initAppSpinner()
}
override fun viewBindingInflate( override fun viewBindingInflate(
inflater: LayoutInflater, inflater: LayoutInflater,
@ -110,6 +116,9 @@ class SettingsFragment : BaseFragment<FragmentSettingsBinding?>(), View.OnClickL
SettingUtils.autoCleanLogsDays = newValue SettingUtils.autoCleanLogsDays = newValue
} }
//监听网络状态变化
switchNetworkStateReceiver(binding!!.sbNetworkStateReceiver)
//监听电池状态变化 //监听电池状态变化
switchBatteryReceiver(binding!!.sbBatteryReceiver) switchBatteryReceiver(binding!!.sbBatteryReceiver)
//电量预警 //电量预警
@ -157,7 +166,10 @@ class SettingsFragment : BaseFragment<FragmentSettingsBinding?>(), View.OnClickL
//纯客户端模式 //纯客户端模式
switchDirectlyToClient(binding!!.sbDirectlyToClient) switchDirectlyToClient(binding!!.sbDirectlyToClient)
}
override fun onResume() {
super.onResume()
//初始化APP下拉列表 //初始化APP下拉列表
initAppSpinner() initAppSpinner()
} }
@ -172,6 +184,10 @@ class SettingsFragment : BaseFragment<FragmentSettingsBinding?>(), View.OnClickL
binding!!.btInsertExtra.setOnClickListener(this) binding!!.btInsertExtra.setOnClickListener(this)
binding!!.btInsertTime.setOnClickListener(this) binding!!.btInsertTime.setOnClickListener(this)
binding!!.btInsertDeviceName.setOnClickListener(this) binding!!.btInsertDeviceName.setOnClickListener(this)
//监听已安装App信息列表加载完成事件
LiveEventBus.get(EVENT_LOAD_APP_LIST, String::class.java).observeStickyForever(appListObserver)
} }
@SuppressLint("SetTextI18n") @SuppressLint("SetTextI18n")
@ -433,20 +449,18 @@ class SettingsFragment : BaseFragment<FragmentSettingsBinding?>(), View.OnClickL
SettingUtils.enableAppNotify = isChecked SettingUtils.enableAppNotify = isChecked
if (isChecked) { if (isChecked) {
//检查权限是否获取 //检查权限是否获取
XXPermissions.with(this) XXPermissions.with(this).permission(Permission.BIND_NOTIFICATION_LISTENER_SERVICE).request(OnPermissionCallback { _, allGranted ->
.permission(Permission.BIND_NOTIFICATION_LISTENER_SERVICE) if (!allGranted) {
.request(OnPermissionCallback { _, allGranted -> SettingUtils.enableAppNotify = false
if (!allGranted) { sbEnableAppNotify.isChecked = false
SettingUtils.enableAppNotify = false XToastUtils.error(R.string.tips_notification_listener)
sbEnableAppNotify.isChecked = false return@OnPermissionCallback
XToastUtils.error(R.string.tips_notification_listener) }
return@OnPermissionCallback
}
SettingUtils.enableAppNotify = true SettingUtils.enableAppNotify = true
sbEnableAppNotify.isChecked = true sbEnableAppNotify.isChecked = true
CommonUtils.toggleNotificationListenerService(requireContext()) CommonUtils.toggleNotificationListenerService(requireContext())
}) })
} }
} }
scbCancelAppNotify.isChecked = SettingUtils.enableCancelAppNotify scbCancelAppNotify.isChecked = SettingUtils.enableCancelAppNotify
@ -487,6 +501,10 @@ class SettingsFragment : BaseFragment<FragmentSettingsBinding?>(), View.OnClickL
return@setOnCheckedChangeListener return@setOnCheckedChangeListener
} }
SettingUtils.enableLoadAppList = isChecked SettingUtils.enableLoadAppList = isChecked
XToastUtils.info(getString(R.string.loading_app_list))
val request = OneTimeWorkRequestBuilder<LoadAppListWorker>().build()
WorkManager.getInstance(XUtil.getContext()).enqueue(request)
} }
scbLoadUserApp.isChecked = SettingUtils.enableLoadUserAppList scbLoadUserApp.isChecked = SettingUtils.enableLoadUserAppList
scbLoadUserApp.setOnCheckedChangeListener { _: SmoothCheckBox, isChecked: Boolean -> scbLoadUserApp.setOnCheckedChangeListener { _: SmoothCheckBox, isChecked: Boolean ->
@ -496,6 +514,13 @@ class SettingsFragment : BaseFragment<FragmentSettingsBinding?>(), View.OnClickL
SettingUtils.enableLoadAppList = false SettingUtils.enableLoadAppList = false
XToastUtils.error(getString(R.string.load_app_list_toast)) 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<LoadAppListWorker>().build()
WorkManager.getInstance(XUtil.getContext()).enqueue(request)
} else {
initAppSpinner()
}
} }
scbLoadSystemApp.isChecked = SettingUtils.enableLoadSystemAppList scbLoadSystemApp.isChecked = SettingUtils.enableLoadSystemAppList
scbLoadSystemApp.setOnCheckedChangeListener { _: SmoothCheckBox, isChecked: Boolean -> scbLoadSystemApp.setOnCheckedChangeListener { _: SmoothCheckBox, isChecked: Boolean ->
@ -505,6 +530,22 @@ class SettingsFragment : BaseFragment<FragmentSettingsBinding?>(), View.OnClickL
SettingUtils.enableLoadAppList = false SettingUtils.enableLoadAppList = false
XToastUtils.error(getString(R.string.load_app_list_toast)) 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<LoadAppListWorker>().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<FragmentSettingsBinding?>(), View.OnClickL
sbBatteryCron.setOnCheckedChangeListener { _: CompoundButton?, isChecked: Boolean -> sbBatteryCron.setOnCheckedChangeListener { _: CompoundButton?, isChecked: Boolean ->
binding!!.layoutBatteryCron.visibility = if (isChecked) View.VISIBLE else View.GONE binding!!.layoutBatteryCron.visibility = if (isChecked) View.VISIBLE else View.GONE
SettingUtils.enableBatteryCron = isChecked SettingUtils.enableBatteryCron = isChecked
//TODO:BatteryReportCronTask
//BatteryReportCronTask.getSingleton().updateTimer()
} }
} }
@ -568,11 +607,7 @@ class SettingsFragment : BaseFragment<FragmentSettingsBinding?>(), View.OnClickL
calendar.time = DateUtils.getNowDate() calendar.time = DateUtils.getNowDate()
val mTimePicker = TimePickerBuilder(context) { date: Date?, _: View? -> val mTimePicker = TimePickerBuilder(context) { date: Date?, _: View? ->
etBatteryCronStartTime.setText(DateUtils.date2String(date, DateUtils.HHmm.get())) etBatteryCronStartTime.setText(DateUtils.date2String(date, DateUtils.HHmm.get()))
//TODO:BatteryReportCronTask }.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()
//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()
mTimePicker.show() mTimePicker.show()
} }
@ -1077,68 +1112,47 @@ class SettingsFragment : BaseFragment<FragmentSettingsBinding?>(), View.OnClickL
} }
//初始化APP下拉列表 //初始化APP下拉列表
@OptIn(DelicateCoroutinesApi::class)
private fun initAppSpinner() { private fun initAppSpinner() {
if (!SettingUtils.enableAppNotify) return
//未开启异步获取已安装App信息开关时规则编辑不显示已安装APP下拉框 //未开启异步获取已安装App信息开关时不显示已安装APP下拉框
if (!SettingUtils.enableLoadUserAppList && !SettingUtils.enableLoadSystemAppList) return if (!SettingUtils.enableLoadUserAppList && !SettingUtils.enableLoadSystemAppList) return
val get = GlobalScope.async(Dispatchers.IO) { if (App.UserAppList.isEmpty() && App.SystemAppList.isEmpty()) {
if ((SettingUtils.enableLoadUserAppList && App.UserAppList.isEmpty()) || (SettingUtils.enableLoadSystemAppList && App.SystemAppList.isEmpty())) { //XToastUtils.info(getString(R.string.loading_app_list))
App.UserAppList.clear() val request = OneTimeWorkRequestBuilder<LoadAppListWorker>().build()
App.SystemAppList.clear() WorkManager.getInstance(XUtil.getContext()).enqueue(request)
val appInfoList = AppUtils.getAppsInfo() return
for (appInfo in appInfoList) { }
if (appInfo.isSystem) {
App.SystemAppList.add(appInfo) appListSpinnerList.clear()
} else { if (SettingUtils.enableLoadUserAppList) {
App.UserAppList.add(appInfo) for (appInfo in App.UserAppList) {
} if (TextUtils.isEmpty(appInfo.packageName)) continue
} appListSpinnerList.add(AppListAdapterItem(appInfo.name, appInfo.icon, appInfo.packageName))
App.UserAppList.sortBy { appInfo -> appInfo.name }
App.SystemAppList.sortBy { appInfo -> appInfo.name }
} }
} }
GlobalScope.launch(Dispatchers.Main) { if (SettingUtils.enableLoadSystemAppList) {
runCatching { for (appInfo in App.SystemAppList) {
get.await() if (TextUtils.isEmpty(appInfo.packageName)) continue
if (App.UserAppList.isEmpty() && App.SystemAppList.isEmpty()) return@runCatching appListSpinnerList.add(AppListAdapterItem(appInfo.name, appInfo.icon, appInfo.packageName))
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 (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
} }
} }

View File

@ -69,6 +69,9 @@ class BatteryService : Service() {
private val batteryReceiver: BroadcastReceiver = object : BroadcastReceiver() { private val batteryReceiver: BroadcastReceiver = object : BroadcastReceiver() {
@SuppressLint("DefaultLocale") @SuppressLint("DefaultLocale")
override fun onReceive(context: Context, intent: Intent) { override fun onReceive(context: Context, intent: Intent) {
if (intent.action != Intent.ACTION_BATTERY_CHANGED) return
//自动删除N天前的转发记录 //自动删除N天前的转发记录
if (SettingUtils.autoCleanLogsDays > 0) { if (SettingUtils.autoCleanLogsDays > 0) {
Log.d(TAG, "自动删除N天前的转发记录") Log.d(TAG, "自动删除N天前的转发记录")
@ -145,21 +148,12 @@ class BatteryService : Service() {
private fun sendMessage(context: Context, msg: String) { private fun sendMessage(context: Context, msg: String) {
Log.i(TAG, msg) Log.i(TAG, msg)
try { try {
val msgInfo = MsgInfo( val msgInfo = MsgInfo("app", "88888888", msg, Date(), getString(R.string.battery_status_monitor), -1)
"app", val request = OneTimeWorkRequestBuilder<SendWorker>().setInputData(
"88888888", workDataOf(
msg, Worker.sendMsgInfo to Gson().toJson(msgInfo),
Date(),
getString(R.string.battery_status_monitor),
-1
)
val request = OneTimeWorkRequestBuilder<SendWorker>()
.setInputData(
workDataOf(
Worker.sendMsgInfo to Gson().toJson(msgInfo),
)
) )
.build() ).build()
WorkManager.getInstance(context).enqueue(request) WorkManager.getInstance(context).enqueue(request)
} catch (e: Exception) { } catch (e: Exception) {
Log.e(TAG, "getLog e:" + e.message) Log.e(TAG, "getLog e:" + e.message)

View File

@ -10,12 +10,16 @@ import android.text.TextUtils
import android.util.Log import android.util.Log
import androidx.core.app.NotificationCompat import androidx.core.app.NotificationCompat
import androidx.lifecycle.Observer import androidx.lifecycle.Observer
import androidx.work.OneTimeWorkRequestBuilder
import androidx.work.WorkManager
import com.idormy.sms.forwarder.App import com.idormy.sms.forwarder.App
import com.idormy.sms.forwarder.R import com.idormy.sms.forwarder.R
import com.idormy.sms.forwarder.activity.MainActivity import com.idormy.sms.forwarder.activity.MainActivity
import com.idormy.sms.forwarder.database.AppDatabase import com.idormy.sms.forwarder.database.AppDatabase
import com.idormy.sms.forwarder.utils.* import com.idormy.sms.forwarder.utils.*
import com.idormy.sms.forwarder.workers.LoadAppListWorker
import com.jeremyliao.liveeventbus.LiveEventBus import com.jeremyliao.liveeventbus.LiveEventBus
import com.xuexiang.xutil.XUtil
import com.xuexiang.xutil.file.FileUtils import com.xuexiang.xutil.file.FileUtils
import frpclib.Frpclib import frpclib.Frpclib
import io.reactivex.Single import io.reactivex.Single
@ -86,6 +90,12 @@ class ForegroundService : Service() {
CommonUtils.toggleNotificationListenerService(this) CommonUtils.toggleNotificationListenerService(this)
} }
//异步获取所有已安装 App 信息
if (SettingUtils.enableLoadAppList) {
val request = OneTimeWorkRequestBuilder<LoadAppListWorker>().build()
WorkManager.getInstance(XUtil.getContext()).enqueue(request)
}
if (FileUtils.isFileExists(filesDir.absolutePath + "/libs/libgojni.so")) { if (FileUtils.isFileExists(filesDir.absolutePath + "/libs/libgojni.so")) {
//监听Frpc启动指令 //监听Frpc启动指令
LiveEventBus.get(INTENT_FRPC_APPLY_FILE, String::class.java).observeStickyForever(frpcObserver) LiveEventBus.get(INTENT_FRPC_APPLY_FILE, String::class.java).observeStickyForever(frpcObserver)

View File

@ -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_3G3g网络, NET_4G4g网络, NET_5G5g网络, NET_WIFIwifi, NET_ETHERNET有线网络, NET_UNKNOWN未知网络
val netStateType = NetworkUtils.getNetStateType()
Log.d(TAG, "netStateType: $netStateType")
val netStateTypeName = when (netStateType) {
NetworkUtils.NetState.NET_NO -> "没有网络"
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<SendWorker>().setInputData(
workDataOf(
Worker.sendMsgInfo to Gson().toJson(msgInfo),
)
).build()
WorkManager.getInstance(context).enqueue(request)
} catch (e: Exception) {
Log.e(TAG, "getLog e:" + e.message)
}
}
companion object {
private const val TAG = "NetworkStateReceiver"
}
}

View File

@ -36,13 +36,11 @@ import com.xuexiang.xui.widget.imageview.preview.PreviewBuilder
import com.xuexiang.xutil.XUtil import com.xuexiang.xutil.XUtil
import com.xuexiang.xutil.common.StringUtils import com.xuexiang.xutil.common.StringUtils
import java.util.regex.Pattern import java.util.regex.Pattern
import kotlin.math.max
import kotlin.math.min
/** /**
* 常用工具类 * 常用工具类
*/ */
@Suppress("RegExpRedundantEscape", "unused") @Suppress("RegExpRedundantEscape", "unused", "RegExpUnnecessaryNonCapturingGroup")
class CommonUtils private constructor() { class CommonUtils private constructor() {
companion object { companion object {
/** /**
@ -161,11 +159,19 @@ class CommonUtils private constructor() {
//焦点位置插入文本 //焦点位置插入文本
fun insertOrReplaceText2Cursor(editText: EditText, str: String) { 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.isFocusable = true
editText.requestFocus() editText.requestFocus()
val start = max(editText.selectionStart, 0) val nSection: Int = editText.selectionStart
val end = max(editText.selectionEnd, 0) editText.text.insert(nSection, str)
editText.text.replace(min(start, end), max(start, end), str, 0, str.length) editText.setSelection(nSection + str.length)
} }
//==========图片预览===========// //==========图片预览===========//

View File

@ -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_SILENT_PERIOD_END = "silent_period_end"
const val SP_AUTO_CLEAN_LOGS_DAYS = "auto_clean_logs_days" 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_RECEIVER = "enable_battery_receiver"
const val SP_BATTERY_STATUS = "battery_status" const val SP_BATTERY_STATUS = "battery_status"
const val SP_BATTERY_LEVEL_MIN = "battery_level_min" 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_TYPE = "key_rule_type"
const val KEY_RULE_CLONE = "key_rule_clone" 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_SIM_SLOT = "EVENT_KEY_SIM_SLOT"
const val EVENT_KEY_PHONE_NUMBERS = "EVENT_KEY_PHONE_NUMBERS" const val EVENT_KEY_PHONE_NUMBERS = "EVENT_KEY_PHONE_NUMBERS"

View File

@ -160,6 +160,7 @@ class HttpServerUtils private constructor() {
cloneInfo.enableLoadUserAppList = SettingUtils.enableLoadUserAppList cloneInfo.enableLoadUserAppList = SettingUtils.enableLoadUserAppList
cloneInfo.enableLoadSystemAppList = SettingUtils.enableLoadSystemAppList cloneInfo.enableLoadSystemAppList = SettingUtils.enableLoadSystemAppList
cloneInfo.duplicateMessagesLimits = SettingUtils.duplicateMessagesLimits cloneInfo.duplicateMessagesLimits = SettingUtils.duplicateMessagesLimits
cloneInfo.enableNetworkStateReceiver = SettingUtils.enableNetworkStateReceiver
cloneInfo.enableBatteryReceiver = SettingUtils.enableBatteryReceiver cloneInfo.enableBatteryReceiver = SettingUtils.enableBatteryReceiver
cloneInfo.batteryLevelMin = SettingUtils.batteryLevelMin cloneInfo.batteryLevelMin = SettingUtils.batteryLevelMin
cloneInfo.batteryLevelMax = SettingUtils.batteryLevelMax cloneInfo.batteryLevelMax = SettingUtils.batteryLevelMax
@ -206,6 +207,7 @@ class HttpServerUtils private constructor() {
SettingUtils.enableLoadUserAppList = cloneInfo.enableLoadUserAppList SettingUtils.enableLoadUserAppList = cloneInfo.enableLoadUserAppList
SettingUtils.enableLoadSystemAppList = cloneInfo.enableLoadSystemAppList SettingUtils.enableLoadSystemAppList = cloneInfo.enableLoadSystemAppList
SettingUtils.duplicateMessagesLimits = cloneInfo.duplicateMessagesLimits SettingUtils.duplicateMessagesLimits = cloneInfo.duplicateMessagesLimits
SettingUtils.enableNetworkStateReceiver = cloneInfo.enableNetworkStateReceiver
SettingUtils.enableBatteryReceiver = cloneInfo.enableBatteryReceiver SettingUtils.enableBatteryReceiver = cloneInfo.enableBatteryReceiver
SettingUtils.batteryLevelMin = cloneInfo.batteryLevelMin SettingUtils.batteryLevelMin = cloneInfo.batteryLevelMin
SettingUtils.batteryLevelMax = cloneInfo.batteryLevelMax SettingUtils.batteryLevelMax = cloneInfo.batteryLevelMax

View File

@ -69,6 +69,9 @@ class SettingUtils private constructor() {
//自动删除N天前的转发记录 //自动删除N天前的转发记录
var autoCleanLogsDays: Int by SharedPreference(SP_AUTO_CLEAN_LOGS_DAYS, 0) 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) var enableBatteryReceiver: Boolean by SharedPreference(SP_BATTERY_RECEIVER, false)

View File

@ -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()
}
}

View File

@ -329,40 +329,47 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:gravity="center_vertical" android:gravity="center_vertical"
android:orientation="horizontal" android:orientation="vertical"
android:paddingEnd="10dp" android:paddingEnd="10dp"
android:visibility="gone" android:visibility="gone"
tools:ignore="RtlSymmetry"> tools:ignore="RtlSymmetry">
<TextView <TextView
android:layout_width="wrap_content" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:ems="2"
android:text="@string/extra_app" android:text="@string/extra_app"
android:textSize="12sp" android:textSize="12sp"
android:textStyle="bold" /> android:textStyle="bold" />
<LinearLayout <com.xuexiang.xui.widget.edittext.materialedittext.MaterialEditText
android:layout_width="0dp" android:id="@+id/et_app_list"
android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="5dp" android:hint="@string/extra_app_hint"
android:layout_weight="1" android:inputType="textMultiLine"
android:orientation="vertical"> app:met_clearButton="true" />
<com.xuexiang.xui.widget.edittext.materialedittext.MaterialEditText <LinearLayout
android:id="@+id/et_app_list" android:id="@+id/layout_sp_app"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="3dp"
android:gravity="center_vertical"
android:orientation="horizontal"
android:visibility="gone">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:hint="@string/extra_app_hint" android:text="@string/choose_app"
android:inputType="textMultiLine" android:textSize="12sp"
app:met_clearButton="true" /> android:textStyle="bold" />
<com.xuexiang.xui.widget.spinner.editspinner.EditSpinner <com.xuexiang.xui.widget.spinner.editspinner.EditSpinner
android:id="@+id/sp_app" android:id="@+id/sp_app"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="3dp" android:layout_marginStart="5dp"
android:visibility="gone"
app:es_hint="@string/choose_app_hint" app:es_hint="@string/choose_app_hint"
app:es_maxLength="20" app:es_maxLength="20"
app:es_maxLine="1" /> app:es_maxLine="1" />
@ -600,6 +607,64 @@
</LinearLayout> </LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="5dp"
android:layout_marginTop="10dp"
android:layout_marginEnd="5dp"
android:gravity="center_vertical"
android:orientation="horizontal">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/network_state_monitor"
android:textStyle="bold" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="10dp"
android:text="@string/network_state_monitor_tips"
android:textSize="10sp"
tools:ignore="SmallSp" />
</LinearLayout>
<LinearLayout
style="@style/settingBarStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/network_state_change_remind"
android:textStyle="bold"
tools:ignore="RelativeOverlap" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/network_state_change_remind_tips"
android:textSize="9sp"
tools:ignore="SmallSp" />
</LinearLayout>
<com.xuexiang.xui.widget.button.switchbutton.SwitchButton
android:id="@+id/sb_network_state_receiver"
style="@style/SwitchButtonStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"

View File

@ -542,6 +542,10 @@
<string name="active_request_tips">Obtain instructions through passive reception or active polling to operate the machine</string> <string name="active_request_tips">Obtain instructions through passive reception or active polling to operate the machine</string>
<string name="httpserver">Local HttpServer</string> <string name="httpserver">Local HttpServer</string>
<string name="httpserver_tips">Available under WiFi network, after startup, other machines in the LAN can directly call the local interface</string> <string name="httpserver_tips">Available under WiFi network, after startup, other machines in the LAN can directly call the local interface</string>
<string name="network_state_monitor">Network State Monitor</string>
<string name="network_state_monitor_tips">[Note] You need to manually create APP forwarding rules, package name: 77777777</string>
<string name="network_state_change_remind">Network State Change Remind</string>
<string name="network_state_change_remind_tips">Send a notification when the network status changes (connection mode/IP change)</string>
<string name="battery_monitor">Battery Monitor</string> <string name="battery_monitor">Battery Monitor</string>
<string name="battery_monitor_tips">[Note] You need to manually create APP forwarding rules, package name: 88888888</string> <string name="battery_monitor_tips">[Note] You need to manually create APP forwarding rules, package name: 88888888</string>
<string name="keep_alive">Keep Alive</string> <string name="keep_alive">Keep Alive</string>
@ -995,4 +999,8 @@
<string name="sender_del">Del Sender</string> <string name="sender_del">Del Sender</string>
<string name="sender_disabled">Sender is disabled</string> <string name="sender_disabled">Sender is disabled</string>
<string name="unknown_sender">Unknown sender</string> <string name="unknown_sender">Unknown sender</string>
<string name="network_type">Network Type</string>
<string name="operator_name">Operator Name</string>
<string name="host_address">Host Address</string>
<string name="loading_app_list">Loading app list async...</string>
</resources> </resources>

View File

@ -543,6 +543,10 @@
<string name="active_request_tips">通过 被动接收 或者 主动轮询 获取指令,从而操作本机</string> <string name="active_request_tips">通过 被动接收 或者 主动轮询 获取指令,从而操作本机</string>
<string name="httpserver">被动接收本地 HttpServer</string> <string name="httpserver">被动接收本地 HttpServer</string>
<string name="httpserver_tips">WiFi网络下可用启动后局域网内其他机器可直接调用本机接口</string> <string name="httpserver_tips">WiFi网络下可用启动后局域网内其他机器可直接调用本机接口</string>
<string name="network_state_monitor">网络状态监控</string>
<string name="network_state_monitor_tips">【注意】需要手动创建APP转发规则包名77777777</string>
<string name="network_state_change_remind">网络状态改变提醒</string>
<string name="network_state_change_remind_tips">网络状态改变(连接方式/IP变化)时发出通知</string>
<string name="battery_monitor">电池监控</string> <string name="battery_monitor">电池监控</string>
<string name="battery_monitor_tips">【注意】需要手动创建APP转发规则包名88888888</string> <string name="battery_monitor_tips">【注意】需要手动创建APP转发规则包名88888888</string>
<string name="keep_alive">保活措施</string> <string name="keep_alive">保活措施</string>
@ -996,4 +1000,8 @@
<string name="sender_del">删除发送通道</string> <string name="sender_del">删除发送通道</string>
<string name="sender_disabled">发送通道已禁用</string> <string name="sender_disabled">发送通道已禁用</string>
<string name="unknown_sender">未知发送通道</string> <string name="unknown_sender">未知发送通道</string>
<string name="network_type">网络类型</string>
<string name="operator_name">运营商</string>
<string name="host_address">本地IP</string>
<string name="loading_app_list">正在异步加载应用列表...</string>
</resources> </resources>