From 65e861ba62c6bf760c808e1b070e83684db0710f Mon Sep 17 00:00:00 2001 From: pppscn <35696959@qq.com> Date: Mon, 30 Jan 2023 09:12:32 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96=EF=BC=9A=E7=9F=AD=E4=BF=A1/?= =?UTF-8?q?=E9=80=9A=E8=AF=9D=E8=BD=AC=E5=8F=91=E8=8E=B7=E5=8F=96=E5=8D=A1?= =?UTF-8?q?=E6=A7=BD=E4=BF=A1=E6=81=AF=E6=9C=BA=E5=88=B6=EF=BC=88=E8=87=AA?= =?UTF-8?q?=E8=A1=8C=E5=A4=87=E6=B3=A8=E5=8D=A1=E6=A7=BDSubId=E5=AF=B9?= =?UTF-8?q?=E5=BA=94=EF=BC=89#228=20#235?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/com/idormy/sms/forwarder/App.kt | 4 +- .../sms/forwarder/database/AppDatabase.kt | 9 +- .../sms/forwarder/database/entity/Logs.kt | 121 +- .../idormy/sms/forwarder/entity/CallInfo.kt | 111 +- .../idormy/sms/forwarder/entity/MsgInfo.kt | 10 +- .../idormy/sms/forwarder/entity/SimInfo.kt | 74 +- .../idormy/sms/forwarder/entity/SmsInfo.kt | 69 +- .../forwarder/fragment/SettingsFragment.kt | 42 + .../forwarder/receiver/PhoneStateReceiver.kt | 141 +- .../sms/forwarder/receiver/SmsReceiver.kt | 189 +- .../idormy/sms/forwarder/utils/Constants.kt | 792 ++--- .../idormy/sms/forwarder/utils/PhoneUtils.kt | 1077 +++---- .../sms/forwarder/utils/SettingUtils.kt | 6 + .../sms/forwarder/utils/sender/SmsUtils.kt | 146 +- .../sms/forwarder/workers/SendWorker.kt | 2 +- app/src/main/res/layout/fragment_settings.xml | 2830 +++++++++-------- app/src/main/res/values-en/strings.xml | 8 +- app/src/main/res/values/strings.xml | 10 +- 18 files changed, 2850 insertions(+), 2791 deletions(-) diff --git a/app/src/main/java/com/idormy/sms/forwarder/App.kt b/app/src/main/java/com/idormy/sms/forwarder/App.kt index f850b7ea..a8dbefaf 100644 --- a/app/src/main/java/com/idormy/sms/forwarder/App.kt +++ b/app/src/main/java/com/idormy/sms/forwarder/App.kt @@ -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()) } diff --git a/app/src/main/java/com/idormy/sms/forwarder/database/AppDatabase.kt b/app/src/main/java/com/idormy/sms/forwarder/database/AppDatabase.kt index a7163cc1..def5a17b 100644 --- a/app/src/main/java/com/idormy/sms/forwarder/database/AppDatabase.kt +++ b/app/src/main/java/com/idormy/sms/forwarder/database/AppDatabase.kt @@ -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 ") + } + } } } diff --git a/app/src/main/java/com/idormy/sms/forwarder/database/entity/Logs.kt b/app/src/main/java/com/idormy/sms/forwarder/database/entity/Logs.kt index 444f8ded..bfe94449 100644 --- a/app/src/main/java/com/idormy/sms/forwarder/database/entity/Logs.kt +++ b/app/src/main/java/com/idormy/sms/forwarder/database/entity/Logs.kt @@ -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 + } + } \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/entity/CallInfo.kt b/app/src/main/java/com/idormy/sms/forwarder/entity/CallInfo.kt index 3d5ea9d0..95348543 100644 --- a/app/src/main/java/com/idormy/sms/forwarder/entity/CallInfo.kt +++ b/app/src/main/java/com/idormy/sms/forwarder/entity/CallInfo.kt @@ -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 + + '}' + } } \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/entity/MsgInfo.kt b/app/src/main/java/com/idormy/sms/forwarder/entity/MsgInfo.kt index b79574ee..2acd4b94 100644 --- a/app/src/main/java/com/idormy/sms/forwarder/entity/MsgInfo.kt +++ b/app/src/main/java/com/idormy/sms/forwarder/entity/MsgInfo.kt @@ -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) diff --git a/app/src/main/java/com/idormy/sms/forwarder/entity/SimInfo.kt b/app/src/main/java/com/idormy/sms/forwarder/entity/SimInfo.kt index 04f3e3bd..f8ee9141 100644 --- a/app/src/main/java/com/idormy/sms/forwarder/entity/SimInfo.kt +++ b/app/src/main/java/com/idormy/sms/forwarder/entity/SimInfo.kt @@ -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 + + '}' + } +} diff --git a/app/src/main/java/com/idormy/sms/forwarder/entity/SmsInfo.kt b/app/src/main/java/com/idormy/sms/forwarder/entity/SmsInfo.kt index 4b60811a..06243814 100644 --- a/app/src/main/java/com/idormy/sms/forwarder/entity/SmsInfo.kt +++ b/app/src/main/java/com/idormy/sms/forwarder/entity/SmsInfo.kt @@ -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 + } + } +} diff --git a/app/src/main/java/com/idormy/sms/forwarder/fragment/SettingsFragment.kt b/app/src/main/java/com/idormy/sms/forwarder/fragment/SettingsFragment.kt index 97a9c7ab..a12c2966 100644 --- a/app/src/main/java/com/idormy/sms/forwarder/fragment/SettingsFragment.kt +++ b/app/src/main/java/com/idormy/sms/forwarder/fragment/SettingsFragment.kt @@ -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(), 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(), 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(), 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(), 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(), 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() diff --git a/app/src/main/java/com/idormy/sms/forwarder/receiver/PhoneStateReceiver.kt b/app/src/main/java/com/idormy/sms/forwarder/receiver/PhoneStateReceiver.kt index 988f9bc8..4b9ed845 100644 --- a/app/src/main/java/com/idormy/sms/forwarder/receiver/PhoneStateReceiver.kt +++ b/app/src/main/java/com/idormy/sms/forwarder/receiver/PhoneStateReceiver.kt @@ -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().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().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().setInputData( workDataOf( diff --git a/app/src/main/java/com/idormy/sms/forwarder/receiver/SmsReceiver.kt b/app/src/main/java/com/idormy/sms/forwarder/receiver/SmsReceiver.kt index 46bf047d..7bb80b73 100644 --- a/app/src/main/java/com/idormy/sms/forwarder/receiver/SmsReceiver.kt +++ b/app/src/main/java/com/idormy/sms/forwarder/receiver/SmsReceiver.kt @@ -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() - .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().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()) + } + } + } \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/utils/Constants.kt b/app/src/main/java/com/idormy/sms/forwarder/utils/Constants.kt index 3b57f20a..063fcb2f 100644 --- a/app/src/main/java/com/idormy/sms/forwarder/utils/Constants.kt +++ b/app/src/main/java/com/idormy/sms/forwarder/utils/Constants.kt @@ -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() { - 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() { - 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() { - 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() { - init { - put("ALL", getString(R.string.rule_all)) - put("SIM1", "SIM1") - put("SIM2", "SIM2") - } -} -val FORWARD_STATUS_MAP = object : HashMap() { - 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() { + 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() { + 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() { + 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() { + init { + put("ALL", getString(R.string.rule_all)) + put("SIM1", "SIM1") + put("SIM2", "SIM2") + } +} +val FORWARD_STATUS_MAP = object : HashMap() { + 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 + ), ) \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/utils/PhoneUtils.kt b/app/src/main/java/com/idormy/sms/forwarder/utils/PhoneUtils.kt index 46937f42..dc5bc65e 100644 --- a/app/src/main/java/com/idormy/sms/forwarder/utils/PhoneUtils.kt +++ b/app/src/main/java/com/idormy/sms/forwarder/utils/PhoneUtils.kt @@ -1,568 +1,511 @@ -package com.idormy.sms.forwarder.utils - -import android.Manifest.permission -import android.annotation.SuppressLint -import android.app.PendingIntent -import android.content.ContentResolver -import android.content.Context -import android.content.Intent -import android.net.Uri -import android.os.Build -import android.provider.CallLog -import android.provider.ContactsContract -import android.provider.Settings -import android.telephony.SmsManager -import android.telephony.SubscriptionInfo -import android.telephony.SubscriptionManager -import android.text.TextUtils -import android.util.Log -import androidx.annotation.RequiresPermission -import androidx.core.app.ActivityCompat -import com.idormy.sms.forwarder.App -import com.idormy.sms.forwarder.R -import com.idormy.sms.forwarder.core.Core -import com.idormy.sms.forwarder.entity.CallInfo -import com.idormy.sms.forwarder.entity.ContactInfo -import com.idormy.sms.forwarder.entity.SimInfo -import com.idormy.sms.forwarder.entity.SmsInfo -import com.xuexiang.xutil.XUtil -import com.xuexiang.xutil.app.IntentUtils -import com.xuexiang.xutil.data.DateUtils -import com.xuexiang.xutil.resource.ResUtils -import java.text.SimpleDateFormat -import java.util.* - -@Suppress("PropertyName") -class PhoneUtils private constructor() { - - companion object { - const val TAG = "PhoneUtils" - - //获取多卡信息 - @SuppressLint("Range") - fun getSimMultiInfo(): MutableMap { - val infoList = HashMap() - try { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1) { - println("1.版本超过5.1,调用系统方法") - val mSubscriptionManager = XUtil.getContext() - .getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE) as SubscriptionManager - ActivityCompat.checkSelfPermission( - XUtil.getContext(), - permission.READ_PHONE_STATE - ) - val activeSubscriptionInfoList: List? = - mSubscriptionManager.activeSubscriptionInfoList - if (activeSubscriptionInfoList != null && activeSubscriptionInfoList.isNotEmpty()) { - //1.1.1 有使用的卡,就遍历所有卡 - for (subscriptionInfo in activeSubscriptionInfoList) { - val simInfo = SimInfo() - simInfo.mCarrierName = subscriptionInfo.carrierName.toString() - simInfo.mIccId = subscriptionInfo.iccId.toString() - simInfo.mSimSlotIndex = subscriptionInfo.simSlotIndex - simInfo.mNumber = subscriptionInfo.number.toString() - simInfo.mCountryIso = subscriptionInfo.countryIso.toString() - simInfo.mSubscriptionId = subscriptionInfo.subscriptionId - println(simInfo.toString()) - infoList[simInfo.mSimSlotIndex] = simInfo - } - } - } else { - println("2.版本低于5.1的系统,首先调用数据库,看能不能访问到") - val uri = Uri.parse("content://telephony/siminfo") //访问raw_contacts表 - val resolver: ContentResolver = XUtil.getContext().contentResolver - val cursor = resolver.query( - uri, - arrayOf( - "_id", - "icc_id", - "sim_id", - "display_name", - "carrier_name", - "name_source", - "color", - "number", - "display_number_format", - "data_roaming", - "mcc", - "mnc" - ), - null, - null, - null - ) - if (cursor != null && cursor.moveToFirst()) { - do { - val simInfo = SimInfo() - simInfo.mCarrierName = - cursor.getString(cursor.getColumnIndex("carrier_name")) - simInfo.mIccId = cursor.getString(cursor.getColumnIndex("icc_id")) - simInfo.mSimSlotIndex = cursor.getInt(cursor.getColumnIndex("sim_id")) - simInfo.mNumber = cursor.getString(cursor.getColumnIndex("number")) - simInfo.mCountryIso = cursor.getString(cursor.getColumnIndex("mcc")) - //val id = cursor.getString(cursor.getColumnIndex("_id")) - println(simInfo.toString()) - infoList[simInfo.mSimSlotIndex] = simInfo - } while (cursor.moveToNext()) - cursor.close() - } - } - } catch (e: java.lang.Exception) { - e.printStackTrace() - } - Log.e(TAG, infoList.toString()) - return infoList - } - - //获取设备名称 - fun getDeviceName(): String { - return try { - Settings.Secure.getString(XUtil.getContentResolver(), "bluetooth_name") - } catch (e: Exception) { - e.printStackTrace() - Build.BRAND + " " + Build.MODEL - } - } - - /** - * 发送短信 - *

需添加权限 {@code }

- * - * @param subId 发送卡的subId,传入 -1 则 SmsManager.getDefault() - * @param mobileList 接收号码列表 - * @param message 短信内容 - */ - @Suppress("DEPRECATION") - @SuppressLint("SoonBlockedPrivateApi", "DiscouragedPrivateApi") - @RequiresPermission(permission.SEND_SMS) - fun sendSms(subId: Int, mobileList: String, message: String): String? { - val mobiles = mobileList.replace(";", ";").replace(",", ";").replace(",", ";") - Log.d(TAG, "subId = $subId, mobiles = $mobiles, message = $message") - val mobileArray = mobiles.split(";".toRegex()).toTypedArray() - for (mobile in mobileArray) { - try { - val sendFlags = - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) PendingIntent.FLAG_IMMUTABLE else PendingIntent.FLAG_ONE_SHOT - val sendPI = - PendingIntent.getBroadcast(XUtil.getContext(), 0, Intent(), sendFlags) - - val smsManager = - if (subId > -1 && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1) SmsManager.getSmsManagerForSubscriptionId( - subId - ) else SmsManager.getDefault() - // Android 5.1.1 以下使用反射指定卡槽 - if (subId > -1 && Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP_MR1) { - Log.d(TAG, "Android 5.1.1 以下使用反射指定卡槽") - val clz = SmsManager::class.java - val field = clz.getDeclaredField("mSubId") // 反射拿到变量 - field.isAccessible = true // 修改权限为可读写 - field.set(smsManager, subId) - } - - // 切割长短信 - if (message.length >= 70) { - val deliverFlags = - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) PendingIntent.FLAG_IMMUTABLE else 0 - val deliverPI = PendingIntent.getBroadcast( - XUtil.getContext(), - 0, - Intent("DELIVERED_SMS_ACTION"), - deliverFlags - ) - - val sentPendingIntents = ArrayList() - val deliveredPendingIntents = ArrayList() - val divideContents = smsManager.divideMessage(message) - - for (i in divideContents.indices) { - sentPendingIntents.add(i, sendPI) - deliveredPendingIntents.add(i, deliverPI) - } - smsManager.sendMultipartTextMessage( - mobile, - null, - divideContents, - sentPendingIntents, - deliveredPendingIntents - ) - } else { - smsManager.sendTextMessage(mobile, null, message, sendPI, null) - } - } catch (e: Exception) { - Log.e(TAG, e.message.toString()) - return e.message.toString() - } - } - - return null - } - - //获取通话记录列表 - fun getCallInfoList( - type: Int, - limit: Int, - offset: Int, - phoneNumber: String? - ): MutableList { - val callInfoList: MutableList = mutableListOf() - try { - var selection = "1=1" - val selectionArgs = ArrayList() - if (type > 0) { - selection += " and " + CallLog.Calls.TYPE + " = ?" - selectionArgs.add("$type") - } - if (!TextUtils.isEmpty(phoneNumber)) { - selection += " and " + CallLog.Calls.NUMBER + " like ?" - selectionArgs.add("%$phoneNumber%") - } - Log.d(TAG, "selection = $selection") - Log.d(TAG, "selectionArgs = $selectionArgs") - - //为了兼容性这里全部取出后手动分页 - val cursor = Core.app.contentResolver.query( - CallLog.Calls.CONTENT_URI, - null, - selection, - selectionArgs.toTypedArray(), - CallLog.Calls.DEFAULT_SORT_ORDER // + " limit $limit offset $offset" - ) ?: return callInfoList - Log.i(TAG, "cursor count:" + cursor.count) - - // 避免超过总数后循环取出 - if (cursor.count == 0 || offset >= cursor.count) { - cursor.close() - return callInfoList - } - - if (cursor.moveToFirst()) { - Log.d(TAG, "Call ColumnNames=${cursor.columnNames.contentToString()}") - val indexName = cursor.getColumnIndex(CallLog.Calls.CACHED_NAME) - val indexNumber = cursor.getColumnIndex(CallLog.Calls.NUMBER) - val indexDate = cursor.getColumnIndex(CallLog.Calls.DATE) - val indexDuration = cursor.getColumnIndex(CallLog.Calls.DURATION) - val indexType = cursor.getColumnIndex(CallLog.Calls.TYPE) - val indexViaNumber = - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && cursor.getColumnIndex( - "via_number" - ) != -1 - ) cursor.getColumnIndex("via_number") else -1 - //TODO:卡槽识别,这里需要适配机型 - var isSimId = false - var indexSimId = -1 - if (cursor.getColumnIndex("simid") != -1) { //MIUI系统必须用这个字段 - indexSimId = cursor.getColumnIndex("simid") - isSimId = true - } else if (cursor.getColumnIndex(CallLog.Calls.PHONE_ACCOUNT_ID) != -1) { - indexSimId = cursor.getColumnIndex(CallLog.Calls.PHONE_ACCOUNT_ID) - } - var curOffset = 0 - do { - if (curOffset >= offset) { - val callInfo = CallInfo( - cursor.getString(indexName) ?: "", //姓名 - cursor.getString(indexNumber) ?: "", //号码 - cursor.getLong(indexDate), //获取通话日期 - cursor.getInt(indexDuration), //获取通话时长,值为多少秒 - cursor.getInt(indexType), //获取通话类型:1.呼入 2.呼出 3.未接 - if (indexViaNumber != -1) cursor.getString(indexViaNumber) else "", //来源号码 - if (indexSimId != -1) getSimId( - cursor.getInt(indexSimId), - isSimId - ) else -1 //卡槽id - ) - Log.d(TAG, callInfo.toString()) - callInfoList.add(callInfo) - if (limit == 1) { - cursor.close() - return callInfoList - } - } - curOffset++ - if (curOffset >= offset + limit) break - } while (cursor.moveToNext()) - if (!cursor.isClosed) cursor.close() - } - } catch (e: java.lang.Exception) { - Log.e(TAG, "getCallInfoList:", e) - } - - return callInfoList - } - - //获取后一条通话记录 - @SuppressLint("Range") - fun getLastCallInfo(callType: Int, phoneNumber: String?): CallInfo? { - val callInfoList = getCallInfoList(callType, 1, 0, phoneNumber) - if (callInfoList.isNotEmpty()) return callInfoList[0] - return null - } - - //获取联系人列表 - fun getContactInfoList( - limit: Int, - offset: Int, - phoneNumber: String?, - name: String? - ): MutableList { - val contactInfoList: MutableList = mutableListOf() - - try { - var selection = "1=1" - val selectionArgs = ArrayList() - if (!TextUtils.isEmpty(phoneNumber)) { - selection += " and replace(replace(" + ContactsContract.CommonDataKinds.Phone.NUMBER + ",' ',''),'-','') like ?" - selectionArgs.add("%$phoneNumber%") - } - if (!TextUtils.isEmpty(name)) { - selection += " and " + ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME + " like ?" - selectionArgs.add("%$name%") - } - Log.d(TAG, "selection = $selection") - Log.d(TAG, "selectionArgs = $selectionArgs") - - val cursor = Core.app.contentResolver.query( - ContactsContract.CommonDataKinds.Phone.CONTENT_URI, - null, - selection, - selectionArgs.toTypedArray(), - ContactsContract.CommonDataKinds.Phone.SORT_KEY_PRIMARY - ) ?: return contactInfoList - Log.i(TAG, "cursor count:" + cursor.count) - - // 避免超过总数后循环取出 - if (cursor.count == 0 || offset >= cursor.count) { - cursor.close() - return contactInfoList - } - - if (cursor.moveToFirst()) { - val displayNameIndex = - cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME) - val mobileNoIndex = - cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER) - do { - val contactInfo = ContactInfo( - cursor.getString(displayNameIndex), //姓名 - cursor.getString(mobileNoIndex), //号码 - ) - Log.d(TAG, contactInfo.toString()) - contactInfoList.add(contactInfo) - if (limit == 1) { - cursor.close() - return contactInfoList - } - } while (cursor.moveToNext()) - if (!cursor.isClosed) cursor.close() - } - } catch (e: java.lang.Exception) { - Log.e(TAG, "getContactInfoList:", e) - } - - return contactInfoList - } - - //获取联系人姓名 - fun getContactByNumber(phoneNumber: String?): MutableList { - val contactInfoList = mutableListOf() - if (TextUtils.isEmpty(phoneNumber)) return contactInfoList - return getContactInfoList(1, 0, phoneNumber, null) - } - - //获取通话记录转发内容 - fun getCallMsg(callInfo: CallInfo): String { - val sb = StringBuilder() - sb.append(ResUtils.getString(R.string.linkman)).append(callInfo.name).append("\n") - if (!TextUtils.isEmpty(callInfo.viaNumber)) sb.append(ResUtils.getString(R.string.via_number)) - .append(callInfo.viaNumber).append("\n") - if (callInfo.dateLong > 0L) sb.append(ResUtils.getString(R.string.call_date)).append( - DateUtils.millis2String( - callInfo.dateLong, - SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault()) - ) - ).append("\n") - if (callInfo.duration > 0) { - if (callInfo.type == 3) { - sb.append(ResUtils.getString(R.string.ring_duration)) - } else { - sb.append(ResUtils.getString(R.string.call_duration)) - } - sb.append(callInfo.duration).append("s\n") - } - sb.append(ResUtils.getString(R.string.mandatory_type)) - //通话类型:1.呼入 2.呼出 3.未接 4.来电提醒 - when (callInfo.type) { - 1 -> sb.append(ResUtils.getString(R.string.received_call)) - 2 -> sb.append(ResUtils.getString(R.string.local_outgoing_call)) - 3 -> sb.append(ResUtils.getString(R.string.missed_call)) - else -> sb.append(ResUtils.getString(R.string.incoming_call)) - } - return sb.toString() - } - - // 获取用户短信列表 - fun getSmsInfoList( - type: Int, - limit: Int, - offset: Int, - keyword: String - ): MutableList { - val smsInfoList: MutableList = mutableListOf() - try { - var selection = "1=1" - val selectionArgs = ArrayList() - if (type > 0) { - selection += " and type = ?" - selectionArgs.add("$type") - } - if (!TextUtils.isEmpty(keyword)) { - selection += " and body like ?" - selectionArgs.add("%$keyword%") - } - Log.d(TAG, "selection = $selection") - Log.d(TAG, "selectionArgs = $selectionArgs") - - // 避免超过总数后循环取出 - val cursorTotal = Core.app.contentResolver.query( - Uri.parse("content://sms/"), - null, - selection, - selectionArgs.toTypedArray(), - "date desc" - ) ?: return smsInfoList - if (offset >= cursorTotal.count) { - cursorTotal.close() - return smsInfoList - } - - val cursor = Core.app.contentResolver.query( - Uri.parse("content://sms/"), - null, - selection, - selectionArgs.toTypedArray(), - "date desc limit $limit offset $offset" - ) ?: return smsInfoList - - Log.i(TAG, "cursor count:" + cursor.count) - if (cursor.count == 0) { - cursor.close() - return smsInfoList - } - - if (cursor.moveToFirst()) { - Log.d(TAG, "SMS ColumnNames=${cursor.columnNames.contentToString()}") - val indexAddress = cursor.getColumnIndex("address") - val indexBody = cursor.getColumnIndex("body") - val indexDate = cursor.getColumnIndex("date") - val indexType = cursor.getColumnIndex("type") - //TODO:卡槽识别,这里需要适配机型 - var isSimId = false - var indexSimId = -1 - if (cursor.getColumnIndex("sim_id") != -1) { //MIUI系统必须用这个字段 - indexSimId = cursor.getColumnIndex("sim_id") - isSimId = true - } else if (cursor.getColumnIndex("sub_id") != -1) { - indexSimId = cursor.getColumnIndex("sub_id") - } - do { - val smsInfo = SmsInfo() - val phoneNumber = cursor.getString(indexAddress) - // 根据手机号码查询用户名 - val contacts = getContactByNumber(phoneNumber) - smsInfo.name = - if (contacts.isNotEmpty()) contacts[0].name else ResUtils.getString(R.string.unknown_number) - // 联系人号码 - smsInfo.number = phoneNumber - // 短信内容 - smsInfo.content = cursor.getString(indexBody) - // 短信时间 - smsInfo.date = cursor.getLong(indexDate) - // 短信类型: 1=接收, 2=发送 - smsInfo.type = cursor.getInt(indexType) - // 卡槽id - smsInfo.simId = if (indexSimId != -1) getSimId( - cursor.getInt(indexSimId), - isSimId - ) else -1 - smsInfoList.add(smsInfo) - } while (cursor.moveToNext()) - - if (!cursorTotal.isClosed) cursorTotal.close() - if (!cursor.isClosed) cursor.close() - } - } catch (e: java.lang.Exception) { - e.printStackTrace() - } - return smsInfoList - } - - /** - * 跳至拨号界面 - * - * @param phoneNumber 电话号码 - */ - fun dial(phoneNumber: String?) { - XUtil.getContext().startActivity(IntentUtils.getDialIntent(phoneNumber, true)) - } - - /** - * 拨打电话 - * - * 需添加权限 `` - * - * @param phoneNumber 电话号码 - */ - fun call(phoneNumber: String?) { - XUtil.getContext().startActivity(IntentUtils.getCallIntent(phoneNumber, true)) - } - - /** - * 将 subscription_id 转成 卡槽ID: 0=Sim1, 1=Sim2, -1=获取失败 - * - * TODO: 这里有坑,每个品牌定制系统的字段不太一样,不一定能获取到卡槽ID - * 测试通过:MIUI 测试失败:原生 Android 11(Google Pixel 2 XL) - * - * @param mId SubscriptionId - * @param isSimId 是否已经是SimId无需转换(待做机型兼容) - */ - private fun getSimId(mId: Int, isSimId: Boolean): Int { - Log.i(TAG, "mId = $mId, isSimId = $isSimId") - //if (isSimId) return mId - - /** - * TODO:特别处理 - * MIUI系统:simId 字段实际为 subscription_id - * EMUI系统:subscription_id 实际为 simId - */ - val manufacturer = Build.MANUFACTURER.lowercase(Locale.getDefault()) - Log.i(TAG, "manufacturer = $manufacturer") - if (isSimId && !manufacturer.contains(Regex(pattern = "xiaomi|redmi"))) { - return mId - } - if (!isSimId && manufacturer.contains(Regex(pattern = "huawei|honor"))) { - return mId - } - - //获取卡槽信息 - if (App.SimInfoList.isEmpty()) { - App.SimInfoList = getSimMultiInfo() - } - Log.i(TAG, "SimInfoList = " + App.SimInfoList.toString()) - - val simSlot = -1 - if (App.SimInfoList.isEmpty()) return simSlot - for (simInfo in App.SimInfoList.values) { - if (simInfo.mSubscriptionId == mId && simInfo.mSimSlotIndex != -1) { - Log.i(TAG, "simInfo = $simInfo") - return simInfo.mSimSlotIndex - } - } - return simSlot - } - - } - - init { - throw UnsupportedOperationException("u can't instantiate me...") - } +package com.idormy.sms.forwarder.utils + +import android.Manifest.permission +import android.annotation.SuppressLint +import android.app.PendingIntent +import android.content.ContentResolver +import android.content.Context +import android.content.Intent +import android.net.Uri +import android.os.Build +import android.provider.CallLog +import android.provider.ContactsContract +import android.provider.Settings +import android.telephony.SmsManager +import android.telephony.SubscriptionInfo +import android.telephony.SubscriptionManager +import android.text.TextUtils +import android.util.Log +import androidx.annotation.RequiresPermission +import androidx.core.app.ActivityCompat +import com.idormy.sms.forwarder.App +import com.idormy.sms.forwarder.R +import com.idormy.sms.forwarder.core.Core +import com.idormy.sms.forwarder.entity.CallInfo +import com.idormy.sms.forwarder.entity.ContactInfo +import com.idormy.sms.forwarder.entity.SimInfo +import com.idormy.sms.forwarder.entity.SmsInfo +import com.xuexiang.xutil.XUtil +import com.xuexiang.xutil.app.IntentUtils +import com.xuexiang.xutil.data.DateUtils +import com.xuexiang.xutil.resource.ResUtils +import java.text.SimpleDateFormat +import java.util.* + +@Suppress("PropertyName") +class PhoneUtils private constructor() { + + companion object { + const val TAG = "PhoneUtils" + + //获取多卡信息 + @SuppressLint("Range") + fun getSimMultiInfo(): MutableMap { + val infoList = HashMap() + try { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1) { + println("1.版本超过5.1,调用系统方法") + val mSubscriptionManager = XUtil.getContext().getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE) as SubscriptionManager + ActivityCompat.checkSelfPermission( + XUtil.getContext(), permission.READ_PHONE_STATE + ) + val activeSubscriptionInfoList: List? = mSubscriptionManager.activeSubscriptionInfoList + if (activeSubscriptionInfoList != null && activeSubscriptionInfoList.isNotEmpty()) { + //1.1.1 有使用的卡,就遍历所有卡 + for (subscriptionInfo in activeSubscriptionInfoList) { + val simInfo = SimInfo() + simInfo.mCarrierName = subscriptionInfo.carrierName.toString() + simInfo.mIccId = subscriptionInfo.iccId.toString() + simInfo.mSimSlotIndex = subscriptionInfo.simSlotIndex + simInfo.mNumber = subscriptionInfo.number.toString() + simInfo.mCountryIso = subscriptionInfo.countryIso.toString() + simInfo.mSubscriptionId = subscriptionInfo.subscriptionId + println(simInfo.toString()) + infoList[simInfo.mSimSlotIndex] = simInfo + } + } + } else { + println("2.版本低于5.1的系统,首先调用数据库,看能不能访问到") + val uri = Uri.parse("content://telephony/siminfo") //访问raw_contacts表 + val resolver: ContentResolver = XUtil.getContext().contentResolver + val cursor = resolver.query( + uri, arrayOf( + "_id", "icc_id", "sim_id", "display_name", "carrier_name", "name_source", "color", "number", "display_number_format", "data_roaming", "mcc", "mnc" + ), null, null, null + ) + if (cursor != null && cursor.moveToFirst()) { + do { + val simInfo = SimInfo() + simInfo.mCarrierName = cursor.getString(cursor.getColumnIndex("carrier_name")) + simInfo.mIccId = cursor.getString(cursor.getColumnIndex("icc_id")) + simInfo.mSimSlotIndex = cursor.getInt(cursor.getColumnIndex("sim_id")) + simInfo.mNumber = cursor.getString(cursor.getColumnIndex("number")) + simInfo.mCountryIso = cursor.getString(cursor.getColumnIndex("mcc")) + //val id = cursor.getString(cursor.getColumnIndex("_id")) + println(simInfo.toString()) + infoList[simInfo.mSimSlotIndex] = simInfo + } while (cursor.moveToNext()) + cursor.close() + } + } + } catch (e: java.lang.Exception) { + e.printStackTrace() + } + Log.e(TAG, infoList.toString()) + return infoList + } + + //获取设备名称 + fun getDeviceName(): String { + return try { + Settings.Secure.getString(XUtil.getContentResolver(), "bluetooth_name") + } catch (e: Exception) { + e.printStackTrace() + Build.BRAND + " " + Build.MODEL + } + } + + /** + * 发送短信 + *

需添加权限 {@code }

+ * + * @param subId 发送卡的subId,传入 -1 则 SmsManager.getDefault() + * @param mobileList 接收号码列表 + * @param message 短信内容 + */ + @Suppress("DEPRECATION") + @SuppressLint("SoonBlockedPrivateApi", "DiscouragedPrivateApi") + @RequiresPermission(permission.SEND_SMS) + fun sendSms(subId: Int, mobileList: String, message: String): String? { + val mobiles = mobileList.replace(";", ";").replace(",", ";").replace(",", ";") + Log.d(TAG, "subId = $subId, mobiles = $mobiles, message = $message") + val mobileArray = mobiles.split(";".toRegex()).toTypedArray() + for (mobile in mobileArray) { + try { + val sendFlags = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) PendingIntent.FLAG_IMMUTABLE else PendingIntent.FLAG_ONE_SHOT + val sendPI = PendingIntent.getBroadcast(XUtil.getContext(), 0, Intent(), sendFlags) + + val smsManager = if (subId > -1 && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1) SmsManager.getSmsManagerForSubscriptionId( + subId + ) else SmsManager.getDefault() + // Android 5.1.1 以下使用反射指定卡槽 + if (subId > -1 && Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP_MR1) { + Log.d(TAG, "Android 5.1.1 以下使用反射指定卡槽") + val clz = SmsManager::class.java + val field = clz.getDeclaredField("mSubId") // 反射拿到变量 + field.isAccessible = true // 修改权限为可读写 + field.set(smsManager, subId) + } + + // 切割长短信 + if (message.length >= 70) { + val deliverFlags = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) PendingIntent.FLAG_IMMUTABLE else 0 + val deliverPI = PendingIntent.getBroadcast( + XUtil.getContext(), 0, Intent("DELIVERED_SMS_ACTION"), deliverFlags + ) + + val sentPendingIntents = ArrayList() + val deliveredPendingIntents = ArrayList() + val divideContents = smsManager.divideMessage(message) + + for (i in divideContents.indices) { + sentPendingIntents.add(i, sendPI) + deliveredPendingIntents.add(i, deliverPI) + } + smsManager.sendMultipartTextMessage( + mobile, null, divideContents, sentPendingIntents, deliveredPendingIntents + ) + } else { + smsManager.sendTextMessage(mobile, null, message, sendPI, null) + } + } catch (e: Exception) { + Log.e(TAG, e.message.toString()) + return e.message.toString() + } + } + + return null + } + + //获取通话记录列表 + fun getCallInfoList( + type: Int, limit: Int, offset: Int, phoneNumber: String? + ): MutableList { + val callInfoList: MutableList = mutableListOf() + try { + var selection = "1=1" + val selectionArgs = ArrayList() + if (type > 0) { + selection += " and " + CallLog.Calls.TYPE + " = ?" + selectionArgs.add("$type") + } + if (!TextUtils.isEmpty(phoneNumber)) { + selection += " and " + CallLog.Calls.NUMBER + " like ?" + selectionArgs.add("%$phoneNumber%") + } + Log.d(TAG, "selection = $selection") + Log.d(TAG, "selectionArgs = $selectionArgs") + + //为了兼容性这里全部取出后手动分页 + val cursor = Core.app.contentResolver.query( + CallLog.Calls.CONTENT_URI, null, selection, selectionArgs.toTypedArray(), CallLog.Calls.DEFAULT_SORT_ORDER // + " limit $limit offset $offset" + ) ?: return callInfoList + Log.i(TAG, "cursor count:" + cursor.count) + + // 避免超过总数后循环取出 + if (cursor.count == 0 || offset >= cursor.count) { + cursor.close() + return callInfoList + } + + if (cursor.moveToFirst()) { + Log.d(TAG, "Call ColumnNames=${cursor.columnNames.contentToString()}") + val indexName = cursor.getColumnIndex(CallLog.Calls.CACHED_NAME) + val indexNumber = cursor.getColumnIndex(CallLog.Calls.NUMBER) + val indexDate = cursor.getColumnIndex(CallLog.Calls.DATE) + val indexDuration = cursor.getColumnIndex(CallLog.Calls.DURATION) + val indexType = cursor.getColumnIndex(CallLog.Calls.TYPE) + val indexViaNumber = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && cursor.getColumnIndex("via_number") != -1) cursor.getColumnIndex("via_number") else -1 + var indexSimId = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) cursor.getColumnIndex(CallLog.Calls.PHONE_ACCOUNT_ID) else -1 + var indexSubId = indexSimId + + /** + * TODO:卡槽识别,这里需要适配机型 + * MIUI系统:simid 字段实际为 subscription_id + * EMUI系统:subscription_id 实际为 sim_id + */ + var isSimId = false + val manufacturer = Build.MANUFACTURER.lowercase(Locale.getDefault()) + Log.i(TAG, "manufacturer = $manufacturer") + if (manufacturer.contains(Regex(pattern = "xiaomi|redmi"))) { + if (cursor.getColumnIndex("simid") != -1) indexSimId = cursor.getColumnIndex("simid") + indexSubId = indexSimId + } else if (manufacturer.contains(Regex(pattern = "huawei|honor"))) { + indexSubId = -1 //TODO:暂时不支持华为 + isSimId = true + } + + var curOffset = 0 + do { + if (curOffset >= offset) { + val callInfo = CallInfo( + cursor.getString(indexName) ?: "", //姓名 + cursor.getString(indexNumber) ?: "", //号码 + cursor.getLong(indexDate), //获取通话日期 + cursor.getInt(indexDuration), //获取通话时长,值为多少秒 + cursor.getInt(indexType), //获取通话类型:1.呼入 2.呼出 3.未接 + if (indexViaNumber != -1) cursor.getString(indexViaNumber) else "", //来源号码 + if (indexSimId != -1) getSimId(cursor.getInt(indexSimId), isSimId) else -1, //卡槽ID: 0=Sim1, 1=Sim2, -1=获取失败 + if (indexSubId != -1) cursor.getInt(indexSubId) else 0, //卡槽主键 + ) + Log.d(TAG, callInfo.toString()) + callInfoList.add(callInfo) + if (limit == 1) { + cursor.close() + return callInfoList + } + } + curOffset++ + if (curOffset >= offset + limit) break + } while (cursor.moveToNext()) + if (!cursor.isClosed) cursor.close() + } + } catch (e: java.lang.Exception) { + Log.e(TAG, "getCallInfoList:", e) + } + + return callInfoList + } + + //获取后一条通话记录 + @SuppressLint("Range") + fun getLastCallInfo(callType: Int, phoneNumber: String?): CallInfo? { + val callInfoList = getCallInfoList(callType, 1, 0, phoneNumber) + if (callInfoList.isNotEmpty()) return callInfoList[0] + return null + } + + //获取联系人列表 + fun getContactInfoList( + limit: Int, offset: Int, phoneNumber: String?, name: String? + ): MutableList { + val contactInfoList: MutableList = mutableListOf() + + try { + var selection = "1=1" + val selectionArgs = ArrayList() + if (!TextUtils.isEmpty(phoneNumber)) { + selection += " and replace(replace(" + ContactsContract.CommonDataKinds.Phone.NUMBER + ",' ',''),'-','') like ?" + selectionArgs.add("%$phoneNumber%") + } + if (!TextUtils.isEmpty(name)) { + selection += " and " + ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME + " like ?" + selectionArgs.add("%$name%") + } + Log.d(TAG, "selection = $selection") + Log.d(TAG, "selectionArgs = $selectionArgs") + + val cursor = Core.app.contentResolver.query( + ContactsContract.CommonDataKinds.Phone.CONTENT_URI, null, selection, selectionArgs.toTypedArray(), ContactsContract.CommonDataKinds.Phone.SORT_KEY_PRIMARY + ) ?: return contactInfoList + Log.i(TAG, "cursor count:" + cursor.count) + + // 避免超过总数后循环取出 + if (cursor.count == 0 || offset >= cursor.count) { + cursor.close() + return contactInfoList + } + + if (cursor.moveToFirst()) { + val displayNameIndex = cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME) + val mobileNoIndex = cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER) + do { + val contactInfo = ContactInfo( + cursor.getString(displayNameIndex), //姓名 + cursor.getString(mobileNoIndex), //号码 + ) + Log.d(TAG, contactInfo.toString()) + contactInfoList.add(contactInfo) + if (limit == 1) { + cursor.close() + return contactInfoList + } + } while (cursor.moveToNext()) + if (!cursor.isClosed) cursor.close() + } + } catch (e: java.lang.Exception) { + Log.e(TAG, "getContactInfoList:", e) + } + + return contactInfoList + } + + //获取联系人姓名 + fun getContactByNumber(phoneNumber: String?): MutableList { + val contactInfoList = mutableListOf() + if (TextUtils.isEmpty(phoneNumber)) return contactInfoList + return getContactInfoList(1, 0, phoneNumber, null) + } + + //获取通话记录转发内容 + fun getCallMsg(callInfo: CallInfo): String { + val sb = StringBuilder() + sb.append(ResUtils.getString(R.string.linkman)).append(callInfo.name).append("\n") + if (!TextUtils.isEmpty(callInfo.viaNumber)) sb.append(ResUtils.getString(R.string.via_number)).append(callInfo.viaNumber).append("\n") + if (callInfo.dateLong > 0L) sb.append(ResUtils.getString(R.string.call_date)).append( + DateUtils.millis2String( + callInfo.dateLong, SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault()) + ) + ).append("\n") + if (callInfo.duration > 0) { + if (callInfo.type == 3) { + sb.append(ResUtils.getString(R.string.ring_duration)) + } else { + sb.append(ResUtils.getString(R.string.call_duration)) + } + sb.append(callInfo.duration).append("s\n") + } + sb.append(ResUtils.getString(R.string.mandatory_type)) + //通话类型:1.呼入 2.呼出 3.未接 4.来电提醒 + when (callInfo.type) { + 1 -> sb.append(ResUtils.getString(R.string.received_call)) + 2 -> sb.append(ResUtils.getString(R.string.local_outgoing_call)) + 3 -> sb.append(ResUtils.getString(R.string.missed_call)) + else -> sb.append(ResUtils.getString(R.string.incoming_call)) + } + return sb.toString() + } + + // 获取用户短信列表 + fun getSmsInfoList( + type: Int, limit: Int, offset: Int, keyword: String + ): MutableList { + val smsInfoList: MutableList = mutableListOf() + try { + var selection = "1=1" + val selectionArgs = ArrayList() + if (type > 0) { + selection += " and type = ?" + selectionArgs.add("$type") + } + if (!TextUtils.isEmpty(keyword)) { + selection += " and body like ?" + selectionArgs.add("%$keyword%") + } + Log.d(TAG, "selection = $selection") + Log.d(TAG, "selectionArgs = $selectionArgs") + + // 避免超过总数后循环取出 + val cursorTotal = Core.app.contentResolver.query( + Uri.parse("content://sms/"), null, selection, selectionArgs.toTypedArray(), "date desc" + ) ?: return smsInfoList + if (offset >= cursorTotal.count) { + cursorTotal.close() + return smsInfoList + } + + val cursor = Core.app.contentResolver.query( + Uri.parse("content://sms/"), null, selection, selectionArgs.toTypedArray(), "date desc limit $limit offset $offset" + ) ?: return smsInfoList + + Log.i(TAG, "cursor count:" + cursor.count) + if (cursor.count == 0) { + cursor.close() + return smsInfoList + } + + if (cursor.moveToFirst()) { + Log.d(TAG, "SMS ColumnNames=${cursor.columnNames.contentToString()}") + val indexAddress = cursor.getColumnIndex("address") + val indexBody = cursor.getColumnIndex("body") + val indexDate = cursor.getColumnIndex("date") + val indexType = cursor.getColumnIndex("type") + var indexSimId = cursor.getColumnIndex("sim_id") + var indexSubId = cursor.getColumnIndex("sub_id") + + /** + * TODO:卡槽识别,这里需要适配机型 + * MIUI系统:sim_id 字段实际为 subscription_id + * EMUI系统:sub_id 实际为 sim_id + */ + var isSimId = false + val manufacturer = Build.MANUFACTURER.lowercase(Locale.getDefault()) + Log.i(TAG, "manufacturer = $manufacturer") + if (manufacturer.contains(Regex(pattern = "xiaomi|redmi"))) { + indexSubId = cursor.getColumnIndex("sim_id") + } else if (manufacturer.contains(Regex(pattern = "huawei|honor"))) { + indexSimId = cursor.getColumnIndex("sub_id") + isSimId = true + } + + do { + val smsInfo = SmsInfo() + val phoneNumber = cursor.getString(indexAddress) + // 根据手机号码查询用户名 + val contacts = getContactByNumber(phoneNumber) + smsInfo.name = if (contacts.isNotEmpty()) contacts[0].name else ResUtils.getString(R.string.unknown_number) + // 联系人号码 + smsInfo.number = phoneNumber + // 短信内容 + smsInfo.content = cursor.getString(indexBody) + // 短信时间 + smsInfo.date = cursor.getLong(indexDate) + // 短信类型: 1=接收, 2=发送 + smsInfo.type = cursor.getInt(indexType) + // 卡槽ID: 0=Sim1, 1=Sim2, -1=获取失败 + smsInfo.simId = if (indexSimId != -1) getSimId(cursor.getInt(indexSimId), isSimId) else -1 + // 卡槽主键 + smsInfo.subId = if (indexSubId != -1) cursor.getInt(indexSubId) else 0 + + smsInfoList.add(smsInfo) + } while (cursor.moveToNext()) + + if (!cursorTotal.isClosed) cursorTotal.close() + if (!cursor.isClosed) cursor.close() + } + } catch (e: java.lang.Exception) { + e.printStackTrace() + } + return smsInfoList + } + + /** + * 跳至拨号界面 + * + * @param phoneNumber 电话号码 + */ + fun dial(phoneNumber: String?) { + XUtil.getContext().startActivity(IntentUtils.getDialIntent(phoneNumber, true)) + } + + /** + * 拨打电话 + * + * 需添加权限 `` + * + * @param phoneNumber 电话号码 + */ + fun call(phoneNumber: String?) { + XUtil.getContext().startActivity(IntentUtils.getCallIntent(phoneNumber, true)) + } + + /** + * 将 subscription_id 转成 卡槽ID: 0=Sim1, 1=Sim2, -1=获取失败 + * + * TODO: 这里有坑,每个品牌定制系统的字段不太一样,不一定能获取到卡槽ID + * 测试通过:MIUI 测试失败:原生 Android 11(Google Pixel 2 XL) + * + * @param mId SubscriptionId + * @param isSimId 是否已经是SimId无需转换(待做机型兼容) + */ + private fun getSimId(mId: Int, isSimId: Boolean): Int { + Log.i(TAG, "mId = $mId, isSimId = $isSimId") + if (isSimId) return mId + + if (SettingUtils.subidSim1 > 0 || SettingUtils.subidSim2 > 0) { + return if (mId == SettingUtils.subidSim1) 0 else 1 + } else { + //获取卡槽信息 + if (App.SimInfoList.isEmpty()) { + App.SimInfoList = getSimMultiInfo() + } + Log.i(TAG, "SimInfoList = " + App.SimInfoList.toString()) + + val simSlot = -1 + if (App.SimInfoList.isEmpty()) return simSlot + for (simInfo in App.SimInfoList.values) { + if (simInfo.mSubscriptionId == mId && simInfo.mSimSlotIndex != -1) { + Log.i(TAG, "simInfo = $simInfo") + return simInfo.mSimSlotIndex + } + } + return simSlot + } + } + + } + + init { + throw UnsupportedOperationException("u can't instantiate me...") + } } \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/utils/SettingUtils.kt b/app/src/main/java/com/idormy/sms/forwarder/utils/SettingUtils.kt index 04b35351..cbb26182 100644 --- a/app/src/main/java/com/idormy/sms/forwarder/utils/SettingUtils.kt +++ b/app/src/main/java/com/idormy/sms/forwarder/utils/SettingUtils.kt @@ -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, "") diff --git a/app/src/main/java/com/idormy/sms/forwarder/utils/sender/SmsUtils.kt b/app/src/main/java/com/idormy/sms/forwarder/utils/sender/SmsUtils.kt index 24b472d4..ffaa1c45 100644 --- a/app/src/main/java/com/idormy/sms/forwarder/utils/sender/SmsUtils.kt +++ b/app/src/main/java/com/idormy/sms/forwarder/utils/sender/SmsUtils.kt @@ -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) + } + } } \ No newline at end of file diff --git a/app/src/main/java/com/idormy/sms/forwarder/workers/SendWorker.kt b/app/src/main/java/com/idormy/sms/forwarder/workers/SendWorker.kt index 61888b00..e2c104e5 100644 --- a/app/src/main/java/com/idormy/sms/forwarder/workers/SendWorker.kt +++ b/app/src/main/java/com/idormy/sms/forwarder/workers/SendWorker.kt @@ -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) diff --git a/app/src/main/res/layout/fragment_settings.xml b/app/src/main/res/layout/fragment_settings.xml index 5fc1aef2..acd063e0 100644 --- a/app/src/main/res/layout/fragment_settings.xml +++ b/app/src/main/res/layout/fragment_settings.xml @@ -1,1398 +1,1434 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values-en/strings.xml b/app/src/main/res/values-en/strings.xml index f99908a4..93f6d3a0 100644 --- a/app/src/main/res/values-en/strings.xml +++ b/app/src/main/res/values-en/strings.xml @@ -334,9 +334,12 @@ Notify Content Device Name - SIM1 Label - SIM2 Label + SIM1 SubId + SIM1 SubId/Label + SIM2 SubId/Label Label of SIM,\neg. AT&T_88888888 + Number must be greater than 0! + ^[1-9]?\\d+$ Low Power Alarm Value range: 0–99.\nLeft blank or 0 is disabled Retry Interval @@ -543,6 +546,7 @@ {{APP_NAME}} {{MSG}} {{CARD_SLOT}} + {{CARD_SUBID}} {{RECEIVE_TIME}} {{DEVICE_NAME}} {{APP_VERSION}} diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 75b1a02a..6c61d1c0 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -335,9 +335,12 @@ 通知文案 设备名称 - SIM1备注 - SIM2备注 - 运营商_手机号 + 卡槽主键 + SIM1主键/备注 + SIM2主键/备注 + 序号/运营商_手机号 + 数字必须大于0! + ^[1-9]?\\d+$ 安全电量范围(%) 超出安全范围将发出预警 请求重试机制 @@ -544,6 +547,7 @@ {{APP名称}} {{通知内容}} {{卡槽信息}} + {{卡槽主键}} {{接收时间}} {{设备名称}} {{当前应用版本号}}