diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 6f0a318c..004f5d80 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -242,11 +242,12 @@ + 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 95348543..80aae874 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 @@ -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") diff --git a/app/src/main/java/com/idormy/sms/forwarder/entity/CloneInfo.kt b/app/src/main/java/com/idormy/sms/forwarder/entity/CloneInfo.kt index 36dbf475..5763d38f 100644 --- a/app/src/main/java/com/idormy/sms/forwarder/entity/CloneInfo.kt +++ b/app/src/main/java/com/idormy/sms/forwarder/entity/CloneInfo.kt @@ -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? = null, - @SerializedName("rule_list") - var ruleList: List? = null, - @SerializedName("frpc_list") - var frpcList: List? = 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? = null, + @SerializedName("rule_list") + var ruleList: List? = null, + @SerializedName("frpc_list") + var frpcList: List? = null, ) : Serializable \ No newline at end of file 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 a12c2966..26796760 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 @@ -80,7 +80,7 @@ class SettingsFragment : BaseFragment(), 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(), 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(), 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(), 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(), 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(), 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 diff --git a/app/src/main/java/com/idormy/sms/forwarder/receiver/CallReceiver.kt b/app/src/main/java/com/idormy/sms/forwarder/receiver/CallReceiver.kt new file mode 100644 index 00000000..c80f7a98 --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/receiver/CallReceiver.kt @@ -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().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().setInputData( + workDataOf( + Worker.sendMsgInfo to Gson().toJson(msgInfo) + ) + ).build() + WorkManager.getInstance(context).enqueue(request) + + } + +} \ No newline at end of file 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 4b9ed845..7164b4db 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 @@ -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:$stateStr,savedNumber:$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().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 + //Derived classes should override these to respond to specific events of interest + protected abstract fun onIncomingCallReceived(context: Context, number: String?, start: Date) - //判断是否开启该类型转发 - if ((callInfo.type == 1 && !SettingUtils.enableCallType1) || (callInfo.type == 2 && !SettingUtils.enableCallType2) || (callInfo.type == 3 && !SettingUtils.enableCallType3)) { - Log.w(TAG, "未开启该类型转发,type=" + callInfo.type) + 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 + //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 } - //卡槽id:-1=获取失败、0=卡槽1、1=卡槽2 - val simSlot = callInfo.simId - //获取卡槽信息 - val simInfo = when (simSlot) { - 0 -> "SIM1_" + SettingUtils.extraSim1 - 1 -> "SIM2_" + SettingUtils.extraSim2 - else -> "" + 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()) + } } - - //获取联系人姓名 - 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().setInputData( - workDataOf( - Worker.sendMsgInfo to Gson().toJson(msgInfo) - ) - ).build() - WorkManager.getInstance(context).enqueue(request) - + 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 } } \ 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 b766fe49..3aefd415 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 @@ -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" diff --git a/app/src/main/java/com/idormy/sms/forwarder/utils/HttpServerUtils.kt b/app/src/main/java/com/idormy/sms/forwarder/utils/HttpServerUtils.kt index c101bf65..72de5370 100644 --- a/app/src/main/java/com/idormy/sms/forwarder/utils/HttpServerUtils.kt +++ b/app/src/main/java/com/idormy/sms/forwarder/utils/HttpServerUtils.kt @@ -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 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 b717827e..4bbdb696 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 @@ -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() } 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 cbb26182..671bdee2 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 @@ -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) diff --git a/app/src/main/res/layout/fragment_settings.xml b/app/src/main/res/layout/fragment_settings.xml index acd063e0..04d853ec 100644 --- a/app/src/main/res/layout/fragment_settings.xml +++ b/app/src/main/res/layout/fragment_settings.xml @@ -100,76 +100,130 @@ - - - + android:layout_weight="1" + android:orientation="vertical"> - + - + - + - + - + - + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/values-en/strings.xml b/app/src/main/res/values-en/strings.xml index 46d452c4..b223fca0 100644 --- a/app/src/main/res/values-en/strings.xml +++ b/app/src/main/res/values-en/strings.xml @@ -222,6 +222,10 @@ Test PackageName Test Notify Title Test Notify Content + Run Logic + All Run + Run Until Fail + Run Until Success SIM Slot Field Phone No. @@ -505,10 +509,13 @@ Ring duration: Type: Call type: + Incoming Received + Incoming Answered + Incoming Ended + Outgoing Started + Outgoing Ended Missed - Incoming - Received - Call out + Unknown Optional: Optional: Active request @@ -840,6 +847,7 @@ Frpc failed to run Successfully deleted [Note] The sending channel has been disabled, and its associated rules will not be sent even if they match! + [Note] The sending channel is already in the list, no need to add it again! Local Call: Remote SMS: Clear @@ -941,4 +949,6 @@ Copy SM4 Key Client or server interaction messages are all encrypted and decrypted using SM4 + + Del Sender diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 0b625bb0..b8a4a915 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -510,10 +510,13 @@ 响铃时长: 类型: 通话类型: + 来电提醒 + 来电接通 + 来电挂机 + 去电拨出 + 去电挂机 未接来电 - 来电提醒 - 已接来电 - 本机去电 + 未知通话 可选操作: 可选类型: 主动请求