4 changed files with 153 additions and 0 deletions
@ -0,0 +1,101 @@ |
|||||||
|
package com.utun.chat.data |
||||||
|
|
||||||
|
import android.net.ConnectivityManager |
||||||
|
import android.net.LinkProperties |
||||||
|
import android.net.Network |
||||||
|
import android.os.Handler |
||||||
|
import android.os.Looper |
||||||
|
import com.utun.chat.ChatApplication |
||||||
|
|
||||||
|
/** |
||||||
|
* Слушатель смены сети (ConnectivityManager.NetworkCallback). |
||||||
|
* |
||||||
|
* На Android нативный netlink-мониторинг сокетов недоступен (Permission denied), |
||||||
|
* поэтому о смене сети сообщает ConnectivityManager. При смене default-сети |
||||||
|
* (mobile↔wifi, смена IP) пересобираем конфиг сокетов и перезапускаем C-ядро, |
||||||
|
* чтобы оно забиндилось на актуальный интерфейс. |
||||||
|
* |
||||||
|
* Регистрируется в HeadlessService (foreground) — работает в фоне. |
||||||
|
*/ |
||||||
|
object NetworkChangeMonitor { |
||||||
|
private var started = false |
||||||
|
|
||||||
|
/* Пропуск первого onAvailable: он приходит сразу при регистрации с текущим |
||||||
|
* состоянием сети, а не как реальное событие смены. */ |
||||||
|
private var primed = false |
||||||
|
|
||||||
|
/* Рестарт в процессе — защита от наложения (C-ядро тоже имеет CAS-гарды). */ |
||||||
|
private var restarting = false |
||||||
|
|
||||||
|
/* Последний известный набор интерфейсов (ifName=ip) для лога «было → стало». */ |
||||||
|
private var lastInterfaces: List<Pair<String, String>>? = null |
||||||
|
|
||||||
|
private const val DEBOUNCE_MS = 500L |
||||||
|
private val handler = Handler(Looper.getMainLooper()) |
||||||
|
private val restartRunnable = Runnable { doRestart() } |
||||||
|
|
||||||
|
private val callback = object : ConnectivityManager.NetworkCallback() { |
||||||
|
override fun onAvailable(network: Network) { |
||||||
|
if (!primed) { primed = true; return } |
||||||
|
schedule() |
||||||
|
} |
||||||
|
|
||||||
|
override fun onLost(network: Network) { |
||||||
|
if (!primed) return |
||||||
|
schedule() |
||||||
|
} |
||||||
|
|
||||||
|
override fun onLinkPropertiesChanged(network: Network, linkProperties: LinkProperties) { |
||||||
|
if (!primed) return |
||||||
|
schedule() |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private fun schedule() { |
||||||
|
handler.removeCallbacks(restartRunnable) |
||||||
|
handler.postDelayed(restartRunnable, DEBOUNCE_MS) |
||||||
|
} |
||||||
|
|
||||||
|
private fun doRestart() { |
||||||
|
if (restarting || !NativeLib.isReady()) return |
||||||
|
restarting = true |
||||||
|
Thread { |
||||||
|
try { |
||||||
|
val provider = ChatApplication.instance.configProvider |
||||||
|
val now = provider.getAvailableInterfaces() |
||||||
|
val prev = lastInterfaces |
||||||
|
lastInterfaces = now |
||||||
|
val prevStr = prev?.joinToString(", ") { (n, ip) -> "$n=$ip" } ?: "n/a" |
||||||
|
val nowStr = now.joinToString(", ") { (n, ip) -> "$n=$ip" } |
||||||
|
LogManager.addLog("INFO", "NET", "network change: [$prevStr] -> [$nowStr]") |
||||||
|
NativeLib.notifyNetworkActive() |
||||||
|
val path = provider.getString("db.path") ?: "" |
||||||
|
val port = provider.getInt("control.port") |
||||||
|
NativeLib.restart(path, port, provider.buildConfigText()) |
||||||
|
} finally { |
||||||
|
restarting = false |
||||||
|
} |
||||||
|
}.start() |
||||||
|
} |
||||||
|
|
||||||
|
fun start() { |
||||||
|
if (started) return |
||||||
|
val ctx = ChatApplication.instance.applicationContext |
||||||
|
val cm = ctx.getSystemService(ConnectivityManager::class.java) ?: return |
||||||
|
/* Если сети ещё нет — первый onAvailable будет реальным событием. */ |
||||||
|
primed = cm.activeNetwork == null |
||||||
|
cm.registerDefaultNetworkCallback(callback) |
||||||
|
started = true |
||||||
|
LogManager.addLog("INFO", "NET", "network change listener started") |
||||||
|
} |
||||||
|
|
||||||
|
fun stop() { |
||||||
|
if (!started) return |
||||||
|
val ctx = ChatApplication.instance.applicationContext |
||||||
|
val cm = ctx.getSystemService(ConnectivityManager::class.java) ?: return |
||||||
|
cm.unregisterNetworkCallback(callback) |
||||||
|
handler.removeCallbacks(restartRunnable) |
||||||
|
started = false |
||||||
|
LogManager.addLog("INFO", "NET", "network change listener stopped") |
||||||
|
} |
||||||
|
} |
||||||
Loading…
Reference in new issue