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

This commit is contained in:
pppscn 2023-02-05 11:56:55 +08:00
parent b79d3d8493
commit 04d8c9015a
13 changed files with 491 additions and 302 deletions

View File

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

View File

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

View File

@ -21,6 +21,12 @@ data class CloneInfo(
var callType2: Boolean = false, var callType2: Boolean = false,
@SerializedName("call_type3") @SerializedName("call_type3")
var callType3: Boolean = false, 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") @SerializedName("enable_app_notify")
var enableAppNotify: Boolean = false, var enableAppNotify: Boolean = false,
@SerializedName("cancel_app_notify") @SerializedName("cancel_app_notify")

View File

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

View File

@ -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)
}
}

View File

@ -1,178 +1,109 @@
package com.idormy.sms.forwarder.receiver package com.idormy.sms.forwarder.receiver
import android.Manifest
import android.content.BroadcastReceiver import android.content.BroadcastReceiver
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.pm.PackageManager
import android.telephony.TelephonyManager import android.telephony.TelephonyManager
import android.text.TextUtils
import android.util.Log import android.util.Log
import androidx.core.app.ActivityCompat import com.idormy.sms.forwarder.utils.SettingUtils
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 java.util.* import java.util.*
@Suppress("DEPRECATION") @Suppress("DEPRECATION")
class PhoneStateReceiver : BroadcastReceiver() { abstract class PhoneStateReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) { override fun onReceive(context: Context, intent: Intent) {
try {
//纯客户端模式 //纯客户端模式
if (SettingUtils.enablePureClientMode) return if (SettingUtils.enablePureClientMode) return
//总开关 //总开关
if (!SettingUtils.enablePhone) return if (!SettingUtils.enablePhone) return
//过滤广播 //We listen to two intents. The new outgoing call only tells us of an outgoing call. We use it to get the number.
if (TelephonyManager.ACTION_PHONE_STATE_CHANGED != intent.action) return if (intent.action == CallReceiver.ACTION_OUT) {
savedNumber = intent.extras!!.getString(CallReceiver.EXTRA_PHONE_NUMBER)
//权限判断 Log.d(TAG, "savedNumber$savedNumber")
if (ActivityCompat.checkSelfPermission( } else {
context, Manifest.permission.READ_PHONE_STATE
) != PackageManager.PERMISSION_GRANTED
) return
//获取来电号码
val number = intent.extras!!.getString(TelephonyManager.EXTRA_INCOMING_NUMBER)
val stateStr = intent.extras!!.getString(TelephonyManager.EXTRA_STATE) 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 var state = 0
when (stateStr) { when (stateStr) {
TelephonyManager.EXTRA_STATE_IDLE -> state = TelephonyManager.CALL_STATE_IDLE TelephonyManager.EXTRA_STATE_IDLE -> state = TelephonyManager.CALL_STATE_IDLE
TelephonyManager.EXTRA_STATE_OFFHOOK -> state = TelephonyManager.CALL_STATE_OFFHOOK TelephonyManager.EXTRA_STATE_OFFHOOK -> state = TelephonyManager.CALL_STATE_OFFHOOK
TelephonyManager.EXTRA_STATE_RINGING -> state = TelephonyManager.CALL_STATE_RINGING TelephonyManager.EXTRA_STATE_RINGING -> state = TelephonyManager.CALL_STATE_RINGING
} }
Log.d(TAG, "state=$state, number=$number") onCallStateChanged(context, state, number)
var callSavedNumber: String by SharedPreference("CALL_SAVED_NUMBER", "") }
if (!TextUtils.isEmpty(number)) callSavedNumber = number.toString() }
//Derived classes should override these to respond to specific events of interest
protected abstract fun onIncomingCallReceived(context: Context, number: String?, start: Date)
protected abstract fun onIncomingCallAnswered(context: Context, number: String?, start: Date)
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 //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 //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) private fun onCallStateChanged(context: Context, state: Int, number: String?) {
if (lastState == state || (state == TelephonyManager.CALL_STATE_RINGING && number == null)) { if (lastState == state || number == null) {
//No change, debounce extras //No change, debounce extras
Log.d(TAG, "状态没变,防止抖动")
return return
} }
lastState = state
var callIsIncoming: Boolean by SharedPreference("CALL_IS_INCOMING", false)
Log.d(TAG, "lastState=$lastState, callIsIncoming=$callIsIncoming, callSavedNumber=$callSavedNumber")
when (state) { when (state) {
TelephonyManager.CALL_STATE_RINGING -> { TelephonyManager.CALL_STATE_RINGING -> {
Log.d(TAG, "电话响铃") isIncoming = true
callIsIncoming = true callStartTime = Date()
savedNumber = number
//来电提醒 onIncomingCallReceived(context, number, callStartTime)
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 -> TelephonyManager.CALL_STATE_OFFHOOK ->
//Transition of ringing->offhook are pickups of incoming calls. Nothing done on them //Transition of ringing->offhook are pickups of incoming calls. Nothing done on them
callIsIncoming = when { if (lastState != TelephonyManager.CALL_STATE_RINGING) {
lastState != TelephonyManager.CALL_STATE_RINGING -> { isIncoming = false
Log.d(TAG, "去电接通") callStartTime = Date()
if (!TextUtils.isEmpty(number)) callSavedNumber = number.toString()
false onOutgoingCallStarted(context, savedNumber, callStartTime)
} } else {
else -> { isIncoming = true
Log.d(TAG, "来电接通") callStartTime = Date()
true
} onIncomingCallAnswered(context, savedNumber, callStartTime)
} }
TelephonyManager.CALL_STATE_IDLE -> TelephonyManager.CALL_STATE_IDLE ->
//Went to idle- this is the end of a call. What type depends on previous state(s) //Went to idle- this is the end of a call. What type depends on previous state(s)
when { if (lastState == TelephonyManager.CALL_STATE_RINGING) {
lastState == TelephonyManager.CALL_STATE_RINGING -> { //Ring but no pickup
Log.d(TAG, "来电未接") onMissedCall(context, savedNumber, callStartTime)
sendReceiveCallMsg(context, 3, callSavedNumber) } else if (isIncoming) {
callSavedNumber = "" onIncomingCallEnded(context, savedNumber, callStartTime, Date())
} } else {
callIsIncoming -> { onOutgoingCallEnded(context, savedNumber, callStartTime, Date())
Log.d(TAG, "来电挂机")
sendReceiveCallMsg(context, 1, callSavedNumber)
callSavedNumber = ""
}
else -> {
Log.d(TAG, "去电挂机")
sendReceiveCallMsg(context, 2, callSavedNumber)
callSavedNumber = ""
} }
} }
} lastState = state
} catch (e: Exception) {
Log.e(TAG, e.message.toString())
}
}
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
}
//卡槽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)
} }
companion object { 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
} }
} }

View File

@ -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_2 = "enable_call_type_2"
const val SP_ENABLE_CALL_TYPE_3 = "enable_call_type_3" 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_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_APP_NOTIFY = "enable_app_notify"
const val SP_ENABLE_CANCEL_APP_NOTIFY = "enable_cancel_app_notify" const val SP_ENABLE_CANCEL_APP_NOTIFY = "enable_cancel_app_notify"

View File

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

View File

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

View File

@ -18,10 +18,10 @@ class SettingUtils private constructor() {
//是否转发通话 //是否转发通话
var enablePhone: Boolean by SharedPreference(SP_ENABLE_PHONE, false) var enablePhone: Boolean by SharedPreference(SP_ENABLE_PHONE, false)
//是否转发通话——已接来电 //是否转发通话——来电挂机
var enableCallType1: Boolean by SharedPreference(SP_ENABLE_CALL_TYPE_1, false) var enableCallType1: Boolean by SharedPreference(SP_ENABLE_CALL_TYPE_1, false)
//是否转发通话——本机去电 //是否转发通话——去电挂机
var enableCallType2: Boolean by SharedPreference(SP_ENABLE_CALL_TYPE_2, 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 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) var enableAppNotify: Boolean by SharedPreference(SP_ENABLE_APP_NOTIFY, false)

View File

@ -100,18 +100,30 @@
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="25dp" android:layout_height="wrap_content"
android:gravity="center_vertical" android:gravity="center_vertical"
android:orientation="horizontal"> android:orientation="horizontal">
<TextView <TextView
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="@string/type" android:text="@string/mandatory_type"
android:textSize="10sp" android:textSize="10sp"
android:textStyle="bold" android:textStyle="bold"
tools:ignore="SmallSp" /> tools:ignore="SmallSp" />
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:orientation="horizontal">
<com.xuexiang.xui.widget.button.SmoothCheckBox <com.xuexiang.xui.widget.button.SmoothCheckBox
android:id="@+id/scb_call_type3" android:id="@+id/scb_call_type3"
android:layout_width="15dp" android:layout_width="15dp"
@ -130,14 +142,14 @@
android:id="@+id/scb_call_type1" android:id="@+id/scb_call_type1"
android:layout_width="15dp" android:layout_width="15dp"
android:layout_height="15dp" android:layout_height="15dp"
android:layout_marginStart="3dp" android:layout_marginStart="5dp"
app:scb_color_checked="@color/colorPrimary" /> app:scb_color_checked="@color/colorPrimary" />
<TextView <TextView
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:singleLine="true" android:singleLine="true"
android:text="@string/received_call" android:text="@string/incoming_call_ended"
android:textSize="10sp" android:textSize="10sp"
tools:ignore="SmallSp" /> tools:ignore="SmallSp" />
@ -145,32 +157,74 @@
android:id="@+id/scb_call_type2" android:id="@+id/scb_call_type2"
android:layout_width="15dp" android:layout_width="15dp"
android:layout_height="15dp" android:layout_height="15dp"
android:layout_marginStart="3dp" android:layout_marginStart="5dp"
app:scb_color_checked="@color/colorPrimary" /> app:scb_color_checked="@color/colorPrimary" />
<TextView <TextView
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:singleLine="true" android:singleLine="true"
android:text="@string/local_outgoing_call" android:text="@string/outgoing_call_ended"
android:textSize="10sp" android:textSize="10sp"
tools:ignore="SmallSp" /> 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 <com.xuexiang.xui.widget.button.SmoothCheckBox
android:id="@+id/scb_call_type4" android:id="@+id/scb_call_type4"
android:layout_width="15dp" android:layout_width="15dp"
android:layout_height="15dp" android:layout_height="15dp"
android:layout_marginStart="3dp"
app:scb_color_checked="@color/colorPrimary" /> app:scb_color_checked="@color/colorPrimary" />
<TextView <TextView
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:singleLine="true" android:singleLine="true"
android:text="@string/incoming_call" android:text="@string/incoming_call_received"
android:textSize="10sp" android:textSize="10sp"
tools:ignore="SmallSp" /> 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>
</LinearLayout>
</LinearLayout> </LinearLayout>
</LinearLayout> </LinearLayout>

View File

@ -222,6 +222,10 @@
<string name="test_package_name">Test PackageName</string> <string name="test_package_name">Test PackageName</string>
<string name="test_inform_title">Test Notify Title</string> <string name="test_inform_title">Test Notify Title</string>
<string name="test_inform_content">Test Notify Content</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_sim_slot">SIM Slot</string>
<string name="match_field">Field</string> <string name="match_field">Field</string>
<string name="phone_number">Phone No.</string> <string name="phone_number">Phone No.</string>
@ -505,10 +509,13 @@
<string name="ring_duration">Ring duration: </string> <string name="ring_duration">Ring duration: </string>
<string name="type">Type: </string> <string name="type">Type: </string>
<string name="mandatory_type">Call 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="missed_call">Missed</string>
<string name="incoming_call">Incoming</string> <string name="unknown_call">Unknown</string>
<string name="received_call">Received</string>
<string name="local_outgoing_call">Call out</string>
<string name="optional_action">Optional: </string> <string name="optional_action">Optional: </string>
<string name="optional_type">Optional: </string> <string name="optional_type">Optional: </string>
<string name="active_request">Active request</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="frpc_failed_to_run">Frpc failed to run</string>
<string name="successfully_deleted">Successfully deleted</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_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="local_call">Local Call:</string>
<string name="remote_sms">Remote SMS</string> <string name="remote_sms">Remote SMS</string>
<string name="clear">Clear</string> <string name="clear">Clear</string>
@ -941,4 +949,6 @@
<string name="copy_public_key">Copy</string> <string name="copy_public_key">Copy</string>
<string name="sm4_key">SM4 Key</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="sm4_key_tips">Client or server interaction messages are all encrypted and decrypted using SM4</string>
<string name="sender_del">Del Sender</string>
</resources> </resources>

View File

@ -510,10 +510,13 @@
<string name="ring_duration">响铃时长:</string> <string name="ring_duration">响铃时长:</string>
<string name="type">类型:</string> <string name="type">类型:</string>
<string name="mandatory_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="missed_call">未接来电</string>
<string name="incoming_call">来电提醒</string> <string name="unknown_call">未知通话</string>
<string name="received_call">已接来电</string>
<string name="local_outgoing_call">本机去电</string>
<string name="optional_action">可选操作:</string> <string name="optional_action">可选操作:</string>
<string name="optional_type">可选类型:</string> <string name="optional_type">可选类型:</string>
<string name="active_request">主动请求</string> <string name="active_request">主动请求</string>