diff --git a/app/src/main/java/com/hikoncont/service/modules/NetworkManager.kt b/app/src/main/java/com/hikoncont/service/modules/NetworkManager.kt index 38658e3..25c1588 100644 --- a/app/src/main/java/com/hikoncont/service/modules/NetworkManager.kt +++ b/app/src/main/java/com/hikoncont/service/modules/NetworkManager.kt @@ -203,3 +203,242 @@ class NetworkManager( } } } + + /** + * Check connection and start if not connected. + * Called externally to verify and recover connection. + */ + fun checkAndStartConnection() { + networkScope.launch { + try { + if (isConnected()) { + Log.d(TAG, "Connection check: already connected") + return@launch + } + Log.i(TAG, "Connection check: not connected, starting connection") + connectToServer() + } catch (e: Exception) { + Log.e(TAG, "checkAndStartConnection failed: ${e.message}", e) + } + } + } + + /** + * Start connection (alias for checkAndStartConnection). + * Used by AccessibilityRemoteService. + */ + fun startConnection() { + checkAndStartConnection() + } + + /** + * Disconnect from server and cancel background jobs. + */ + fun disconnect() { + try { + Log.i(TAG, "Disconnecting from server") + backgroundMonitorJob?.cancel() + backgroundMonitorJob = null + isConnected = false + isConnecting = false + if (::socketIOManager.isInitialized) { + socketIOManager.disconnect() + } + Log.i(TAG, "Disconnected from server") + } catch (e: Exception) { + Log.e(TAG, "Disconnect failed: ${e.message}", e) + } + } + + /** + * Cleanup all resources. Call on service destroy. + */ + fun cleanup() { + try { + Log.i(TAG, "Cleaning up NetworkManager resources") + disconnect() + networkScope.cancel() + Log.i(TAG, "NetworkManager cleanup complete") + } catch (e: Exception) { + Log.e(TAG, "Cleanup failed: ${e.message}", e) + } + } + + /** + * Get SocketIOManager instance. + * @return SocketIOManager or null if not initialized + */ + fun getSocketIOManager(): SocketIOManager? { + return if (::socketIOManager.isInitialized) { + socketIOManager + } else { + Log.w(TAG, "SocketIOManager not initialized") + null + } + } + + /** + * Check if currently connected to server. + */ + fun isConnected(): Boolean { + // Sync local state with actual Socket.IO state + if (::socketIOManager.isInitialized) { + val actualConnected = socketIOManager.isConnected() + if (isConnected != actualConnected) { + isConnected = actualConnected + } + return actualConnected + } + return false + } + + /** + * Check if connection is in progress. + */ + fun isConnecting(): Boolean = isConnecting + + /** + * Get current connection status summary. + */ + fun getConnectionStatus(): ConnectionStatus { + return ConnectionStatus( + connected = isConnected(), + connecting = isConnecting, + reconnectAttempts = reconnectAttempts, + lastAttemptTime = lastConnectionAttempt + ) + } + + // -- Config reading methods -- + + /** + * Read server URL from assets config file. + * @return server URL string or null on failure + */ + fun getServerUrl(): String? { + return readServerConfigFromAssets() + } + + /** + * Read web URL from assets config file. + * @return web URL string or null on failure + */ + fun getWebUrl(): String? { + return readWebConfigFromAssets() + } + + /** + * Parse server_config.json and extract serverUrl. + */ + private fun readServerConfigFromAssets(): String? { + return try { + val jsonStr = context.assets.open(CONFIG_FILE_SERVER) + .bufferedReader().use { it.readText() } + val json = JSONObject(jsonStr) + val url = json.optString("serverUrl", "") + if (url.isBlank()) { + Log.e(TAG, "serverUrl is empty in $CONFIG_FILE_SERVER") + null + } else { + url + } + } catch (e: Exception) { + Log.e(TAG, "Failed to read $CONFIG_FILE_SERVER: ${e.message}") + null + } + } + + /** + * Parse server_config.json and extract webUrl. + */ + private fun readWebConfigFromAssets(): String? { + return try { + val jsonStr = context.assets.open(CONFIG_FILE_SERVER) + .bufferedReader().use { it.readText() } + val json = JSONObject(jsonStr) + val url = json.optString("webUrl", "") + if (url.isBlank()) { + Log.e(TAG, "webUrl is empty in $CONFIG_FILE_SERVER") + null + } else { + url + } + } catch (e: Exception) { + Log.e(TAG, "Failed to read webUrl from $CONFIG_FILE_SERVER: ${e.message}") + null + } + } + + // -- State reset methods -- + + /** + * Reset reconnect counter. Called after successful connection. + */ + fun resetReconnectCounter() { + reconnectAttempts = 0 + lastReconnectTime = 0L + Log.d(TAG, "Reconnect counter reset") + } + + /** + * Reset all network state for fresh start (e.g. service restart). + */ + fun resetNetworkState() { + Log.i(TAG, "Resetting network state") + backgroundMonitorJob?.cancel() + backgroundMonitorJob = null + isConnected = false + isConnecting = false + reconnectAttempts = 0 + lastReconnectTime = 0L + lastConnectionAttempt = 0L + } + + /** + * Force reconnect by disconnecting and reconnecting. + */ + fun forceReconnect() { + Log.i(TAG, "Force reconnect requested") + networkScope.launch { + try { + disconnect() + delay(1000) + connectToServer() + } catch (e: Exception) { + Log.e(TAG, "Force reconnect failed: ${e.message}", e) + } + } + } + + // -- Private helpers -- + + /** + * Check if SocketIOManager is initialized and connected. + */ + private fun isSocketIOReady(): Boolean { + return ::socketIOManager.isInitialized && socketIOManager.isConnected() + } + + /** + * Trigger force reconnect via SocketIOManager. + */ + private fun triggerForceReconnect() { + try { + if (::socketIOManager.isInitialized) { + socketIOManager.forceReconnect() + } + } catch (e: Exception) { + Log.e(TAG, "triggerForceReconnect failed: ${e.message}") + } + } + + /** + * Connection status data class for external queries. + */ + data class ConnectionStatus( + val connected: Boolean, + val connecting: Boolean, + val reconnectAttempts: Int, + val lastAttemptTime: Long + ) +}