Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 4 additions & 27 deletions .auto-claude-security.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": [
Expand All @@ -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"
}
14 changes: 7 additions & 7 deletions .auto-claude-status
Original file line number Diff line number Diff line change
@@ -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
},
Expand All @@ -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"
}
16 changes: 8 additions & 8 deletions .claude_settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -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/**)",
Expand Down
9 changes: 9 additions & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,15 @@
</intent-filter>
</receiver>

<receiver
android:name=".NetworkChangeReceiver"
android:enabled="true"
android:exported="false">
<intent-filter>
<action android:name="android.net.conn.CONNECTIVITY_ACTION" />
</intent-filter>
Comment on lines +62 to +68
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Use a manifest CONNECTIVITY_ACTION receiver won’t fire on API 24+

Because the app targets SDK 34, a manifest-declared receiver for android.net.conn.CONNECTIVITY_ACTION will not receive broadcasts on Android 7.0+; those implicit broadcasts are delivered only to context-registered receivers. That means NetworkChangeReceiver won’t be invoked in normal production scenarios, so auto‑reconnect never triggers. Consider switching to ConnectivityManager.registerNetworkCallback (or dynamically registering a receiver) instead of relying on the manifest entry.

Useful? React with 👍 / 👎.

</receiver>

<!-- Shizuku Provider for non-root privileged access -->
<provider
android:name="rikka.shizuku.ShizukuProvider"
Expand Down
37 changes: 37 additions & 0 deletions app/src/main/java/com/phenix/wirelessadb/AdbService.kt
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,17 @@ class AdbService : Service() {

private val serviceScope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
private var relayServer: AdbRelayServer? = null
private var currentIp: String = "Unknown"
private var currentPort: Int = 5555
private var currentRelayEnabled: Boolean = false

companion object {
private const val CHANNEL_ID = "adb_service_channel"
private const val NOTIFICATION_ID = 1001
const val ACTION_PENDING_AUTH = "com.phenix.wirelessadb.PENDING_AUTH"
const val ACTION_APPROVE_DEVICE = "com.phenix.wirelessadb.APPROVE_DEVICE"
const val ACTION_DENY_DEVICE = "com.phenix.wirelessadb.DENY_DEVICE"
const val ACTION_NETWORK_CHANGED = "com.phenix.wirelessadb.NETWORK_CHANGED"
const val EXTRA_CLIENT_IP = "client_ip"

fun start(context: Context, ip: String, port: Int, relayEnabled: Boolean = false) {
Expand Down Expand Up @@ -64,6 +68,13 @@ class AdbService : Service() {
}
context.startService(intent)
}

fun onNetworkChanged(context: Context) {
val intent = Intent(context, AdbService::class.java).apply {
action = ACTION_NETWORK_CHANGED
}
context.startService(intent)
}
}

override fun onCreate() {
Expand All @@ -87,12 +98,21 @@ class AdbService : Service() {
}
return START_STICKY
}
ACTION_NETWORK_CHANGED -> {
handleNetworkChange()
return START_STICKY
}
Comment on lines 98 to +104
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Starting the service from a broadcast can crash in background

The network-change path calls AdbService.onNetworkChanged from a manifest receiver, which uses context.startService(...). On Android 8.0+, starting a background service from a broadcast receiver throws IllegalStateException unless it’s a foreground service; additionally, the ACTION_NETWORK_CHANGED branch never calls startForeground, so a newly started service can’t promote itself within the 5‑second window. If the service isn’t already running (e.g., killed while ADB remains enabled), the auto‑reconnect flow will crash instead of updating the notification.

Useful? React with 👍 / 👎.

}

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

Expand Down Expand Up @@ -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
}
Expand Down
188 changes: 188 additions & 0 deletions app/src/main/java/com/phenix/wirelessadb/NetworkChangeReceiver.kt
Original file line number Diff line number Diff line change
@@ -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 == "<unknown 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)
}
}
}
}
Loading
Loading