package com.beancount.mobile.screenshot import android.net.Uri import android.util.Base64 import android.provider.MediaStore import com.facebook.react.bridge.ReactApplicationContext import com.facebook.react.bridge.ReactContextBaseJavaModule import com.facebook.react.bridge.ReactMethod import com.facebook.react.bridge.WritableNativeMap import com.facebook.react.modules.core.DeviceEventManagerModule import com.beancount.mobile.accessibility.ReactContextHolder /** * 截图监控 RN Module(plan.md「3.13 截图自动记账通道」)。 * * 由 JS 端调用 start()/stop() 控制 ContentObserver 的注册/注销。 * 检测到截图时读取 base64 并通过 DeviceEventEmitter 发送到 JS。 * * 事件格式(WritableNativeMap)与 BillingAccessibilityService.sendScreenshotEvent 一致: * { base64: "data:image/jpeg;base64,...", packageName: "...", timestamp: Long, displayName: "..." } */ class ScreenshotModule(private val reactContext: ReactApplicationContext) : ReactContextBaseJavaModule(reactContext) { private var observer: ScreenshotObserver? = null override fun getName() = "ScreenshotMonitor" override fun invalidate() { stop() super.invalidate() } /** * 启动截图监控。 * 在主线程注册 ContentObserver,检测到截图时发送 billingScreenshot 事件。 */ @ReactMethod fun start() { if (observer != null) return // 确保 ReactContextHolder 有上下文 ReactContextHolder.context = reactContext observer = ScreenshotObserver(reactContext) { uri -> sendScreenshotEvent(uri) } observer?.register() } /** 停止截图监控。 */ @ReactMethod fun stop() { observer?.unregister() observer = null } /** 读取截图 base64 并发送到 JS(WritableNativeMap 格式,与无障碍服务一致)。 */ private fun sendScreenshotEvent(uri: Uri) { try { val resolver = reactContext.contentResolver // 读取图片为 base64 val inputStream = resolver.openInputStream(uri) ?: return val bytes = inputStream.use { it.readBytes() } inputStream.close() val base64 = "data:image/png;base64," + Base64.encodeToString(bytes, Base64.NO_WRAP) // 同时查询显示名 val projection = arrayOf(MediaStore.Images.Media.DISPLAY_NAME) var displayName = "" resolver.query(uri, projection, null, null, null)?.use { cursor -> if (cursor.moveToFirst()) { displayName = cursor.getString(0) ?: "" } } val map = WritableNativeMap() map.putString("base64", base64) map.putString("uri", uri.toString()) map.putString("packageName", "") map.putString("displayName", displayName) map.putDouble("timestamp", System.currentTimeMillis().toDouble()) reactContext .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java) .emit("billingScreenshot", map) } catch (e: Exception) { android.util.Log.e("ScreenshotModule", "发送截图事件失败: ${e.message}", e) } } }