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) + } }