优化:短信/通话转发获取卡槽信息机制(自行备注卡槽SubId对应)#228 #235

pull/286/head
pppscn 1 year ago
parent 442c29fd3d
commit 65e861ba62

@ -144,8 +144,8 @@ class App : Application(), CactusCallback, Configuration.Provider by Core {
runCatching {
get.await()
Log.d("GlobalScope", "AppUtils.getAppsInfo() Done")
Log.d("GlobalScope", "UserAppList = $UserAppList")
Log.d("GlobalScope", "SystemAppList = $SystemAppList")
//Log.d("GlobalScope", "UserAppList = $UserAppList")
//Log.d("GlobalScope", "SystemAppList = $SystemAppList")
}.onFailure {
//Log.e("GlobalScope", it.message.toString())
}

@ -20,7 +20,7 @@ import com.idormy.sms.forwarder.utils.DATABASE_NAME
@Database(
entities = [Frpc::class, Logs::class, Rule::class, Sender::class],
version = 10,
version = 11,
exportSchema = false
)
@TypeConverters(Converters::class)
@ -95,6 +95,7 @@ custom_domains = smsf.demo.com
MIGRATION_7_8,
MIGRATION_8_9,
MIGRATION_9_10,
MIGRATION_10_11,
)
/*if (BuildConfig.DEBUG) {
@ -277,6 +278,12 @@ CREATE TABLE "Sender" (
}
}
//转发日志添加SIM卡槽ID
private val MIGRATION_10_11 = object : Migration(10, 11) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("Alter table Logs add column sub_id INTEGER NOT NULL DEFAULT 0 ")
}
}
}
}

@ -1,61 +1,62 @@
package com.idormy.sms.forwarder.database.entity
import android.os.Parcelable
import androidx.room.*
import com.idormy.sms.forwarder.R
import kotlinx.parcelize.Parcelize
import java.util.*
@Parcelize
@Entity(
tableName = "Logs",
foreignKeys = [
ForeignKey(
entity = Rule::class,
parentColumns = ["id"],
childColumns = ["rule_id"],
onDelete = ForeignKey.CASCADE, //级联操作
onUpdate = ForeignKey.CASCADE //级联操作
)
],
indices = [
Index(value = ["id"], unique = true),
Index(value = ["rule_id"])
]
)
data class Logs(
@PrimaryKey(autoGenerate = true)
@ColumnInfo(name = "id") var id: Long,
@ColumnInfo(name = "type", defaultValue = "sms") var type: String,
@ColumnInfo(name = "from", defaultValue = "") var from: String,
@ColumnInfo(name = "content", defaultValue = "") var content: String,
@ColumnInfo(name = "rule_id", defaultValue = "0") var ruleId: Long = 0,
@ColumnInfo(name = "sim_info", defaultValue = "") var simInfo: String = "",
@ColumnInfo(name = "forward_status", defaultValue = "1") var forwardStatus: Int = 1,
@ColumnInfo(name = "forward_response", defaultValue = "") var forwardResponse: String = "",
@ColumnInfo(name = "time") var time: Date = Date(),
) : Parcelable {
val simImageId: Int
get() {
if (simInfo.isNotEmpty()) {
if (simInfo.replace("-", "").startsWith("SIM2")) {
return R.drawable.ic_sim2 //mipmap
} else if (simInfo.replace("-", "").startsWith("SIM1")) {
return R.drawable.ic_sim1
}
}
return R.drawable.ic_sim
}
val statusImageId: Int
get() {
if (forwardStatus == 1) {
return R.drawable.ic_round_warning
} else if (forwardStatus == 2) {
return R.drawable.ic_round_check
}
return R.drawable.ic_round_cancel
}
package com.idormy.sms.forwarder.database.entity
import android.os.Parcelable
import androidx.room.*
import com.idormy.sms.forwarder.R
import kotlinx.parcelize.Parcelize
import java.util.*
@Parcelize
@Entity(
tableName = "Logs",
foreignKeys = [
ForeignKey(
entity = Rule::class,
parentColumns = ["id"],
childColumns = ["rule_id"],
onDelete = ForeignKey.CASCADE, //级联操作
onUpdate = ForeignKey.CASCADE //级联操作
)
],
indices = [
Index(value = ["id"], unique = true),
Index(value = ["rule_id"])
]
)
data class Logs(
@PrimaryKey(autoGenerate = true)
@ColumnInfo(name = "id") var id: Long,
@ColumnInfo(name = "type", defaultValue = "sms") var type: String,
@ColumnInfo(name = "from", defaultValue = "") var from: String,
@ColumnInfo(name = "content", defaultValue = "") var content: String,
@ColumnInfo(name = "rule_id", defaultValue = "0") var ruleId: Long = 0,
@ColumnInfo(name = "sim_info", defaultValue = "") var simInfo: String = "",
@ColumnInfo(name = "sub_id", defaultValue = "0") var subId: Int = 0,
@ColumnInfo(name = "forward_status", defaultValue = "1") var forwardStatus: Int = 1,
@ColumnInfo(name = "forward_response", defaultValue = "") var forwardResponse: String = "",
@ColumnInfo(name = "time") var time: Date = Date(),
) : Parcelable {
val simImageId: Int
get() {
if (simInfo.isNotEmpty()) {
if (simInfo.replace("-", "").startsWith("SIM2")) {
return R.drawable.ic_sim2 //mipmap
} else if (simInfo.replace("-", "").startsWith("SIM1")) {
return R.drawable.ic_sim1
}
}
return R.drawable.ic_sim
}
val statusImageId: Int
get() {
if (forwardStatus == 1) {
return R.drawable.ic_round_warning
} else if (forwardStatus == 2) {
return R.drawable.ic_round_check
}
return R.drawable.ic_round_cancel
}
}

@ -1,55 +1,58 @@
package com.idormy.sms.forwarder.entity
import com.google.gson.annotations.SerializedName
import com.idormy.sms.forwarder.R
import java.io.Serializable
data class CallInfo(
//姓名
var name: String = "",
//号码
var number: String = "",
//获取通话日期
var dateLong: Long = 0L,
//获取通话时长,值为多少秒
var duration: Int = 0,
//通话类型1=呼入, 2=呼出, 3=未接, 4=未接提醒
var type: Int = 1,
//被呼号码
@SerializedName("via_number")
var viaNumber: String = "",
//卡槽ID 0=Sim1, 1=Sim2, -1=获取失败
@SerializedName("sim_id")
var simId: Int = -1,
) : Serializable {
val typeImageId: Int
get() {
return when (type) {
1 -> R.drawable.ic_phone_in
2 -> R.drawable.ic_phone_out
else -> R.drawable.ic_phone_missed
}
}
val simImageId: Int
get() {
return when (simId) {
0 -> R.drawable.ic_sim1
1 -> R.drawable.ic_sim2
else -> R.drawable.ic_sim
}
}
override fun toString(): String {
return "CallInfo{" +
"name='" + name + '\'' +
", number='" + number + '\'' +
", dateLong=" + dateLong +
", duration=" + duration +
", type=" + type +
", viaNumber=" + viaNumber +
", simId=" + simId +
'}'
}
package com.idormy.sms.forwarder.entity
import com.google.gson.annotations.SerializedName
import com.idormy.sms.forwarder.R
import java.io.Serializable
data class CallInfo(
//姓名
var name: String = "",
//号码
var number: String = "",
//获取通话日期
var dateLong: Long = 0L,
//获取通话时长,值为多少秒
var duration: Int = 0,
//通话类型1=呼入, 2=呼出, 3=未接, 4=未接提醒
var type: Int = 1,
//被呼号码
@SerializedName("via_number")
var viaNumber: String = "",
//卡槽ID 0=Sim1, 1=Sim2, -1=获取失败
@SerializedName("sim_id")
var simId: Int = -1,
//卡槽主键
@SerializedName("sub_id")
var subId: Int = 0,
) : Serializable {
val typeImageId: Int
get() {
return when (type) {
1 -> R.drawable.ic_phone_in
2 -> R.drawable.ic_phone_out
else -> R.drawable.ic_phone_missed
}
}
val simImageId: Int
get() {
return when (simId) {
0 -> R.drawable.ic_sim1
1 -> R.drawable.ic_sim2
else -> R.drawable.ic_sim
}
}
override fun toString(): String {
return "CallInfo{" +
"name='" + name + '\'' +
", number='" + number + '\'' +
", dateLong=" + dateLong +
", duration=" + duration +
", type=" + type +
", viaNumber=" + viaNumber +
", simId=" + simId +
'}'
}
}

@ -23,6 +23,7 @@ data class MsgInfo(
var date: Date,
var simInfo: String,
var simSlot: Int = -1, //卡槽id-1=获取失败、0=卡槽1、1=卡槽2
var subId: Int = 0, //卡槽主键
) : Serializable {
val titleForSend: String
@ -36,13 +37,14 @@ data class MsgInfo(
fun getTitleForSend(titleTemplate: String, regexReplace: String): String {
var template = titleTemplate.replace("null", "")
if (TextUtils.isEmpty(template)) template = getString(R.string.tag_from)
val deviceMark = extraDeviceMark!!.trim()
val deviceMark = extraDeviceMark.trim()
val versionName = AppUtils.getAppVersionName()
val titleForSend: String = template.replace(getString(R.string.tag_from), from)
.replace(getString(R.string.tag_package_name), from)
.replace(getString(R.string.tag_sms), content)
.replace(getString(R.string.tag_msg), content)
.replace(getString(R.string.tag_card_slot), simInfo)
.replace(getString(R.string.tag_card_subid), subId.toString())
.replace(getString(R.string.tag_title), simInfo)
.replace(getString(R.string.tag_receive_time), SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(date))
.replace(getString(R.string.tag_device_name), deviceMark)
@ -60,10 +62,11 @@ data class MsgInfo(
@SuppressLint("SimpleDateFormat")
fun getContentForSend(ruleSmsTemplate: String, regexReplace: String): String {
val deviceMark = extraDeviceMark!!.trim()
val deviceMark = extraDeviceMark.trim()
var customSmsTemplate: String = getString(R.string.tag_from).toString() + "\n" +
getString(R.string.tag_sms) + "\n" +
getString(R.string.tag_card_slot) + "\n" +
"SubId" + getString(R.string.tag_card_subid) + "\n" +
getString(R.string.tag_receive_time) + "\n" +
getString(R.string.tag_device_name)
@ -72,7 +75,7 @@ data class MsgInfo(
customSmsTemplate = ruleSmsTemplate.replace("null", "")
} else {
val switchSmsTemplate = enableSmsTemplate
val smsTemplate = smsTemplate.toString().trim()
val smsTemplate = smsTemplate.trim()
if (switchSmsTemplate && smsTemplate.isNotEmpty()) {
customSmsTemplate = smsTemplate.replace("null", "")
}
@ -83,6 +86,7 @@ data class MsgInfo(
.replace(getString(R.string.tag_sms), content)
.replace(getString(R.string.tag_msg), content)
.replace(getString(R.string.tag_card_slot), simInfo)
.replace(getString(R.string.tag_card_subid), subId.toString())
.replace(getString(R.string.tag_title), simInfo)
.replace(getString(R.string.tag_receive_time), SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(date))
.replace(getString(R.string.tag_device_name), deviceMark)

@ -1,37 +1,37 @@
package com.idormy.sms.forwarder.entity
import com.google.gson.annotations.SerializedName
import java.io.Serializable
//SIM卡信息
data class SimInfo(
//运营商信息:中国移动 中国联通 中国电信
@SerializedName("carrier_name")
var mCarrierName: String? = null,
//集成电路卡识别码即SIM卡卡号
@SerializedName("icc_id")
var mIccId: String? = null,
//卡槽id-1=没插入、 0=卡槽1 、1=卡槽2
@SerializedName("sim_slot_index")
var mSimSlotIndex: Int = 0,
//号码
@SerializedName("number")
var mNumber: String? = null,
//国家代码
@SerializedName("country_iso")
var mCountryIso: String? = null,
//SIM的 Subscription Id (SIM插入顺序)
@SerializedName("subscription_id")
var mSubscriptionId: Int = 0,
) : Serializable {
override fun toString(): String {
return "SimInfo{" +
"mCarrierName=" + mCarrierName +
", mIccId=" + mIccId +
", mSimSlotIndex=" + mSimSlotIndex +
", mNumber=" + mNumber +
", mCountryIso=" + mCountryIso +
", mSubscriptionId=" + mSubscriptionId +
'}'
}
}
package com.idormy.sms.forwarder.entity
import com.google.gson.annotations.SerializedName
import java.io.Serializable
//SIM卡信息
data class SimInfo(
//运营商信息:中国移动 中国联通 中国电信
@SerializedName("carrier_name")
var mCarrierName: String? = null,
//集成电路卡识别码即SIM卡卡号
@SerializedName("icc_id")
var mIccId: String? = null,
//卡槽id-1=没插入、 0=卡槽1 、1=卡槽2
@SerializedName("sim_slot_index")
var mSimSlotIndex: Int = 0,
//号码
@SerializedName("number")
var mNumber: String? = null,
//国家代码
@SerializedName("country_iso")
var mCountryIso: String? = null,
//SIM的 Subscription Id (SIM插入顺序)
@SerializedName("subscription_id")
var mSubscriptionId: Int = 0,
) : Serializable {
override fun toString(): String {
return "SimInfo{" +
"mCarrierName=" + mCarrierName +
", mIccId=" + mIccId +
", mSimSlotIndex=" + mSimSlotIndex +
", mNumber=" + mNumber +
", mCountryIso=" + mCountryIso +
", mSubscriptionId=" + mSubscriptionId +
'}'
}
}

@ -1,33 +1,36 @@
package com.idormy.sms.forwarder.entity
import com.google.gson.annotations.SerializedName
import com.idormy.sms.forwarder.R
import java.io.Serializable
data class SmsInfo(
// 联系人姓名
var name: String = "",
// 联系人号码
var number: String = "",
// 短信内容
var content: String = "",
// 短信时间
var date: Long = 0L,
// 短信类型: 1=接收, 2=发送
var type: Int = 1,
// 卡槽ID 0=Sim1, 1=Sim2, -1=获取失败
@SerializedName("sim_id")
var simId: Int = -1,
) : Serializable {
val typeImageId: Int = R.drawable.ic_sms
val simImageId: Int
get() {
return when (simId) {
0 -> R.drawable.ic_sim1
1 -> R.drawable.ic_sim2
else -> R.drawable.ic_sim
}
}
}
package com.idormy.sms.forwarder.entity
import com.google.gson.annotations.SerializedName
import com.idormy.sms.forwarder.R
import java.io.Serializable
data class SmsInfo(
// 联系人姓名
var name: String = "",
// 联系人号码
var number: String = "",
// 短信内容
var content: String = "",
// 短信时间
var date: Long = 0L,
// 短信类型: 1=接收, 2=发送
var type: Int = 1,
// 卡槽ID 0=Sim1, 1=Sim2, -1=获取失败
@SerializedName("sim_id")
var simId: Int = -1,
// 卡槽主键
@SerializedName("sub_id")
var subId: Int = 0,
) : Serializable {
val typeImageId: Int = R.drawable.ic_sms
val simImageId: Int
get() {
return when (simId) {
0 -> R.drawable.ic_sim1
1 -> R.drawable.ic_sim2
else -> R.drawable.ic_sim
}
}
}

@ -10,6 +10,7 @@ import android.net.Uri
import android.os.Build
import android.provider.Settings
import android.text.Editable
import android.text.TextUtils
import android.text.TextWatcher
import android.util.Log
import android.view.LayoutInflater
@ -128,6 +129,10 @@ class SettingsFragment : BaseFragment<FragmentSettingsBinding?>(), View.OnClickL
//设备备注
editAddExtraDeviceMark(binding!!.etExtraDeviceMark)
//SIM1主键
editAddSubidSim1(binding!!.etSubidSim1)
//SIM2主键
editAddSubidSim2(binding!!.etSubidSim2)
//SIM1备注
editAddExtraSim1(binding!!.etExtraSim1)
//SIM2备注
@ -200,6 +205,7 @@ class SettingsFragment : BaseFragment<FragmentSettingsBinding?>(), View.OnClickL
return
}
val simInfo: SimInfo? = App.SimInfoList[0]
binding!!.etSubidSim1.setText(simInfo?.mSubscriptionId.toString())
binding!!.etExtraSim1.setText(simInfo?.mCarrierName.toString() + "_" + simInfo?.mNumber.toString())
return
}
@ -222,6 +228,7 @@ class SettingsFragment : BaseFragment<FragmentSettingsBinding?>(), View.OnClickL
return
}
val simInfo: SimInfo? = App.SimInfoList[1]
binding!!.etSubidSim2.setText(simInfo?.mSubscriptionId.toString())
binding!!.etExtraSim2.setText(simInfo?.mCarrierName.toString() + "_" + simInfo?.mNumber.toString())
return
}
@ -729,6 +736,40 @@ class SettingsFragment : BaseFragment<FragmentSettingsBinding?>(), View.OnClickL
})
}
//设置SIM1主键
private fun editAddSubidSim1(etSubidSim1: EditText) {
etSubidSim1.setText(SettingUtils.subidSim1.toString())
etSubidSim1.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) {
val v = etSubidSim1.text.toString()
SettingUtils.subidSim1 = if (!TextUtils.isEmpty(v)) {
v.toInt()
} else {
1
}
}
})
}
//设置SIM2主键
private fun editAddSubidSim2(etSubidSim2: EditText) {
etSubidSim2.setText(SettingUtils.subidSim2.toString())
etSubidSim2.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) {
val v = etSubidSim2.text.toString()
SettingUtils.subidSim2 = if (!TextUtils.isEmpty(v)) {
v.toInt()
} else {
2
}
}
})
}
//设置SIM1备注
private fun editAddExtraSim1(etExtraSim1: EditText) {
etExtraSim1.setText(SettingUtils.extraSim1)
@ -783,6 +824,7 @@ class SettingsFragment : BaseFragment<FragmentSettingsBinding?>(), View.OnClickL
${getString(R.string.tag_from)}
${getString(R.string.tag_sms)}
${getString(R.string.tag_card_slot)}
SubId${getString(R.string.tag_card_subid)}
${getString(R.string.tag_receive_time)}
${getString(R.string.tag_device_name)}
""".trimIndent()

@ -51,79 +51,82 @@ class PhoneStateReceiver : BroadcastReceiver() {
TelephonyManager.EXTRA_STATE_RINGING -> state = TelephonyManager.CALL_STATE_RINGING
}
Log.d(TAG, "state=$state, number=$number")
onCallStateChanged(context, state, number)
} catch (e: Exception) {
Log.e(TAG, e.message.toString())
}
}
//Incoming call- goes from IDLE to RINGING when it rings, to OFFHOOK when it's answered, to IDLE when its hung up
//Outgoing call- goes from IDLE to OFFHOOK when it dials out, to IDLE when hung up
private fun onCallStateChanged(context: Context, state: Int, number: String?) {
var lastState: Int by SharedPreference("CALL_LAST_STATE", TelephonyManager.CALL_STATE_IDLE)
if (lastState == state || (state == TelephonyManager.CALL_STATE_RINGING && number == null)) {
//No change, debounce extras
return
}
lastState = state
var callIsIncoming: Boolean by SharedPreference("CALL_IS_INCOMING", false)
var callSavedNumber: String by SharedPreference("CALL_SAVED_NUMBER", "")
when (state) {
TelephonyManager.CALL_STATE_RINGING -> {
Log.d(TAG, "来电响铃")
callIsIncoming = true
callSavedNumber = number.toString()
//来电提醒
if (!TextUtils.isEmpty(number) && SettingUtils.enableCallType4) {
val contacts = PhoneUtils.getContactByNumber(number)
val contactName = if (contacts.isNotEmpty()) contacts[0].name else getString(R.string.unknown_number)
val sb = StringBuilder()
sb.append(getString(R.string.linkman)).append(contactName).append("\n")
sb.append(getString(R.string.mandatory_type))
sb.append(getString(R.string.incoming_call))
val msgInfo = MsgInfo("call", number.toString(), sb.toString(), Date(), "", -1)
val request = OneTimeWorkRequestBuilder<SendWorker>().setInputData(
workDataOf(
Worker.sendMsgInfo to Gson().toJson(msgInfo)
)
).build()
WorkManager.getInstance(context).enqueue(request)
}
var callSavedNumber: String by SharedPreference("CALL_SAVED_NUMBER", "")
if (!TextUtils.isEmpty(number)) callSavedNumber = number.toString()
//Incoming call- goes from IDLE to RINGING when it rings, to OFFHOOK when it's answered, to IDLE when its hung up
//Outgoing call- goes from IDLE to OFFHOOK when it dials out, to IDLE when hung up
var lastState: Int by SharedPreference("CALL_LAST_STATE", TelephonyManager.CALL_STATE_IDLE)
if (lastState == state || (state == TelephonyManager.CALL_STATE_RINGING && number == null)) {
//No change, debounce extras
Log.d(TAG, "状态没变,防止抖动")
return
}
TelephonyManager.CALL_STATE_OFFHOOK ->
//Transition of ringing->offhook are pickups of incoming calls. Nothing done on them
callIsIncoming = when {
lastState != TelephonyManager.CALL_STATE_RINGING -> {
Log.d(TAG, "去电接通")
false
}
else -> {
Log.d(TAG, "来电接通")
true
lastState = state
var callIsIncoming: Boolean by SharedPreference("CALL_IS_INCOMING", false)
Log.d(TAG, "lastState=$lastState, callIsIncoming=$callIsIncoming, callSavedNumber=$callSavedNumber")
when (state) {
TelephonyManager.CALL_STATE_RINGING -> {
Log.d(TAG, "电话响铃")
callIsIncoming = true
//来电提醒
if (!TextUtils.isEmpty(number) && SettingUtils.enableCallType4) {
val contacts = PhoneUtils.getContactByNumber(number)
val contactName = if (contacts.isNotEmpty()) contacts[0].name else getString(R.string.unknown_number)
val sb = StringBuilder()
sb.append(getString(R.string.linkman)).append(contactName).append("\n")
sb.append(getString(R.string.mandatory_type))
sb.append(getString(R.string.incoming_call))
val msgInfo = MsgInfo("call", number.toString(), sb.toString(), Date(), "", -1)
val request = OneTimeWorkRequestBuilder<SendWorker>().setInputData(
workDataOf(
Worker.sendMsgInfo to Gson().toJson(msgInfo)
)
).build()
WorkManager.getInstance(context).enqueue(request)
}
}
TelephonyManager.CALL_STATE_IDLE ->
//Went to idle- this is the end of a call. What type depends on previous state(s)
when {
lastState == TelephonyManager.CALL_STATE_RINGING -> {
Log.d(TAG, "来电未接")
sendReceiveCallMsg(context, 3, callSavedNumber)
TelephonyManager.CALL_STATE_OFFHOOK ->
//Transition of ringing->offhook are pickups of incoming calls. Nothing done on them
callIsIncoming = when {
lastState != TelephonyManager.CALL_STATE_RINGING -> {
Log.d(TAG, "去电接通")
if (!TextUtils.isEmpty(number)) callSavedNumber = number.toString()
false
}
else -> {
Log.d(TAG, "来电接通")
true
}
}
callIsIncoming -> {
Log.d(TAG, "来电挂机")
sendReceiveCallMsg(context, 1, callSavedNumber)
TelephonyManager.CALL_STATE_IDLE ->
//Went to idle- this is the end of a call. What type depends on previous state(s)
when {
lastState == TelephonyManager.CALL_STATE_RINGING -> {
Log.d(TAG, "来电未接")
sendReceiveCallMsg(context, 3, callSavedNumber)
callSavedNumber = ""
}
callIsIncoming -> {
Log.d(TAG, "来电挂机")
sendReceiveCallMsg(context, 1, callSavedNumber)
callSavedNumber = ""
}
else -> {
Log.d(TAG, "去电挂机")
sendReceiveCallMsg(context, 2, callSavedNumber)
callSavedNumber = ""
}
}
else -> {
Log.d(TAG, "去电挂机")
sendReceiveCallMsg(context, 2, callSavedNumber)
}
}
}
} catch (e: Exception) {
Log.e(TAG, e.message.toString())
}
}
@ -158,7 +161,7 @@ class PhoneStateReceiver : BroadcastReceiver() {
}
val msgInfo = MsgInfo(
"call", callInfo.number, PhoneUtils.getCallMsg(callInfo), Date(), simInfo, simSlot
"call", callInfo.number, PhoneUtils.getCallMsg(callInfo), Date(), simInfo, simSlot, callInfo.subId
)
val request = OneTimeWorkRequestBuilder<SendWorker>().setInputData(
workDataOf(

@ -1,95 +1,96 @@
package com.idormy.sms.forwarder.receiver
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.provider.Telephony
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.App
import com.idormy.sms.forwarder.entity.MsgInfo
import com.idormy.sms.forwarder.utils.PhoneUtils
import com.idormy.sms.forwarder.utils.SettingUtils
import com.idormy.sms.forwarder.utils.Worker
import com.idormy.sms.forwarder.workers.SendWorker
import java.util.*
//短信广播
@Suppress("PrivatePropertyName", "DEPRECATION")
class SmsReceiver : BroadcastReceiver() {
private var TAG = "SmsReceiver"
override fun onReceive(context: Context, intent: Intent) {
try {
//纯客户端模式
if (SettingUtils.enablePureClientMode) return
//总开关
if (!SettingUtils.enableSms) return
//过滤广播
if (intent.action != Telephony.Sms.Intents.SMS_RECEIVED_ACTION && intent.action != Telephony.Sms.Intents.SMS_DELIVER_ACTION) return
//权限判断
//if (ActivityCompat.checkSelfPermission(context, Manifest.permission.RECEIVE_SMS) != PackageManager.PERMISSION_GRANTED) return
var from = ""
var content = ""
for (smsMessage in Telephony.Sms.Intents.getMessagesFromIntent(intent)) {
from = smsMessage.displayOriginatingAddress
content += smsMessage.messageBody
}
Log.d(TAG, "from = $from")
Log.d(TAG, "content = $content")
//获取卡槽信息
if (App.SimInfoList.isEmpty()) {
App.SimInfoList = PhoneUtils.getSimMultiInfo()
}
Log.e(TAG, "SimInfoList = " + App.SimInfoList.toString())
//TODO准确获取卡槽信息目前测试结果只有 subscription 相对靠谱
val slot = intent.extras?.getInt("slot") ?: -1
val simId = intent.extras?.getInt("simId") ?: slot
val subscription = intent.extras?.getInt("subscription") ?: simId
Log.d(TAG, "slot = $slot, simId = $simId, subscription = $subscription")
//卡槽id-1=获取失败、0=卡槽1、1=卡槽2
var simSlot = -1
if (App.SimInfoList.isNotEmpty()) {
for (simInfo in App.SimInfoList.values) {
if (simInfo.mSubscriptionId == subscription) {
simSlot = simInfo.mSimSlotIndex
break
}
}
}
//获取卡槽信息
val simInfo = when (simSlot) {
0 -> "SIM1_" + SettingUtils.extraSim1
1 -> "SIM2_" + SettingUtils.extraSim2
else -> ""
}
val msgInfo = MsgInfo("sms", from, content, Date(), simInfo, simSlot)
Log.d(TAG, "msgInfo = $msgInfo")
val request = OneTimeWorkRequestBuilder<SendWorker>()
.setInputData(
workDataOf(
Worker.sendMsgInfo to Gson().toJson(msgInfo)
)
)
.build()
WorkManager.getInstance(context).enqueue(request)
} catch (e: Exception) {
Log.e(TAG, "Parsing SMS failed: " + e.message.toString())
}
}
package com.idormy.sms.forwarder.receiver
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.provider.Telephony
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.App
import com.idormy.sms.forwarder.entity.MsgInfo
import com.idormy.sms.forwarder.utils.PhoneUtils
import com.idormy.sms.forwarder.utils.SettingUtils
import com.idormy.sms.forwarder.utils.Worker
import com.idormy.sms.forwarder.workers.SendWorker
import java.util.*
//短信广播
@Suppress("PrivatePropertyName", "DEPRECATION")
class SmsReceiver : BroadcastReceiver() {
private var TAG = "SmsReceiver"
override fun onReceive(context: Context, intent: Intent) {
try {
//纯客户端模式
if (SettingUtils.enablePureClientMode) return
//总开关
if (!SettingUtils.enableSms) return
//过滤广播
if (intent.action != Telephony.Sms.Intents.SMS_RECEIVED_ACTION && intent.action != Telephony.Sms.Intents.SMS_DELIVER_ACTION) return
var from = ""
var content = ""
for (smsMessage in Telephony.Sms.Intents.getMessagesFromIntent(intent)) {
from = smsMessage.displayOriginatingAddress
content += smsMessage.messageBody
}
Log.d(TAG, "from = $from")
Log.d(TAG, "content = $content")
//TODO准确获取卡槽信息目前测试结果只有 subscription 相对靠谱
val slot = intent.extras?.getInt("slot") ?: -1
val simId = intent.extras?.getInt("simId") ?: slot
val subscription = intent.extras?.getInt("subscription") ?: simId
Log.d(TAG, "slot = $slot, simId = $simId, subscription = $subscription")
//卡槽id-1=获取失败、0=卡槽1、1=卡槽2
var simSlot = -1
//以自定义卡槽信息优先
if (SettingUtils.subidSim1 > 0 || SettingUtils.subidSim2 > 0) {
simSlot = if (subscription == SettingUtils.subidSim1) 0 else 1
} else {
//获取卡槽信息
if (App.SimInfoList.isEmpty()) {
App.SimInfoList = PhoneUtils.getSimMultiInfo()
}
Log.d(TAG, "SimInfoList = " + App.SimInfoList.toString())
if (App.SimInfoList.isNotEmpty()) {
for (simInfo in App.SimInfoList.values) {
if (simInfo.mSubscriptionId == subscription) {
simSlot = simInfo.mSimSlotIndex
break
}
}
}
}
//获取卡槽信息
val simInfo = when (simSlot) {
0 -> "SIM1_" + SettingUtils.extraSim1
1 -> "SIM2_" + SettingUtils.extraSim2
else -> ""
}
val msgInfo = MsgInfo("sms", from, content, Date(), simInfo, simSlot, subscription)
Log.d(TAG, "msgInfo = $msgInfo")
val request = OneTimeWorkRequestBuilder<SendWorker>().setInputData(
workDataOf(
Worker.sendMsgInfo to Gson().toJson(msgInfo)
)
).build()
WorkManager.getInstance(context).enqueue(request)
} catch (e: Exception) {
Log.e(TAG, "Parsing SMS failed: " + e.message.toString())
}
}
}

@ -1,396 +1,398 @@
@file:Suppress("unused")
package com.idormy.sms.forwarder.utils
import com.idormy.sms.forwarder.R
import com.xuexiang.xpage.enums.CoreAnim
import com.xuexiang.xpage.model.PageInfo
import com.xuexiang.xui.utils.ResUtils.getString
object Worker {
const val sendMsgInfo = "send_msg_info"
const val sendLogId = "send_log_id"
const val sendSbnId = "send_sbn_id"
const val updateLogs = "update_logs"
}
//初始化相关
const val IS_FIRST_OPEN_KEY = "is_first_open_key"
const val IS_AGREE_PRIVACY_KEY = "is_agree_privacy_key"
//数据库
const val DATABASE_NAME = "sms_forwarder.db"
const val PACKAGE_NAME = "com.idormy.sms.forwarder"
//通用设置
const val SP_ENABLE_SMS = "enable_sms"
const val SP_ENABLE_PHONE = "enable_phone"
const val SP_ENABLE_CALL_TYPE_1 = "enable_call_type_1"
const val SP_ENABLE_CALL_TYPE_2 = "enable_call_type_2"
const val SP_ENABLE_CALL_TYPE_3 = "enable_call_type_3"
const val SP_ENABLE_CALL_TYPE_4 = "enable_call_type_4"
const val SP_ENABLE_APP_NOTIFY = "enable_app_notify"
const val SP_ENABLE_CANCEL_APP_NOTIFY = "enable_cancel_app_notify"
const val SP_ENABLE_NOT_USER_PRESENT = "enable_not_user_present"
const val ENABLE_LOAD_APP_LIST = "enable_load_app_list"
const val ENABLE_LOAD_USER_APP_LIST = "enable_load_user_app_list"
const val ENABLE_LOAD_SYSTEM_APP_LIST = "enable_load_system_app_list"
const val SP_DUPLICATE_MESSAGES_LIMITS = "duplicate_messages_limits"
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_BATTERY_RECEIVER = "enable_battery_receiver"
const val SP_BATTERY_STATUS = "battery_status"
const val SP_BATTERY_LEVEL_MIN = "battery_level_min"
const val SP_BATTERY_LEVEL_MAX = "battery_level_max"
const val SP_BATTERY_LEVEL_ONCE = "battery_level_once"
const val SP_BATTERY_LEVEL_CURRENT = "battery_level_current"
const val SP_BATTERY_CRON = "enable_battery_cron"
const val SP_BATTERY_CRON_START_TIME = "battery_cron_start_time"
const val SP_BATTERY_CRON_INTERVAL = "battery_cron_interval"
const val SP_ENABLE_EXCLUDE_FROM_RECENTS = "enable_exclude_from_recents"
const val SP_ENABLE_PLAY_SILENCE_MUSIC = "enable_play_silence_music"
const val SP_ENABLE_ONE_PIXEL_ACTIVITY = "enable_one_pixel_activity"
const val SP_REQUEST_RETRY_TIMES = "request_retry_times"
const val SP_REQUEST_DELAY_TIME = "request_delay_time"
const val SP_REQUEST_TIMEOUT = "request_timeout"
const val SP_NOTIFY_CONTENT = "notify_content"
const val SP_EXTRA_DEVICE_MARK = "extra_device_mark"
const val SP_EXTRA_SIM1 = "extra_sim1"
const val SP_EXTRA_SIM2 = "extra_sim2"
const val SP_ENABLE_SMS_TEMPLATE = "enable_sms_template"
const val SP_SMS_TEMPLATE = "sms_template"
const val SP_ENABLE_HELP_TIP = "enable_help_tip"
const val SP_PURE_CLIENT_MODE = "enable_pure_client_mode"
const val SP_ENABLE_CACTUS = "enable_cactus"
const val CACTUS_TIMER = "cactus_timer"
const val CACTUS_LAST_TIMER = "cactus_last_timer"
const val CACTUS_DATE = "cactus_date"
const val CACTUS_END_DATE = "cactus_end_date"
//OkHttp 请求超时时间
const val REQUEST_TIMEOUT_SECONDS = 5
//规则相关
const val STATUS_ON = 1
const val STATUS_OFF = 0
const val FILED_TRANSPOND_ALL = "transpond_all"
const val FILED_PHONE_NUM = "phone_num"
const val FILED_PACKAGE_NAME = "package_name"
const val FILED_MSG_CONTENT = "msg_content"
const val FILED_INFORM_CONTENT = "inform_content"
const val FILED_MULTI_MATCH = "multi_match"
const val CHECK_IS = "is"
const val CHECK_CONTAIN = "contain"
const val CHECK_NOT_CONTAIN = "notcontain"
const val CHECK_START_WITH = "startwith"
const val CHECK_END_WITH = "endwith"
const val CHECK_NOT_IS = "notis"
const val CHECK_REGEX = "regex"
const val CHECK_SIM_SLOT_ALL = "ALL"
const val CHECK_SIM_SLOT_1 = "SIM1"
const val CHECK_SIM_SLOT_2 = "SIM2"
val TYPE_MAP = object : HashMap<String, String>() {
init {
put("sms", getString(R.string.rule_sms))
put("call", getString(R.string.rule_call))
put("app", getString(R.string.rule_app))
}
}
val FILED_MAP = object : HashMap<String, String>() {
init {
put("transpond_all", getString(R.string.rule_transpond_all))
put("phone_num", getString(R.string.rule_phone_num))
put("msg_content", getString(R.string.rule_msg_content))
put("multi_match", getString(R.string.rule_multi_match))
put("package_name", getString(R.string.rule_package_name))
put("inform_content", getString(R.string.rule_inform_content))
}
}
val CHECK_MAP = object : HashMap<String, String>() {
init {
put("is", getString(R.string.rule_is))
put("notis", getString(R.string.rule_notis))
put("contain", getString(R.string.rule_contain))
put("startwith", getString(R.string.rule_startwith))
put("endwith", getString(R.string.rule_endwith))
put("notcontain", getString(R.string.rule_notcontain))
put("regex", getString(R.string.rule_regex))
}
}
val SIM_SLOT_MAP = object : HashMap<String, String>() {
init {
put("ALL", getString(R.string.rule_all))
put("SIM1", "SIM1")
put("SIM2", "SIM2")
}
}
val FORWARD_STATUS_MAP = object : HashMap<Int, String>() {
init {
put(0, getString(R.string.failed))
put(1, getString(R.string.processing))
put(2, getString(R.string.success))
}
}
val BARK_LEVEL_MAP = mapOf(
"active" to getString(R.string.bark_level_active),
"timeSensitive" to getString(R.string.bark_level_timeSensitive),
"passive" to getString(R.string.bark_level_passive)
)
//发送通道
const val TYPE_DINGTALK_GROUP_ROBOT = 0
const val TYPE_EMAIL = 1
const val TYPE_BARK = 2
const val TYPE_WEBHOOK = 3
const val TYPE_WEWORK_ROBOT = 4
const val TYPE_WEWORK_AGENT = 5
const val TYPE_SERVERCHAN = 6
const val TYPE_TELEGRAM = 7
const val TYPE_SMS = 8
const val TYPE_FEISHU = 9
const val TYPE_PUSHPLUS = 10
const val TYPE_GOTIFY = 11
const val TYPE_DINGTALK_INNER_ROBOT = 12
const val TYPE_FEISHU_APP = 13
var SENDER_FRAGMENT_LIST = listOf(
PageInfo(
getString(R.string.dingtalk_robot),
"com.idormy.sms.forwarder.fragment.senders.DingtalkGroupRobotFragment",
"{\"\":\"\"}",
CoreAnim.slide,
R.drawable.icon_dingtalk
),
PageInfo(
getString(R.string.email),
"com.idormy.sms.forwarder.fragment.senders.EmailFragment",
"{\"\":\"\"}",
CoreAnim.slide,
R.drawable.icon_email
),
PageInfo(
getString(R.string.bark),
"com.idormy.sms.forwarder.fragment.senders.BarkFragment",
"{\"\":\"\"}",
CoreAnim.slide,
R.drawable.icon_bark
),
PageInfo(
getString(R.string.webhook),
"com.idormy.sms.forwarder.fragment.senders.WebhookFragment",
"{\"\":\"\"}",
CoreAnim.slide,
R.drawable.icon_webhook
),
PageInfo(
getString(R.string.wework_robot),
"com.idormy.sms.forwarder.fragment.senders.WeworkRobotFragment",
"{\"\":\"\"}",
CoreAnim.slide,
R.drawable.icon_wework_robot
),
PageInfo(
getString(R.string.wework_agent),
"com.idormy.sms.forwarder.fragment.senders.WeworkAgentFragment",
"{\"\":\"\"}",
CoreAnim.slide,
R.drawable.icon_wework_agent
),
PageInfo(
getString(R.string.server_chan),
"com.idormy.sms.forwarder.fragment.senders.ServerchanFragment",
"{\"\":\"\"}",
CoreAnim.slide,
R.drawable.icon_serverchan
),
PageInfo(
getString(R.string.telegram),
"com.idormy.sms.forwarder.fragment.senders.TelegramFragment",
"{\"\":\"\"}",
CoreAnim.slide,
R.drawable.icon_telegram
),
PageInfo(
getString(R.string.sms_menu),
"com.idormy.sms.forwarder.fragment.senders.SmsFragment",
"{\"\":\"\"}",
CoreAnim.slide,
R.drawable.icon_sms
),
PageInfo(
getString(R.string.feishu),
"com.idormy.sms.forwarder.fragment.senders.FeishuFragment",
"{\"\":\"\"}",
CoreAnim.slide,
R.drawable.icon_feishu
),
PageInfo(
getString(R.string.pushplus),
"com.idormy.sms.forwarder.fragment.senders.PushplusFragment",
"{\"\":\"\"}",
CoreAnim.slide,
R.drawable.icon_pushplus
),
PageInfo(
getString(R.string.gotify),
"com.idormy.sms.forwarder.fragment.senders.GotifyFragment",
"{\"\":\"\"}",
CoreAnim.slide,
R.drawable.icon_gotify
),
PageInfo(
getString(R.string.dingtalk_inner_robot),
"com.idormy.sms.forwarder.fragment.senders.DingtalkInnerRobotFragment",
"{\"\":\"\"}",
CoreAnim.slide,
R.drawable.icon_dingtalk_inner
),
PageInfo(
getString(R.string.feishu_app),
"com.idormy.sms.forwarder.fragment.senders.FeishuAppFragment",
"{\"\":\"\"}",
CoreAnim.slide,
R.drawable.icon_feishu_app
),
)
//前台服务
const val FRONT_NOTIFY_ID = 0x1010
const val FRONT_CHANNEL_ID = "com.idormy.sms.forwarder"
const val FRONT_CHANNEL_NAME = "SmsForwarder Foreground Service"
//Frp内网穿透
const val FRPC_LIB_DOWNLOAD_URL = "https://xupdate.ppps.cn/uploads/%s/%s/libgojni.so"
const val FRPC_LIB_VERSION = "0.44.0"
const val EVENT_FRPC_UPDATE_CONFIG = "EVENT_FRPC_UPDATE_CONFIG"
const val EVENT_FRPC_DELETE_CONFIG = "EVENT_FRPC_DELETE_CONFIG"
const val EVENT_FRPC_RUNNING_ERROR = "EVENT_FRPC_RUNNING_ERROR"
const val EVENT_FRPC_RUNNING_SUCCESS = "EVENT_FRPC_RUNNING_SUCCESS"
const val INTENT_FRPC_EDIT_FILE = "INTENT_FRPC_EDIT_FILE"
const val INTENT_FRPC_APPLY_FILE = "INTENT_FRPC_APPLY_FILE"
//来电监听
const val ACTION_CALL_IN = "android.intent.action.PHONE_STATE"
const val ACTION_CALL_OUT = "android.intent.action.NEW_OUTGOING_CALL"
const val EXTRA_PHONE_NUMBER = "android.intent.extra.PHONE_NUMBER"
//Markdown 查看页面
const val KEY_TITLE = "key_title"
const val KEY_URL = "key_url"
//主页监听时间
const val EVENT_UPDATE_LOGS_TYPE = "key_logs_type"
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_TYPE = "key_sender_type"
const val KEY_SENDER_CLONE = "key_sender_clone"
const val KEY_SENDER_TEST = "key_sender_test"
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_KEY_SIM_SLOT = "EVENT_KEY_SIM_SLOT"
const val EVENT_KEY_PHONE_NUMBERS = "EVENT_KEY_PHONE_NUMBERS"
//在线升级URL
const val KEY_UPDATE_URL = "https://xupdate.ppps.cn/update/checkVersion"
//HttpServer相关
const val ENABLE_HTTP_SERVER = "enable_http_server"
const val HTTP_SERVER_PORT = 5000
const val HTTP_SERVER_TIME_OUT = 10
const val HTTP_SERVER_NOTIFY_ID = 0x1011
const val HTTP_SERVER_CHANNEL_ID = "http_server_notification_channel"
const val HTTP_SERVER_CHANNEL_NAME = "Http-Server Service"
const val START_ACTION = "start"
const val STOP_ACTION = "stop"
const val HTTP_SUCCESS_CODE: Int = 200
const val HTTP_FAILURE_CODE: Int = 500
const val SP_ENABLE_SERVER = "enable_server"
const val SP_ENABLE_SERVER_AUTORUN = "enable_server_autorun"
const val SP_SERVER_SAFETY_MEASURES = "server_safety_measures"
const val SP_SERVER_SIGN_KEY = "server_sign_key"
const val SP_SERVER_TIME_TOLERANCE = "server_time_tolerance"
const val SP_SERVER_SM4_KEY = "server_sm4_key"
const val SP_SERVER_PUBLIC_KEY = "server_public_key"
const val SP_SERVER_PRIVATE_KEY = "server_private_key"
const val SP_SERVER_WEB_PATH = "server_web_path"
const val SP_ENABLE_API_CLONE = "enable_api_clone"
const val SP_ENABLE_API_SMS_SEND = "enable_api_sms_send"
const val SP_ENABLE_API_SMS_QUERY = "enable_api_sms_query"
const val SP_ENABLE_API_CALL_QUERY = "enable_api_call_query"
const val SP_ENABLE_API_CONTACT_QUERY = "enable_api_contact_query"
const val SP_ENABLE_API_BATTERY_QUERY = "enable_api_battery_query"
const val SP_ENABLE_API_WOL = "enable_api_wol"
const val SP_WOL_HISTORY = "wol_history"
const val SP_SERVER_ADDRESS = "server_address"
const val SP_SERVER_HISTORY = "server_history"
const val SP_SERVER_CONFIG = "server_config"
const val SP_CLIENT_SAFETY_MEASURES = "client_safety_measures"
const val SP_CLIENT_SIGN_KEY = "client_sign_key"
var CLIENT_FRAGMENT_LIST = listOf(
PageInfo(
getString(R.string.api_clone),
"com.idormy.sms.forwarder.fragment.client.CloneFragment",
"{\"\":\"\"}",
CoreAnim.slide,
R.drawable.icon_api_clone
),
PageInfo(
getString(R.string.api_sms_send),
"com.idormy.sms.forwarder.fragment.client.SmsSendFragment",
"{\"\":\"\"}",
CoreAnim.slide,
R.drawable.icon_api_sms_send
),
PageInfo(
getString(R.string.api_sms_query),
"com.idormy.sms.forwarder.fragment.client.SmsQueryFragment",
"{\"\":\"\"}",
CoreAnim.slide,
R.drawable.icon_api_sms_query
),
PageInfo(
getString(R.string.api_call_query),
"com.idormy.sms.forwarder.fragment.client.CallQueryFragment",
"{\"\":\"\"}",
CoreAnim.slide,
R.drawable.icon_api_call_query
),
PageInfo(
getString(R.string.api_contact_query),
"com.idormy.sms.forwarder.fragment.client.ContactQueryFragment",
"{\"\":\"\"}",
CoreAnim.slide,
R.drawable.icon_api_contact_query
),
PageInfo(
getString(R.string.api_battery_query),
"com.idormy.sms.forwarder.fragment.client.BatteryQueryFragment",
"{\"\":\"\"}",
CoreAnim.slide,
R.drawable.icon_api_battery_query
),
PageInfo(
getString(R.string.api_wol),
"com.idormy.sms.forwarder.fragment.client.WolSendFragment",
"{\"\":\"\"}",
CoreAnim.slide,
R.drawable.icon_api_wol
),
@file:Suppress("unused")
package com.idormy.sms.forwarder.utils
import com.idormy.sms.forwarder.R
import com.xuexiang.xpage.enums.CoreAnim
import com.xuexiang.xpage.model.PageInfo
import com.xuexiang.xui.utils.ResUtils.getString
object Worker {
const val sendMsgInfo = "send_msg_info"
const val sendLogId = "send_log_id"
const val sendSbnId = "send_sbn_id"
const val updateLogs = "update_logs"
}
//初始化相关
const val IS_FIRST_OPEN_KEY = "is_first_open_key"
const val IS_AGREE_PRIVACY_KEY = "is_agree_privacy_key"
//数据库
const val DATABASE_NAME = "sms_forwarder.db"
const val PACKAGE_NAME = "com.idormy.sms.forwarder"
//通用设置
const val SP_ENABLE_SMS = "enable_sms"
const val SP_ENABLE_PHONE = "enable_phone"
const val SP_ENABLE_CALL_TYPE_1 = "enable_call_type_1"
const val SP_ENABLE_CALL_TYPE_2 = "enable_call_type_2"
const val SP_ENABLE_CALL_TYPE_3 = "enable_call_type_3"
const val SP_ENABLE_CALL_TYPE_4 = "enable_call_type_4"
const val SP_ENABLE_APP_NOTIFY = "enable_app_notify"
const val SP_ENABLE_CANCEL_APP_NOTIFY = "enable_cancel_app_notify"
const val SP_ENABLE_NOT_USER_PRESENT = "enable_not_user_present"
const val ENABLE_LOAD_APP_LIST = "enable_load_app_list"
const val ENABLE_LOAD_USER_APP_LIST = "enable_load_user_app_list"
const val ENABLE_LOAD_SYSTEM_APP_LIST = "enable_load_system_app_list"
const val SP_DUPLICATE_MESSAGES_LIMITS = "duplicate_messages_limits"
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_BATTERY_RECEIVER = "enable_battery_receiver"
const val SP_BATTERY_STATUS = "battery_status"
const val SP_BATTERY_LEVEL_MIN = "battery_level_min"
const val SP_BATTERY_LEVEL_MAX = "battery_level_max"
const val SP_BATTERY_LEVEL_ONCE = "battery_level_once"
const val SP_BATTERY_LEVEL_CURRENT = "battery_level_current"
const val SP_BATTERY_CRON = "enable_battery_cron"
const val SP_BATTERY_CRON_START_TIME = "battery_cron_start_time"
const val SP_BATTERY_CRON_INTERVAL = "battery_cron_interval"
const val SP_ENABLE_EXCLUDE_FROM_RECENTS = "enable_exclude_from_recents"
const val SP_ENABLE_PLAY_SILENCE_MUSIC = "enable_play_silence_music"
const val SP_ENABLE_ONE_PIXEL_ACTIVITY = "enable_one_pixel_activity"
const val SP_REQUEST_RETRY_TIMES = "request_retry_times"
const val SP_REQUEST_DELAY_TIME = "request_delay_time"
const val SP_REQUEST_TIMEOUT = "request_timeout"
const val SP_NOTIFY_CONTENT = "notify_content"
const val SP_EXTRA_DEVICE_MARK = "extra_device_mark"
const val SP_SUBID_SIM1 = "subid_sim1"
const val SP_SUBID_SIM2 = "subid_sim2"
const val SP_EXTRA_SIM1 = "extra_sim1"
const val SP_EXTRA_SIM2 = "extra_sim2"
const val SP_ENABLE_SMS_TEMPLATE = "enable_sms_template"
const val SP_SMS_TEMPLATE = "sms_template"
const val SP_ENABLE_HELP_TIP = "enable_help_tip"
const val SP_PURE_CLIENT_MODE = "enable_pure_client_mode"
const val SP_ENABLE_CACTUS = "enable_cactus"
const val CACTUS_TIMER = "cactus_timer"
const val CACTUS_LAST_TIMER = "cactus_last_timer"
const val CACTUS_DATE = "cactus_date"
const val CACTUS_END_DATE = "cactus_end_date"
//OkHttp 请求超时时间
const val REQUEST_TIMEOUT_SECONDS = 5
//规则相关
const val STATUS_ON = 1
const val STATUS_OFF = 0
const val FILED_TRANSPOND_ALL = "transpond_all"
const val FILED_PHONE_NUM = "phone_num"
const val FILED_PACKAGE_NAME = "package_name"
const val FILED_MSG_CONTENT = "msg_content"
const val FILED_INFORM_CONTENT = "inform_content"
const val FILED_MULTI_MATCH = "multi_match"
const val CHECK_IS = "is"
const val CHECK_CONTAIN = "contain"
const val CHECK_NOT_CONTAIN = "notcontain"
const val CHECK_START_WITH = "startwith"
const val CHECK_END_WITH = "endwith"
const val CHECK_NOT_IS = "notis"
const val CHECK_REGEX = "regex"
const val CHECK_SIM_SLOT_ALL = "ALL"
const val CHECK_SIM_SLOT_1 = "SIM1"
const val CHECK_SIM_SLOT_2 = "SIM2"
val TYPE_MAP = object : HashMap<String, String>() {
init {
put("sms", getString(R.string.rule_sms))
put("call", getString(R.string.rule_call))
put("app", getString(R.string.rule_app))
}
}
val FILED_MAP = object : HashMap<String, String>() {
init {
put("transpond_all", getString(R.string.rule_transpond_all))
put("phone_num", getString(R.string.rule_phone_num))
put("msg_content", getString(R.string.rule_msg_content))
put("multi_match", getString(R.string.rule_multi_match))
put("package_name", getString(R.string.rule_package_name))
put("inform_content", getString(R.string.rule_inform_content))
}
}
val CHECK_MAP = object : HashMap<String, String>() {
init {
put("is", getString(R.string.rule_is))
put("notis", getString(R.string.rule_notis))
put("contain", getString(R.string.rule_contain))
put("startwith", getString(R.string.rule_startwith))
put("endwith", getString(R.string.rule_endwith))
put("notcontain", getString(R.string.rule_notcontain))
put("regex", getString(R.string.rule_regex))
}
}
val SIM_SLOT_MAP = object : HashMap<String, String>() {
init {
put("ALL", getString(R.string.rule_all))
put("SIM1", "SIM1")
put("SIM2", "SIM2")
}
}
val FORWARD_STATUS_MAP = object : HashMap<Int, String>() {
init {
put(0, getString(R.string.failed))
put(1, getString(R.string.processing))
put(2, getString(R.string.success))
}
}
val BARK_LEVEL_MAP = mapOf(
"active" to getString(R.string.bark_level_active),
"timeSensitive" to getString(R.string.bark_level_timeSensitive),
"passive" to getString(R.string.bark_level_passive)
)
//发送通道
const val TYPE_DINGTALK_GROUP_ROBOT = 0
const val TYPE_EMAIL = 1
const val TYPE_BARK = 2
const val TYPE_WEBHOOK = 3
const val TYPE_WEWORK_ROBOT = 4
const val TYPE_WEWORK_AGENT = 5
const val TYPE_SERVERCHAN = 6
const val TYPE_TELEGRAM = 7
const val TYPE_SMS = 8
const val TYPE_FEISHU = 9
const val TYPE_PUSHPLUS = 10
const val TYPE_GOTIFY = 11
const val TYPE_DINGTALK_INNER_ROBOT = 12
const val TYPE_FEISHU_APP = 13
var SENDER_FRAGMENT_LIST = listOf(
PageInfo(
getString(R.string.dingtalk_robot),
"com.idormy.sms.forwarder.fragment.senders.DingtalkGroupRobotFragment",
"{\"\":\"\"}",
CoreAnim.slide,
R.drawable.icon_dingtalk
),
PageInfo(
getString(R.string.email),
"com.idormy.sms.forwarder.fragment.senders.EmailFragment",
"{\"\":\"\"}",
CoreAnim.slide,
R.drawable.icon_email
),
PageInfo(
getString(R.string.bark),
"com.idormy.sms.forwarder.fragment.senders.BarkFragment",
"{\"\":\"\"}",
CoreAnim.slide,
R.drawable.icon_bark
),
PageInfo(
getString(R.string.webhook),
"com.idormy.sms.forwarder.fragment.senders.WebhookFragment",
"{\"\":\"\"}",
CoreAnim.slide,
R.drawable.icon_webhook
),
PageInfo(
getString(R.string.wework_robot),
"com.idormy.sms.forwarder.fragment.senders.WeworkRobotFragment",
"{\"\":\"\"}",
CoreAnim.slide,
R.drawable.icon_wework_robot
),
PageInfo(
getString(R.string.wework_agent),
"com.idormy.sms.forwarder.fragment.senders.WeworkAgentFragment",
"{\"\":\"\"}",
CoreAnim.slide,
R.drawable.icon_wework_agent
),
PageInfo(
getString(R.string.server_chan),
"com.idormy.sms.forwarder.fragment.senders.ServerchanFragment",
"{\"\":\"\"}",
CoreAnim.slide,
R.drawable.icon_serverchan
),
PageInfo(
getString(R.string.telegram),
"com.idormy.sms.forwarder.fragment.senders.TelegramFragment",
"{\"\":\"\"}",
CoreAnim.slide,
R.drawable.icon_telegram
),
PageInfo(
getString(R.string.sms_menu),
"com.idormy.sms.forwarder.fragment.senders.SmsFragment",
"{\"\":\"\"}",
CoreAnim.slide,
R.drawable.icon_sms
),
PageInfo(
getString(R.string.feishu),
"com.idormy.sms.forwarder.fragment.senders.FeishuFragment",
"{\"\":\"\"}",
CoreAnim.slide,
R.drawable.icon_feishu
),
PageInfo(
getString(R.string.pushplus),
"com.idormy.sms.forwarder.fragment.senders.PushplusFragment",
"{\"\":\"\"}",
CoreAnim.slide,
R.drawable.icon_pushplus
),
PageInfo(
getString(R.string.gotify),
"com.idormy.sms.forwarder.fragment.senders.GotifyFragment",
"{\"\":\"\"}",
CoreAnim.slide,
R.drawable.icon_gotify
),
PageInfo(
getString(R.string.dingtalk_inner_robot),
"com.idormy.sms.forwarder.fragment.senders.DingtalkInnerRobotFragment",
"{\"\":\"\"}",
CoreAnim.slide,
R.drawable.icon_dingtalk_inner
),
PageInfo(
getString(R.string.feishu_app),
"com.idormy.sms.forwarder.fragment.senders.FeishuAppFragment",
"{\"\":\"\"}",
CoreAnim.slide,
R.drawable.icon_feishu_app
),
)
//前台服务
const val FRONT_NOTIFY_ID = 0x1010
const val FRONT_CHANNEL_ID = "com.idormy.sms.forwarder"
const val FRONT_CHANNEL_NAME = "SmsForwarder Foreground Service"
//Frp内网穿透
const val FRPC_LIB_DOWNLOAD_URL = "https://xupdate.ppps.cn/uploads/%s/%s/libgojni.so"
const val FRPC_LIB_VERSION = "0.44.0"
const val EVENT_FRPC_UPDATE_CONFIG = "EVENT_FRPC_UPDATE_CONFIG"
const val EVENT_FRPC_DELETE_CONFIG = "EVENT_FRPC_DELETE_CONFIG"
const val EVENT_FRPC_RUNNING_ERROR = "EVENT_FRPC_RUNNING_ERROR"
const val EVENT_FRPC_RUNNING_SUCCESS = "EVENT_FRPC_RUNNING_SUCCESS"
const val INTENT_FRPC_EDIT_FILE = "INTENT_FRPC_EDIT_FILE"
const val INTENT_FRPC_APPLY_FILE = "INTENT_FRPC_APPLY_FILE"
//来电监听
const val ACTION_CALL_IN = "android.intent.action.PHONE_STATE"
const val ACTION_CALL_OUT = "android.intent.action.NEW_OUTGOING_CALL"
const val EXTRA_PHONE_NUMBER = "android.intent.extra.PHONE_NUMBER"
//Markdown 查看页面
const val KEY_TITLE = "key_title"
const val KEY_URL = "key_url"
//主页监听时间
const val EVENT_UPDATE_LOGS_TYPE = "key_logs_type"
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_TYPE = "key_sender_type"
const val KEY_SENDER_CLONE = "key_sender_clone"
const val KEY_SENDER_TEST = "key_sender_test"
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_KEY_SIM_SLOT = "EVENT_KEY_SIM_SLOT"
const val EVENT_KEY_PHONE_NUMBERS = "EVENT_KEY_PHONE_NUMBERS"
//在线升级URL
const val KEY_UPDATE_URL = "https://xupdate.ppps.cn/update/checkVersion"
//HttpServer相关
const val ENABLE_HTTP_SERVER = "enable_http_server"
const val HTTP_SERVER_PORT = 5000
const val HTTP_SERVER_TIME_OUT = 10
const val HTTP_SERVER_NOTIFY_ID = 0x1011
const val HTTP_SERVER_CHANNEL_ID = "http_server_notification_channel"
const val HTTP_SERVER_CHANNEL_NAME = "Http-Server Service"
const val START_ACTION = "start"
const val STOP_ACTION = "stop"
const val HTTP_SUCCESS_CODE: Int = 200
const val HTTP_FAILURE_CODE: Int = 500
const val SP_ENABLE_SERVER = "enable_server"
const val SP_ENABLE_SERVER_AUTORUN = "enable_server_autorun"
const val SP_SERVER_SAFETY_MEASURES = "server_safety_measures"
const val SP_SERVER_SIGN_KEY = "server_sign_key"
const val SP_SERVER_TIME_TOLERANCE = "server_time_tolerance"
const val SP_SERVER_SM4_KEY = "server_sm4_key"
const val SP_SERVER_PUBLIC_KEY = "server_public_key"
const val SP_SERVER_PRIVATE_KEY = "server_private_key"
const val SP_SERVER_WEB_PATH = "server_web_path"
const val SP_ENABLE_API_CLONE = "enable_api_clone"
const val SP_ENABLE_API_SMS_SEND = "enable_api_sms_send"
const val SP_ENABLE_API_SMS_QUERY = "enable_api_sms_query"
const val SP_ENABLE_API_CALL_QUERY = "enable_api_call_query"
const val SP_ENABLE_API_CONTACT_QUERY = "enable_api_contact_query"
const val SP_ENABLE_API_BATTERY_QUERY = "enable_api_battery_query"
const val SP_ENABLE_API_WOL = "enable_api_wol"
const val SP_WOL_HISTORY = "wol_history"
const val SP_SERVER_ADDRESS = "server_address"
const val SP_SERVER_HISTORY = "server_history"
const val SP_SERVER_CONFIG = "server_config"
const val SP_CLIENT_SAFETY_MEASURES = "client_safety_measures"
const val SP_CLIENT_SIGN_KEY = "client_sign_key"
var CLIENT_FRAGMENT_LIST = listOf(
PageInfo(
getString(R.string.api_clone),
"com.idormy.sms.forwarder.fragment.client.CloneFragment",
"{\"\":\"\"}",
CoreAnim.slide,
R.drawable.icon_api_clone
),
PageInfo(
getString(R.string.api_sms_send),
"com.idormy.sms.forwarder.fragment.client.SmsSendFragment",
"{\"\":\"\"}",
CoreAnim.slide,
R.drawable.icon_api_sms_send
),
PageInfo(
getString(R.string.api_sms_query),
"com.idormy.sms.forwarder.fragment.client.SmsQueryFragment",
"{\"\":\"\"}",
CoreAnim.slide,
R.drawable.icon_api_sms_query
),
PageInfo(
getString(R.string.api_call_query),
"com.idormy.sms.forwarder.fragment.client.CallQueryFragment",
"{\"\":\"\"}",
CoreAnim.slide,
R.drawable.icon_api_call_query
),
PageInfo(
getString(R.string.api_contact_query),
"com.idormy.sms.forwarder.fragment.client.ContactQueryFragment",
"{\"\":\"\"}",
CoreAnim.slide,
R.drawable.icon_api_contact_query
),
PageInfo(
getString(R.string.api_battery_query),
"com.idormy.sms.forwarder.fragment.client.BatteryQueryFragment",
"{\"\":\"\"}",
CoreAnim.slide,
R.drawable.icon_api_battery_query
),
PageInfo(
getString(R.string.api_wol),
"com.idormy.sms.forwarder.fragment.client.WolSendFragment",
"{\"\":\"\"}",
CoreAnim.slide,
R.drawable.icon_api_wol
),
)

@ -114,6 +114,12 @@ class SettingUtils private constructor() {
//设备名称
var extraDeviceMark: String by SharedPreference(SP_EXTRA_DEVICE_MARK, "")
//SM1主键
var subidSim1: Int by SharedPreference(SP_SUBID_SIM1, 0)
//SM2主键
var subidSim2: Int by SharedPreference(SP_SUBID_SIM2, 0)
//SM1备注
var extraSim1: String by SharedPreference(SP_EXTRA_SIM1, "")

@ -1,74 +1,74 @@
package com.idormy.sms.forwarder.utils.sender
import android.Manifest
import android.content.pm.PackageManager
import android.util.Log
import androidx.core.app.ActivityCompat
import com.idormy.sms.forwarder.App
import com.idormy.sms.forwarder.R
import com.idormy.sms.forwarder.database.entity.Rule
import com.idormy.sms.forwarder.entity.MsgInfo
import com.idormy.sms.forwarder.entity.setting.SmsSetting
import com.idormy.sms.forwarder.utils.PhoneUtils
import com.idormy.sms.forwarder.utils.SendUtils
import com.idormy.sms.forwarder.utils.SettingUtils
import com.xuexiang.xui.utils.ResUtils
import com.xuexiang.xutil.XUtil
import com.xuexiang.xutil.net.NetworkUtils
@Suppress("PrivatePropertyName", "UNUSED_PARAMETER", "unused")
class SmsUtils {
companion object {
private val TAG: String = SmsUtils::class.java.simpleName
fun sendMsg(
setting: SmsSetting,
msgInfo: MsgInfo,
rule: Rule?,
logId: Long?,
) {
//仅当无网络时启用 && 判断是否真实有网络
if (setting.onlyNoNetwork == true && NetworkUtils.isHaveInternet() && NetworkUtils.isAvailableByPing()) {
SendUtils.updateLogs(logId, 0, ResUtils.getString(R.string.OnlyNoNetwork))
return
}
if (ActivityCompat.checkSelfPermission(XUtil.getContext(), Manifest.permission.SEND_SMS) != PackageManager.PERMISSION_GRANTED) {
SendUtils.updateLogs(logId, 0, ResUtils.getString(R.string.no_sms_sending_permission))
return
}
val content: String = if (rule != null) {
msgInfo.getContentForSend(rule.smsTemplate, rule.regexReplace)
} else {
msgInfo.getContentForSend(SettingUtils.smsTemplate.toString())
}
//【注意】判断卡槽配置0=原进原出、1=卡槽1、2=卡槽2
val simSlotIndex = if (setting.simSlot == 0) msgInfo.simSlot else setting.simSlot - 1
//获取卡槽信息
if (App.SimInfoList.isEmpty()) {
App.SimInfoList = PhoneUtils.getSimMultiInfo()
}
Log.d(TAG, App.SimInfoList.toString())
//替换 {{来源号码}} 标签
val mobiles = setting.mobiles.replace(ResUtils.getString(R.string.tag_from), msgInfo.from)
//TODO取不到卡槽信息时采用默认卡槽发送
val mSubscriptionId: Int = App.SimInfoList[simSlotIndex]?.mSubscriptionId ?: -1
val res: String? = PhoneUtils.sendSms(mSubscriptionId, mobiles, content)
if (res == null) {
SendUtils.updateLogs(logId, 2, ResUtils.getString(R.string.request_succeeded))
} else {
SendUtils.updateLogs(logId, 0, res)
}
}
fun sendMsg(setting: SmsSetting, msgInfo: MsgInfo) {
sendMsg(setting, msgInfo, null, null)
}
}
package com.idormy.sms.forwarder.utils.sender
import android.Manifest
import android.content.pm.PackageManager
import android.util.Log
import androidx.core.app.ActivityCompat
import com.idormy.sms.forwarder.App
import com.idormy.sms.forwarder.R
import com.idormy.sms.forwarder.database.entity.Rule
import com.idormy.sms.forwarder.entity.MsgInfo
import com.idormy.sms.forwarder.entity.setting.SmsSetting
import com.idormy.sms.forwarder.utils.PhoneUtils
import com.idormy.sms.forwarder.utils.SendUtils
import com.idormy.sms.forwarder.utils.SettingUtils
import com.xuexiang.xui.utils.ResUtils
import com.xuexiang.xutil.XUtil
import com.xuexiang.xutil.net.NetworkUtils
@Suppress("PrivatePropertyName", "UNUSED_PARAMETER", "unused")
class SmsUtils {
companion object {
private val TAG: String = SmsUtils::class.java.simpleName
fun sendMsg(
setting: SmsSetting,
msgInfo: MsgInfo,
rule: Rule?,
logId: Long?,
) {
//仅当无网络时启用 && 判断是否真实有网络
if (setting.onlyNoNetwork == true && NetworkUtils.isHaveInternet() && NetworkUtils.isAvailableByPing()) {
SendUtils.updateLogs(logId, 0, ResUtils.getString(R.string.OnlyNoNetwork))
return
}
if (ActivityCompat.checkSelfPermission(XUtil.getContext(), Manifest.permission.SEND_SMS) != PackageManager.PERMISSION_GRANTED) {
SendUtils.updateLogs(logId, 0, ResUtils.getString(R.string.no_sms_sending_permission))
return
}
val content: String = if (rule != null) {
msgInfo.getContentForSend(rule.smsTemplate, rule.regexReplace)
} else {
msgInfo.getContentForSend(SettingUtils.smsTemplate)
}
//【注意】判断卡槽配置0=原进原出、1=卡槽1、2=卡槽2
val simSlotIndex = if (setting.simSlot == 0) msgInfo.simSlot else setting.simSlot - 1
//获取卡槽信息
if (App.SimInfoList.isEmpty()) {
App.SimInfoList = PhoneUtils.getSimMultiInfo()
}
Log.d(TAG, App.SimInfoList.toString())
//替换 {{来源号码}} 标签
val mobiles = setting.mobiles.replace(ResUtils.getString(R.string.tag_from), msgInfo.from)
//TODO取不到卡槽信息时采用默认卡槽发送
val mSubscriptionId: Int = App.SimInfoList[simSlotIndex]?.mSubscriptionId ?: -1
val res: String? = PhoneUtils.sendSms(mSubscriptionId, mobiles, content)
if (res == null) {
SendUtils.updateLogs(logId, 2, ResUtils.getString(R.string.request_succeeded))
} else {
SendUtils.updateLogs(logId, 0, res)
}
}
fun sendMsg(setting: SmsSetting, msgInfo: MsgInfo) {
sendMsg(setting, msgInfo, null, null)
}
}
}

@ -83,7 +83,7 @@ class SendWorker(
for (rule in ruleList) {
if (!rule.rule.checkMsg(msgInfo)) continue
val log = Logs(
0, msgInfo.type, msgInfo.from, msgInfo.content, rule.rule.id, msgInfo.simInfo
0, msgInfo.type, msgInfo.from, msgInfo.content, rule.rule.id, msgInfo.simInfo, msgInfo.subId
)
val logId = Core.logs.insert(log)
SendUtils.sendMsgSender(msgInfo, rule.rule, rule.sender, logId)

File diff suppressed because it is too large Load Diff

@ -334,9 +334,12 @@
<!--SettingActivity-->
<string name="notify_content">Notify Content</string>
<string name="device_name">Device Name</string>
<string name="sim1_remark" tools:ignore="Typos">SIM1 Label</string>
<string name="sim2_remark" tools:ignore="Typos">SIM2 Label</string>
<string name="sim_sub_id">SIM1 SubId</string>
<string name="sim1_remark" tools:ignore="Typos">SIM1 SubId/Label</string>
<string name="sim2_remark" tools:ignore="Typos">SIM2 SubId/Label</string>
<string name="carrier_mobile" tools:ignore="Typos">Label of SIM,\neg. AT&amp;T_88888888</string>
<string name="tip_number_only_error_message">Number must be greater than 0!</string>
<string name="regexp_number_only">^[1-9]?\\d+$</string>
<string name="low_power_alarm_threshold">Low Power Alarm</string>
<string name="low_power_alarm_threshold_tips">Value range: 099.\nLeft blank or 0 is disabled</string>
<string name="retry_interval">Retry Interval</string>
@ -543,6 +546,7 @@
<string name="tag_app_name">{{APP_NAME}}</string>
<string name="tag_msg">{{MSG}}</string>
<string name="tag_card_slot">{{CARD_SLOT}}</string>
<string name="tag_card_subid">{{CARD_SUBID}}</string>
<string name="tag_receive_time">{{RECEIVE_TIME}}</string>
<string name="tag_device_name">{{DEVICE_NAME}}</string>
<string name="tag_app_version">{{APP_VERSION}}</string>

@ -335,9 +335,12 @@
<!--SettingActivity-->
<string name="notify_content">通知文案</string>
<string name="device_name">设备名称</string>
<string name="sim1_remark" tools:ignore="Typos">SIM1备注</string>
<string name="sim2_remark" tools:ignore="Typos">SIM2备注</string>
<string name="carrier_mobile">运营商_手机号</string>
<string name="sim_sub_id">卡槽主键</string>
<string name="sim1_remark" tools:ignore="Typos">SIM1主键/备注</string>
<string name="sim2_remark" tools:ignore="Typos">SIM2主键/备注</string>
<string name="carrier_mobile">序号/运营商_手机号</string>
<string name="tip_number_only_error_message">数字必须大于0!</string>
<string name="regexp_number_only">^[1-9]?\\d+$</string>
<string name="low_power_alarm_threshold">安全电量范围(%)</string>
<string name="low_power_alarm_threshold_tips">超出安全范围将发出预警</string>
<string name="retry_interval">请求重试机制</string>
@ -544,6 +547,7 @@
<string name="tag_app_name">{{APP名称}}</string>
<string name="tag_msg">{{通知内容}}</string>
<string name="tag_card_slot">{{卡槽信息}}</string>
<string name="tag_card_subid">{{卡槽主键}}</string>
<string name="tag_receive_time">{{接收时间}}</string>
<string name="tag_device_name">{{设备名称}}</string>
<string name="tag_app_version">{{当前应用版本号}}</string>

Loading…
Cancel
Save