Files
alfred-mobile/RECONNECTION.md
jknapp 6d4ae2e5c3 Initial commit: Alfred Mobile - AI Assistant Android App
- OAuth authentication via Authentik
- WebSocket connection to OpenClaw gateway
- Configurable gateway URL with first-run setup
- User preferences sync across devices
- Multi-user support with custom assistant names
- ElevenLabs TTS integration (local + remote)
- FCM push notifications for alarms
- Voice input via Google Speech API
- No hardcoded secrets or internal IPs in tracked files
2026-02-09 11:12:51 -08:00

379 lines
9.8 KiB
Markdown

# 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)