package com.beancount.mobile.sms import android.content.BroadcastReceiver import android.content.Context import android.content.Intent import android.provider.Telephony import android.telephony.SmsMessage import android.util.Log import com.facebook.react.bridge.WritableNativeMap import com.facebook.react.modules.core.DeviceEventManagerModule import com.beancount.mobile.accessibility.ReactContextHolder /** * 短信监听 Receiver(plan.md「4.2 短信监听服务」+「决策 4 Config Plugin」)。 * * 参考 AutoAccounting 的 SmsReceiver: * - 监听 SMS_RECEIVED_ACTION * - 从 PDU 解析发送方 + 正文 * - 关键词预过滤(银行短信含「交易/消费/余额」等,JS 层进一步过滤) * - 通过 DeviceEventEmitter 推送到 JS 层 SmsChannel * * 需在 manifest 注册 RECEIVE_SMS 权限(由 Config Plugin 注入)。 */ class BillingSmsReceiver : BroadcastReceiver() { companion object { private const val TAG = "BillingSms" /** 银行短信关键词(预过滤,减少 JS 层负担)。 */ private val BANK_KEYWORDS = listOf("交易", "消费", "收入", "支出", "余额", "转账", "入账", "扣款", "退款") } override fun onReceive(context: Context, intent: Intent) { if (intent.action != Telephony.Sms.Intents.SMS_RECEIVED_ACTION) return runCatching { val messages = Telephony.Sms.Intents.getMessagesFromIntent(intent) for (message in messages) { val sender = message.displayOriginatingAddress ?: "" val body = message.displayMessageBody ?: "" if (sender.isBlank() || body.isBlank()) continue // 关键词预过滤(仅处理疑似银行短信) if (!BANK_KEYWORDS.any { body.contains(it) }) continue Log.d(TAG, "收到银行短信: sender=$sender") sendSmsEvent(sender, body) } }.onFailure { Log.e(TAG, "短信处理异常: ${it.message}", it) } } /** 把短信事件推送到 JS 层 SmsChannel.handleSms。 */ private fun sendSmsEvent(sender: String, body: String) { val reactContext = ReactContextHolder.context ?: run { Log.w(TAG, "RN 上下文未就绪,丢弃短信") return } try { val map = WritableNativeMap().apply { putString("sender", sender) putString("body", body) putDouble("timestamp", System.currentTimeMillis().toDouble()) } reactContext .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java) .emit("billingSms", map) } catch (e: Exception) { Log.e(TAG, "推送短信事件失败: ${e.message}") } } }