- Create a kill switch

- Fix some reconnection issues
This commit is contained in:
greenart7c3
2025-08-27 08:32:52 -03:00
parent ab8e9d84c9
commit 60626b4623
11 changed files with 119 additions and 30 deletions
+5 -1
View File
@@ -31,9 +31,13 @@
android:windowSoftInputMode="adjustResize"
tools:targetApi="33">
<receiver
android:name=".service.ReconnectReceiver"
android:name=".service.KillSwitchReceiver"
android:enabled="true"
android:exported="false"></receiver>
<receiver
android:name=".service.ReconnectReceiver"
android:enabled="true"
android:exported="false" />
<receiver
android:name=".service.BootReceiver"
android:enabled="true"
@@ -46,6 +46,7 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.cancel
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.launch
@@ -183,28 +184,6 @@ class Amber : Application(), LifecycleObserver {
override fun onCreate() {
super.onCreate()
ProcessLifecycleOwner.get().lifecycle.addObserver(object : DefaultLifecycleObserver {
override fun onStart(owner: LifecycleOwner) {
Log.d("ProcessLifecycleOwner", "App in foreground")
isAppInForeground = true
// activates the profile filter only when the app is in the foreground
applicationIOScope.launch {
profileSubscription.updateFilter()
}
}
override fun onStop(owner: LifecycleOwner) {
Log.d("ProcessLifecycleOwner", "App in background")
isAppInForeground = false
// closes the filter when in the background
applicationIOScope.launch {
profileSubscription.closeSub()
}
}
})
isStartingApp.value = true
Log.d(TAG, "onCreate Amber")
@@ -248,7 +227,36 @@ class Amber : Application(), LifecycleObserver {
settings = LocalPreferences.loadSettingsFromEncryptedStorage()
LocalPreferences.reloadApp()
isStartingApp.value = false
checkForNewRelaysAndUpdateAllFilters(true)
if (settings.killSwitch) {
client.disconnect()
}
launch(Dispatchers.Main) {
ProcessLifecycleOwner.get().lifecycle.addObserver(object : DefaultLifecycleObserver {
override fun onStart(owner: LifecycleOwner) {
Log.d("ProcessLifecycleOwner", "App in foreground")
isAppInForeground = true
// activates the profile filter only when the app is in the foreground
if (!settings.killSwitch) {
applicationIOScope.launch {
profileSubscription.updateFilter()
}
}
}
override fun onStop(owner: LifecycleOwner) {
Log.d("ProcessLifecycleOwner", "App in background")
isAppInForeground = false
// closes the filter when in the background
applicationIOScope.launch {
profileSubscription.closeSub()
}
}
})
}
} catch (e: Exception) {
Log.e(TAG, "Failed to run migrations", e)
isStartingApp.value = false
@@ -269,8 +277,15 @@ class Amber : Application(), LifecycleObserver {
}
fun reconnect() {
if (settings.killSwitch) {
return
}
Log.d(TAG, "reconnecting relays")
client.reconnect(true)
val wasActive = client.isActive()
if (!wasActive) {
client.connect()
}
client.reconnect(wasActive)
stats.updateNotification()
}
@@ -297,9 +312,16 @@ class Amber : Application(), LifecycleObserver {
return savedRelays
}
fun checkForNewRelaysAndUpdateAllFilters(
suspend fun checkForNewRelaysAndUpdateAllFilters(
shouldReconnect: Boolean = false,
) {
if (settings.killSwitch) {
client.disconnect()
return
}
val wasActive = client.isActive()
@Suppress("KotlinConstantConditions")
// TODO: You can filter inside each update filter for only
// localhost relays and keep these alive even on the offline
@@ -312,9 +334,14 @@ class Amber : Application(), LifecycleObserver {
}
notificationSubscription.updateFilter()
}
delay(3000)
Log.d(TAG, "checkForNewRelaysAndUpdateAllFilters wasActive: $wasActive")
if (!wasActive) {
client.connect()
}
if (shouldReconnect) {
client.reconnect(true)
client.reconnect(wasActive)
}
}
@@ -55,6 +55,7 @@ private enum class SettingsKeys(val key: String) {
USE_PROXY("use_proxy"),
PROXY_PORT("proxy_port"),
BATERRY_OPTIMIZATION("battery_optimization"),
KILL_SWITCH("kill_switch"),
}
@Immutable
@@ -137,6 +138,7 @@ object LocalPreferences {
putBoolean(SettingsKeys.USE_PIN.key, settings.usePin)
putBoolean(SettingsKeys.USE_PROXY.key, settings.useProxy)
putInt(SettingsKeys.PROXY_PORT.key, settings.proxyPort)
putBoolean(SettingsKeys.KILL_SWITCH.key, settings.killSwitch)
}
}
}
@@ -246,6 +248,7 @@ object LocalPreferences {
usePin = getBoolean(SettingsKeys.USE_PIN.key, false),
useProxy = getBoolean(SettingsKeys.USE_PROXY.key, false),
proxyPort = getInt(SettingsKeys.PROXY_PORT.key, 9050),
killSwitch = getBoolean(SettingsKeys.KILL_SWITCH.key, false),
)
}
}
@@ -25,4 +25,5 @@ data class AmberSettings(
val usePin: Boolean = false,
val useProxy: Boolean = false,
val proxyPort: Int = 9050,
val killSwitch: Boolean = false,
)
@@ -16,6 +16,7 @@ import androidx.core.app.NotificationManagerCompat
import com.greenart7c3.nostrsigner.Amber
import com.greenart7c3.nostrsigner.MainActivity
import com.greenart7c3.nostrsigner.R
import com.greenart7c3.nostrsigner.service.KillSwitchReceiver
import com.greenart7c3.nostrsigner.service.ReconnectReceiver
import com.vitorpamplona.quartz.nip01Core.relay.client.NostrClient
import com.vitorpamplona.quartz.nip01Core.relay.client.stats.RelayStats
@@ -119,6 +120,14 @@ class AmberRelayStats(
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_MUTABLE,
)
val killSwitchIntent = Intent(appContext, KillSwitchReceiver::class.java)
val killSwitchPendingIntent = PendingIntent.getBroadcast(
appContext,
0,
killSwitchIntent,
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_MUTABLE,
)
val notificationBuilder =
NotificationCompat.Builder(appContext, channelId)
.setGroup(group.id)
@@ -128,6 +137,7 @@ class AmberRelayStats(
.setSmallIcon(R.drawable.ic_notification)
.setContentIntent(contentPendingIntent)
.addAction(R.drawable.ic_notification, appContext.getString(R.string.reconnect), reconnectPendingIntent)
.addAction(R.drawable.ic_notification, appContext.getString(if (Amber.instance.settings.killSwitch) R.string.disable_kill_switch else R.string.enable_kill_switch), killSwitchPendingIntent)
return notificationBuilder.build()
}
@@ -353,7 +353,10 @@ object BunkerRequestUtils {
),
)
if (didChangeRelays) {
Amber.instance.notificationSubscription.updateFilter()
Amber.instance.checkForNewRelaysAndUpdateAllFilters(true)
}
if (!Amber.instance.client.isActive()) {
Amber.instance.client.connect()
}
sendBunkerResponse(
@@ -30,9 +30,13 @@ class ConnectivityService : Service() {
super.onAvailable(network)
@Suppress("KotlinConstantConditions")
if (BuildConfig.FLAVOR == "offline") return
if (Amber.instance.settings.killSwitch) return
if (lastNetwork != null && lastNetwork != network) {
scope.launch(Dispatchers.IO) {
if (!Amber.instance.client.isActive()) {
Amber.instance.client.connect()
}
Amber.instance.client.reconnect(true)
}
}
@@ -48,13 +52,16 @@ class ConnectivityService : Service() {
@Suppress("KotlinConstantConditions")
if (BuildConfig.FLAVOR == "offline") return
if (Amber.instance.settings.killSwitch) return
scope.launch(Dispatchers.IO) {
Log.d(
"ServiceManager NetworkCallback",
"onCapabilitiesChanged: ${network.networkHandle} hasMobileData ${Amber.instance.isOnMobileDataState.value} hasWifi ${Amber.instance.isOnWifiDataState.value}",
)
if (!Amber.instance.client.isActive()) {
Amber.instance.client.connect()
}
Amber.instance.client.reconnect(true)
}
}
@@ -90,7 +97,7 @@ class ConnectivityService : Service() {
delay(1000)
}
@Suppress("KotlinConstantConditions")
if (BuildConfig.FLAVOR != "offline") {
if (BuildConfig.FLAVOR != "offline" && !Amber.instance.settings.killSwitch) {
Amber.instance.client.connect()
Amber.instance.applicationIOScope.launch {
Amber.instance.checkForNewRelaysAndUpdateAllFilters()
@@ -114,8 +121,12 @@ class ConnectivityService : Service() {
if (BuildConfig.FLAVOR == "offline") {
return
}
if (Amber.instance.settings.killSwitch) return
scope.launch {
if (!Amber.instance.client.isActive()) {
Amber.instance.client.connect()
}
Amber.instance.client.reconnect(true)
}
}
@@ -0,0 +1,22 @@
package com.greenart7c3.nostrsigner.service
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import com.greenart7c3.nostrsigner.Amber
import com.greenart7c3.nostrsigner.LocalPreferences
import kotlinx.coroutines.launch
class KillSwitchReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
Amber.instance.applicationIOScope.launch {
Amber.instance.settings = Amber.instance.settings.copy(killSwitch = !Amber.instance.settings.killSwitch)
LocalPreferences.saveSettingsToEncryptedStorage(Amber.instance.settings)
if (Amber.instance.settings.killSwitch) {
Amber.instance.client.disconnect()
} else {
Amber.instance.checkForNewRelaysAndUpdateAllFilters(shouldReconnect = true)
}
}
}
}
@@ -21,6 +21,7 @@
package com.greenart7c3.nostrsigner.service
import android.content.Context
import android.util.Log
import com.greenart7c3.nostrsigner.Amber
import com.greenart7c3.nostrsigner.LocalPreferences
import com.vitorpamplona.quartz.nip01Core.core.Event
@@ -53,6 +54,11 @@ class NotificationSubscription(
}
}
override fun onSend(relay: IRelayClient, msg: String, success: Boolean) {
Log.d("NotificationSubscription", "onSend: ${relay.url}, $msg, $success")
super.onSend(relay, msg, success)
}
/**
* Call this method every time the relay list or the user list changes
*/
+2
View File
@@ -550,4 +550,6 @@
<string name="of_connected_relays">%1$d of %2$d connected relays</string>
<string name="proxy">Proxy</string>
<string name="default_relay_text">Default</string>
<string name="enable_kill_switch">Enable kill switch</string>
<string name="disable_kill_switch">Disable kill switch</string>
</resources>
+1 -1
View File
@@ -13,7 +13,7 @@ lifecycle_version = "2.9.2"
material3 = "1.3.2"
mockk = "1.14.5"
nav_version = "2.9.3"
quartz = "v1.00.3"
quartz = "v1.00.5"
compose_ui = "1.9.0"
richtextUi = "e1151c8"
roomKtx = "2.7.2"