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