@ -7,93 +7,99 @@ 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 ) — работает в фоне .
* /
/ * * ConnectivityManager передаёт актуальный конфиг сокетов в uasync без рестарта ядра .
* Регистрация живёт в HeadlessService ; состояние очереди обновлений — на main thread . * /
object NetworkChangeMonitor {
private var started = false
/ * Пропуск первого onAvailable : он приходит сразу при регистрации с текущим
* состоянием сети , а не как реальное событие смены . * /
private var primed = false
/* Рестарт в процессе — защита от наложения (C-ядро тоже имеет CAS-гарды). */
private var restarting = false
/* Последний известный набор интерфейсов (ifName=ip) для лога «было → стало». */
private var generation = 0
private var updating = false
private var pending = false
private var lastInterfaces : List < Pair < String , String > > ? = null
private const val DEBOUNCE _MS = 500L
private val handler = Handler ( Looper . getMainLooper ( ) )
private val restart Runnable = Runnable { doRestart ( ) }
private val updateRunnable = Runnable { updateSockets ( ) }
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 ( )
}
override fun onAvailable ( network : Network ) = schedule ( )
override fun onLost ( network : Network ) = schedule ( )
override fun onLinkPropertiesChanged ( network : Network , linkProperties : LinkProperties ) = schedule ( )
}
private fun schedule ( ) {
handler . removeCallbacks ( restartRunnable )
handler . postDelayed ( restartRunnable , DEBOUNCE _MS )
handler . post {
if ( ! started ) return @post
handler . removeCallbacks ( updateRunnable )
handler . postDelayed ( updateRunnable , DEBOUNCE _MS )
}
}
private fun doRestart ( ) {
if ( restarting || ! NativeLib . isReady ( ) ) return
restarting = true
private fun updateSockets ( ) {
if ( ! started || ! NativeLib . isReady ( ) ) return
if ( updating ) { pending = true ; return }
updating = true
val token = generation
Thread {
var interfaces : List < Pair < String , String > > ? = null
var config : String ? = null
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 ( )
NativeLib . updateSockets ( provider . buildConfigText ( ) )
interfaces = provider . getAvailableInterfaces ( )
config = provider . buildConfigText ( )
} catch ( e : Exception ) {
LogManager . addLog ( " ERROR " , " NET " , " network config update failed: ${e.message} " )
} finally {
restarting = false
val current = interfaces
val text = config
handler . post {
if ( ! started || token != generation ) return @post
updating = false
if ( text != null && current != null && NativeLib . isReady ( ) ) {
val previous = lastInterfaces ?. joinToString ( " , " ) { ( n , ip ) -> " $n = $ip " } ?: " n/a "
val next = current . joinToString ( " , " ) { ( n , ip ) -> " $n = $ip " }
lastInterfaces = current
LogManager . addLog ( " INFO " , " NET " , " network change: [ $previous ] -> [ $next ] " )
NativeLib . notifyNetworkActive ( )
if ( ! NativeLib . updateSockets ( text ) ) LogManager . addLog ( " ERROR " , " NET " , " socket update rejected " )
}
if ( pending ) { pending = false ; schedule ( ) }
}
}
} . 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 )
// Повторный снимок после SERVICE_STARTED покрывает изменения сети во время инициализации.
fun onCoreStarted ( ) = schedule ( )
fun start ( ) : Boolean {
if ( started ) return true
val cm = ChatApplication . instance . getSystemService ( ConnectivityManager :: class . java )
if ( cm == null ) { LogManager . addLog ( " ERROR " , " NET " , " ConnectivityManager unavailable " ) ; return false }
try {
cm . registerDefaultNetworkCallback ( callback )
} catch ( e : Exception ) {
LogManager . addLog ( " ERROR " , " NET " , " network listener registration failed: ${e.message} " )
return false
}
generation ++
started = true
LogManager . addLog ( " INFO " , " NET " , " network change listener started " )
return true
}
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
generation ++
pending = false
updating = false
lastInterfaces = null
handler . removeCallbacks ( updateRunnable )
val cm = ChatApplication . instance . getSystemService ( ConnectivityManager :: class . java )
try {
cm ?. unregisterNetworkCallback ( callback )
} catch ( e : Exception ) {
LogManager . addLog ( " WARN " , " NET " , " network listener removal failed: ${e.message} " )
}
LogManager . addLog ( " INFO " , " NET " , " network change listener stopped " )
}
}