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

优化:已安装App信息列表异步加载机制
pull/286/head
pppscn 2 years ago
parent 032837615f
commit dd798e42cc

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

@ -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<Int, SimInfo> = mutableMapOf()
//已安装App信息
var LoadingAppList = false
var UserAppList: MutableList<AppUtils.AppInfo> = mutableListOf()
var SystemAppList: MutableList<AppUtils.AppInfo> = 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))

@ -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")

@ -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<FragmentAppListBinding?>() {
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<AppUtils.AppInfo> {
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<FragmentAppListBinding?>() {
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<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
}
}

@ -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<FragmentRulesEditBinding?>(), View.OnClic
//已安装App信息列表
private val appListSpinnerList = ArrayList<AppListAdapterItem>()
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<FragmentRulesEditBinding?>(), 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<FragmentRulesEditBinding?>(), View.OnClic
initForm()
}
//初始化APP下拉列表
initAppSpinner()
}
override fun initListeners() {
@ -408,68 +419,48 @@ class RulesEditFragment : BaseFragment<FragmentRulesEditBinding?>(), 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<LoadAppListWorker>().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))
}
}
if (SettingUtils.enableLoadSystemAppList) {
for (appInfo in App.SystemAppList) {
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
//列表为空也不显示下拉框
if (appListSpinnerList.isEmpty()) return
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())
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
}
//初始化表单

@ -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<FragmentSettingsBinding?>(), View.OnClickListener {
@ -63,6 +65,10 @@ class SettingsFragment : BaseFragment<FragmentSettingsBinding?>(), View.OnClickL
//已安装App信息列表
private val appListSpinnerList = ArrayList<AppListAdapterItem>()
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<FragmentSettingsBinding?>(), View.OnClickL
SettingUtils.autoCleanLogsDays = newValue
}
//监听网络状态变化
switchNetworkStateReceiver(binding!!.sbNetworkStateReceiver)
//监听电池状态变化
switchBatteryReceiver(binding!!.sbBatteryReceiver)
//电量预警
@ -157,7 +166,10 @@ class SettingsFragment : BaseFragment<FragmentSettingsBinding?>(), View.OnClickL
//纯客户端模式
switchDirectlyToClient(binding!!.sbDirectlyToClient)
}
override fun onResume() {
super.onResume()
//初始化APP下拉列表
initAppSpinner()
}
@ -172,6 +184,10 @@ class SettingsFragment : BaseFragment<FragmentSettingsBinding?>(), 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<FragmentSettingsBinding?>(), 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<FragmentSettingsBinding?>(), View.OnClickL
return@setOnCheckedChangeListener
}
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.setOnCheckedChangeListener { _: SmoothCheckBox, isChecked: Boolean ->
@ -496,6 +514,13 @@ class SettingsFragment : BaseFragment<FragmentSettingsBinding?>(), 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<LoadAppListWorker>().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<FragmentSettingsBinding?>(), 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<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 ->
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<FragmentSettingsBinding?>(), 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<FragmentSettingsBinding?>(), 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<LoadAppListWorker>().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))
}
}
if (SettingUtils.enableLoadSystemAppList) {
for (appInfo in App.SystemAppList) {
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 (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
}
}

@ -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<SendWorker>()
.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<SendWorker>().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)

@ -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<LoadAppListWorker>().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)

@ -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"
}
}

@ -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)
}
//==========图片预览===========//

@ -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"

@ -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

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

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

@ -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">
<TextView
android:layout_width="wrap_content"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ems="2"
android:text="@string/extra_app"
android:textSize="12sp"
android:textStyle="bold" />
<com.xuexiang.xui.widget.edittext.materialedittext.MaterialEditText
android:id="@+id/et_app_list"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/extra_app_hint"
android:inputType="textMultiLine"
app:met_clearButton="true" />
<LinearLayout
android:layout_width="0dp"
android:id="@+id/layout_sp_app"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="5dp"
android:layout_weight="1"
android:orientation="vertical">
android:layout_marginTop="3dp"
android:gravity="center_vertical"
android:orientation="horizontal"
android:visibility="gone">
<com.xuexiang.xui.widget.edittext.materialedittext.MaterialEditText
android:id="@+id/et_app_list"
android:layout_width="match_parent"
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:hint="@string/extra_app_hint"
android:inputType="textMultiLine"
app:met_clearButton="true" />
android:text="@string/choose_app"
android:textSize="12sp"
android:textStyle="bold" />
<com.xuexiang.xui.widget.spinner.editspinner.EditSpinner
android:id="@+id/sp_app"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="3dp"
android:visibility="gone"
android:layout_marginStart="5dp"
app:es_hint="@string/choose_app_hint"
app:es_maxLength="20"
app:es_maxLine="1" />
@ -600,6 +607,64 @@
</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
android:layout_width="match_parent"
android:layout_height="wrap_content"

@ -542,6 +542,10 @@
<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_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_tips">[Note] You need to manually create APP forwarding rules, package name: 88888888</string>
<string name="keep_alive">Keep Alive</string>
@ -995,4 +999,8 @@
<string name="sender_del">Del Sender</string>
<string name="sender_disabled">Sender is disabled</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>

@ -543,6 +543,10 @@
<string name="active_request_tips">通过 被动接收 或者 主动轮询 获取指令,从而操作本机</string>
<string name="httpserver">被动接收本地 HttpServer</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_tips">【注意】需要手动创建APP转发规则包名88888888</string>
<string name="keep_alive">保活措施</string>
@ -996,4 +1000,8 @@
<string name="sender_del">删除发送通道</string>
<string name="sender_disabled">发送通道已禁用</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>

Loading…
Cancel
Save