diff --git a/.auto-claude-security.json b/.auto-claude-security.json
index f740b91..a6f14ec 100644
--- a/.auto-claude-security.json
+++ b/.auto-claude-security.json
@@ -128,37 +128,15 @@
"zsh"
],
"stack_commands": [
- "ant",
"gradle",
"gradlew",
- "ipython",
- "jar",
- "java",
- "javac",
- "jupyter",
"kotlin",
- "kotlinc",
- "maven",
- "mvn",
- "node",
- "notebook",
- "npm",
- "npx",
- "pdb",
- "pip",
- "pip3",
- "pipx",
- "pudb",
- "python",
- "python3"
+ "kotlinc"
],
"script_commands": [],
"custom_commands": [],
"detected_stack": {
"languages": [
- "python",
- "javascript",
- "java",
"kotlin"
],
"package_managers": [
@@ -178,8 +156,7 @@
"cargo_aliases": [],
"shell_scripts": []
},
- "project_dir": "D:\\AI\\Android\\APK\\Android-Apps\\apps\\wirelessadb",
- "created_at": "2026-02-05T12:56:49.824459",
- "project_hash": "0d6be561318e22002fe7a184fb411324",
- "inherited_from": "D:\\AI\\Android\\APK\\Android-Apps\\apps\\wirelessadb"
+ "project_dir": "D:\\AI\\Android\\APK\\Android-Apps\\apps\\wirelessadb\\.auto-claude\\worktrees\\tasks\\007-auto-reconnect-on-network-change",
+ "created_at": "2026-02-07T04:33:20.891367",
+ "project_hash": "952e2679948002cb8b971be36b6a9f99"
}
\ No newline at end of file
diff --git a/.auto-claude-status b/.auto-claude-status
index db8b938..ba41619 100644
--- a/.auto-claude-status
+++ b/.auto-claude-status
@@ -1,15 +1,15 @@
{
"active": true,
- "spec": "002-quick-settings-tile",
+ "spec": "007-auto-reconnect-on-network-change",
"state": "building",
"subtasks": {
- "completed": 9,
- "total": 10,
+ "completed": 5,
+ "total": 8,
"in_progress": 1,
"failed": 0
},
"phase": {
- "current": "Integration Testing",
+ "current": "Trusted Networks (Optional)",
"id": null,
"total": 2
},
@@ -18,8 +18,8 @@
"max": 1
},
"session": {
- "number": 12,
- "started_at": "2026-02-07T01:52:02.329153"
+ "number": 7,
+ "started_at": "2026-02-07T04:33:05.351503"
},
- "last_update": "2026-02-07T02:19:18.722326"
+ "last_update": "2026-02-07T05:13:04.677678"
}
\ No newline at end of file
diff --git a/.claude_settings.json b/.claude_settings.json
index 40e7866..b4bd7ff 100644
--- a/.claude_settings.json
+++ b/.claude_settings.json
@@ -11,14 +11,14 @@
"Edit(./**)",
"Glob(./**)",
"Grep(./**)",
- "Read(D:\\AI\\Android\\APK\\Android-Apps\\apps\\wirelessadb\\.auto-claude\\worktrees\\tasks\\002-quick-settings-tile/**)",
- "Write(D:\\AI\\Android\\APK\\Android-Apps\\apps\\wirelessadb\\.auto-claude\\worktrees\\tasks\\002-quick-settings-tile/**)",
- "Edit(D:\\AI\\Android\\APK\\Android-Apps\\apps\\wirelessadb\\.auto-claude\\worktrees\\tasks\\002-quick-settings-tile/**)",
- "Glob(D:\\AI\\Android\\APK\\Android-Apps\\apps\\wirelessadb\\.auto-claude\\worktrees\\tasks\\002-quick-settings-tile/**)",
- "Grep(D:\\AI\\Android\\APK\\Android-Apps\\apps\\wirelessadb\\.auto-claude\\worktrees\\tasks\\002-quick-settings-tile/**)",
- "Read(D:\\AI\\Android\\APK\\Android-Apps\\apps\\wirelessadb\\.auto-claude\\worktrees\\tasks\\002-quick-settings-tile\\.auto-claude\\specs\\002-quick-settings-tile/**)",
- "Write(D:\\AI\\Android\\APK\\Android-Apps\\apps\\wirelessadb\\.auto-claude\\worktrees\\tasks\\002-quick-settings-tile\\.auto-claude\\specs\\002-quick-settings-tile/**)",
- "Edit(D:\\AI\\Android\\APK\\Android-Apps\\apps\\wirelessadb\\.auto-claude\\worktrees\\tasks\\002-quick-settings-tile\\.auto-claude\\specs\\002-quick-settings-tile/**)",
+ "Read(D:\\AI\\Android\\APK\\Android-Apps\\apps\\wirelessadb\\.auto-claude\\worktrees\\tasks\\007-auto-reconnect-on-network-change/**)",
+ "Write(D:\\AI\\Android\\APK\\Android-Apps\\apps\\wirelessadb\\.auto-claude\\worktrees\\tasks\\007-auto-reconnect-on-network-change/**)",
+ "Edit(D:\\AI\\Android\\APK\\Android-Apps\\apps\\wirelessadb\\.auto-claude\\worktrees\\tasks\\007-auto-reconnect-on-network-change/**)",
+ "Glob(D:\\AI\\Android\\APK\\Android-Apps\\apps\\wirelessadb\\.auto-claude\\worktrees\\tasks\\007-auto-reconnect-on-network-change/**)",
+ "Grep(D:\\AI\\Android\\APK\\Android-Apps\\apps\\wirelessadb\\.auto-claude\\worktrees\\tasks\\007-auto-reconnect-on-network-change/**)",
+ "Read(D:\\AI\\Android\\APK\\Android-Apps\\apps\\wirelessadb\\.auto-claude\\worktrees\\tasks\\007-auto-reconnect-on-network-change\\.auto-claude\\specs\\007-auto-reconnect-on-network-change/**)",
+ "Write(D:\\AI\\Android\\APK\\Android-Apps\\apps\\wirelessadb\\.auto-claude\\worktrees\\tasks\\007-auto-reconnect-on-network-change\\.auto-claude\\specs\\007-auto-reconnect-on-network-change/**)",
+ "Edit(D:\\AI\\Android\\APK\\Android-Apps\\apps\\wirelessadb\\.auto-claude\\worktrees\\tasks\\007-auto-reconnect-on-network-change\\.auto-claude\\specs\\007-auto-reconnect-on-network-change/**)",
"Read(D:\\AI\\Android\\APK\\Android-Apps\\apps\\wirelessadb\\.auto-claude/**)",
"Write(D:\\AI\\Android\\APK\\Android-Apps\\apps\\wirelessadb\\.auto-claude/**)",
"Edit(D:\\AI\\Android\\APK\\Android-Apps\\apps\\wirelessadb\\.auto-claude/**)",
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index c8a3740..fd2135a 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -59,6 +59,15 @@
+
+
+
+
+
+
{
+ handleNetworkChange()
+ return START_STICKY
+ }
}
val ip = intent?.getStringExtra("ip") ?: "Unknown"
val port = intent?.getIntExtra("port", 5555) ?: 5555
val relayEnabled = intent?.getBooleanExtra("relay_enabled", false) ?: false
+ // Update current state
+ currentIp = ip
+ currentPort = port
+ currentRelayEnabled = relayEnabled
+
val tailscaleIp = TailscaleHelper.getTailscaleIp()
startForeground(NOTIFICATION_ID, createNotification(ip, port, tailscaleIp, relayEnabled))
@@ -137,6 +157,23 @@ class AdbService : Service() {
LocalBroadcastManager.getInstance(this).sendBroadcast(intent)
}
+ private fun handleNetworkChange() {
+ serviceScope.launch {
+ val status = AdbManager.getStatus(this@AdbService)
+ if (status.enabled && status.ip != null) {
+ currentIp = status.ip
+ updateNotification()
+ }
+ }
+ }
+
+ private fun updateNotification() {
+ val tailscaleIp = TailscaleHelper.getTailscaleIp()
+ val notification = createNotification(currentIp, currentPort, tailscaleIp, currentRelayEnabled)
+ val notificationManager = getSystemService(NotificationManager::class.java)
+ notificationManager.notify(NOTIFICATION_ID, notification)
+ }
+
private fun updateNotificationWithActiveConnection() {
// Could update notification to show active connections
}
diff --git a/app/src/main/java/com/phenix/wirelessadb/NetworkChangeReceiver.kt b/app/src/main/java/com/phenix/wirelessadb/NetworkChangeReceiver.kt
new file mode 100644
index 0000000..61fa86d
--- /dev/null
+++ b/app/src/main/java/com/phenix/wirelessadb/NetworkChangeReceiver.kt
@@ -0,0 +1,188 @@
+package com.phenix.wirelessadb
+
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import android.net.ConnectivityManager
+import android.net.Network
+import android.net.NetworkCapabilities
+import android.net.NetworkRequest
+import android.net.wifi.WifiManager
+import android.util.Log
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
+
+/**
+ * Monitors network connectivity changes to automatically re-enable wireless ADB
+ * when the device connects to a new WiFi network or when network changes occur.
+ */
+class NetworkChangeReceiver : BroadcastReceiver() {
+
+ override fun onReceive(context: Context, intent: Intent) {
+ Log.d(TAG, "onReceive: action=${intent.action}")
+
+ when (intent.action) {
+ ConnectivityManager.CONNECTIVITY_ACTION,
+ "android.net.wifi.STATE_CHANGE",
+ "android.net.wifi.WIFI_STATE_CHANGED" -> {
+ handleNetworkChange(context)
+ }
+ }
+ }
+
+ private fun handleNetworkChange(context: Context) {
+ Log.d(TAG, "handleNetworkChange: Network state changed")
+
+ // Check if device is connected to WiFi
+ val connectivityManager = context.getSystemService(Context.CONNECTIVITY_SERVICE) as? ConnectivityManager
+ if (connectivityManager == null) {
+ Log.e(TAG, "handleNetworkChange: ConnectivityManager not available")
+ return
+ }
+
+ val network = connectivityManager.activeNetwork
+ if (network == null) {
+ Log.d(TAG, "handleNetworkChange: No active network")
+ return
+ }
+
+ val capabilities = connectivityManager.getNetworkCapabilities(network)
+ if (capabilities == null) {
+ Log.d(TAG, "handleNetworkChange: No network capabilities")
+ return
+ }
+
+ val isWifi = capabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)
+ Log.d(TAG, "handleNetworkChange: isWifi=$isWifi")
+
+ if (isWifi) {
+ Log.d(TAG, "handleNetworkChange: WiFi connected, checking auto-reconnect")
+
+ // Check if auto-reconnect is enabled
+ if (!PrefsManager.isAutoReconnectEnabled(context)) {
+ Log.d(TAG, "handleNetworkChange: Auto-reconnect is disabled")
+ return
+ }
+
+ // Check trusted networks if configured
+ val trustedNetworks = PrefsManager.getTrustedNetworks(context)
+ if (trustedNetworks.isNotEmpty()) {
+ val currentSsid = getCurrentWifiSsid(context)
+ Log.d(TAG, "handleNetworkChange: Current SSID=$currentSsid, trusted networks=$trustedNetworks")
+
+ if (currentSsid == null) {
+ Log.d(TAG, "handleNetworkChange: Cannot determine current SSID, skipping auto-reconnect")
+ return
+ }
+
+ if (!PrefsManager.isTrustedNetwork(context, currentSsid)) {
+ Log.d(TAG, "handleNetworkChange: Network '$currentSsid' is not trusted, skipping auto-reconnect")
+ return
+ }
+
+ Log.d(TAG, "handleNetworkChange: Network '$currentSsid' is trusted, proceeding with auto-reconnect")
+ } else {
+ Log.d(TAG, "handleNetworkChange: No trusted networks configured, allowing all networks")
+ }
+
+ val port = PrefsManager.getPort(context)
+
+ // Launch coroutine to check status and re-enable if needed
+ CoroutineScope(Dispatchers.IO).launch {
+ val status = AdbManager.getStatus(context)
+ Log.d(TAG, "handleNetworkChange: Current ADB status - enabled=${status.enabled}, port=${status.port}")
+
+ // Only re-enable if ADB was previously enabled
+ if (status.enabled) {
+ Log.d(TAG, "handleNetworkChange: Re-enabling ADB on port $port")
+ AdbManager.enable(port)
+
+ // Notify service to update notification with new IP
+ AdbService.onNetworkChanged(context)
+ } else {
+ Log.d(TAG, "handleNetworkChange: ADB was not previously enabled, skipping auto-reconnect")
+ }
+ }
+ }
+ }
+
+ /**
+ * Get the current WiFi SSID, removing quotes that Android adds.
+ * Returns null if not connected to WiFi or SSID cannot be determined.
+ */
+ private fun getCurrentWifiSsid(context: Context): String? {
+ return try {
+ val wifiManager = context.applicationContext.getSystemService(Context.WIFI_SERVICE) as? WifiManager
+ if (wifiManager == null) {
+ Log.e(TAG, "getCurrentWifiSsid: WifiManager not available")
+ return null
+ }
+
+ val connectionInfo = wifiManager.connectionInfo
+ if (connectionInfo == null) {
+ Log.d(TAG, "getCurrentWifiSsid: No connection info")
+ return null
+ }
+
+ val ssid = connectionInfo.ssid
+ if (ssid == null || ssid == "" || ssid.isEmpty()) {
+ Log.d(TAG, "getCurrentWifiSsid: SSID unknown or empty")
+ return null
+ }
+
+ // Remove quotes that Android adds around SSID
+ val cleanSsid = ssid.trim('"')
+ Log.d(TAG, "getCurrentWifiSsid: raw='$ssid', clean='$cleanSsid'")
+ cleanSsid
+ } catch (e: Exception) {
+ Log.e(TAG, "getCurrentWifiSsid: ERROR ${e.message}")
+ null
+ }
+ }
+
+ companion object {
+ private const val TAG = "NetworkChangeReceiver"
+
+ /**
+ * Register a NetworkCallback for more granular network monitoring.
+ * This is the modern approach compared to using broadcast receivers.
+ */
+ fun registerNetworkCallback(context: Context, callback: ConnectivityManager.NetworkCallback) {
+ val connectivityManager = context.getSystemService(Context.CONNECTIVITY_SERVICE) as? ConnectivityManager
+ if (connectivityManager == null) {
+ Log.e(TAG, "registerNetworkCallback: ConnectivityManager not available")
+ return
+ }
+
+ val request = NetworkRequest.Builder()
+ .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
+ .build()
+
+ try {
+ connectivityManager.registerNetworkCallback(request, callback)
+ Log.d(TAG, "registerNetworkCallback: NetworkCallback registered")
+ } catch (e: Exception) {
+ Log.e(TAG, "registerNetworkCallback: Failed to register callback", e)
+ }
+ }
+
+ /**
+ * Unregister a previously registered NetworkCallback.
+ */
+ fun unregisterNetworkCallback(context: Context, callback: ConnectivityManager.NetworkCallback) {
+ val connectivityManager = context.getSystemService(Context.CONNECTIVITY_SERVICE) as? ConnectivityManager
+ if (connectivityManager == null) {
+ Log.e(TAG, "unregisterNetworkCallback: ConnectivityManager not available")
+ return
+ }
+
+ try {
+ connectivityManager.unregisterNetworkCallback(callback)
+ Log.d(TAG, "unregisterNetworkCallback: NetworkCallback unregistered")
+ } catch (e: Exception) {
+ Log.e(TAG, "unregisterNetworkCallback: Failed to unregister callback", e)
+ }
+ }
+ }
+}
diff --git a/app/src/main/java/com/phenix/wirelessadb/PrefsManager.kt b/app/src/main/java/com/phenix/wirelessadb/PrefsManager.kt
index f238dd7..47a66f4 100644
--- a/app/src/main/java/com/phenix/wirelessadb/PrefsManager.kt
+++ b/app/src/main/java/com/phenix/wirelessadb/PrefsManager.kt
@@ -36,6 +36,16 @@ object PrefsManager {
private const val KEY_WARPGATE_TARGET = "warpgate_target"
private const val KEY_WARPGATE_LOCAL_PORT = "warpgate_local_port"
+ // Auto-reconnect settings
+ private const val KEY_AUTO_RECONNECT_ENABLED = "auto_reconnect_enabled"
+ private const val KEY_AUTO_RECONNECT_DELAY = "auto_reconnect_delay"
+ private const val KEY_AUTO_RECONNECT_MAX_RETRIES = "auto_reconnect_max_retries"
+ private const val DEFAULT_AUTO_RECONNECT_DELAY = 5000 // 5 seconds
+ private const val DEFAULT_AUTO_RECONNECT_MAX_RETRIES = 3
+
+ // Trusted networks settings
+ private const val KEY_TRUSTED_NETWORKS = "trusted_networks"
+
private fun getPrefs(context: Context): SharedPreferences {
return context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
}
@@ -149,4 +159,54 @@ object PrefsManager {
fun setAccentColor(context: Context, color: AccentColor) {
getPrefs(context).edit().putInt(KEY_ACCENT_COLOR, color.ordinal).apply()
}
+
+ // Auto-reconnect Configuration
+ fun isAutoReconnectEnabled(context: Context): Boolean {
+ return getPrefs(context).getBoolean(KEY_AUTO_RECONNECT_ENABLED, true)
+ }
+
+ fun setAutoReconnectEnabled(context: Context, enabled: Boolean) {
+ getPrefs(context).edit().putBoolean(KEY_AUTO_RECONNECT_ENABLED, enabled).apply()
+ }
+
+ fun getAutoReconnectDelay(context: Context): Int {
+ return getPrefs(context).getInt(KEY_AUTO_RECONNECT_DELAY, DEFAULT_AUTO_RECONNECT_DELAY)
+ }
+
+ fun setAutoReconnectDelay(context: Context, delay: Int) {
+ getPrefs(context).edit().putInt(KEY_AUTO_RECONNECT_DELAY, delay).apply()
+ }
+
+ fun getAutoReconnectMaxRetries(context: Context): Int {
+ return getPrefs(context).getInt(KEY_AUTO_RECONNECT_MAX_RETRIES, DEFAULT_AUTO_RECONNECT_MAX_RETRIES)
+ }
+
+ fun setAutoReconnectMaxRetries(context: Context, retries: Int) {
+ getPrefs(context).edit().putInt(KEY_AUTO_RECONNECT_MAX_RETRIES, retries).apply()
+ }
+
+ // Trusted Networks
+ fun getTrustedNetworks(context: Context): Set {
+ return getPrefs(context).getStringSet(KEY_TRUSTED_NETWORKS, emptySet()) ?: emptySet()
+ }
+
+ fun setTrustedNetworks(context: Context, networks: Set) {
+ getPrefs(context).edit().putStringSet(KEY_TRUSTED_NETWORKS, networks).apply()
+ }
+
+ fun addTrustedNetwork(context: Context, ssid: String) {
+ val networks = getTrustedNetworks(context).toMutableSet()
+ networks.add(ssid)
+ setTrustedNetworks(context, networks)
+ }
+
+ fun removeTrustedNetwork(context: Context, ssid: String) {
+ val networks = getTrustedNetworks(context).toMutableSet()
+ networks.remove(ssid)
+ setTrustedNetworks(context, networks)
+ }
+
+ fun isTrustedNetwork(context: Context, ssid: String): Boolean {
+ return getTrustedNetworks(context).contains(ssid)
+ }
}