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 3b8f6fd4..fc932202 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 @@ -37,10 +37,7 @@ import io.reactivex.SingleObserver import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.disposables.Disposable import io.reactivex.schedulers.Schedulers -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.GlobalScope -import kotlinx.coroutines.async -import kotlinx.coroutines.launch +import kotlinx.coroutines.* import java.util.* @Page(name = "转发规则·编辑器") @@ -411,6 +408,7 @@ class RulesEditFragment : BaseFragment(), View.OnClic } //初始化APP下拉列表 + @OptIn(DelicateCoroutinesApi::class) private fun initAppSpinner() { if (ruleType != "app") return 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 26796760..644f51cb 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 @@ -16,16 +16,15 @@ import android.util.Log import android.view.LayoutInflater import android.view.View import android.view.ViewGroup -import android.widget.CompoundButton -import android.widget.EditText -import android.widget.LinearLayout -import android.widget.TextView +import android.widget.* import androidx.annotation.RequiresApi import com.hjq.permissions.OnPermissionCallback import com.hjq.permissions.Permission import com.hjq.permissions.XXPermissions import com.idormy.sms.forwarder.App import com.idormy.sms.forwarder.R +import com.idormy.sms.forwarder.adapter.spinner.AppListAdapterItem +import com.idormy.sms.forwarder.adapter.spinner.AppListSpinnerAdapter import com.idormy.sms.forwarder.core.BaseFragment import com.idormy.sms.forwarder.databinding.FragmentSettingsBinding import com.idormy.sms.forwarder.entity.SimInfo @@ -47,8 +46,10 @@ 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.* @@ -59,6 +60,10 @@ class SettingsFragment : BaseFragment(), View.OnClickL val TAG: String = SettingsFragment::class.java.simpleName private val mTimeOption = DataProvider.timePeriodOption + //已安装App信息列表 + private val appListSpinnerList = ArrayList() + private lateinit var appListSpinnerAdapter: AppListSpinnerAdapter<*> + override fun viewBindingInflate( inflater: LayoutInflater, container: ViewGroup, @@ -86,6 +91,8 @@ class SettingsFragment : BaseFragment(), View.OnClickL switchEnableAppNotify( binding!!.sbEnableAppNotify, binding!!.scbCancelAppNotify, binding!!.scbNotUserPresent ) + //设置自动消除额外APP通知 + editExtraAppList(binding!!.etAppList) //启动时异步获取已安装App信息 switchEnableLoadAppList( binding!!.sbEnableLoadAppList, binding!!.scbLoadUserApp, binding!!.scbLoadSystemApp @@ -150,6 +157,9 @@ class SettingsFragment : BaseFragment(), View.OnClickL //纯客户端模式 switchDirectlyToClient(binding!!.sbDirectlyToClient) + + //初始化APP下拉列表 + initAppSpinner() } override fun initListeners() { @@ -409,13 +419,17 @@ class SettingsFragment : BaseFragment(), View.OnClickL fun switchEnableAppNotify( sbEnableAppNotify: SwitchButton, scbCancelAppNotify: SmoothCheckBox, scbNotUserPresent: SmoothCheckBox ) { - val layoutOptionalAction: LinearLayout = binding!!.layoutOptionalAction val isEnable: Boolean = SettingUtils.enableAppNotify sbEnableAppNotify.isChecked = isEnable + + val layoutOptionalAction: LinearLayout = binding!!.layoutOptionalAction layoutOptionalAction.visibility = if (isEnable) View.VISIBLE else View.GONE + val layoutAppList: LinearLayout = binding!!.layoutAppList + layoutAppList.visibility = if (isEnable) View.VISIBLE else View.GONE sbEnableAppNotify.setOnCheckedChangeListener { _: CompoundButton?, isChecked: Boolean -> layoutOptionalAction.visibility = if (isChecked) View.VISIBLE else View.GONE + layoutAppList.visibility = if (isChecked) View.VISIBLE else View.GONE SettingUtils.enableAppNotify = isChecked if (isChecked) { //检查权限是否获取 @@ -454,7 +468,19 @@ class SettingsFragment : BaseFragment(), View.OnClickL } } - //启动时异步获取已安装App信息 (binding!!.sbEnableLoadAppList, binding!!.scbLoadUserApp, binding!!.scbLoadSystemApp) + //设置自动消除额外APP通知 + private fun editExtraAppList(textAppList: EditText) { + textAppList.setText(SettingUtils.cancelExtraAppNotify) + textAppList.addTextChangedListener(object : TextWatcher { + override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {} + override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {} + override fun afterTextChanged(s: Editable) { + SettingUtils.cancelExtraAppNotify = textAppList.text.toString().trim().removeSuffix("\n") + } + }) + } + + //启动时异步获取已安装App信息 @SuppressLint("UseSwitchCompatOrMaterialCode") fun switchEnableLoadAppList( sbEnableLoadAppList: SwitchButton, scbLoadUserApp: SmoothCheckBox, scbLoadSystemApp: SmoothCheckBox @@ -1059,4 +1085,69 @@ class SettingsFragment : BaseFragment(), View.OnClickL } } + //初始化APP下拉列表 + @OptIn(DelicateCoroutinesApi::class) + private fun initAppSpinner() { + if (!SettingUtils.enableAppNotify) 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 } + } + } + 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()) + } + } + } + } \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/service/NotifyService.kt b/app/src/main/java/com/idormy/sms/forwarder/service/NotifyService.kt index 278bbfc8..b28eb50a 100644 --- a/app/src/main/java/com/idormy/sms/forwarder/service/NotifyService.kt +++ b/app/src/main/java/com/idormy/sms/forwarder/service/NotifyService.kt @@ -1,119 +1,132 @@ -package com.idormy.sms.forwarder.service - -import android.content.ComponentName -import android.os.Build -import android.service.notification.NotificationListenerService -import android.service.notification.StatusBarNotification -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.core.Core -import com.idormy.sms.forwarder.database.entity.Rule -import com.idormy.sms.forwarder.entity.MsgInfo -import com.idormy.sms.forwarder.utils.PACKAGE_NAME -import com.idormy.sms.forwarder.utils.SettingUtils -import com.idormy.sms.forwarder.utils.Worker -import com.idormy.sms.forwarder.workers.SendWorker -import com.xuexiang.xrouter.utils.TextUtils -import com.xuexiang.xutil.display.ScreenUtils -import java.util.* - - -@Suppress("PrivatePropertyName", "DEPRECATION") -class NotifyService : NotificationListenerService() { - - private val TAG: String = "NotifyService" - - override fun onListenerConnected() { - Log.d(TAG, "onListenerConnected") - } - - override fun onListenerDisconnected() { - //纯客户端模式 - if (SettingUtils.enablePureClientMode) return - - //总开关 - if (!SettingUtils.enableAppNotify) return - - Log.d(TAG, "通知侦听器断开连接 - 请求重新绑定") - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { - requestRebind(ComponentName(this, NotificationListenerService::class.java)) - } - } - - override fun onNotificationPosted(sbn: StatusBarNotification?) { - try { - //纯客户端模式 - if (SettingUtils.enablePureClientMode) return - - //总开关 - if (!SettingUtils.enableAppNotify) return - - //异常通知跳过 - if (sbn!!.notification == null) return - if (sbn.notification.extras == null) return - - //仅锁屏状态转发APP通知 - if (SettingUtils.enableNotUserPresent && !ScreenUtils.isScreenLock()) return - - val from = sbn.packageName - //自身通知跳过 - if (PACKAGE_NAME == sbn.packageName) return - - //通知标题 - var title = "" - if (sbn.notification.extras["android.title"] != null) { - title = sbn.notification.extras["android.title"].toString() - } - //通知内容 - var text = "" - if (sbn.notification.extras["android.text"] != null) { - text = sbn.notification.extras["android.text"].toString() - } - if (text.isEmpty() && sbn.notification.tickerText != null) { - text = sbn.notification.tickerText.toString() - } - - //不处理空消息(标题跟内容都为空) - if (TextUtils.isEmpty(title) && TextUtils.isEmpty(text)) return - - val msgInfo = MsgInfo("app", from, text, Date(), title, -1) - - //TODO:自动消除通知(临时方案,重复查询换取准确性) - if (SettingUtils.enableCancelAppNotify) { - val ruleList: List = Core.rule.getRuleList(msgInfo.type, 1, "SIM0") - for (rule in ruleList) { - if (rule.checkMsg(msgInfo)) { - Log.d(TAG, "自动消除通知") - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - cancelNotification(sbn.key) - } else { - cancelNotification(sbn.packageName, sbn.tag, sbn.id) - } - break - } - } - } - - val request = OneTimeWorkRequestBuilder() - .setInputData( - workDataOf( - Worker.sendMsgInfo to Gson().toJson(msgInfo), - ) - ) - .build() - WorkManager.getInstance(applicationContext).enqueue(request) - - } catch (e: Exception) { - Log.e(TAG, "Parsing Notification failed: " + e.message.toString()) - } - - } - - override fun onNotificationRemoved(sbn: StatusBarNotification?) { - Log.d(TAG, "Removed Package Name : ${sbn?.packageName}") - } - +package com.idormy.sms.forwarder.service + +import android.content.ComponentName +import android.os.Build +import android.service.notification.NotificationListenerService +import android.service.notification.StatusBarNotification +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.core.Core +import com.idormy.sms.forwarder.database.entity.Rule +import com.idormy.sms.forwarder.entity.MsgInfo +import com.idormy.sms.forwarder.utils.PACKAGE_NAME +import com.idormy.sms.forwarder.utils.SettingUtils +import com.idormy.sms.forwarder.utils.Worker +import com.idormy.sms.forwarder.workers.SendWorker +import com.xuexiang.xrouter.utils.TextUtils +import com.xuexiang.xutil.display.ScreenUtils +import java.util.* + + +@Suppress("PrivatePropertyName", "DEPRECATION") +class NotifyService : NotificationListenerService() { + + private val TAG: String = "NotifyService" + + override fun onListenerConnected() { + Log.d(TAG, "onListenerConnected") + } + + override fun onListenerDisconnected() { + //纯客户端模式 + if (SettingUtils.enablePureClientMode) return + + //总开关 + if (!SettingUtils.enableAppNotify) return + + Log.d(TAG, "通知侦听器断开连接 - 请求重新绑定") + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + requestRebind(ComponentName(this, NotificationListenerService::class.java)) + } + } + + override fun onNotificationPosted(sbn: StatusBarNotification?) { + try { + //纯客户端模式 + if (SettingUtils.enablePureClientMode) return + + //总开关 + if (!SettingUtils.enableAppNotify) return + + //异常通知跳过 + if (sbn!!.notification == null) return + if (sbn.notification.extras == null) return + + //自动消除额外APP通知 + if (!TextUtils.isEmpty(SettingUtils.cancelExtraAppNotify)) { + for (app in SettingUtils.cancelExtraAppNotify.split("\n")) { + if (sbn.packageName == app.trim()) { + Log.d(TAG, "自动消除额外APP通知:$app") + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + cancelNotification(sbn.key) + } else { + cancelNotification(sbn.packageName, sbn.tag, sbn.id) + } + break + } + } + } + + //仅锁屏状态转发APP通知 + if (SettingUtils.enableNotUserPresent && !ScreenUtils.isScreenLock()) return + + val from = sbn.packageName + //自身通知跳过 + if (PACKAGE_NAME == sbn.packageName) return + + //通知标题 + var title = "" + if (sbn.notification.extras["android.title"] != null) { + title = sbn.notification.extras["android.title"].toString() + } + //通知内容 + var text = "" + if (sbn.notification.extras["android.text"] != null) { + text = sbn.notification.extras["android.text"].toString() + } + if (text.isEmpty() && sbn.notification.tickerText != null) { + text = sbn.notification.tickerText.toString() + } + + //不处理空消息(标题跟内容都为空) + if (TextUtils.isEmpty(title) && TextUtils.isEmpty(text)) return + + val msgInfo = MsgInfo("app", from, text, Date(), title, -1) + + //TODO:自动消除通知(临时方案,重复查询换取准确性) + if (SettingUtils.enableCancelAppNotify) { + val ruleList: List = Core.rule.getRuleList(msgInfo.type, 1, "SIM0") + for (rule in ruleList) { + if (rule.checkMsg(msgInfo)) { + Log.d(TAG, "自动消除通知") + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + cancelNotification(sbn.key) + } else { + cancelNotification(sbn.packageName, sbn.tag, sbn.id) + } + break + } + } + } + + val request = OneTimeWorkRequestBuilder().setInputData( + workDataOf( + Worker.sendMsgInfo to Gson().toJson(msgInfo), + ) + ).build() + WorkManager.getInstance(applicationContext).enqueue(request) + + } catch (e: Exception) { + Log.e(TAG, "Parsing Notification failed: " + e.message.toString()) + } + + } + + override fun onNotificationRemoved(sbn: StatusBarNotification?) { + Log.d(TAG, "Removed Package Name : ${sbn?.packageName}") + } + } \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/utils/Constants.kt b/app/src/main/java/com/idormy/sms/forwarder/utils/Constants.kt index 3aefd415..8dda3cca 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 @@ -38,6 +38,7 @@ const val SP_ENABLE_CALL_TYPE_6 = "enable_call_type_6" const val SP_ENABLE_APP_NOTIFY = "enable_app_notify" const val SP_ENABLE_CANCEL_APP_NOTIFY = "enable_cancel_app_notify" +const val SP_CANCEL_EXTRA_APP_NOTIFY = "cancel_extra_app_notify" const val SP_ENABLE_NOT_USER_PRESENT = "enable_not_user_present" const val ENABLE_LOAD_APP_LIST = "enable_load_app_list" 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 671bdee2..6015a575 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 @@ -40,7 +40,10 @@ class SettingUtils private constructor() { var enableAppNotify: Boolean by SharedPreference(SP_ENABLE_APP_NOTIFY, false) //是否转发应用通知——自动消除通知 - var enableCancelAppNotify: Boolean by SharedPreference(SP_ENABLE_CANCEL_APP_NOTIFY, false) + var enableCancelAppNotify: Boolean by SharedPreference(SP_CANCEL_EXTRA_APP_NOTIFY, false) + + //是否转发应用通知——自动消除额外APP通知 + var cancelExtraAppNotify: String by SharedPreference(SP_BATTERY_CRON_START_TIME, "") //是否转发应用通知——仅锁屏状态 var enableNotUserPresent: Boolean by SharedPreference(SP_ENABLE_NOT_USER_PRESENT, false) diff --git a/app/src/main/res/layout/fragment_settings.xml b/app/src/main/res/layout/fragment_settings.xml index 04d853ec..32f09872 100644 --- a/app/src/main/res/layout/fragment_settings.xml +++ b/app/src/main/res/layout/fragment_settings.xml @@ -101,6 +101,7 @@ @@ -240,81 +241,135 @@ + android:layout_height="wrap_content" + android:orientation="vertical"> - - - - + android:orientation="horizontal"> + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_weight="1" + android:orientation="vertical"> - - + tools:ignore="RelativeOverlap" /> - + - + + + + + + + + + + + + + + + + + + + + + + + + + android:hint="@string/extra_app_hint" + android:inputType="textMultiLine" + app:met_clearButton="true" /> + + - - Group Robot → Security Settings → Signature Verification Please go to the corresponding official website to obtain Drop-down selection, keyword fuzzy match - Installed apps + Installed Apps + Extra Apps + One package name per line\nEnable async loading of the App list for selection. Drop-down selection to get package name, keyword fuzzy matching APP name ^\\s*(AND|OR)\\s(IS|NOTIS)\\s(PHONE_NUM|PACKAGE_NAME|MSG_CONTENT|INFORM_CONTENT|INFORM_TITLE|CARD_SLOT)\\s(EQUALS|CONTAIN|NOTCONTAIN|STARTWITH|ENDWITH|REGEX)\\s(.*)$ Welcome to diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index b8a4a915..563dc088 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -723,6 +723,8 @@ 请前往对应的官网地址获取 下拉选择,关键字模糊匹配 已装APP列表 + 额外消除应用通知 + 一行一个包名\n开启异步加载App列表以便选择 下拉选择获取包名,关键字模糊匹配APP名称 ^\\s*(并且|或者)\\s(是|不是)\\s(手机号|APP包名|短信内容|通知内容|通知标题|卡槽信息)\\s(相等|包含|不包含|开头|结尾|正则匹配)\\s(.*)$ 欢迎使用 diff --git a/gradle.properties b/gradle.properties index 1f8a0f8b..23e0fb3c 100644 --- a/gradle.properties +++ b/gradle.properties @@ -18,4 +18,4 @@ isUseBooster=false android.precompileDependenciesResources=false android.useAndroidX=true android.enableJetifier=true -android.enableD8=true +#android.enableD8=true