# Auto-Reconnection Feature **Alfred mobile app now automatically reconnects when the connection is lost!** ## Overview The app now includes intelligent auto-reconnection with exponential backoff to handle network issues gracefully. When the WebSocket connection drops, the app will automatically attempt to reconnect without requiring user intervention. ## Features ### ✅ Automatic Reconnection - **Triggered on any connection failure:** - Network interruptions - Server disconnections - "Software caused connection abort" errors - WiFi/cellular switching - Proxy restarts ### 📈 Exponential Backoff - **Smart retry timing:** - Attempt 1: 1 second delay - Attempt 2: 2 seconds delay - Attempt 3: 4 seconds delay - Attempt 4: 8 seconds delay - Attempt 5: 16 seconds delay - Attempts 6+: 30 seconds delay (max) ### 🔢 Max Retry Limit - **10 reconnection attempts** before giving up - Prevents infinite retry loops - Battery-friendly ### 📊 UI Feedback - **Connection status shows:** - "Connecting..." - Initial connection - "Connected ✅" - Successfully connected - "Disconnected" - Connection lost - "Reconnecting... (attempt X, Ys)" - Auto-reconnecting - "Error: Connection lost - max retries exceeded" - Gave up after 10 attempts ## How It Works ``` Connection Loss Detected │ ▼ ┌────────────────────┐ │ shouldReconnect? │ │ (enabled) │ └────────┬───────────┘ │ yes ▼ ┌────────────────────┐ │ Check attempt # │ │ < 10 attempts? │ └────────┬───────────┘ │ yes ▼ ┌────────────────────┐ │ Calculate delay │ │ (exponential) │ └────────┬───────────┘ │ ▼ ┌────────────────────┐ │ Schedule retry │ │ (Handler) │ └────────┬───────────┘ │ ▼ ┌────────────────────┐ │ Wait delay... │ └────────┬───────────┘ │ ▼ ┌────────────────────┐ │ Attempt connect │ └────────┬───────────┘ │ ├─ Success → Reset counter, connected ✅ │ └─ Failure → Loop back to retry ``` ## Code Details ### GatewayClient.kt Changes **New State Variables:** ```kotlin private var shouldReconnect = true private var reconnectAttempts = 0 private val maxReconnectAttempts = 10 private val baseReconnectDelayMs = 1000L private val maxReconnectDelayMs = 30000L private var reconnectHandler: Handler? = null ``` **Reconnection Logic:** ```kotlin private fun scheduleReconnect() { if (reconnectAttempts >= maxReconnectAttempts) { Log.e(TAG, "Max reconnection attempts reached") listener.onError("Connection lost - max retries exceeded") shouldReconnect = false return } val delay = minOf( baseReconnectDelayMs * (1 shl reconnectAttempts), maxReconnectDelayMs ) reconnectAttempts++ listener.onReconnecting(reconnectAttempts, delay) reconnectHandler?.postDelayed({ if (shouldReconnect && !isConnected) { connect() } }, delay) } ``` **Reset on Success:** ```kotlin private fun resetReconnectionState() { reconnectAttempts = 0 reconnectHandler?.removeCallbacksAndMessages(null) reconnectHandler = null } ``` ### GatewayListener Interface **New Callback:** ```kotlin interface GatewayListener { // ... existing callbacks ... fun onReconnecting(attempt: Int, delayMs: Long) } ``` ### MainScreen.kt Implementation ```kotlin override fun onReconnecting(attempt: Int, delayMs: Long) { val delaySec = delayMs / 1000 connectionStatus = "Reconnecting... (attempt $attempt, ${delaySec}s)" Log.d("MainScreen", "Reconnecting: attempt $attempt, delay ${delayMs}ms") } ``` ## Testing ### Test Scenarios **1. Restart OAuth Proxy** ```bash # Kill and restart the proxy pkill -f "node server.js" cd ~/.openclaw/workspace/alfred-proxy node server.js > /tmp/alfred-proxy.log 2>&1 & ``` Expected behavior: - App shows "Disconnected" - After ~1 second: "Reconnecting... (attempt 1, 1s)" - After ~3 seconds: "Connected ✅" **2. Network Switch (WiFi ↔ Cellular)** - Turn off WiFi on mobile device - App automatically reconnects via cellular - Or vice versa **3. Temporary Network Loss** - Enable airplane mode for 5 seconds - Disable airplane mode - App reconnects automatically **4. Extended Network Outage** - Enable airplane mode for 5 minutes - App will attempt 10 reconnections over ~2 minutes - Shows "Connection lost - max retries exceeded" - Disable airplane mode - **Restart app** to reconnect ### Manual Testing 1. **Connect the app** - Status should show "Connected ✅" 2. **Kill the proxy** ```bash pkill -f "node server.js" ``` - Watch the status bar for reconnection attempts 3. **Restart the proxy** ```bash cd ~/.openclaw/workspace/alfred-proxy node server.js > /tmp/alfred-proxy.log 2>&1 & ``` - App should reconnect within a few seconds 4. **Check logs** ```bash adb logcat GatewayClient:D MainScreen:D *:S ``` - Should see reconnection attempts and backoff delays ## Edge Cases ### When Reconnection is Disabled **User-initiated disconnect:** - Logging out - App closure - Manual disconnect The `shouldReconnect` flag is set to `false` in these cases to prevent unwanted reconnection attempts. ### Max Retries Reached After 10 failed attempts: - `shouldReconnect` is set to `false` - Error message displayed - User must restart the app to reconnect **Why 10 attempts?** - Total time: ~2 minutes of reconnection attempts - Balance between persistence and battery life - Prevents infinite loops ### State Cleanup **On successful connection:** - `reconnectAttempts` reset to 0 - Reconnection handler cleared - Fresh state for next potential disconnection **On manual disconnect:** - `shouldReconnect` set to false - All handlers cancelled - Clean shutdown ## Benefits ### User Experience - ✅ Seamless reconnection after temporary network issues - ✅ No manual intervention required - ✅ Clear status feedback - ✅ Works across network type changes (WiFi ↔ Cellular) ### Battery Efficiency - ✅ Exponential backoff reduces connection attempts over time - ✅ Max retry limit prevents infinite loops - ✅ Handler cleanup prevents memory leaks ### Reliability - ✅ Handles "Software caused connection abort" errors - ✅ Resilient to proxy/server restarts - ✅ Graceful degradation (max retries) ## Monitoring ### Logs to Watch **GatewayClient:** ``` D/GatewayClient: WebSocket failure D/GatewayClient: Scheduling reconnect attempt 1 in 1000ms D/GatewayClient: Attempting reconnection (attempt 1) D/GatewayClient: Connect successful! ``` **MainScreen:** ``` D/MainScreen: Reconnecting: attempt 1, delay 1000ms D/MainScreen: Reconnecting: attempt 2, delay 2000ms ``` ### Status Bar Messages | Status | Meaning | |--------|---------| | Connecting... | Initial connection in progress | | Connected ✅ | Successfully connected | | Disconnected | Connection lost | | Reconnecting... (attempt 1, 1s) | First reconnection attempt | | Reconnecting... (attempt 5, 16s) | Fifth attempt with backoff | | Error: Connection lost - max retries exceeded | Gave up after 10 attempts | ## Configuration ### Adjustable Parameters Edit `GatewayClient.kt` to customize: ```kotlin // Maximum reconnection attempts private val maxReconnectAttempts = 10 // Change to 5, 20, etc. // Initial retry delay private val baseReconnectDelayMs = 1000L // Change to 2000L (2s), etc. // Maximum retry delay private val maxReconnectDelayMs = 30000L // Change to 60000L (60s), etc. ``` ### Exponential Backoff Formula ```kotlin delay = min( baseReconnectDelayMs * (2 ^ reconnectAttempts), maxReconnectDelayMs ) ``` **Example with current settings:** - Attempt 1: min(1000 * 2^0, 30000) = 1000ms - Attempt 2: min(1000 * 2^1, 30000) = 2000ms - Attempt 3: min(1000 * 2^2, 30000) = 4000ms - Attempt 4: min(1000 * 2^3, 30000) = 8000ms - Attempt 5: min(1000 * 2^4, 30000) = 16000ms - Attempt 6: min(1000 * 2^5, 30000) = 30000ms (capped) - Attempts 7-10: 30000ms (capped) ## Troubleshooting ### App not reconnecting **Check logs:** ```bash adb logcat | grep -E "(GatewayClient|MainScreen)" ``` **Possible causes:** - Max retries exceeded (restart app) - `shouldReconnect` set to false - Handler not scheduled ### Reconnecting too slowly **Reduce backoff delays:** ```kotlin private val baseReconnectDelayMs = 500L // 500ms instead of 1s private val maxReconnectDelayMs = 10000L // 10s instead of 30s ``` ### Too many reconnection attempts **Reduce max attempts:** ```kotlin private val maxReconnectAttempts = 5 // 5 instead of 10 ``` ### Memory leaks **Ensure cleanup:** - Check `disconnect()` calls `resetReconnectionState()` - Verify `reconnectHandler?.removeCallbacksAndMessages(null)` - Look for uncancelled handlers in logs ## Future Enhancements Potential improvements: - [ ] **Jitter** - Add randomness to backoff to prevent thundering herd - [ ] **Network detection** - Skip retries when network is unavailable - [ ] **Manual retry button** - User can trigger reconnection after max retries - [ ] **Smarter reset** - Reset counter after stable connection (e.g., 5 minutes) - [ ] **Configurable via settings** - User-adjustable retry behavior - [ ] **Background reconnection** - Continue attempting when app is backgrounded ## Version 1.0.0 - Initial auto-reconnection implementation (February 2026)