新增:自定义模板可用标签 {{定位信息}}(英文系统:{{LOCATION}}#341 #343

This commit is contained in:
pppscn 2023-11-22 11:29:23 +08:00
parent 5741cdfe96
commit 3dd2e41123
14 changed files with 298 additions and 205 deletions

View File

@ -112,12 +112,12 @@ class App : Application(), CactusCallback, Configuration.Provider by Core {
} }
//启动前台服务 //启动前台服务
Intent(this, ForegroundService::class.java).also { val serviceIntent = Intent(this, ForegroundService::class.java)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { serviceIntent.action = "START"
startForegroundService(it) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
} else { startForegroundService(serviceIntent)
startService(it) } else {
} startService(serviceIntent)
} }
//网络状态监听 //网络状态监听

View File

@ -104,12 +104,12 @@ class MainActivity : BaseActivity<ActivityMainBinding?>(),
//启动前台服务 //启动前台服务
if (!ForegroundService.isRunning) { if (!ForegroundService.isRunning) {
Intent(this, ForegroundService::class.java).also { val serviceIntent = Intent(this, ForegroundService::class.java)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { serviceIntent.action = "START"
startForegroundService(it) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
} else { startForegroundService(serviceIntent)
startService(it) } else {
} startService(serviceIntent)
} }
} }
}) })
@ -204,6 +204,7 @@ class MainActivity : BaseActivity<ActivityMainBinding?>(),
.show() .show()
} }
} }
R.id.nav_app_list -> openNewPage(AppListFragment::class.java) R.id.nav_app_list -> openNewPage(AppListFragment::class.java)
//R.id.nav_logcat -> openNewPage(LogcatFragment::class.java) //R.id.nav_logcat -> openNewPage(LogcatFragment::class.java)
R.id.nav_help -> AgentWebActivity.goWeb(this, getString(R.string.url_help)) R.id.nav_help -> AgentWebActivity.goWeb(this, getString(R.string.url_help))
@ -232,12 +233,15 @@ class MainActivity : BaseActivity<ActivityMainBinding?>(),
getString(R.string.menu_rules) -> binding!!.includeMain.toolbar.inflateMenu( getString(R.string.menu_rules) -> binding!!.includeMain.toolbar.inflateMenu(
R.menu.menu_rules R.menu.menu_rules
) )
getString(R.string.menu_senders) -> binding!!.includeMain.toolbar.inflateMenu( getString(R.string.menu_senders) -> binding!!.includeMain.toolbar.inflateMenu(
R.menu.menu_senders R.menu.menu_senders
) )
getString(R.string.menu_settings) -> binding!!.includeMain.toolbar.inflateMenu( getString(R.string.menu_settings) -> binding!!.includeMain.toolbar.inflateMenu(
R.menu.menu_settings R.menu.menu_settings
) )
else -> binding!!.includeMain.toolbar.inflateMenu(R.menu.menu_logs) else -> binding!!.includeMain.toolbar.inflateMenu(R.menu.menu_logs)
} }
item.isChecked = true item.isChecked = true
@ -255,13 +259,6 @@ class MainActivity : BaseActivity<ActivityMainBinding?>(),
LiveEventBus.get(EVENT_UPDATE_RULE_TYPE, String::class.java).observe(this) { type: String -> LiveEventBus.get(EVENT_UPDATE_RULE_TYPE, String::class.java).observe(this) { type: String ->
ruleType = type ruleType = type
} }
//更新通知栏文案
LiveEventBus.get(EVENT_UPDATE_NOTIFY, String::class.java).observe(this) { notify: String ->
cactusUpdateNotification {
setContent(notify)
}
}
} }
/** /**
@ -287,6 +284,7 @@ class MainActivity : BaseActivity<ActivityMainBinding?>(),
R.id.action_notifications -> { R.id.action_notifications -> {
showTipsForce(this) showTipsForce(this)
} }
R.id.action_clear_logs -> { R.id.action_clear_logs -> {
MaterialDialog.Builder(this) MaterialDialog.Builder(this)
.content(R.string.delete_type_log_tips) .content(R.string.delete_type_log_tips)
@ -311,6 +309,7 @@ class MainActivity : BaseActivity<ActivityMainBinding?>(),
} }
.show() .show()
} }
R.id.action_add_sender -> { R.id.action_add_sender -> {
val dialog = BottomSheetDialog(this) val dialog = BottomSheetDialog(this)
val view: View = val view: View =
@ -328,6 +327,7 @@ class MainActivity : BaseActivity<ActivityMainBinding?>(),
dialog.show() dialog.show()
WidgetUtils.transparentBottomSheetDialogBackground(dialog) WidgetUtils.transparentBottomSheetDialogBackground(dialog)
} }
R.id.action_add_rule -> { R.id.action_add_rule -> {
PageOption.to(RulesEditFragment::class.java) PageOption.to(RulesEditFragment::class.java)
.putString(KEY_RULE_TYPE, ruleType) .putString(KEY_RULE_TYPE, ruleType)

View File

@ -19,28 +19,6 @@ object Core : Configuration.Provider {
val logs: LogsRepository by lazy { (app as App).logsRepository } val logs: LogsRepository by lazy { (app as App).logsRepository }
val rule: RuleRepository by lazy { (app as App).ruleRepository } val rule: RuleRepository by lazy { (app as App).ruleRepository }
val sender: SenderRepository by lazy { (app as App).senderRepository } val sender: SenderRepository by lazy { (app as App).senderRepository }
/*
val telephonyManager: TelephonyManager by lazy { app.getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager }
val smsManager: SmsManager by lazy { app.getSystemService(SmsManager::class.java) }
val subscriptionManager: SubscriptionManager by lazy {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) {
SubscriptionManager.from(app)
} else {
app.getSystemService(SubscriptionManager::class.java)
}
}
val user by lazy { app.getSystemService<UserManager>()!! }*/
/*val directBootAware: Boolean get() = directBootSupported && dataStore.canToggleLocked
val directBootSupported by lazy {
Build.VERSION.SDK_INT >= 24 && try {
app.getSystemService<DevicePolicyManager>()?.storageEncryptionStatus ==
DevicePolicyManager.ENCRYPTION_STATUS_ACTIVE_PER_USER
} catch (_: RuntimeException) {
false
}
}*/
fun init(app: Application) { fun init(app: Application) {
this.app = app this.app = app
@ -54,6 +32,4 @@ object Core : Configuration.Provider {
setTaskExecutor { (app as App).applicationScope.launch { it.run() } } setTaskExecutor { (app as App).applicationScope.launch { it.run() } }
}.build() }.build()
} }
fun startService() = ContextCompat.startForegroundService(app, Intent(app, ForegroundService::class.java))
} }

View File

@ -6,6 +6,7 @@ import android.util.Log
import com.idormy.sms.forwarder.App import com.idormy.sms.forwarder.App
import com.idormy.sms.forwarder.R import com.idormy.sms.forwarder.R
import com.idormy.sms.forwarder.utils.CALL_TYPE_MAP import com.idormy.sms.forwarder.utils.CALL_TYPE_MAP
import com.idormy.sms.forwarder.utils.HttpServerUtils
import com.idormy.sms.forwarder.utils.SettingUtils import com.idormy.sms.forwarder.utils.SettingUtils
import com.idormy.sms.forwarder.utils.SettingUtils.Companion.enableSmsTemplate import com.idormy.sms.forwarder.utils.SettingUtils.Companion.enableSmsTemplate
import com.idormy.sms.forwarder.utils.SettingUtils.Companion.extraDeviceMark import com.idormy.sms.forwarder.utils.SettingUtils.Companion.extraDeviceMark
@ -58,7 +59,7 @@ data class MsgInfo(
.replace(getString(R.string.tag_app_version), versionName) .replace(getString(R.string.tag_app_version), versionName)
.replace(getString(R.string.tag_call_type), CALL_TYPE_MAP[callType.toString()] ?: getString(R.string.unknown_call)) .replace(getString(R.string.tag_call_type), CALL_TYPE_MAP[callType.toString()] ?: getString(R.string.unknown_call))
.trim() .trim()
return replaceAppName(regexReplace(titleForSend, regexReplace), from) return replaceLocationTag(replaceAppName(regexReplace(titleForSend, regexReplace), from))
} }
val smsVoForSend: String val smsVoForSend: String
@ -106,7 +107,7 @@ data class MsgInfo(
.replace(getString(R.string.tag_app_version), versionName) .replace(getString(R.string.tag_app_version), versionName)
.replace(getString(R.string.tag_call_type), CALL_TYPE_MAP[callType.toString()] ?: getString(R.string.unknown_call)) .replace(getString(R.string.tag_call_type), CALL_TYPE_MAP[callType.toString()] ?: getString(R.string.unknown_call))
.trim() .trim()
return replaceAppName(regexReplace(smsVoForSend, regexReplace), from) return replaceLocationTag(replaceAppName(regexReplace(smsVoForSend, regexReplace), from))
} }
//正则替换内容 //正则替换内容
@ -154,6 +155,15 @@ data class MsgInfo(
return content.replace(getString(R.string.tag_app_name), appName) return content.replace(getString(R.string.tag_app_name), appName)
} }
//替换 {{定位信息}} 标签
private fun replaceLocationTag(content: String): String {
if (TextUtils.isEmpty(content)) return content
if (content.indexOf(getString(R.string.tag_location)) == -1) return content
val location = HttpServerUtils.apiLocationCache.toString()
return content.replace(getString(R.string.tag_location), location)
}
override fun toString(): String { override fun toString(): String {
return "MsgInfo{" + return "MsgInfo{" +
"mobile='" + from + '\'' + "mobile='" + from + '\'' +

View File

@ -138,12 +138,12 @@ class FrpcFragment : BaseFragment<FragmentFrpcsBinding?>(), FrpcPagingAdapter.On
R.id.iv_play -> { R.id.iv_play -> {
if (!ForegroundService.isRunning) { if (!ForegroundService.isRunning) {
Intent(requireContext(), ForegroundService::class.java).also { val serviceIntent = Intent(requireContext(), ForegroundService::class.java)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { serviceIntent.action = "START"
requireContext().startForegroundService(it) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
} else { requireContext().startForegroundService(serviceIntent)
requireContext().startService(it) } else {
} requireContext().startService(serviceIntent)
} }
} }

View File

@ -471,7 +471,7 @@ class ServerFragment : BaseFragment<FragmentServerBinding?>(), View.OnClickListe
}) })
} }
//联系人权限 //定位权限
private fun checkLocationPermission() { private fun checkLocationPermission() {
XXPermissions.with(this).permission(Permission.ACCESS_COARSE_LOCATION).permission(Permission.ACCESS_FINE_LOCATION).permission(Permission.ACCESS_BACKGROUND_LOCATION).request(object : OnPermissionCallback { XXPermissions.with(this).permission(Permission.ACCESS_COARSE_LOCATION).permission(Permission.ACCESS_FINE_LOCATION).permission(Permission.ACCESS_BACKGROUND_LOCATION).request(object : OnPermissionCallback {
override fun onGranted(permissions: List<String>, all: Boolean) { override fun onGranted(permissions: List<String>, all: Boolean) {

View File

@ -18,6 +18,7 @@ import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.* import android.widget.*
import androidx.annotation.RequiresApi import androidx.annotation.RequiresApi
import androidx.core.content.ContextCompat
import androidx.lifecycle.Observer import androidx.lifecycle.Observer
import androidx.work.OneTimeWorkRequestBuilder import androidx.work.OneTimeWorkRequestBuilder
import androidx.work.WorkManager import androidx.work.WorkManager
@ -32,6 +33,7 @@ import com.idormy.sms.forwarder.core.BaseFragment
import com.idormy.sms.forwarder.databinding.FragmentSettingsBinding import com.idormy.sms.forwarder.databinding.FragmentSettingsBinding
import com.idormy.sms.forwarder.entity.SimInfo import com.idormy.sms.forwarder.entity.SimInfo
import com.idormy.sms.forwarder.receiver.BootReceiver import com.idormy.sms.forwarder.receiver.BootReceiver
import com.idormy.sms.forwarder.service.ForegroundService
import com.idormy.sms.forwarder.utils.* import com.idormy.sms.forwarder.utils.*
import com.idormy.sms.forwarder.workers.LoadAppListWorker import com.idormy.sms.forwarder.workers.LoadAppListWorker
import com.jeremyliao.liveeventbus.LiveEventBus import com.jeremyliao.liveeventbus.LiveEventBus
@ -172,6 +174,9 @@ class SettingsFragment : BaseFragment<FragmentSettingsBinding?>(), View.OnClickL
//纯客户端模式 //纯客户端模式
switchDirectlyToClient(binding!!.sbDirectlyToClient) switchDirectlyToClient(binding!!.sbDirectlyToClient)
//启用 {{定位信息}} 标签
switchEnableLocationTag(binding!!.sbEnableLocationTag)
} }
override fun onResume() { override fun onResume() {
@ -918,21 +923,25 @@ class SettingsFragment : BaseFragment<FragmentSettingsBinding?>(), View.OnClickL
override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {} override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {}
override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {} override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {}
override fun afterTextChanged(s: Editable) { override fun afterTextChanged(s: Editable) {
SettingUtils.notifyContent = etNotifyContent.text.toString().trim() val notifyContent = etNotifyContent.text.toString().trim()
LiveEventBus.get(EVENT_UPDATE_NOTIFY, String::class.java).post(SettingUtils.notifyContent) SettingUtils.notifyContent = notifyContent
val updateIntent = Intent(context, ForegroundService::class.java)
updateIntent.action = "UPDATE_NOTIFICATION"
updateIntent.putExtra("UPDATED_CONTENT", notifyContent)
context?.let { ContextCompat.startForegroundService(it, updateIntent) }
} }
}) })
} }
//设置转发时启用自定义模版 //设置转发时启用自定义模版
@SuppressLint("UseSwitchCompatOrMaterialCode", "SetTextI18n") @SuppressLint("UseSwitchCompatOrMaterialCode", "SetTextI18n")
fun switchSmsTemplate(sb_sms_template: SwitchButton) { fun switchSmsTemplate(sbSmsTemplate: SwitchButton) {
val isOn: Boolean = SettingUtils.enableSmsTemplate val isOn: Boolean = SettingUtils.enableSmsTemplate
sb_sms_template.isChecked = isOn sbSmsTemplate.isChecked = isOn
val layoutSmsTemplate: LinearLayout = binding!!.layoutSmsTemplate val layoutSmsTemplate: LinearLayout = binding!!.layoutSmsTemplate
layoutSmsTemplate.visibility = if (isOn) View.VISIBLE else View.GONE layoutSmsTemplate.visibility = if (isOn) View.VISIBLE else View.GONE
val etSmsTemplate: EditText = binding!!.etSmsTemplate val etSmsTemplate: EditText = binding!!.etSmsTemplate
sb_sms_template.setOnCheckedChangeListener { _: CompoundButton?, isChecked: Boolean -> sbSmsTemplate.setOnCheckedChangeListener { _: CompoundButton?, isChecked: Boolean ->
layoutSmsTemplate.visibility = if (isChecked) View.VISIBLE else View.GONE layoutSmsTemplate.visibility = if (isChecked) View.VISIBLE else View.GONE
SettingUtils.enableSmsTemplate = isChecked SettingUtils.enableSmsTemplate = isChecked
if (!isChecked) { if (!isChecked) {
@ -983,6 +992,40 @@ class SettingsFragment : BaseFragment<FragmentSettingsBinding?>(), View.OnClickL
} }
} }
//启用 {{定位信息}} 标签
private fun switchEnableLocationTag(@SuppressLint("UseSwitchCompatOrMaterialCode") switchEnableLocationTag: SwitchButton) {
switchEnableLocationTag.isChecked = SettingUtils.enableLocationTag
switchEnableLocationTag.setOnCheckedChangeListener { _: CompoundButton?, isChecked: Boolean ->
SettingUtils.enableLocationTag = isChecked
if (isChecked) {
XXPermissions.with(this).permission(Permission.ACCESS_COARSE_LOCATION).permission(Permission.ACCESS_FINE_LOCATION).permission(Permission.ACCESS_BACKGROUND_LOCATION).request(object : OnPermissionCallback {
override fun onGranted(permissions: List<String>, all: Boolean) {
//重启前台服务
val serviceIntent = Intent(requireContext(), ForegroundService::class.java)
serviceIntent.action = "START"
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
requireContext().startForegroundService(serviceIntent)
} else {
requireContext().startService(serviceIntent)
}
}
override fun onDenied(permissions: List<String>, never: Boolean) {
if (never) {
XToastUtils.error(R.string.toast_denied_never)
// 如果是被永久拒绝就跳转到应用权限系统设置页面
XXPermissions.startPermissionActivity(requireContext(), permissions)
} else {
XToastUtils.error(R.string.toast_denied)
}
SettingUtils.enableLocationTag = false
switchEnableLocationTag.isChecked = false
}
})
}
}
}
//获取当前手机品牌 //获取当前手机品牌
private fun getAutoStartTips(): String { private fun getAutoStartTips(): String {
return when (Build.BRAND.lowercase(Locale.ROOT)) { return when (Build.BRAND.lowercase(Locale.ROOT)) {

View File

@ -1,9 +1,13 @@
package com.idormy.sms.forwarder.service package com.idormy.sms.forwarder.service
import android.annotation.SuppressLint
import android.app.* import android.app.*
import android.content.Intent import android.content.Intent
import android.graphics.BitmapFactory import android.graphics.BitmapFactory
import android.graphics.Color import android.graphics.Color
import android.location.Criteria
import android.location.Geocoder
import android.location.Location
import android.os.Build import android.os.Build
import android.os.IBinder import android.os.IBinder
import android.text.TextUtils import android.text.TextUtils
@ -16,9 +20,14 @@ import com.idormy.sms.forwarder.App
import com.idormy.sms.forwarder.R import com.idormy.sms.forwarder.R
import com.idormy.sms.forwarder.activity.MainActivity import com.idormy.sms.forwarder.activity.MainActivity
import com.idormy.sms.forwarder.database.AppDatabase import com.idormy.sms.forwarder.database.AppDatabase
import com.idormy.sms.forwarder.entity.LocationInfo
import com.idormy.sms.forwarder.utils.* import com.idormy.sms.forwarder.utils.*
import com.idormy.sms.forwarder.workers.LoadAppListWorker import com.idormy.sms.forwarder.workers.LoadAppListWorker
import com.jeremyliao.liveeventbus.LiveEventBus import com.jeremyliao.liveeventbus.LiveEventBus
import com.king.location.LocationClient
import com.king.location.LocationErrorCode
import com.king.location.OnExceptionListener
import com.king.location.OnLocationListener
import com.xuexiang.xutil.XUtil import com.xuexiang.xutil.XUtil
import com.xuexiang.xutil.file.FileUtils import com.xuexiang.xutil.file.FileUtils
import frpclib.Frpclib import frpclib.Frpclib
@ -31,45 +40,48 @@ import io.reactivex.schedulers.Schedulers
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.async import kotlinx.coroutines.async
import java.text.SimpleDateFormat
import java.util.Date
@SuppressLint("SimpleDateFormat")
@Suppress("PrivatePropertyName", "DeferredResultUnused", "OPT_IN_USAGE", "DEPRECATION") @Suppress("PrivatePropertyName", "DeferredResultUnused", "OPT_IN_USAGE", "DEPRECATION")
class ForegroundService : Service() { class ForegroundService : Service() {
private val TAG: String = "ForegroundService" private val TAG: String = "ForegroundService"
private var notificationManager: NotificationManager? = null
private val compositeDisposable = CompositeDisposable() private val compositeDisposable = CompositeDisposable()
private val frpcObserver = Observer { uid: String -> private val frpcObserver = Observer { uid: String ->
if (Frpclib.isRunning(uid)) { if (Frpclib.isRunning(uid)) {
return@Observer return@Observer
} }
AppDatabase.getInstance(App.context) AppDatabase.getInstance(App.context).frpcDao().get(uid).flatMap { (uid1, _, config) ->
.frpcDao() val error = Frpclib.runContent(uid1, config)
.get(uid) Single.just(error)
.flatMap { (uid1, _, config) -> }.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()).subscribe(object : SingleObserver<String> {
val error = Frpclib.runContent(uid1, config) override fun onSubscribe(d: Disposable) {
Single.just(error) compositeDisposable.add(d)
} }
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(object : SingleObserver<String> {
override fun onSubscribe(d: Disposable) {
compositeDisposable.add(d)
}
override fun onError(e: Throwable) { override fun onError(e: Throwable) {
e.printStackTrace() e.printStackTrace()
LiveEventBus.get(EVENT_FRPC_RUNNING_ERROR, String::class.java).post(uid)
}
override fun onSuccess(msg: String) {
if (!TextUtils.isEmpty(msg)) {
Log.e(TAG, msg)
LiveEventBus.get(EVENT_FRPC_RUNNING_ERROR, String::class.java).post(uid) LiveEventBus.get(EVENT_FRPC_RUNNING_ERROR, String::class.java).post(uid)
} else {
LiveEventBus.get(EVENT_FRPC_RUNNING_SUCCESS, String::class.java).post(uid)
} }
}
override fun onSuccess(msg: String) { })
if (!TextUtils.isEmpty(msg)) {
Log.e(TAG, msg)
LiveEventBus.get(EVENT_FRPC_RUNNING_ERROR, String::class.java).post(uid)
} else {
LiveEventBus.get(EVENT_FRPC_RUNNING_SUCCESS, String::class.java).post(uid)
}
}
})
} }
private var notificationManager: NotificationManager? = null
private val locationClient by lazy { LocationClient(App.context) }
private val geocoder by lazy { Geocoder(App.context) }
private val simpleDateFormat by lazy { SimpleDateFormat("yyyy-MM-dd HH:mm:ss") }
companion object { companion object {
var isRunning = false var isRunning = false
@ -78,13 +90,52 @@ class ForegroundService : Service() {
override fun onCreate() { override fun onCreate() {
super.onCreate() super.onCreate()
//纯客户端模式
if (SettingUtils.enablePureClientMode) return
createNotificationChannel()
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
//纯客户端模式
if (SettingUtils.enablePureClientMode) return START_STICKY
if (intent != null) {
when (intent.action) {
"START" -> {
startForegroundService()
}
"STOP" -> {
stopForegroundService()
}
"UPDATE_NOTIFICATION" -> {
val updatedContent = intent.getStringExtra("UPDATED_CONTENT")
updateNotification(updatedContent ?: "")
}
}
}
return START_STICKY
}
override fun onDestroy() {
//非纯客户端模式
if (!SettingUtils.enablePureClientMode) stopForegroundService()
super.onDestroy()
}
override fun onBind(intent: Intent): IBinder? {
return null
}
private fun startForegroundService() {
val notification = createNotification(SettingUtils.notifyContent)
startForeground(NOTIFICATION_ID, notification)
try { try {
//纯客户端模式
if (SettingUtils.enablePureClientMode) return
notificationManager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager
startForeground(FRONT_NOTIFY_ID, createForegroundNotification())
//开关通知监听服务 //开关通知监听服务
if (SettingUtils.enableAppNotify && CommonUtils.isNotificationListenerServiceEnabled(this)) { if (SettingUtils.enableAppNotify && CommonUtils.isNotificationListenerServiceEnabled(this)) {
CommonUtils.toggleNotificationListenerService(this) CommonUtils.toggleNotificationListenerService(this)
@ -96,6 +147,7 @@ class ForegroundService : Service() {
WorkManager.getInstance(XUtil.getContext()).enqueue(request) WorkManager.getInstance(XUtil.getContext()).enqueue(request)
} }
//启动 Frpc
if (FileUtils.isFileExists(filesDir.absolutePath + "/libs/libgojni.so")) { if (FileUtils.isFileExists(filesDir.absolutePath + "/libs/libgojni.so")) {
//监听Frpc启动指令 //监听Frpc启动指令
LiveEventBus.get(INTENT_FRPC_APPLY_FILE, String::class.java).observeStickyForever(frpcObserver) LiveEventBus.get(INTENT_FRPC_APPLY_FILE, String::class.java).observeStickyForever(frpcObserver)
@ -117,6 +169,65 @@ class ForegroundService : Service() {
} }
} }
//远程找手机
if (SettingUtils.enableLocationTag || HttpServerUtils.enableApiLocation) {
//可根据具体需求设置定位配置参数(这里只列出一些主要的参数)
val locationOption = locationClient.getLocationOption().setAccuracy(Criteria.ACCURACY_FINE)//设置位置精度:高精度
.setPowerRequirement(Criteria.POWER_LOW) //设置电量消耗:低电耗
.setMinTime(10000)//设置位置更新最小时间间隔(单位:毫秒); 默认间隔10000毫秒最小间隔1000毫秒
.setMinDistance(0)//设置位置更新最小距离单位默认距离0米
.setOnceLocation(false)//设置是否只定位一次,默认为 false当设置为 true 时,则只定位一次后,会自动停止定位
.setLastKnownLocation(false)//设置是否获取最后一次缓存的已知位置,默认为 true
//设置定位配置参数
locationClient.setLocationOption(locationOption)
locationClient.startLocation()
//设置定位监听
locationClient.setOnLocationListener(object : OnLocationListener() {
override fun onLocationChanged(location: Location) {
//位置信息
Log.d(TAG, "onLocationChanged(location = ${location})")
val locationInfo = LocationInfo(
location.longitude, location.latitude, "", simpleDateFormat.format(Date(location.time)), location.provider.toString()
)
//根据坐标经纬度获取位置地址信息WGS-84坐标系
val list = geocoder.getFromLocation(location.latitude, location.longitude, 1)
if (list?.isNotEmpty() == true) {
locationInfo.address = list[0].getAddressLine(0)
}
Log.d(TAG, "locationInfo = $locationInfo")
HttpServerUtils.apiLocationCache = locationInfo
}
override fun onProviderEnabled(provider: String) {
super.onProviderEnabled(provider)
Log.d(TAG, "onProviderEnabled(provider = ${provider})")
}
override fun onProviderDisabled(provider: String) {
super.onProviderDisabled(provider)
Log.d(TAG, "onProviderDisabled(provider = ${provider})")
}
})
//设置异常监听
locationClient.setOnExceptionListener(object : OnExceptionListener {
override fun onException(@LocationErrorCode errorCode: Int, e: Exception) {
//定位出现异常
Log.w(TAG, "onException(errorCode = ${errorCode}, e = ${e})")
}
})
if (locationClient.isStarted()) {//如果已经开始定位,则先停止定位
locationClient.stopLocation()
}
locationClient.startLocation()
}
isRunning = true isRunning = true
} catch (e: Exception) { } catch (e: Exception) {
e.printStackTrace() e.printStackTrace()
@ -125,34 +236,25 @@ class ForegroundService : Service() {
} }
override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int { private fun stopForegroundService() {
isRunning = true
return START_STICKY
}
override fun onDestroy() {
//纯客户端模式
if (SettingUtils.enablePureClientMode) {
super.onDestroy()
return
}
try { try {
//如果已经开始定位,则先停止定位
if (HttpServerUtils.enableApiLocation && locationClient.isStarted()) {
locationClient.stopLocation()
}
stopForeground(true) stopForeground(true)
stopSelf()
compositeDisposable.dispose() compositeDisposable.dispose()
isRunning = false isRunning = false
} catch (e: Exception) { } catch (e: Exception) {
e.printStackTrace() e.printStackTrace()
isRunning = true
} }
super.onDestroy()
} }
override fun onBind(intent: Intent): IBinder? { private fun createNotificationChannel() {
return null notificationManager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager
}
private fun createForegroundNotification(): Notification {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val importance = NotificationManager.IMPORTANCE_HIGH val importance = NotificationManager.IMPORTANCE_HIGH
val notificationChannel = NotificationChannel(FRONT_CHANNEL_ID, FRONT_CHANNEL_NAME, importance) val notificationChannel = NotificationChannel(FRONT_CHANNEL_ID, FRONT_CHANNEL_NAME, importance)
@ -165,20 +267,19 @@ class ForegroundService : Service() {
notificationManager!!.createNotificationChannel(notificationChannel) notificationManager!!.createNotificationChannel(notificationChannel)
} }
} }
val builder = NotificationCompat.Builder(this, FRONT_CHANNEL_ID) }
builder.setSmallIcon(R.drawable.ic_forwarder)
builder.setLargeIcon(BitmapFactory.decodeResource(resources, R.drawable.ic_menu_frpc)) private fun createNotification(content: String): Notification {
// TODO: 部分机型标题会重复待排除 val notificationIntent = Intent(this, MainActivity::class.java)
// if (DeviceUtils.getDeviceBrand().contains("Xiaomi")) {
builder.setContentTitle(getString(R.string.app_name))
//}
builder.setContentText(SettingUtils.notifyContent)
builder.setWhen(System.currentTimeMillis())
val activityIntent = Intent(this, MainActivity::class.java)
val flags = if (Build.VERSION.SDK_INT >= 30) PendingIntent.FLAG_IMMUTABLE else PendingIntent.FLAG_UPDATE_CURRENT val flags = if (Build.VERSION.SDK_INT >= 30) PendingIntent.FLAG_IMMUTABLE else PendingIntent.FLAG_UPDATE_CURRENT
val pendingIntent = PendingIntent.getActivity(this, 0, activityIntent, flags) val pendingIntent = PendingIntent.getActivity(this, 0, notificationIntent, flags)
builder.setContentIntent(pendingIntent)
return builder.build() return NotificationCompat.Builder(this, FRONT_CHANNEL_ID).setContentTitle(getString(R.string.app_name)).setContentText(content).setSmallIcon(R.drawable.ic_forwarder).setLargeIcon(BitmapFactory.decodeResource(resources, R.drawable.ic_menu_frpc)).setContentIntent(pendingIntent).setWhen(System.currentTimeMillis()).build()
}
private fun updateNotification(updatedContent: String) {
val notification = createNotification(updatedContent)
notificationManager?.notify(NOTIFICATION_ID, notification)
} }
} }

View File

@ -1,40 +1,24 @@
package com.idormy.sms.forwarder.service package com.idormy.sms.forwarder.service
import android.annotation.SuppressLint
import android.app.Service import android.app.Service
import android.content.Intent import android.content.Intent
import android.location.Criteria
import android.location.Geocoder
import android.location.Location
import android.os.IBinder import android.os.IBinder
import android.util.Log import android.util.Log
import com.idormy.sms.forwarder.App
import com.idormy.sms.forwarder.entity.LocationInfo
import com.idormy.sms.forwarder.utils.HTTP_SERVER_PORT import com.idormy.sms.forwarder.utils.HTTP_SERVER_PORT
import com.idormy.sms.forwarder.utils.HTTP_SERVER_TIME_OUT import com.idormy.sms.forwarder.utils.HTTP_SERVER_TIME_OUT
import com.idormy.sms.forwarder.utils.HttpServerUtils
import com.idormy.sms.forwarder.utils.SettingUtils import com.idormy.sms.forwarder.utils.SettingUtils
import com.king.location.LocationClient
import com.king.location.LocationErrorCode
import com.king.location.OnExceptionListener
import com.king.location.OnLocationListener
import com.yanzhenjie.andserver.AndServer import com.yanzhenjie.andserver.AndServer
import com.yanzhenjie.andserver.Server import com.yanzhenjie.andserver.Server
import java.text.SimpleDateFormat
import java.util.* import java.util.*
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
@SuppressLint("SimpleDateFormat") @Suppress("PrivatePropertyName")
@Suppress("PrivatePropertyName", "DEPRECATION")
class HttpService : Service(), Server.ServerListener { class HttpService : Service(), Server.ServerListener {
private val TAG: String = "HttpService" private val TAG: String = "HttpService"
private val server by lazy { private val server by lazy {
AndServer.webServer(this).port(HTTP_SERVER_PORT).listener(this).timeout(HTTP_SERVER_TIME_OUT, TimeUnit.SECONDS).build() AndServer.webServer(this).port(HTTP_SERVER_PORT).listener(this).timeout(HTTP_SERVER_TIME_OUT, TimeUnit.SECONDS).build()
} }
private val locationClient by lazy { LocationClient(App.context) }
private val geocoder by lazy { Geocoder(App.context) }
private val simpleDateFormat by lazy { SimpleDateFormat("yyyy-MM-dd HH:mm:ss") }
override fun onBind(p0: Intent?): IBinder? { override fun onBind(p0: Intent?): IBinder? {
return null return null
@ -48,69 +32,6 @@ class HttpService : Service(), Server.ServerListener {
Log.i(TAG, "onCreate: ") Log.i(TAG, "onCreate: ")
server.startup() server.startup()
//远程找手机
if (HttpServerUtils.enableApiLocation) {
//可根据具体需求设置定位配置参数(这里只列出一些主要的参数)
val locationOption = locationClient.getLocationOption().setAccuracy(Criteria.ACCURACY_FINE)//设置位置精度:高精度
.setPowerRequirement(Criteria.POWER_LOW) //设置电量消耗:低电耗
.setMinTime(10000)//设置位置更新最小时间间隔(单位:毫秒); 默认间隔10000毫秒最小间隔1000毫秒
.setMinDistance(0)//设置位置更新最小距离单位默认距离0米
.setOnceLocation(false)//设置是否只定位一次,默认为 false当设置为 true 时,则只定位一次后,会自动停止定位
.setLastKnownLocation(false)//设置是否获取最后一次缓存的已知位置,默认为 true
//设置定位配置参数
locationClient.setLocationOption(locationOption)
locationClient.startLocation()
//设置定位监听
locationClient.setOnLocationListener(object : OnLocationListener() {
override fun onLocationChanged(location: Location) {
//位置信息
Log.d(TAG, "onLocationChanged(location = ${location})")
val locationInfo = LocationInfo(
location.longitude,
location.latitude,
"",
simpleDateFormat.format(Date(location.time)),
location.provider.toString()
)
//根据坐标经纬度获取位置地址信息WGS-84坐标系
val list = geocoder.getFromLocation(location.latitude, location.longitude, 1)
if (list?.isNotEmpty() == true) {
locationInfo.address = list[0].getAddressLine(0)
}
Log.d(TAG, "locationInfo = $locationInfo")
HttpServerUtils.apiLocationCache = locationInfo
}
override fun onProviderEnabled(provider: String) {
super.onProviderEnabled(provider)
Log.d(TAG, "onProviderEnabled(provider = ${provider})")
}
override fun onProviderDisabled(provider: String) {
super.onProviderDisabled(provider)
Log.d(TAG, "onProviderDisabled(provider = ${provider})")
}
})
//设置异常监听
locationClient.setOnExceptionListener(object : OnExceptionListener {
override fun onException(@LocationErrorCode errorCode: Int, e: Exception) {
//定位出现异常
Log.w(TAG, "onException(errorCode = ${errorCode}, e = ${e})")
}
})
if (locationClient.isStarted()) {//如果已经开始定位,则先停止定位
locationClient.stopLocation()
}
locationClient.startLocation()
}
} }
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
@ -126,10 +47,6 @@ class HttpService : Service(), Server.ServerListener {
Log.i(TAG, "onDestroy: ") Log.i(TAG, "onDestroy: ")
server.shutdown() server.shutdown()
if (HttpServerUtils.enableApiLocation && locationClient.isStarted()) {//如果已经开始定位,则先停止定位
locationClient.stopLocation()
}
} }
override fun onException(e: Exception?) { override fun onException(e: Exception?) {

View File

@ -86,6 +86,7 @@ const val SP_SMS_TEMPLATE = "sms_template"
const val SP_ENABLE_HELP_TIP = "enable_help_tip" const val SP_ENABLE_HELP_TIP = "enable_help_tip"
const val SP_PURE_CLIENT_MODE = "enable_pure_client_mode" const val SP_PURE_CLIENT_MODE = "enable_pure_client_mode"
const val SP_LOCATION_TAG = "enable_location_tag"
const val SP_ENABLE_CACTUS = "enable_cactus" const val SP_ENABLE_CACTUS = "enable_cactus"
const val CACTUS_TIMER = "cactus_timer" const val CACTUS_TIMER = "cactus_timer"
@ -324,6 +325,7 @@ var SENDER_FRAGMENT_LIST = listOf(
//前台服务 //前台服务
const val FRONT_NOTIFY_ID = 0x1010 const val FRONT_NOTIFY_ID = 0x1010
const val NOTIFICATION_ID = 101
const val FRONT_CHANNEL_ID = "com.idormy.sms.forwarder" const val FRONT_CHANNEL_ID = "com.idormy.sms.forwarder"
const val FRONT_CHANNEL_NAME = "SmsForwarder Foreground Service" const val FRONT_CHANNEL_NAME = "SmsForwarder Foreground Service"
@ -349,7 +351,6 @@ const val KEY_URL = "key_url"
//主页监听时间 //主页监听时间
const val EVENT_UPDATE_LOGS_TYPE = "key_logs_type" const val EVENT_UPDATE_LOGS_TYPE = "key_logs_type"
const val EVENT_UPDATE_RULE_TYPE = "key_status" const val EVENT_UPDATE_RULE_TYPE = "key_status"
const val EVENT_UPDATE_NOTIFY = "key_notify"
const val KEY_SENDER_ID = "key_sender_id" const val KEY_SENDER_ID = "key_sender_id"
const val KEY_SENDER_TYPE = "key_sender_type" const val KEY_SENDER_TYPE = "key_sender_type"

View File

@ -157,6 +157,9 @@ class SettingUtils private constructor() {
//是否纯客户端模式 //是否纯客户端模式
var enablePureClientMode: Boolean by SharedPreference(SP_PURE_CLIENT_MODE, false) var enablePureClientMode: Boolean by SharedPreference(SP_PURE_CLIENT_MODE, false)
//是否启用定位标签
var enableLocationTag: Boolean by SharedPreference(SP_LOCATION_TAG, false)
} }
init { init {

View File

@ -344,6 +344,40 @@
tools:ignore="SmallSp" /> tools:ignore="SmallSp" />
</LinearLayout> </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/enable_location_tag"
android:textStyle="bold"
tools:ignore="RelativeOverlap" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/enable_location_tag_tips"
android:textSize="9sp"
tools:ignore="SmallSp" />
</LinearLayout>
<com.xuexiang.xui.widget.button.switchbutton.SwitchButton
android:id="@+id/sb_enable_location_tag"
style="@style/SwitchButtonStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>
<LinearLayout <LinearLayout
style="@style/settingBarStyle" style="@style/settingBarStyle"
android:layout_width="match_parent" android:layout_width="match_parent"

View File

@ -607,6 +607,7 @@
<string name="tag_title">{{TITLE}}</string> <string name="tag_title">{{TITLE}}</string>
<string name="tag_scheme">{{SCHEME}}</string> <string name="tag_scheme">{{SCHEME}}</string>
<string name="tag_call_type">{{CALL_TYPE}}</string> <string name="tag_call_type">{{CALL_TYPE}}</string>
<string name="tag_location">{{LOCATION}}</string>
<string name="rule_sms">SMS</string> <string name="rule_sms">SMS</string>
<string name="rule_call">CALL</string> <string name="rule_call">CALL</string>
<string name="rule_app">APP</string> <string name="rule_app">APP</string>
@ -1057,4 +1058,7 @@
<string name="number">Number</string> <string name="number">Number</string>
<string name="country_iso">Country</string> <string name="country_iso">Country</string>
<string name="subscription_id">Subscription ID</string> <string name="subscription_id">Subscription ID</string>
<string name="enable_location_tag">Enable {{LOCATION}} Tag</string>
<string name="enable_location_tag_tips">Insert location info into forwarded msg.</string>
</resources> </resources>

View File

@ -608,6 +608,7 @@
<string name="tag_title">{{通知标题}}</string> <string name="tag_title">{{通知标题}}</string>
<string name="tag_scheme">{{通知Scheme}}</string> <string name="tag_scheme">{{通知Scheme}}</string>
<string name="tag_call_type">{{通话类型}}</string> <string name="tag_call_type">{{通话类型}}</string>
<string name="tag_location">{{定位信息}}</string>
<string name="rule_sms">短信</string> <string name="rule_sms">短信</string>
<string name="rule_call">来电</string> <string name="rule_call">来电</string>
<string name="rule_app">应用</string> <string name="rule_app">应用</string>
@ -1058,4 +1059,7 @@
<string name="number">手机号码</string> <string name="number">手机号码</string>
<string name="country_iso">国家代码</string> <string name="country_iso">国家代码</string>
<string name="subscription_id">订阅标识</string> <string name="subscription_id">订阅标识</string>
<string name="enable_location_tag">启用 {{定位信息}} 标签</string>
<string name="enable_location_tag_tips">在转发信息中插入手机的当前定位信息</string>
</resources> </resources>