优化:来电转发逻辑 & 新增提醒类型(1.来电挂机 2.去电挂机 3.未接来电 4.来电提醒 5.来电接通 6.去电拨出)

pull/286/head
pppscn 2 years ago
parent b79d3d8493
commit 04d8c9015a

@ -242,11 +242,12 @@
</intent-filter>
</receiver>
<receiver
android:name=".receiver.PhoneStateReceiver"
android:name=".receiver.CallReceiver"
android:exported="true"
tools:ignore="IntentFilterExportedReceiver">
<intent-filter>
<action android:name="android.intent.action.PHONE_STATE" />
<action android:name="android.intent.action.NEW_OUTGOING_CALL" />
</intent-filter>
</receiver>
</application>

@ -13,7 +13,7 @@ data class CallInfo(
var dateLong: Long = 0L,
//获取通话时长,值为多少秒
var duration: Int = 0,
//通话类型1=呼入, 2=呼出, 3=未接, 4=未接提醒
//通话类型1.来电挂机 2.去电挂机 3.未接来电 4.来电提醒 5.来电接通 6.去电拨出
var type: Int = 1,
//被呼号码
@SerializedName("via_number")

@ -1,82 +1,88 @@
package com.idormy.sms.forwarder.entity
import com.google.gson.annotations.SerializedName
import com.idormy.sms.forwarder.database.entity.Frpc
import com.idormy.sms.forwarder.database.entity.Rule
import com.idormy.sms.forwarder.database.entity.Sender
import java.io.Serializable
data class CloneInfo(
@SerializedName("version_code")
var versionCode: Int = 0,
@SerializedName("version_name")
var versionName: String? = null,
@SerializedName("enable_sms")
var enableSms: Boolean = false,
@SerializedName("enable_phone")
var enablePhone: Boolean = false,
@SerializedName("call_type1")
var callType1: Boolean = false,
@SerializedName("call_type2")
var callType2: Boolean = false,
@SerializedName("call_type3")
var callType3: Boolean = false,
@SerializedName("enable_app_notify")
var enableAppNotify: Boolean = false,
@SerializedName("cancel_app_notify")
var cancelAppNotify: Boolean = false,
@SerializedName("enable_not_user_present")
var enableNotUserPresent: Boolean = false,
@SerializedName("enable_load_app_list")
var enableLoadAppList: Boolean = false,
@SerializedName("enable_load_user_app_list")
var enableLoadUserAppList: Boolean = false,
@SerializedName("enable_load_system_app_list")
var enableLoadSystemAppList: Boolean = false,
@SerializedName("duplicate_messages_limits")
var duplicateMessagesLimits: Int = 0,
@SerializedName("enable_battery_receiver")
var enableBatteryReceiver: Boolean = false,
@SerializedName("battery_level_min")
var batteryLevelMin: Int = 0,
@SerializedName("battery_level_max")
var batteryLevelMax: Int = 0,
@SerializedName("battery_level_once")
var batteryLevelOnce: Boolean = false,
@SerializedName("enable_battery_cron")
var enableBatteryCron: Boolean = false,
@SerializedName("battery_cron_start_time")
var batteryCronStartTime: String? = null,
@SerializedName("battery_cron_interval")
var batteryCronInterval: Int = 0,
@SerializedName("enable_exclude_from_recents")
var enableExcludeFromRecents: Boolean = false,
@SerializedName("enable_cactus")
var enableCactus: Boolean = false,
@SerializedName("enable_play_silence_music")
var enablePlaySilenceMusic: Boolean = false,
@SerializedName("enable_one_pixel_activity")
var enableOnePixelActivity: Boolean = false,
@SerializedName("request_retry_times")
var requestRetryTimes: Int = 0,
@SerializedName("request_delay_time")
var requestDelayTime: Int = 0,
@SerializedName("request_timeout")
var requestTimeout: Int = 0,
@SerializedName("notify_content")
var notifyContent: String? = null,
@SerializedName("enable_sms_template")
var enableSmsTemplate: Boolean = false,
@SerializedName("sms_template")
var smsTemplate: String? = null,
@SerializedName("enable_help_tip")
var enableHelpTip: Boolean = false,
@SerializedName("enable_pure_client_mode")
var enablePureClientMode: Boolean = false,
@SerializedName("sender_list")
var senderList: List<Sender>? = null,
@SerializedName("rule_list")
var ruleList: List<Rule>? = null,
@SerializedName("frpc_list")
var frpcList: List<Frpc>? = null,
package com.idormy.sms.forwarder.entity
import com.google.gson.annotations.SerializedName
import com.idormy.sms.forwarder.database.entity.Frpc
import com.idormy.sms.forwarder.database.entity.Rule
import com.idormy.sms.forwarder.database.entity.Sender
import java.io.Serializable
data class CloneInfo(
@SerializedName("version_code")
var versionCode: Int = 0,
@SerializedName("version_name")
var versionName: String? = null,
@SerializedName("enable_sms")
var enableSms: Boolean = false,
@SerializedName("enable_phone")
var enablePhone: Boolean = false,
@SerializedName("call_type1")
var callType1: Boolean = false,
@SerializedName("call_type2")
var callType2: Boolean = false,
@SerializedName("call_type3")
var callType3: Boolean = false,
@SerializedName("call_type4")
var callType4: Boolean = false,
@SerializedName("call_type5")
var callType5: Boolean = false,
@SerializedName("call_type6")
var callType6: Boolean = false,
@SerializedName("enable_app_notify")
var enableAppNotify: Boolean = false,
@SerializedName("cancel_app_notify")
var cancelAppNotify: Boolean = false,
@SerializedName("enable_not_user_present")
var enableNotUserPresent: Boolean = false,
@SerializedName("enable_load_app_list")
var enableLoadAppList: Boolean = false,
@SerializedName("enable_load_user_app_list")
var enableLoadUserAppList: Boolean = false,
@SerializedName("enable_load_system_app_list")
var enableLoadSystemAppList: Boolean = false,
@SerializedName("duplicate_messages_limits")
var duplicateMessagesLimits: Int = 0,
@SerializedName("enable_battery_receiver")
var enableBatteryReceiver: Boolean = false,
@SerializedName("battery_level_min")
var batteryLevelMin: Int = 0,
@SerializedName("battery_level_max")
var batteryLevelMax: Int = 0,
@SerializedName("battery_level_once")
var batteryLevelOnce: Boolean = false,
@SerializedName("enable_battery_cron")
var enableBatteryCron: Boolean = false,
@SerializedName("battery_cron_start_time")
var batteryCronStartTime: String? = null,
@SerializedName("battery_cron_interval")
var batteryCronInterval: Int = 0,
@SerializedName("enable_exclude_from_recents")
var enableExcludeFromRecents: Boolean = false,
@SerializedName("enable_cactus")
var enableCactus: Boolean = false,
@SerializedName("enable_play_silence_music")
var enablePlaySilenceMusic: Boolean = false,
@SerializedName("enable_one_pixel_activity")
var enableOnePixelActivity: Boolean = false,
@SerializedName("request_retry_times")
var requestRetryTimes: Int = 0,
@SerializedName("request_delay_time")
var requestDelayTime: Int = 0,
@SerializedName("request_timeout")
var requestTimeout: Int = 0,
@SerializedName("notify_content")
var notifyContent: String? = null,
@SerializedName("enable_sms_template")
var enableSmsTemplate: Boolean = false,
@SerializedName("sms_template")
var smsTemplate: String? = null,
@SerializedName("enable_help_tip")
var enableHelpTip: Boolean = false,
@SerializedName("enable_pure_client_mode")
var enablePureClientMode: Boolean = false,
@SerializedName("sender_list")
var senderList: List<Sender>? = null,
@SerializedName("rule_list")
var ruleList: List<Rule>? = null,
@SerializedName("frpc_list")
var frpcList: List<Frpc>? = null,
) : Serializable

@ -80,7 +80,7 @@ class SettingsFragment : BaseFragment<FragmentSettingsBinding?>(), View.OnClickL
switchEnableSms(binding!!.sbEnableSms)
//转发通话记录
switchEnablePhone(
binding!!.sbEnablePhone, binding!!.scbCallType1, binding!!.scbCallType2, binding!!.scbCallType3, binding!!.scbCallType4
binding!!.sbEnablePhone, binding!!.scbCallType1, binding!!.scbCallType2, binding!!.scbCallType3, binding!!.scbCallType4, binding!!.scbCallType5, binding!!.scbCallType6
)
//转发应用通知
switchEnableAppNotify(
@ -304,15 +304,17 @@ class SettingsFragment : BaseFragment<FragmentSettingsBinding?>(), View.OnClickL
//转发通话
@SuppressLint("UseSwitchCompatOrMaterialCode")
fun switchEnablePhone(
sbEnablePhone: SwitchButton, scbCallType1: SmoothCheckBox, scbCallType2: SmoothCheckBox, scbCallType3: SmoothCheckBox, scbCallType4: SmoothCheckBox
sbEnablePhone: SwitchButton, scbCallType1: SmoothCheckBox, scbCallType2: SmoothCheckBox, scbCallType3: SmoothCheckBox, scbCallType4: SmoothCheckBox, scbCallType5: SmoothCheckBox, scbCallType6: SmoothCheckBox
) {
sbEnablePhone.isChecked = SettingUtils.enablePhone
scbCallType1.isChecked = SettingUtils.enableCallType1
scbCallType2.isChecked = SettingUtils.enableCallType2
scbCallType3.isChecked = SettingUtils.enableCallType3
scbCallType4.isChecked = SettingUtils.enableCallType4
scbCallType5.isChecked = SettingUtils.enableCallType5
scbCallType6.isChecked = SettingUtils.enableCallType6
sbEnablePhone.setOnCheckedChangeListener { _: CompoundButton?, isChecked: Boolean ->
if (isChecked && !SettingUtils.enableCallType1 && !SettingUtils.enableCallType2 && !SettingUtils.enableCallType3 && !SettingUtils.enableCallType4) {
if (isChecked && !SettingUtils.enableCallType1 && !SettingUtils.enableCallType2 && !SettingUtils.enableCallType3 && !SettingUtils.enableCallType4 && !SettingUtils.enableCallType5 && !SettingUtils.enableCallType6) {
XToastUtils.info(R.string.enable_phone_fw_tips)
SettingUtils.enablePhone = false
sbEnablePhone.isChecked = false
@ -354,7 +356,7 @@ class SettingsFragment : BaseFragment<FragmentSettingsBinding?>(), View.OnClickL
}
scbCallType1.setOnCheckedChangeListener { _: SmoothCheckBox, isChecked: Boolean ->
SettingUtils.enableCallType1 = isChecked
if (!isChecked && !SettingUtils.enableCallType1 && !SettingUtils.enableCallType2 && !SettingUtils.enableCallType3 && !SettingUtils.enableCallType4) {
if (!isChecked && !SettingUtils.enableCallType1 && !SettingUtils.enableCallType2 && !SettingUtils.enableCallType3 && !SettingUtils.enableCallType4 && !SettingUtils.enableCallType5 && !SettingUtils.enableCallType6) {
XToastUtils.info(R.string.enable_phone_fw_tips)
SettingUtils.enablePhone = false
sbEnablePhone.isChecked = false
@ -362,7 +364,7 @@ class SettingsFragment : BaseFragment<FragmentSettingsBinding?>(), View.OnClickL
}
scbCallType2.setOnCheckedChangeListener { _: SmoothCheckBox, isChecked: Boolean ->
SettingUtils.enableCallType2 = isChecked
if (!isChecked && !SettingUtils.enableCallType1 && !SettingUtils.enableCallType2 && !SettingUtils.enableCallType3 && !SettingUtils.enableCallType4) {
if (!isChecked && !SettingUtils.enableCallType1 && !SettingUtils.enableCallType2 && !SettingUtils.enableCallType3 && !SettingUtils.enableCallType4 && !SettingUtils.enableCallType5 && !SettingUtils.enableCallType6) {
XToastUtils.info(R.string.enable_phone_fw_tips)
SettingUtils.enablePhone = false
sbEnablePhone.isChecked = false
@ -370,7 +372,7 @@ class SettingsFragment : BaseFragment<FragmentSettingsBinding?>(), View.OnClickL
}
scbCallType3.setOnCheckedChangeListener { _: SmoothCheckBox, isChecked: Boolean ->
SettingUtils.enableCallType3 = isChecked
if (!isChecked && !SettingUtils.enableCallType1 && !SettingUtils.enableCallType2 && !SettingUtils.enableCallType3 && !SettingUtils.enableCallType4) {
if (!isChecked && !SettingUtils.enableCallType1 && !SettingUtils.enableCallType2 && !SettingUtils.enableCallType3 && !SettingUtils.enableCallType4 && !SettingUtils.enableCallType5 && !SettingUtils.enableCallType6) {
XToastUtils.info(R.string.enable_phone_fw_tips)
SettingUtils.enablePhone = false
sbEnablePhone.isChecked = false
@ -378,7 +380,23 @@ class SettingsFragment : BaseFragment<FragmentSettingsBinding?>(), View.OnClickL
}
scbCallType4.setOnCheckedChangeListener { _: SmoothCheckBox, isChecked: Boolean ->
SettingUtils.enableCallType4 = isChecked
if (!isChecked && !SettingUtils.enableCallType1 && !SettingUtils.enableCallType2 && !SettingUtils.enableCallType3 && !SettingUtils.enableCallType4) {
if (!isChecked && !SettingUtils.enableCallType1 && !SettingUtils.enableCallType2 && !SettingUtils.enableCallType3 && !SettingUtils.enableCallType4 && !SettingUtils.enableCallType5 && !SettingUtils.enableCallType6) {
XToastUtils.info(R.string.enable_phone_fw_tips)
SettingUtils.enablePhone = false
sbEnablePhone.isChecked = false
}
}
scbCallType5.setOnCheckedChangeListener { _: SmoothCheckBox, isChecked: Boolean ->
SettingUtils.enableCallType5 = isChecked
if (!isChecked && !SettingUtils.enableCallType1 && !SettingUtils.enableCallType2 && !SettingUtils.enableCallType3 && !SettingUtils.enableCallType4 && !SettingUtils.enableCallType5 && !SettingUtils.enableCallType6) {
XToastUtils.info(R.string.enable_phone_fw_tips)
SettingUtils.enablePhone = false
sbEnablePhone.isChecked = false
}
}
scbCallType6.setOnCheckedChangeListener { _: SmoothCheckBox, isChecked: Boolean ->
SettingUtils.enableCallType6 = isChecked
if (!isChecked && !SettingUtils.enableCallType1 && !SettingUtils.enableCallType2 && !SettingUtils.enableCallType3 && !SettingUtils.enableCallType4 && !SettingUtils.enableCallType5 && !SettingUtils.enableCallType6) {
XToastUtils.info(R.string.enable_phone_fw_tips)
SettingUtils.enablePhone = false
sbEnablePhone.isChecked = false

@ -0,0 +1,151 @@
package com.idormy.sms.forwarder.receiver
import android.content.Context
import android.util.Log
import androidx.work.OneTimeWorkRequestBuilder
import androidx.work.WorkManager
import androidx.work.workDataOf
import com.google.gson.Gson
import com.idormy.sms.forwarder.R
import com.idormy.sms.forwarder.entity.CallInfo
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 com.xuexiang.xrouter.utils.TextUtils
import com.xuexiang.xui.utils.ResUtils.getString
import com.xuexiang.xutil.resource.ResUtils
import java.util.*
open class CallReceiver : PhoneStateReceiver() {
companion object {
private val TAG = CallReceiver::class.java.simpleName
//const val ACTION_IN = "android.intent.action.PHONE_STATE"
const val ACTION_OUT = "android.intent.action.NEW_OUTGOING_CALL"
const val EXTRA_PHONE_NUMBER = "android.intent.extra.PHONE_NUMBER"
}
//来电提醒
override fun onIncomingCallReceived(context: Context, number: String?, start: Date) {
Log.d(TAG, "onIncomingCallReceived$number")
sendNotice(context, 4, number)
}
//来电接通
override fun onIncomingCallAnswered(context: Context, number: String?, start: Date) {
Log.d(TAG, "onIncomingCallAnswered$number")
sendNotice(context, 5, number)
}
//来电挂机
override fun onIncomingCallEnded(context: Context, number: String?, start: Date, end: Date) {
Log.d(TAG, "onIncomingCallEnded$number")
sendCallMsg(context, 1, number)
}
//去电拨出
override fun onOutgoingCallStarted(context: Context, number: String?, start: Date) {
Log.d(TAG, "onOutgoingCallStarted$number")
sendNotice(context, 6, number)
}
//去电挂机
override fun onOutgoingCallEnded(context: Context, number: String?, start: Date, end: Date) {
Log.d(TAG, "onOutgoingCallEnded$number")
sendCallMsg(context, 2, number)
}
//未接来电
override fun onMissedCall(context: Context, number: String?, start: Date) {
Log.d(TAG, "onMissedCall$number")
sendCallMsg(context, 3, number)
}
//转发通话提醒
private fun sendNotice(context: Context, callType: Int, phoneNumber: String?) {
if (TextUtils.isEmpty(phoneNumber)) return
//判断是否开启该类型转发
if ((callType == 4 && !SettingUtils.enableCallType4) || (callType == 5 && !SettingUtils.enableCallType5) || (callType == 6 && !SettingUtils.enableCallType6)) {
Log.w(TAG, "未开启该类型转发type=$callType")
return
}
val contacts = PhoneUtils.getContactByNumber(phoneNumber)
val contactName = if (contacts.isNotEmpty()) contacts[0].name else getString(R.string.unknown_number)
val msg = StringBuilder()
msg.append(getString(R.string.linkman)).append(contactName).append("\n")
msg.append(getString(R.string.mandatory_type))
//通话类型1.来电挂机 2.去电挂机 3.未接来电 4.来电提醒 5.来电接通 6.去电拨出
when (callType) {
1 -> msg.append(ResUtils.getString(R.string.incoming_call_ended))
2 -> msg.append(ResUtils.getString(R.string.outgoing_call_ended))
3 -> msg.append(ResUtils.getString(R.string.missed_call))
4 -> msg.append(ResUtils.getString(R.string.incoming_call_received))
5 -> msg.append(ResUtils.getString(R.string.incoming_call_answered))
6 -> msg.append(ResUtils.getString(R.string.outgoing_call_started))
else -> msg.append(ResUtils.getString(R.string.unknown_call))
}
val msgInfo = MsgInfo("call", phoneNumber.toString(), msg.toString(), Date(), "")
val request = OneTimeWorkRequestBuilder<SendWorker>().setInputData(
workDataOf(
Worker.sendMsgInfo to Gson().toJson(msgInfo)
)
).build()
WorkManager.getInstance(context).enqueue(request)
}
//转发通话记录
private fun sendCallMsg(context: Context, callType: Int, phoneNumber: String?) {
//必须休眠才能获取来电记录,否则可能获取到上一次通话的
Thread.sleep(1000)
//获取后一条通话记录
Log.d(TAG, "callType = $callType, phoneNumber = $phoneNumber")
val callInfo: CallInfo? = PhoneUtils.getLastCallInfo(callType, phoneNumber)
Log.d(TAG, "callInfo = $callInfo")
if (callInfo?.number == null) {
Log.w(TAG, "查不到通话记录直接发通知")
sendNotice(context, callType, phoneNumber)
return
}
//判断是否开启该类型转发
if ((callInfo.type == 1 && !SettingUtils.enableCallType1) || (callInfo.type == 2 && !SettingUtils.enableCallType2) || (callInfo.type == 3 && !SettingUtils.enableCallType3)) {
Log.w(TAG, "未开启该类型转发type=" + callInfo.type)
return
}
//卡槽id-1=获取失败、0=卡槽1、1=卡槽2
val simSlot = callInfo.simId
//获取卡槽信息
val simInfo = when (simSlot) {
0 -> "SIM1_" + SettingUtils.extraSim1
1 -> "SIM2_" + SettingUtils.extraSim2
else -> ""
}
//获取联系人姓名
if (TextUtils.isEmpty(callInfo.name)) {
val contacts = PhoneUtils.getContactByNumber(phoneNumber)
callInfo.name = if (contacts.isNotEmpty()) contacts[0].name else getString(R.string.unknown_number)
}
val msgInfo = MsgInfo(
"call", callInfo.number, PhoneUtils.getCallMsg(callInfo), Date(), simInfo, simSlot, callInfo.subId
)
val request = OneTimeWorkRequestBuilder<SendWorker>().setInputData(
workDataOf(
Worker.sendMsgInfo to Gson().toJson(msgInfo)
)
).build()
WorkManager.getInstance(context).enqueue(request)
}
}

@ -1,178 +1,109 @@
package com.idormy.sms.forwarder.receiver
import android.Manifest
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.telephony.TelephonyManager
import android.text.TextUtils
import android.util.Log
import androidx.core.app.ActivityCompat
import androidx.work.OneTimeWorkRequestBuilder
import androidx.work.WorkManager
import androidx.work.workDataOf
import com.google.gson.Gson
import com.idormy.sms.forwarder.R
import com.idormy.sms.forwarder.entity.CallInfo
import com.idormy.sms.forwarder.entity.MsgInfo
import com.idormy.sms.forwarder.utils.*
import com.idormy.sms.forwarder.workers.SendWorker
import com.xuexiang.xutil.resource.ResUtils.getString
import com.idormy.sms.forwarder.utils.SettingUtils
import java.util.*
@Suppress("DEPRECATION")
class PhoneStateReceiver : BroadcastReceiver() {
abstract class PhoneStateReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
try {
//纯客户端模式
if (SettingUtils.enablePureClientMode) return
//总开关
if (!SettingUtils.enablePhone) return
//纯客户端模式
if (SettingUtils.enablePureClientMode) return
//过滤广播
if (TelephonyManager.ACTION_PHONE_STATE_CHANGED != intent.action) return
//总开关
if (!SettingUtils.enablePhone) return
//权限判断
if (ActivityCompat.checkSelfPermission(
context, Manifest.permission.READ_PHONE_STATE
) != PackageManager.PERMISSION_GRANTED
) return
//获取来电号码
val number = intent.extras!!.getString(TelephonyManager.EXTRA_INCOMING_NUMBER)
//We listen to two intents. The new outgoing call only tells us of an outgoing call. We use it to get the number.
if (intent.action == CallReceiver.ACTION_OUT) {
savedNumber = intent.extras!!.getString(CallReceiver.EXTRA_PHONE_NUMBER)
Log.d(TAG, "savedNumber$savedNumber")
} else {
val stateStr = intent.extras!!.getString(TelephonyManager.EXTRA_STATE)
val number = intent.extras!!.getString(TelephonyManager.EXTRA_INCOMING_NUMBER)
savedNumber = number
Log.d(TAG, "stateStr$stateStrsavedNumber$savedNumber")
var state = 0
when (stateStr) {
TelephonyManager.EXTRA_STATE_IDLE -> state = TelephonyManager.CALL_STATE_IDLE
TelephonyManager.EXTRA_STATE_OFFHOOK -> state = TelephonyManager.CALL_STATE_OFFHOOK
TelephonyManager.EXTRA_STATE_RINGING -> state = TelephonyManager.CALL_STATE_RINGING
}
Log.d(TAG, "state=$state, number=$number")
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
}
lastState = state
var callIsIncoming: Boolean by SharedPreference("CALL_IS_INCOMING", false)
Log.d(TAG, "lastState=$lastState, callIsIncoming=$callIsIncoming, callSavedNumber=$callSavedNumber")
when (state) {
TelephonyManager.CALL_STATE_RINGING -> {
Log.d(TAG, "电话响铃")
callIsIncoming = true
//来电提醒
if (!TextUtils.isEmpty(number) && SettingUtils.enableCallType4) {
val contacts = PhoneUtils.getContactByNumber(number)
val contactName = if (contacts.isNotEmpty()) contacts[0].name else getString(R.string.unknown_number)
val sb = StringBuilder()
sb.append(getString(R.string.linkman)).append(contactName).append("\n")
sb.append(getString(R.string.mandatory_type))
sb.append(getString(R.string.incoming_call))
val msgInfo = MsgInfo("call", number.toString(), sb.toString(), Date(), "", -1)
val request = OneTimeWorkRequestBuilder<SendWorker>().setInputData(
workDataOf(
Worker.sendMsgInfo to Gson().toJson(msgInfo)
)
).build()
WorkManager.getInstance(context).enqueue(request)
}
}
TelephonyManager.CALL_STATE_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
}
}
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 = ""
}
}
}
} catch (e: Exception) {
Log.e(TAG, e.message.toString())
onCallStateChanged(context, state, number)
}
}
private fun sendReceiveCallMsg(context: Context, callType: Int, phoneNumber: String?) {
//必须休眠才能获取来电记录,否则可能获取到上一次通话的
Thread.sleep(500)
//获取后一条通话记录
Log.d(TAG, "callType = $callType, phoneNumber = $phoneNumber")
val callInfo: CallInfo? = PhoneUtils.getLastCallInfo(callType, phoneNumber)
Log.d(TAG, "callInfo = $callInfo")
if (callInfo?.number == null) return
//判断是否开启该类型转发
if ((callInfo.type == 1 && !SettingUtils.enableCallType1) || (callInfo.type == 2 && !SettingUtils.enableCallType2) || (callInfo.type == 3 && !SettingUtils.enableCallType3)) {
Log.w(TAG, "未开启该类型转发type=" + callInfo.type)
return
}
//Derived classes should override these to respond to specific events of interest
protected abstract fun onIncomingCallReceived(context: Context, number: String?, start: Date)
//卡槽id-1=获取失败、0=卡槽1、1=卡槽2
val simSlot = callInfo.simId
//获取卡槽信息
val simInfo = when (simSlot) {
0 -> "SIM1_" + SettingUtils.extraSim1
1 -> "SIM2_" + SettingUtils.extraSim2
else -> ""
}
protected abstract fun onIncomingCallAnswered(context: Context, number: String?, start: Date)
//获取联系人姓名
if (TextUtils.isEmpty(callInfo.name)) {
val contacts = PhoneUtils.getContactByNumber(phoneNumber)
callInfo.name = if (contacts.isNotEmpty()) contacts[0].name else getString(R.string.unknown_number)
protected abstract fun onIncomingCallEnded(context: Context, number: String?, start: Date, end: Date)
protected abstract fun onOutgoingCallStarted(context: Context, number: String?, start: Date)
protected abstract fun onOutgoingCallEnded(context: Context, number: String?, start: Date, end: Date)
protected abstract fun onMissedCall(context: Context, number: String?, start: Date)
//Deals with actual events
//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?) {
if (lastState == state || number == null) {
//No change, debounce extras
return
}
val msgInfo = MsgInfo(
"call", callInfo.number, PhoneUtils.getCallMsg(callInfo), Date(), simInfo, simSlot, callInfo.subId
)
val request = OneTimeWorkRequestBuilder<SendWorker>().setInputData(
workDataOf(
Worker.sendMsgInfo to Gson().toJson(msgInfo)
)
).build()
WorkManager.getInstance(context).enqueue(request)
when (state) {
TelephonyManager.CALL_STATE_RINGING -> {
isIncoming = true
callStartTime = Date()
savedNumber = number
onIncomingCallReceived(context, number, callStartTime)
}
TelephonyManager.CALL_STATE_OFFHOOK ->
//Transition of ringing->offhook are pickups of incoming calls. Nothing done on them
if (lastState != TelephonyManager.CALL_STATE_RINGING) {
isIncoming = false
callStartTime = Date()
onOutgoingCallStarted(context, savedNumber, callStartTime)
} else {
isIncoming = true
callStartTime = Date()
onIncomingCallAnswered(context, savedNumber, callStartTime)
}
TelephonyManager.CALL_STATE_IDLE ->
//Went to idle- this is the end of a call. What type depends on previous state(s)
if (lastState == TelephonyManager.CALL_STATE_RINGING) {
//Ring but no pickup
onMissedCall(context, savedNumber, callStartTime)
} else if (isIncoming) {
onIncomingCallEnded(context, savedNumber, callStartTime, Date())
} else {
onOutgoingCallEnded(context, savedNumber, callStartTime, Date())
}
}
lastState = state
}
companion object {
private const val TAG = "PhoneStateReceiver"
private val TAG = PhoneStateReceiver::class.java.simpleName
//The receiver will be recreated whenever android feels like it. We need a static variable to remember data between instantiations
private var lastState = TelephonyManager.CALL_STATE_IDLE
private var callStartTime: Date = Date()
private var isIncoming: Boolean = false
private var savedNumber: String? = null //because the passed incoming is only valid in ringing
}
}

@ -33,6 +33,8 @@ 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_CALL_TYPE_5 = "enable_call_type_5"
const val SP_ENABLE_CALL_TYPE_6 = "enable_call_type_6"
const val SP_ENABLE_APP_NOTIFY = "enable_app_notify"
const val SP_ENABLE_CANCEL_APP_NOTIFY = "enable_cancel_app_notify"

@ -139,6 +139,9 @@ class HttpServerUtils private constructor() {
cloneInfo.callType1 = SettingUtils.enableCallType1
cloneInfo.callType2 = SettingUtils.enableCallType2
cloneInfo.callType3 = SettingUtils.enableCallType3
cloneInfo.callType4 = SettingUtils.enableCallType4
cloneInfo.callType5 = SettingUtils.enableCallType5
cloneInfo.callType6 = SettingUtils.enableCallType6
cloneInfo.enableAppNotify = SettingUtils.enableAppNotify
cloneInfo.cancelAppNotify = SettingUtils.enableCancelAppNotify
cloneInfo.enableNotUserPresent = SettingUtils.enableNotUserPresent
@ -181,6 +184,9 @@ class HttpServerUtils private constructor() {
SettingUtils.enableCallType1 = cloneInfo.callType1
SettingUtils.enableCallType2 = cloneInfo.callType2
SettingUtils.enableCallType3 = cloneInfo.callType3
SettingUtils.enableCallType4 = cloneInfo.callType4
SettingUtils.enableCallType5 = cloneInfo.callType5
SettingUtils.enableCallType6 = cloneInfo.callType6
SettingUtils.enableAppNotify = cloneInfo.enableAppNotify
SettingUtils.enableCancelAppNotify = cloneInfo.cancelAppNotify
SettingUtils.enableNotUserPresent = cloneInfo.enableNotUserPresent

@ -187,11 +187,9 @@ class PhoneUtils private constructor() {
Log.d(TAG, "selectionArgs = $selectionArgs")
//为了兼容性这里全部取出后手动分页
val cursor = (if (limit == 1) Core.app.contentResolver.query(
CallLog.Calls.CONTENT_URI, null, selection, selectionArgs.toTypedArray(), CallLog.Calls.DEFAULT_SORT_ORDER + " limit $limit offset $offset"
) else Core.app.contentResolver.query(
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
) ?: return callInfoList
Log.i(TAG, "cursor count:" + cursor.count)
// 避免超过总数后循环取出
@ -348,12 +346,15 @@ class PhoneUtils private constructor() {
sb.append(callInfo.duration).append("s\n")
}
sb.append(ResUtils.getString(R.string.mandatory_type))
//通话类型1.呼入 2.呼出 3.未接 4.来电提醒
//通话类型1.来电挂机 2.去电挂机 3.未接来电 4.来电提醒 5.来电接通 6.去电拨出
when (callInfo.type) {
1 -> sb.append(ResUtils.getString(R.string.received_call))
2 -> sb.append(ResUtils.getString(R.string.local_outgoing_call))
1 -> sb.append(ResUtils.getString(R.string.incoming_call_ended))
2 -> sb.append(ResUtils.getString(R.string.outgoing_call_ended))
3 -> sb.append(ResUtils.getString(R.string.missed_call))
else -> sb.append(ResUtils.getString(R.string.incoming_call))
4 -> sb.append(ResUtils.getString(R.string.incoming_call_received))
5 -> sb.append(ResUtils.getString(R.string.incoming_call_answered))
6 -> sb.append(ResUtils.getString(R.string.outgoing_call_started))
else -> sb.append(ResUtils.getString(R.string.unknown_call))
}
return sb.toString()
}

@ -18,10 +18,10 @@ class SettingUtils private constructor() {
//是否转发通话
var enablePhone: Boolean by SharedPreference(SP_ENABLE_PHONE, false)
//是否转发通话——已接来电
//是否转发通话——来电挂机
var enableCallType1: Boolean by SharedPreference(SP_ENABLE_CALL_TYPE_1, false)
//是否转发通话——本机去电
//是否转发通话——去电挂机
var enableCallType2: Boolean by SharedPreference(SP_ENABLE_CALL_TYPE_2, false)
//是否转发通话——未接来电
@ -30,6 +30,12 @@ class SettingUtils private constructor() {
//是否转发通话——来电提醒
var enableCallType4: Boolean by SharedPreference(SP_ENABLE_CALL_TYPE_4, false)
//是否转发通话——来电接通
var enableCallType5: Boolean by SharedPreference(SP_ENABLE_CALL_TYPE_5, false)
//是否转发通话——去电拨出
var enableCallType6: Boolean by SharedPreference(SP_ENABLE_CALL_TYPE_6, false)
//是否转发应用通知
var enableAppNotify: Boolean by SharedPreference(SP_ENABLE_APP_NOTIFY, false)

@ -100,76 +100,130 @@
<LinearLayout
android:layout_width="match_parent"
android:layout_height="25dp"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:orientation="horizontal">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/type"
android:text="@string/mandatory_type"
android:textSize="10sp"
android:textStyle="bold"
tools:ignore="SmallSp" />
<com.xuexiang.xui.widget.button.SmoothCheckBox
android:id="@+id/scb_call_type3"
android:layout_width="15dp"
android:layout_height="15dp"
app:scb_color_checked="@color/colorPrimary" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:singleLine="true"
android:text="@string/missed_call"
android:textSize="10sp"
tools:ignore="SmallSp" />
<com.xuexiang.xui.widget.button.SmoothCheckBox
android:id="@+id/scb_call_type1"
android:layout_width="15dp"
android:layout_height="15dp"
android:layout_marginStart="3dp"
app:scb_color_checked="@color/colorPrimary" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:singleLine="true"
android:text="@string/received_call"
android:textSize="10sp"
tools:ignore="SmallSp" />
<com.xuexiang.xui.widget.button.SmoothCheckBox
android:id="@+id/scb_call_type2"
android:layout_width="15dp"
android:layout_height="15dp"
android:layout_marginStart="3dp"
app:scb_color_checked="@color/colorPrimary" />
<TextView
android:layout_width="wrap_content"
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:singleLine="true"
android:text="@string/local_outgoing_call"
android:textSize="10sp"
tools:ignore="SmallSp" />
android:layout_weight="1"
android:orientation="vertical">
<com.xuexiang.xui.widget.button.SmoothCheckBox
android:id="@+id/scb_call_type4"
android:layout_width="15dp"
android:layout_height="15dp"
android:layout_marginStart="3dp"
app:scb_color_checked="@color/colorPrimary" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:orientation="horizontal">
<com.xuexiang.xui.widget.button.SmoothCheckBox
android:id="@+id/scb_call_type3"
android:layout_width="15dp"
android:layout_height="15dp"
app:scb_color_checked="@color/colorPrimary" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:singleLine="true"
android:text="@string/missed_call"
android:textSize="10sp"
tools:ignore="SmallSp" />
<com.xuexiang.xui.widget.button.SmoothCheckBox
android:id="@+id/scb_call_type1"
android:layout_width="15dp"
android:layout_height="15dp"
android:layout_marginStart="5dp"
app:scb_color_checked="@color/colorPrimary" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:singleLine="true"
android:text="@string/incoming_call_ended"
android:textSize="10sp"
tools:ignore="SmallSp" />
<com.xuexiang.xui.widget.button.SmoothCheckBox
android:id="@+id/scb_call_type2"
android:layout_width="15dp"
android:layout_height="15dp"
android:layout_marginStart="5dp"
app:scb_color_checked="@color/colorPrimary" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:singleLine="true"
android:text="@string/outgoing_call_ended"
android:textSize="10sp"
tools:ignore="SmallSp" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="3dp"
android:gravity="center_vertical"
android:orientation="horizontal">
<com.xuexiang.xui.widget.button.SmoothCheckBox
android:id="@+id/scb_call_type4"
android:layout_width="15dp"
android:layout_height="15dp"
app:scb_color_checked="@color/colorPrimary" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:singleLine="true"
android:text="@string/incoming_call_received"
android:textSize="10sp"
tools:ignore="SmallSp" />
<com.xuexiang.xui.widget.button.SmoothCheckBox
android:id="@+id/scb_call_type5"
android:layout_width="15dp"
android:layout_height="15dp"
android:layout_marginStart="5dp"
app:scb_color_checked="@color/colorPrimary" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:singleLine="true"
android:text="@string/incoming_call_answered"
android:textSize="10sp"
tools:ignore="SmallSp" />
<com.xuexiang.xui.widget.button.SmoothCheckBox
android:id="@+id/scb_call_type6"
android:layout_width="15dp"
android:layout_height="15dp"
android:layout_marginStart="5dp"
app:scb_color_checked="@color/colorPrimary" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:singleLine="true"
android:text="@string/outgoing_call_started"
android:textSize="10sp"
tools:ignore="SmallSp" />
</LinearLayout>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:singleLine="true"
android:text="@string/incoming_call"
android:textSize="10sp"
tools:ignore="SmallSp" />
</LinearLayout>
</LinearLayout>

@ -222,6 +222,10 @@
<string name="test_package_name">Test PackageName</string>
<string name="test_inform_title">Test Notify Title</string>
<string name="test_inform_content">Test Notify Content</string>
<string name="sender_logic">Run Logic</string>
<string name="sender_logic_all">All Run</string>
<string name="sender_logic_until_fail">Run Until Fail</string>
<string name="sender_logic_until_success">Run Until Success</string>
<string name="match_sim_slot">SIM Slot</string>
<string name="match_field">Field</string>
<string name="phone_number">Phone No.</string>
@ -505,10 +509,13 @@
<string name="ring_duration">Ring duration: </string>
<string name="type">Type: </string>
<string name="mandatory_type">Call type: </string>
<string name="incoming_call_received">Incoming Received</string>
<string name="incoming_call_answered">Incoming Answered</string>
<string name="incoming_call_ended">Incoming Ended</string>
<string name="outgoing_call_started">Outgoing Started</string>
<string name="outgoing_call_ended">Outgoing Ended</string>
<string name="missed_call">Missed</string>
<string name="incoming_call">Incoming</string>
<string name="received_call">Received</string>
<string name="local_outgoing_call">Call out</string>
<string name="unknown_call">Unknown</string>
<string name="optional_action">Optional: </string>
<string name="optional_type">Optional: </string>
<string name="active_request">Active request</string>
@ -840,6 +847,7 @@
<string name="frpc_failed_to_run">Frpc failed to run</string>
<string name="successfully_deleted">Successfully deleted</string>
<string name="sender_disabled_tips">[Note] The sending channel has been disabled, and its associated rules will not be sent even if they match!</string>
<string name="sender_contains_tips">[Note] The sending channel is already in the list, no need to add it again!</string>
<string name="local_call">Local Call:</string>
<string name="remote_sms">Remote SMS</string>
<string name="clear">Clear</string>
@ -941,4 +949,6 @@
<string name="copy_public_key">Copy</string>
<string name="sm4_key">SM4 Key</string>
<string name="sm4_key_tips">Client or server interaction messages are all encrypted and decrypted using SM4</string>
<string name="sender_del">Del Sender</string>
</resources>

@ -510,10 +510,13 @@
<string name="ring_duration">响铃时长:</string>
<string name="type">类型:</string>
<string name="mandatory_type">通话类型:</string>
<string name="incoming_call_received">来电提醒</string>
<string name="incoming_call_answered">来电接通</string>
<string name="incoming_call_ended">来电挂机</string>
<string name="outgoing_call_started">去电拨出</string>
<string name="outgoing_call_ended">去电挂机</string>
<string name="missed_call">未接来电</string>
<string name="incoming_call">来电提醒</string>
<string name="received_call">已接来电</string>
<string name="local_outgoing_call">本机去电</string>
<string name="unknown_call">未知通话</string>
<string name="optional_action">可选操作:</string>
<string name="optional_type">可选类型:</string>
<string name="active_request">主动请求</string>

Loading…
Cancel
Save