chore: auto-merge develop → main
Triggered by: 09fd122 fix(kiosk): rewrite autoDiscover — real-time IP feedback + CompletionService + TCP pre-check
This commit is contained in:
@@ -12,7 +12,6 @@ import android.content.SharedPreferences
|
|||||||
import android.content.pm.PackageManager
|
import android.content.pm.PackageManager
|
||||||
import android.content.res.Configuration
|
import android.content.res.Configuration
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.net.wifi.WifiManager
|
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.os.Handler
|
import android.os.Handler
|
||||||
@@ -30,11 +29,17 @@ import androidx.core.content.ContextCompat
|
|||||||
import com.google.android.material.button.MaterialButton
|
import com.google.android.material.button.MaterialButton
|
||||||
import com.google.android.material.switchmaterial.SwitchMaterial
|
import com.google.android.material.switchmaterial.SwitchMaterial
|
||||||
import java.net.HttpURLConnection
|
import java.net.HttpURLConnection
|
||||||
|
import java.net.InetSocketAddress
|
||||||
|
import java.net.NetworkInterface
|
||||||
|
import java.net.Socket
|
||||||
import java.net.URL
|
import java.net.URL
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
|
import java.util.concurrent.ExecutorCompletionService
|
||||||
import java.util.concurrent.Executors
|
import java.util.concurrent.Executors
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
import java.util.concurrent.atomic.AtomicBoolean
|
import java.util.concurrent.atomic.AtomicBoolean
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger
|
||||||
|
import java.util.concurrent.atomic.AtomicLong
|
||||||
import javax.net.ssl.HttpsURLConnection
|
import javax.net.ssl.HttpsURLConnection
|
||||||
import javax.net.ssl.SSLContext
|
import javax.net.ssl.SSLContext
|
||||||
import javax.net.ssl.TrustManager
|
import javax.net.ssl.TrustManager
|
||||||
@@ -502,7 +507,6 @@ class SetupActivity : AppCompatActivity() {
|
|||||||
|
|
||||||
// ── Auto-Discover ─────────────────────────────────────────────────────
|
// ── Auto-Discover ─────────────────────────────────────────────────────
|
||||||
|
|
||||||
@Suppress("DEPRECATION")
|
|
||||||
private fun autoDiscover() {
|
private fun autoDiscover() {
|
||||||
discoverCancelled.set(false)
|
discoverCancelled.set(false)
|
||||||
btnDiscover.isEnabled = false
|
btnDiscover.isEnabled = false
|
||||||
@@ -511,92 +515,125 @@ class SetupActivity : AppCompatActivity() {
|
|||||||
discoverStatus.text = getString(R.string.setup_discovering_detail)
|
discoverStatus.text = getString(R.string.setup_discovering_detail)
|
||||||
discoverStatus.setTextColor(0xFF94a3b8.toInt())
|
discoverStatus.setTextColor(0xFF94a3b8.toInt())
|
||||||
|
|
||||||
// Determine local subnet
|
|
||||||
val wifiMgr = applicationContext.getSystemService(Context.WIFI_SERVICE) as WifiManager
|
|
||||||
val ipInt = wifiMgr.connectionInfo.ipAddress
|
|
||||||
val subnets = mutableListOf<String>()
|
|
||||||
if (ipInt != 0) {
|
|
||||||
val a = (ipInt shr 0) and 0xFF
|
|
||||||
val b = (ipInt shr 8) and 0xFF
|
|
||||||
val c = (ipInt shr 16) and 0xFF
|
|
||||||
subnets += "$a.$b.$c"
|
|
||||||
}
|
|
||||||
// Always include common subnets as fallback
|
|
||||||
for (s in listOf("192.168.1", "192.168.0", "192.168.2", "10.0.0")) {
|
|
||||||
if (!subnets.contains(s)) subnets += s
|
|
||||||
}
|
|
||||||
|
|
||||||
val ports = listOf(80, 8080)
|
|
||||||
val paths = listOf(
|
|
||||||
"/api/index.php?action=get_settings",
|
|
||||||
"/dispensa/api/index.php?action=get_settings",
|
|
||||||
"/evershelf/api/index.php?action=get_settings"
|
|
||||||
)
|
|
||||||
val executor = Executors.newFixedThreadPool(40)
|
|
||||||
val found = AtomicBoolean(false)
|
|
||||||
|
|
||||||
Thread {
|
Thread {
|
||||||
val futures = mutableListOf<java.util.concurrent.Future<String?>>()
|
// ── 1. Detect subnets via NetworkInterface (not deprecated WifiManager) ──
|
||||||
outer@ for (subnet in subnets) {
|
val subnets = mutableListOf<String>()
|
||||||
for (i in 1..254) {
|
try {
|
||||||
if (discoverCancelled.get() || found.get()) break@outer
|
val interfaces = NetworkInterface.getNetworkInterfaces()
|
||||||
val ip = "$subnet.$i"
|
while (interfaces != null && interfaces.hasMoreElements()) {
|
||||||
for (port in ports) {
|
val intf = interfaces.nextElement()
|
||||||
if (discoverCancelled.get() || found.get()) break@outer
|
if (!intf.isUp || intf.isLoopback) continue
|
||||||
futures += executor.submit<String?> submit@{
|
for (addr in intf.interfaceAddresses) {
|
||||||
if (discoverCancelled.get() || found.get()) return@submit null
|
val ip = addr.address
|
||||||
val scheme = if (port == 443 || port == 8443) "https" else "http"
|
if (ip is java.net.Inet4Address && !ip.isLoopbackAddress) {
|
||||||
for (path in paths) {
|
val parts = ip.hostAddress?.split(".") ?: continue
|
||||||
val urlStr = "$scheme://$ip:$port$path"
|
if (parts.size == 4) subnets += "${parts[0]}.${parts[1]}.${parts[2]}"
|
||||||
try {
|
|
||||||
val conn = openConn(urlStr) ?: continue
|
|
||||||
val code = conn.responseCode
|
|
||||||
if (code in 200..399) {
|
|
||||||
val body = conn.inputStream.bufferedReader().readText()
|
|
||||||
conn.disconnect()
|
|
||||||
if (body.contains("gemini_key_set") || body.contains("\"success\"")) {
|
|
||||||
val base = urlStr.substringBefore("/api/")
|
|
||||||
return@submit "$base/"
|
|
||||||
}
|
|
||||||
} else conn.disconnect()
|
|
||||||
} catch (_: Exception) {}
|
|
||||||
}
|
|
||||||
null
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} catch (_: Exception) {}
|
||||||
|
// Append common fallback subnets (deduped)
|
||||||
|
for (s in listOf("192.168.1", "192.168.0", "192.168.2", "10.0.0", "10.0.1")) {
|
||||||
|
if (!subnets.contains(s)) subnets += s
|
||||||
}
|
}
|
||||||
|
|
||||||
// Collect results
|
val ports = listOf(80, 8080)
|
||||||
for (f in futures) {
|
val paths = listOf(
|
||||||
if (discoverCancelled.get()) break
|
"/api/index.php?action=get_settings",
|
||||||
val result = try { f.get(4, TimeUnit.SECONDS) } catch (_: Exception) { null }
|
"/dispensa/api/index.php?action=get_settings",
|
||||||
if (result != null && found.compareAndSet(false, true)) {
|
"/evershelf/api/index.php?action=get_settings",
|
||||||
runOnUiThread {
|
)
|
||||||
urlEdit.setText(result)
|
|
||||||
discoverStatus.text = "✅ ${getString(R.string.setup_server_found)}: $result"
|
// Build full task list: subnet-first ordering ensures local subnet is scanned first
|
||||||
discoverStatus.setTextColor(0xFF34d399.toInt())
|
val allTargets = mutableListOf<Pair<String, Int>>()
|
||||||
showUrlStatus("✅ ${getString(R.string.setup_server_found)}", true)
|
for (subnet in subnets.distinct()) {
|
||||||
btnDiscover.isEnabled = true
|
for (i in 1..254) {
|
||||||
btnDiscover.text = getString(R.string.setup_discover_btn)
|
for (port in ports) {
|
||||||
|
allTargets += "$subnet.$i" to port
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val executor = Executors.newFixedThreadPool(60)
|
||||||
|
val cs = ExecutorCompletionService<String?>(executor)
|
||||||
|
val found = AtomicBoolean(false)
|
||||||
|
val scanned = AtomicInteger(0)
|
||||||
|
val total = allTargets.size
|
||||||
|
val lastUiMs = AtomicLong(0L)
|
||||||
|
|
||||||
|
// ── 2. Submit all tasks ─────────────────────────────────────────────
|
||||||
|
for ((ip, port) in allTargets) {
|
||||||
|
cs.submit {
|
||||||
|
if (discoverCancelled.get() || found.get()) return@submit null
|
||||||
|
|
||||||
|
val n = scanned.incrementAndGet()
|
||||||
|
// Update status ~8 fps (every 120 ms) without hammering the UI thread
|
||||||
|
val now = System.currentTimeMillis()
|
||||||
|
if (now - lastUiMs.get() > 120) {
|
||||||
|
lastUiMs.set(now)
|
||||||
|
runOnUiThread {
|
||||||
|
discoverStatus.text = "🔍 $ip:$port ($n / $total)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TCP pre-check (600 ms) — skips unreachable hosts instantly
|
||||||
|
val reachable = try {
|
||||||
|
Socket().use { s -> s.connect(InetSocketAddress(ip, port), 600); true }
|
||||||
|
} catch (_: Exception) { false }
|
||||||
|
|
||||||
|
if (!reachable || discoverCancelled.get() || found.get()) return@submit null
|
||||||
|
|
||||||
|
// Full HTTP probe on reachable host
|
||||||
|
val scheme = if (port == 443 || port == 8443) "https" else "http"
|
||||||
|
for (path in paths) {
|
||||||
|
if (discoverCancelled.get() || found.get()) break
|
||||||
|
val urlStr = "$scheme://$ip:$port$path"
|
||||||
|
try {
|
||||||
|
val conn = openConn(urlStr) ?: continue
|
||||||
|
val code = conn.responseCode
|
||||||
|
if (code in 200..399) {
|
||||||
|
val body = conn.inputStream.bufferedReader().readText()
|
||||||
|
conn.disconnect()
|
||||||
|
if (body.contains("gemini_key_set") || body.contains("\"success\"")) {
|
||||||
|
return@submit urlStr.substringBefore("/api/") + "/"
|
||||||
|
}
|
||||||
|
} else conn.disconnect()
|
||||||
|
} catch (_: Exception) {}
|
||||||
|
}
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── 3. Collect results as they complete (not in submission order) ────
|
||||||
|
var result: String? = null
|
||||||
|
var collected = 0
|
||||||
|
while (collected < total && !discoverCancelled.get()) {
|
||||||
|
val future = cs.poll(3, TimeUnit.SECONDS) ?: break
|
||||||
|
collected++
|
||||||
|
val r = try { future.get() } catch (_: Exception) { null }
|
||||||
|
if (r != null && found.compareAndSet(false, true)) {
|
||||||
|
result = r
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
executor.shutdown()
|
executor.shutdownNow()
|
||||||
|
|
||||||
if (!found.get() && !discoverCancelled.get()) {
|
val finalResult = result
|
||||||
runOnUiThread {
|
runOnUiThread {
|
||||||
discoverStatus.text = getString(R.string.setup_discover_not_found)
|
when {
|
||||||
discoverStatus.setTextColor(0xFFf87171.toInt())
|
finalResult != null -> {
|
||||||
btnDiscover.isEnabled = true
|
urlEdit.setText(finalResult)
|
||||||
btnDiscover.text = getString(R.string.setup_discover_btn)
|
discoverStatus.text = "✅ ${getString(R.string.setup_server_found)}: $finalResult"
|
||||||
}
|
discoverStatus.setTextColor(0xFF34d399.toInt())
|
||||||
} else if (!found.get()) {
|
showUrlStatus("✅ ${getString(R.string.setup_server_found)}", true)
|
||||||
runOnUiThread {
|
}
|
||||||
btnDiscover.isEnabled = true
|
!discoverCancelled.get() -> {
|
||||||
btnDiscover.text = getString(R.string.setup_discover_btn)
|
discoverStatus.text = getString(R.string.setup_discover_not_found)
|
||||||
|
discoverStatus.setTextColor(0xFFf87171.toInt())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
btnDiscover.isEnabled = true
|
||||||
|
btnDiscover.text = getString(R.string.setup_discover_btn)
|
||||||
}
|
}
|
||||||
}.start()
|
}.start()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
<string name="setup_unreachable">Server nicht erreichbar</string>
|
<string name="setup_unreachable">Server nicht erreichbar</string>
|
||||||
<string name="setup_discover_btn">🔍 Lokales Netzwerk durchsuchen</string>
|
<string name="setup_discover_btn">🔍 Lokales Netzwerk durchsuchen</string>
|
||||||
<string name="setup_discovering">Suche läuft…</string>
|
<string name="setup_discovering">Suche läuft…</string>
|
||||||
<string name="setup_discovering_detail">Suche nach EverShelf-Servern im lokalen Netzwerk (kann bis zu 30 s dauern)…</string>
|
<string name="setup_discovering_detail">Suche nach EverShelf-Servern im lokalen Netzwerk…</string>
|
||||||
<string name="setup_discover_not_found">Kein EverShelf-Server automatisch gefunden. URL manuell eingeben.</string>
|
<string name="setup_discover_not_found">Kein EverShelf-Server automatisch gefunden. URL manuell eingeben.</string>
|
||||||
<string name="setup_exit_title">Setup beenden?</string>
|
<string name="setup_exit_title">Setup beenden?</string>
|
||||||
<string name="setup_exit_message">Die Einrichtung kann später beim erneuten Öffnen der App abgeschlossen werden.</string>
|
<string name="setup_exit_message">Die Einrichtung kann später beim erneuten Öffnen der App abgeschlossen werden.</string>
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
<string name="setup_unreachable">Impossibile raggiungere il server</string>
|
<string name="setup_unreachable">Impossibile raggiungere il server</string>
|
||||||
<string name="setup_discover_btn">🔍 Cerca nella rete locale</string>
|
<string name="setup_discover_btn">🔍 Cerca nella rete locale</string>
|
||||||
<string name="setup_discovering">Scansione in corso…</string>
|
<string name="setup_discovering">Scansione in corso…</string>
|
||||||
<string name="setup_discovering_detail">Ricerca server EverShelf nella rete locale (potrebbe richiedere fino a 30 s)…</string>
|
<string name="setup_discovering_detail">Ricerca server EverShelf nella rete locale…</string>
|
||||||
<string name="setup_discover_not_found">Nessun server EverShelf trovato automaticamente. Inserisci l\'URL manualmente.</string>
|
<string name="setup_discover_not_found">Nessun server EverShelf trovato automaticamente. Inserisci l\'URL manualmente.</string>
|
||||||
<string name="setup_exit_title">Uscire dalla configurazione?</string>
|
<string name="setup_exit_title">Uscire dalla configurazione?</string>
|
||||||
<string name="setup_exit_message">Puoi completare la configurazione più tardi riaprendo l\'app.</string>
|
<string name="setup_exit_message">Puoi completare la configurazione più tardi riaprendo l\'app.</string>
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
<string name="setup_unreachable">Cannot reach server</string>
|
<string name="setup_unreachable">Cannot reach server</string>
|
||||||
<string name="setup_discover_btn">🔍 Search local network</string>
|
<string name="setup_discover_btn">🔍 Search local network</string>
|
||||||
<string name="setup_discovering">Scanning…</string>
|
<string name="setup_discovering">Scanning…</string>
|
||||||
<string name="setup_discovering_detail">Scanning local network for EverShelf servers (this may take up to 30 s)…</string>
|
<string name="setup_discovering_detail">Searching for EverShelf servers on the local network…</string>
|
||||||
<string name="setup_discover_not_found">No EverShelf server found automatically. Enter the URL manually.</string>
|
<string name="setup_discover_not_found">No EverShelf server found automatically. Enter the URL manually.</string>
|
||||||
<string name="setup_exit_title">Exit setup?</string>
|
<string name="setup_exit_title">Exit setup?</string>
|
||||||
<string name="setup_exit_message">You can complete setup later when you reopen the app.</string>
|
<string name="setup_exit_message">You can complete setup later when you reopen the app.</string>
|
||||||
|
|||||||
Reference in New Issue
Block a user