Correctly manages airplane mode changes: reconnects Tor and Relays

This commit is contained in:
Vitor Pamplona
2025-08-20 12:16:01 -04:00
parent 7339f0ea3b
commit 4065adac83
8 changed files with 74 additions and 19 deletions

View File

@@ -75,9 +75,10 @@ class Amethyst : Application() {
// App services that should be run as soon as there are subscribers to their flows // App services that should be run as soon as there are subscribers to their flows
val locationManager = LocationState(this, applicationIOScope) val locationManager = LocationState(this, applicationIOScope)
val torManager = TorManager(this, applicationIOScope)
val connManager = ConnectivityManager(this, applicationIOScope) val connManager = ConnectivityManager(this, applicationIOScope)
val torManager = TorManager(this, applicationIOScope)
// Service that will run at all times to receive events from Pokey // Service that will run at all times to receive events from Pokey
val pokeyReceiver = PokeyReceiver() val pokeyReceiver = PokeyReceiver()
@@ -109,7 +110,7 @@ class Amethyst : Application() {
val client: NostrClient = NostrClient(websocketBuilder, applicationIOScope) val client: NostrClient = NostrClient(websocketBuilder, applicationIOScope)
// Watches for changes on Tor and Relay List Settings // Watches for changes on Tor and Relay List Settings
val relayProxyClientConnector = RelayProxyClientConnector(torProxySettingsAnchor, okHttpClients, connManager, client, applicationIOScope) val relayProxyClientConnector = RelayProxyClientConnector(torProxySettingsAnchor, okHttpClients, connManager, client, torManager, applicationIOScope)
// Verifies and inserts in the cache from all relays, all subscriptions // Verifies and inserts in the cache from all relays, all subscriptions
val cacheClientConnector = CacheClientConnector(client, cache) val cacheClientConnector = CacheClientConnector(client, cache)

View File

@@ -63,6 +63,12 @@ class ConnectivityFlow(
Log.d("ConnectivityFlow", "onCapabilitiesChanged ${network.networkHandle} $isMobile") Log.d("ConnectivityFlow", "onCapabilitiesChanged ${network.networkHandle} $isMobile")
trySend(ConnectivityStatus.Active(network.networkHandle, isMobile)) trySend(ConnectivityStatus.Active(network.networkHandle, isMobile))
} }
override fun onLost(network: Network) {
super.onLost(network)
Log.d("ConnectivityFlow", "onLost ${network.networkHandle} ")
trySend(ConnectivityStatus.Off)
}
} }
connectivityManager.registerDefaultNetworkCallback(networkCallback) connectivityManager.registerDefaultNetworkCallback(networkCallback)

View File

@@ -21,14 +21,20 @@
package com.vitorpamplona.amethyst.service.relayClient package com.vitorpamplona.amethyst.service.relayClient
import android.util.Log import android.util.Log
import com.vitorpamplona.amethyst.model.torState.TorRelayEvaluation
import com.vitorpamplona.amethyst.service.connectivity.ConnectivityManager import com.vitorpamplona.amethyst.service.connectivity.ConnectivityManager
import com.vitorpamplona.amethyst.service.connectivity.ConnectivityStatus
import com.vitorpamplona.amethyst.service.okhttp.DualHttpClientManager import com.vitorpamplona.amethyst.service.okhttp.DualHttpClientManager
import com.vitorpamplona.amethyst.service.okhttp.ProxySettingsAnchor import com.vitorpamplona.amethyst.service.okhttp.ProxySettingsAnchor
import com.vitorpamplona.amethyst.ui.tor.TorManager
import com.vitorpamplona.amethyst.ui.tor.TorServiceStatus
import com.vitorpamplona.quartz.nip01Core.relay.client.NostrClient import com.vitorpamplona.quartz.nip01Core.relay.client.NostrClient
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.FlowPreview import kotlinx.coroutines.FlowPreview
import kotlinx.coroutines.NonCancellable.isActive
import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.debounce import kotlinx.coroutines.flow.debounce
import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.flowOn
@@ -36,14 +42,24 @@ import kotlinx.coroutines.flow.onCompletion
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.onStart import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.stateIn
import net.freehaven.tor.control.TorControlCommands
import okhttp3.OkHttpClient
class RelayProxyClientConnector( class RelayProxyClientConnector(
val torProxySettingsAnchor: ProxySettingsAnchor, val torProxySettingsAnchor: ProxySettingsAnchor,
val okHttpClients: DualHttpClientManager, val okHttpClients: DualHttpClientManager,
val connManager: ConnectivityManager, val connManager: ConnectivityManager,
val client: NostrClient, val client: NostrClient,
val torManager: TorManager,
val scope: CoroutineScope, val scope: CoroutineScope,
) { ) {
data class RelayServiceInfra(
val torSettings: StateFlow<TorRelayEvaluation>,
val torConnection: OkHttpClient,
val clearConnection: OkHttpClient,
val connectivity: ConnectivityStatus,
)
@OptIn(FlowPreview::class) @OptIn(FlowPreview::class)
val relayServices = val relayServices =
combine( combine(
@@ -52,11 +68,35 @@ class RelayProxyClientConnector(
okHttpClients.defaultHttpClientWithoutProxy, okHttpClients.defaultHttpClientWithoutProxy,
connManager.status, connManager.status,
) { torSettings, torConnection, clearConnection, connectivity -> ) { torSettings, torConnection, clearConnection, connectivity ->
torSettings.hashCode() + torConnection.hashCode() + clearConnection.hashCode() + connectivity.hashCode() RelayServiceInfra(torSettings, torConnection, clearConnection, connectivity)
}.debounce(100) }.debounce(100)
.onEach { .onEach {
Log.d("ManageRelayServices", "Relay Services have changed") if (it.connectivity is ConnectivityStatus.Off) {
Log.d("ManageRelayServices", "Pausing Relay Services ${it.connectivity}")
if (client.isActive()) {
client.disconnect()
}
val torStatus = torManager.status.value
if (torStatus is TorServiceStatus.Active) {
torStatus.torControlConnection.signal(TorControlCommands.SIGNAL_DORMANT)
Log.d("ManageRelayServices", "Pausing Tor Activity")
}
} else if (it.connectivity is ConnectivityStatus.Active && !client.isActive()) {
Log.d("ManageRelayServices", "Resuming Relay Services")
val torStatus = torManager.status.value
if (torStatus is TorServiceStatus.Active) {
torStatus.torControlConnection.signal(TorControlCommands.SIGNAL_ACTIVE)
torStatus.torControlConnection.signal(TorControlCommands.SIGNAL_NEWNYM)
Log.d("ManageRelayServices", "Resuming Tor Activity with new nym")
}
// only calls this if the client is not active. Otherwise goes to the else below
client.connect()
} else {
Log.d("ManageRelayServices", "Relay Services have changed, reconnecting relays that need to")
client.reconnect(true) client.reconnect(true)
}
}.onStart { }.onStart {
Log.d("ManageRelayServices", "Resuming Relay Services") Log.d("ManageRelayServices", "Resuming Relay Services")
client.connect() client.connect()

View File

@@ -138,7 +138,6 @@ fun ObserveImageLoadingTor(accountViewModel: AccountViewModel) {
fun ManageRelayServices(accountViewModel: AccountViewModel) { fun ManageRelayServices(accountViewModel: AccountViewModel) {
val relayServices by Amethyst.instance.relayProxyClientConnector.relayServices val relayServices by Amethyst.instance.relayProxyClientConnector.relayServices
.collectAsStateWithLifecycle() .collectAsStateWithLifecycle()
Log.d("ManageRelayServices", "Relay Services changed $relayServices")
} }
@Composable @Composable

View File

@@ -62,7 +62,7 @@ class TorService(
delay(100) delay(100)
} }
trySend(TorServiceStatus.Active(torService.socksPort)) trySend(TorServiceStatus.Active(torService.socksPort, torService.torControlConnection))
Log.d("TorService", "Tor Service Connected ${torService.socksPort}") Log.d("TorService", "Tor Service Connected ${torService.socksPort}")
} }
} }

View File

@@ -20,9 +20,12 @@
*/ */
package com.vitorpamplona.amethyst.ui.tor package com.vitorpamplona.amethyst.ui.tor
import net.freehaven.tor.control.TorControlConnection
sealed class TorServiceStatus { sealed class TorServiceStatus {
data class Active( data class Active(
val port: Int, val port: Int,
val torControlConnection: TorControlConnection,
) : TorServiceStatus() ) : TorServiceStatus()
object Off : TorServiceStatus() object Off : TorServiceStatus()

View File

@@ -133,6 +133,8 @@ class NostrClient(
relayPool.disconnect() relayPool.disconnect()
} }
fun isActive() = isActive
@Synchronized @Synchronized
fun reconnect(onlyIfChanged: Boolean = false) { fun reconnect(onlyIfChanged: Boolean = false) {
if (onlyIfChanged) { if (onlyIfChanged) {

View File

@@ -75,6 +75,13 @@ class RelayPool(
fun reconnectIfNeedsToORIfItIsTime() { fun reconnectIfNeedsToORIfItIsTime() {
if (lastReconnectCall < TimeUtils.oneMinuteAgo()) { if (lastReconnectCall < TimeUtils.oneMinuteAgo()) {
reconnectIfNeedsTo()
lastReconnectCall = TimeUtils.now()
}
}
fun reconnectIfNeedsTo() {
relays.forEach { url, relay -> relays.forEach { url, relay ->
if (relay.isConnected()) { if (relay.isConnected()) {
if (relay.needsToReconnect()) { if (relay.needsToReconnect()) {
@@ -87,9 +94,6 @@ class RelayPool(
relay.connectAndSyncFiltersIfDisconnected() relay.connectAndSyncFiltersIfDisconnected()
} }
} }
lastReconnectCall = TimeUtils.now()
}
} }
fun connect() = fun connect() =