mirror of
https://github.com/greenart7c3/Amber.git
synced 2026-10-05 10:58:23 +00:00
Merge pull request #494 from greenart7c3/claude/tor-notification-status-restart-b0pktj
Add restart action and live status to the built-in Tor notification
This commit is contained in:
@@ -21,6 +21,7 @@ import io.matthewnelson.kmp.tor.runtime.Action.Companion.startDaemonAsync
|
|||||||
import io.matthewnelson.kmp.tor.runtime.Action.Companion.stopDaemonAsync
|
import io.matthewnelson.kmp.tor.runtime.Action.Companion.stopDaemonAsync
|
||||||
import io.matthewnelson.kmp.tor.runtime.RuntimeEvent
|
import io.matthewnelson.kmp.tor.runtime.RuntimeEvent
|
||||||
import io.matthewnelson.kmp.tor.runtime.TorRuntime
|
import io.matthewnelson.kmp.tor.runtime.TorRuntime
|
||||||
|
import io.matthewnelson.kmp.tor.runtime.TorState
|
||||||
import io.matthewnelson.kmp.tor.runtime.core.OnEvent
|
import io.matthewnelson.kmp.tor.runtime.core.OnEvent
|
||||||
import io.matthewnelson.kmp.tor.runtime.core.config.TorOption
|
import io.matthewnelson.kmp.tor.runtime.core.config.TorOption
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
@@ -36,6 +37,7 @@ object TorManager {
|
|||||||
private const val TOR_NOTIFICATION_ID = 3
|
private const val TOR_NOTIFICATION_ID = 3
|
||||||
private const val TOR_CHANNEL_ID = "TorChannel"
|
private const val TOR_CHANNEL_ID = "TorChannel"
|
||||||
private const val TOR_CHANNEL_GROUP_ID = "TorGroup"
|
private const val TOR_CHANNEL_GROUP_ID = "TorGroup"
|
||||||
|
private const val TOR_RESTART_REQUEST_CODE = 15
|
||||||
|
|
||||||
@Volatile private var appContext: Context? = null
|
@Volatile private var appContext: Context? = null
|
||||||
|
|
||||||
@@ -45,12 +47,27 @@ object TorManager {
|
|||||||
@Volatile
|
@Volatile
|
||||||
private var portCollectorStarted = false
|
private var portCollectorStarted = false
|
||||||
|
|
||||||
|
@Volatile
|
||||||
|
private var stopRequested = false
|
||||||
|
|
||||||
|
@Volatile
|
||||||
|
private var restarting = false
|
||||||
|
|
||||||
|
@Volatile
|
||||||
|
private var daemonStarted = false
|
||||||
|
|
||||||
|
@Volatile
|
||||||
|
private var bootstrapped = false
|
||||||
|
|
||||||
private val _isRunning = MutableStateFlow(false)
|
private val _isRunning = MutableStateFlow(false)
|
||||||
val isRunning: StateFlow<Boolean> = _isRunning.asStateFlow()
|
val isRunning: StateFlow<Boolean> = _isRunning.asStateFlow()
|
||||||
|
|
||||||
private val _socksPort = MutableStateFlow(0)
|
private val _socksPort = MutableStateFlow(0)
|
||||||
val socksPort: StateFlow<Int> = _socksPort.asStateFlow()
|
val socksPort: StateFlow<Int> = _socksPort.asStateFlow()
|
||||||
|
|
||||||
|
private val _status = MutableStateFlow<TorStatus>(TorStatus.Stopped)
|
||||||
|
val status: StateFlow<TorStatus> = _status.asStateFlow()
|
||||||
|
|
||||||
fun init(context: Context) {
|
fun init(context: Context) {
|
||||||
if (appContext != null) return
|
if (appContext != null) return
|
||||||
appContext = context.applicationContext
|
appContext = context.applicationContext
|
||||||
@@ -74,21 +91,58 @@ object TorManager {
|
|||||||
nm.createNotificationChannel(channel)
|
nm.createNotificationChannel(channel)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun updateStatus(status: TorStatus) {
|
||||||
|
if (_status.value == status) return
|
||||||
|
_status.value = status
|
||||||
|
val ctx = appContext ?: return
|
||||||
|
when (status) {
|
||||||
|
is TorStatus.Stopped -> cancelNotification()
|
||||||
|
is TorStatus.Connecting -> {
|
||||||
|
val text = if (status.percentage <= 0) {
|
||||||
|
ctx.getString(R.string.tor_starting)
|
||||||
|
} else {
|
||||||
|
ctx.getString(R.string.tor_connecting, status.percentage)
|
||||||
|
}
|
||||||
|
showNotification(text, progress = status.percentage)
|
||||||
|
}
|
||||||
|
is TorStatus.Connected -> showNotification(ctx.getString(R.string.builtin_tor_active))
|
||||||
|
is TorStatus.Failed -> {
|
||||||
|
val text = if (status.message.isNullOrBlank()) {
|
||||||
|
ctx.getString(R.string.tor_connection_failed)
|
||||||
|
} else {
|
||||||
|
"${ctx.getString(R.string.tor_connection_failed)}: ${status.message}"
|
||||||
|
}
|
||||||
|
showNotification(text, ongoing = false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@SuppressLint("MissingPermission")
|
@SuppressLint("MissingPermission")
|
||||||
private fun showNotification(text: String) {
|
private fun showNotification(text: String, progress: Int? = null, ongoing: Boolean = true) {
|
||||||
val ctx = appContext ?: return
|
val ctx = appContext ?: return
|
||||||
if (ActivityCompat.checkSelfPermission(ctx, Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED) return
|
if (ActivityCompat.checkSelfPermission(ctx, Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED) return
|
||||||
val contentIntent = Intent(ctx, MainActivity::class.java)
|
val contentIntent = Intent(ctx, MainActivity::class.java)
|
||||||
.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP or Intent.FLAG_ACTIVITY_CLEAR_TOP)
|
.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP or Intent.FLAG_ACTIVITY_CLEAR_TOP)
|
||||||
val pendingIntent = PendingIntent.getActivity(ctx, 0, contentIntent, PendingIntent.FLAG_MUTABLE)
|
val pendingIntent = PendingIntent.getActivity(ctx, 0, contentIntent, PendingIntent.FLAG_MUTABLE)
|
||||||
val notification = NotificationCompat.Builder(ctx, TOR_CHANNEL_ID)
|
val restartIntent = Intent(ctx, TorRestartReceiver::class.java)
|
||||||
|
val restartPendingIntent = PendingIntent.getBroadcast(
|
||||||
|
ctx,
|
||||||
|
TOR_RESTART_REQUEST_CODE,
|
||||||
|
restartIntent,
|
||||||
|
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE,
|
||||||
|
)
|
||||||
|
val builder = NotificationCompat.Builder(ctx, TOR_CHANNEL_ID)
|
||||||
.setContentTitle(ctx.getString(R.string.builtin_tor_title))
|
.setContentTitle(ctx.getString(R.string.builtin_tor_title))
|
||||||
.setContentText(text)
|
.setContentText(text)
|
||||||
.setSmallIcon(R.drawable.ic_tor)
|
.setSmallIcon(R.drawable.ic_tor)
|
||||||
.setOngoing(true)
|
.setOngoing(ongoing)
|
||||||
|
.setOnlyAlertOnce(true)
|
||||||
.setContentIntent(pendingIntent)
|
.setContentIntent(pendingIntent)
|
||||||
.build()
|
.addAction(0, ctx.getString(R.string.tor_restart), restartPendingIntent)
|
||||||
NotificationManagerCompat.from(ctx).notify(TOR_NOTIFICATION_ID, notification)
|
if (progress != null) {
|
||||||
|
builder.setProgress(100, progress, progress <= 0)
|
||||||
|
}
|
||||||
|
NotificationManagerCompat.from(ctx).notify(TOR_NOTIFICATION_ID, builder.build())
|
||||||
}
|
}
|
||||||
|
|
||||||
fun cancelNotification() {
|
fun cancelNotification() {
|
||||||
@@ -115,7 +169,10 @@ object TorManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (torRuntime != null) return
|
if (torRuntime != null) return
|
||||||
showNotification(context.getString(R.string.tor_starting))
|
stopRequested = false
|
||||||
|
daemonStarted = false
|
||||||
|
bootstrapped = false
|
||||||
|
updateStatus(TorStatus.Connecting(0))
|
||||||
scope.launch(Dispatchers.IO) {
|
scope.launch(Dispatchers.IO) {
|
||||||
try {
|
try {
|
||||||
val workDir = context.filesDir.resolve("kmptor")
|
val workDir = context.filesDir.resolve("kmptor")
|
||||||
@@ -131,6 +188,42 @@ object TorManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Observe the daemon state to report bootstrap progress
|
||||||
|
observerStatic(RuntimeEvent.STATE, OnEvent.Executor.Immediate) { state ->
|
||||||
|
when (val daemon = state.daemon) {
|
||||||
|
is TorState.Daemon.Starting -> {
|
||||||
|
daemonStarted = true
|
||||||
|
updateStatus(TorStatus.Connecting(0))
|
||||||
|
}
|
||||||
|
is TorState.Daemon.On -> {
|
||||||
|
daemonStarted = true
|
||||||
|
bootstrapped = daemon.isBootstrapped
|
||||||
|
if (daemon.isBootstrapped && _socksPort.value > 0) {
|
||||||
|
updateStatus(TorStatus.Connected)
|
||||||
|
} else {
|
||||||
|
updateStatus(TorStatus.Connecting(daemon.bootstrap.toInt()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is TorState.Daemon.Stopping -> {}
|
||||||
|
is TorState.Daemon.Off -> {
|
||||||
|
if (daemonStarted && !stopRequested) {
|
||||||
|
Log.e(TAG, "Built-in Tor daemon stopped unexpectedly")
|
||||||
|
bootstrapped = false
|
||||||
|
_isRunning.value = false
|
||||||
|
updateStatus(TorStatus.Failed(null))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Surface runtime errors in the status notification
|
||||||
|
observerStatic(RuntimeEvent.ERROR, OnEvent.Executor.Immediate) { error ->
|
||||||
|
Log.e(TAG, "Built-in Tor runtime error", error)
|
||||||
|
if (_status.value !is TorStatus.Connected) {
|
||||||
|
updateStatus(TorStatus.Failed(error.message))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Observe SOCKS listeners to get the assigned port
|
// Observe SOCKS listeners to get the assigned port
|
||||||
observerStatic(RuntimeEvent.LISTENERS, OnEvent.Executor.Immediate) { listeners ->
|
observerStatic(RuntimeEvent.LISTENERS, OnEvent.Executor.Immediate) { listeners ->
|
||||||
val addr = listeners.socks.firstOrNull()
|
val addr = listeners.socks.firstOrNull()
|
||||||
@@ -140,7 +233,9 @@ object TorManager {
|
|||||||
Log.i(TAG, "Built-in Tor SOCKS proxy on port $port")
|
Log.i(TAG, "Built-in Tor SOCKS proxy on port $port")
|
||||||
_socksPort.value = port
|
_socksPort.value = port
|
||||||
_isRunning.value = true
|
_isRunning.value = true
|
||||||
appContext?.let { showNotification(it.getString(R.string.builtin_tor_active)) }
|
if (bootstrapped) {
|
||||||
|
updateStatus(TorStatus.Connected)
|
||||||
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e(TAG, "Failed to read Tor SOCKS port", e)
|
Log.e(TAG, "Failed to read Tor SOCKS port", e)
|
||||||
}
|
}
|
||||||
@@ -159,16 +254,35 @@ object TorManager {
|
|||||||
Log.e(TAG, "Failed to start built-in Tor", e)
|
Log.e(TAG, "Failed to start built-in Tor", e)
|
||||||
torRuntime = null
|
torRuntime = null
|
||||||
_isRunning.value = false
|
_isRunning.value = false
|
||||||
|
updateStatus(TorStatus.Failed(e.message))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun restart(context: Context, scope: CoroutineScope) {
|
||||||
|
if (restarting) return
|
||||||
|
restarting = true
|
||||||
|
scope.launch(Dispatchers.IO) {
|
||||||
|
try {
|
||||||
|
Log.i(TAG, "Restarting built-in Tor")
|
||||||
|
stop()
|
||||||
|
start(context, scope)
|
||||||
|
} finally {
|
||||||
|
restarting = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun stop() {
|
fun stop() {
|
||||||
val runtime = torRuntime ?: return
|
stopRequested = true
|
||||||
|
bootstrapped = false
|
||||||
|
val runtime = torRuntime
|
||||||
torRuntime = null
|
torRuntime = null
|
||||||
_isRunning.value = false
|
_isRunning.value = false
|
||||||
_socksPort.value = 0
|
_socksPort.value = 0
|
||||||
|
updateStatus(TorStatus.Stopped)
|
||||||
cancelNotification()
|
cancelNotification()
|
||||||
|
if (runtime == null) return
|
||||||
try {
|
try {
|
||||||
runBlocking(Dispatchers.IO) { runtime.stopDaemonAsync() }
|
runBlocking(Dispatchers.IO) { runtime.stopDaemonAsync() }
|
||||||
Log.i(TAG, "Built-in Tor stopped")
|
Log.i(TAG, "Built-in Tor stopped")
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import io.matthewnelson.kmp.tor.runtime.Action.Companion.startDaemonAsync
|
|||||||
import io.matthewnelson.kmp.tor.runtime.Action.Companion.stopDaemonAsync
|
import io.matthewnelson.kmp.tor.runtime.Action.Companion.stopDaemonAsync
|
||||||
import io.matthewnelson.kmp.tor.runtime.RuntimeEvent
|
import io.matthewnelson.kmp.tor.runtime.RuntimeEvent
|
||||||
import io.matthewnelson.kmp.tor.runtime.TorRuntime
|
import io.matthewnelson.kmp.tor.runtime.TorRuntime
|
||||||
|
import io.matthewnelson.kmp.tor.runtime.TorState
|
||||||
import io.matthewnelson.kmp.tor.runtime.core.OnEvent
|
import io.matthewnelson.kmp.tor.runtime.core.OnEvent
|
||||||
import io.matthewnelson.kmp.tor.runtime.core.config.TorOption
|
import io.matthewnelson.kmp.tor.runtime.core.config.TorOption
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
@@ -36,6 +37,7 @@ object TorManager {
|
|||||||
private const val TOR_NOTIFICATION_ID = 3
|
private const val TOR_NOTIFICATION_ID = 3
|
||||||
private const val TOR_CHANNEL_ID = "TorChannel"
|
private const val TOR_CHANNEL_ID = "TorChannel"
|
||||||
private const val TOR_CHANNEL_GROUP_ID = "TorGroup"
|
private const val TOR_CHANNEL_GROUP_ID = "TorGroup"
|
||||||
|
private const val TOR_RESTART_REQUEST_CODE = 15
|
||||||
|
|
||||||
@Volatile private var appContext: Context? = null
|
@Volatile private var appContext: Context? = null
|
||||||
|
|
||||||
@@ -45,12 +47,27 @@ object TorManager {
|
|||||||
@Volatile
|
@Volatile
|
||||||
private var portCollectorStarted = false
|
private var portCollectorStarted = false
|
||||||
|
|
||||||
|
@Volatile
|
||||||
|
private var stopRequested = false
|
||||||
|
|
||||||
|
@Volatile
|
||||||
|
private var restarting = false
|
||||||
|
|
||||||
|
@Volatile
|
||||||
|
private var daemonStarted = false
|
||||||
|
|
||||||
|
@Volatile
|
||||||
|
private var bootstrapped = false
|
||||||
|
|
||||||
private val _isRunning = MutableStateFlow(false)
|
private val _isRunning = MutableStateFlow(false)
|
||||||
val isRunning: StateFlow<Boolean> = _isRunning.asStateFlow()
|
val isRunning: StateFlow<Boolean> = _isRunning.asStateFlow()
|
||||||
|
|
||||||
private val _socksPort = MutableStateFlow(0)
|
private val _socksPort = MutableStateFlow(0)
|
||||||
val socksPort: StateFlow<Int> = _socksPort.asStateFlow()
|
val socksPort: StateFlow<Int> = _socksPort.asStateFlow()
|
||||||
|
|
||||||
|
private val _status = MutableStateFlow<TorStatus>(TorStatus.Stopped)
|
||||||
|
val status: StateFlow<TorStatus> = _status.asStateFlow()
|
||||||
|
|
||||||
fun init(context: Context) {
|
fun init(context: Context) {
|
||||||
if (appContext != null) return
|
if (appContext != null) return
|
||||||
appContext = context.applicationContext
|
appContext = context.applicationContext
|
||||||
@@ -74,21 +91,58 @@ object TorManager {
|
|||||||
nm.createNotificationChannel(channel)
|
nm.createNotificationChannel(channel)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun updateStatus(status: TorStatus) {
|
||||||
|
if (_status.value == status) return
|
||||||
|
_status.value = status
|
||||||
|
val ctx = appContext ?: return
|
||||||
|
when (status) {
|
||||||
|
is TorStatus.Stopped -> cancelNotification()
|
||||||
|
is TorStatus.Connecting -> {
|
||||||
|
val text = if (status.percentage <= 0) {
|
||||||
|
ctx.getString(R.string.tor_starting)
|
||||||
|
} else {
|
||||||
|
ctx.getString(R.string.tor_connecting, status.percentage)
|
||||||
|
}
|
||||||
|
showNotification(text, progress = status.percentage)
|
||||||
|
}
|
||||||
|
is TorStatus.Connected -> showNotification(ctx.getString(R.string.builtin_tor_active))
|
||||||
|
is TorStatus.Failed -> {
|
||||||
|
val text = if (status.message.isNullOrBlank()) {
|
||||||
|
ctx.getString(R.string.tor_connection_failed)
|
||||||
|
} else {
|
||||||
|
"${ctx.getString(R.string.tor_connection_failed)}: ${status.message}"
|
||||||
|
}
|
||||||
|
showNotification(text, ongoing = false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@SuppressLint("MissingPermission")
|
@SuppressLint("MissingPermission")
|
||||||
private fun showNotification(text: String) {
|
private fun showNotification(text: String, progress: Int? = null, ongoing: Boolean = true) {
|
||||||
val ctx = appContext ?: return
|
val ctx = appContext ?: return
|
||||||
if (ActivityCompat.checkSelfPermission(ctx, Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED) return
|
if (ActivityCompat.checkSelfPermission(ctx, Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED) return
|
||||||
val contentIntent = Intent(ctx, MainActivity::class.java)
|
val contentIntent = Intent(ctx, MainActivity::class.java)
|
||||||
.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP or Intent.FLAG_ACTIVITY_CLEAR_TOP)
|
.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP or Intent.FLAG_ACTIVITY_CLEAR_TOP)
|
||||||
val pendingIntent = PendingIntent.getActivity(ctx, 0, contentIntent, PendingIntent.FLAG_MUTABLE)
|
val pendingIntent = PendingIntent.getActivity(ctx, 0, contentIntent, PendingIntent.FLAG_MUTABLE)
|
||||||
val notification = NotificationCompat.Builder(ctx, TOR_CHANNEL_ID)
|
val restartIntent = Intent(ctx, TorRestartReceiver::class.java)
|
||||||
|
val restartPendingIntent = PendingIntent.getBroadcast(
|
||||||
|
ctx,
|
||||||
|
TOR_RESTART_REQUEST_CODE,
|
||||||
|
restartIntent,
|
||||||
|
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE,
|
||||||
|
)
|
||||||
|
val builder = NotificationCompat.Builder(ctx, TOR_CHANNEL_ID)
|
||||||
.setContentTitle(ctx.getString(R.string.builtin_tor_title))
|
.setContentTitle(ctx.getString(R.string.builtin_tor_title))
|
||||||
.setContentText(text)
|
.setContentText(text)
|
||||||
.setSmallIcon(R.drawable.ic_tor)
|
.setSmallIcon(R.drawable.ic_tor)
|
||||||
.setOngoing(true)
|
.setOngoing(ongoing)
|
||||||
|
.setOnlyAlertOnce(true)
|
||||||
.setContentIntent(pendingIntent)
|
.setContentIntent(pendingIntent)
|
||||||
.build()
|
.addAction(0, ctx.getString(R.string.tor_restart), restartPendingIntent)
|
||||||
NotificationManagerCompat.from(ctx).notify(TOR_NOTIFICATION_ID, notification)
|
if (progress != null) {
|
||||||
|
builder.setProgress(100, progress, progress <= 0)
|
||||||
|
}
|
||||||
|
NotificationManagerCompat.from(ctx).notify(TOR_NOTIFICATION_ID, builder.build())
|
||||||
}
|
}
|
||||||
|
|
||||||
fun cancelNotification() {
|
fun cancelNotification() {
|
||||||
@@ -115,7 +169,10 @@ object TorManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (torRuntime != null) return
|
if (torRuntime != null) return
|
||||||
showNotification(context.getString(R.string.tor_starting))
|
stopRequested = false
|
||||||
|
daemonStarted = false
|
||||||
|
bootstrapped = false
|
||||||
|
updateStatus(TorStatus.Connecting(0))
|
||||||
scope.launch(Dispatchers.IO) {
|
scope.launch(Dispatchers.IO) {
|
||||||
try {
|
try {
|
||||||
val workDir = context.filesDir.resolve("kmptor")
|
val workDir = context.filesDir.resolve("kmptor")
|
||||||
@@ -131,6 +188,42 @@ object TorManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Observe the daemon state to report bootstrap progress
|
||||||
|
observerStatic(RuntimeEvent.STATE, OnEvent.Executor.Immediate) { state ->
|
||||||
|
when (val daemon = state.daemon) {
|
||||||
|
is TorState.Daemon.Starting -> {
|
||||||
|
daemonStarted = true
|
||||||
|
updateStatus(TorStatus.Connecting(0))
|
||||||
|
}
|
||||||
|
is TorState.Daemon.On -> {
|
||||||
|
daemonStarted = true
|
||||||
|
bootstrapped = daemon.isBootstrapped
|
||||||
|
if (daemon.isBootstrapped && _socksPort.value > 0) {
|
||||||
|
updateStatus(TorStatus.Connected)
|
||||||
|
} else {
|
||||||
|
updateStatus(TorStatus.Connecting(daemon.bootstrap.toInt()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is TorState.Daemon.Stopping -> {}
|
||||||
|
is TorState.Daemon.Off -> {
|
||||||
|
if (daemonStarted && !stopRequested) {
|
||||||
|
AmberLog.e(TAG, "Built-in Tor daemon stopped unexpectedly")
|
||||||
|
bootstrapped = false
|
||||||
|
_isRunning.value = false
|
||||||
|
updateStatus(TorStatus.Failed(null))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Surface runtime errors in the status notification
|
||||||
|
observerStatic(RuntimeEvent.ERROR, OnEvent.Executor.Immediate) { error ->
|
||||||
|
AmberLog.e(TAG, "Built-in Tor runtime error", error)
|
||||||
|
if (_status.value !is TorStatus.Connected) {
|
||||||
|
updateStatus(TorStatus.Failed(error.message))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Observe SOCKS listeners to get the assigned port
|
// Observe SOCKS listeners to get the assigned port
|
||||||
observerStatic(RuntimeEvent.LISTENERS, OnEvent.Executor.Immediate) { listeners ->
|
observerStatic(RuntimeEvent.LISTENERS, OnEvent.Executor.Immediate) { listeners ->
|
||||||
val addr = listeners.socks.firstOrNull()
|
val addr = listeners.socks.firstOrNull()
|
||||||
@@ -140,7 +233,9 @@ object TorManager {
|
|||||||
AmberLog.i(TAG, "Built-in Tor SOCKS proxy on port $port")
|
AmberLog.i(TAG, "Built-in Tor SOCKS proxy on port $port")
|
||||||
_socksPort.value = port
|
_socksPort.value = port
|
||||||
_isRunning.value = true
|
_isRunning.value = true
|
||||||
appContext?.let { showNotification(it.getString(R.string.builtin_tor_active)) }
|
if (bootstrapped) {
|
||||||
|
updateStatus(TorStatus.Connected)
|
||||||
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
AmberLog.e(TAG, "Failed to read Tor SOCKS port", e)
|
AmberLog.e(TAG, "Failed to read Tor SOCKS port", e)
|
||||||
}
|
}
|
||||||
@@ -159,16 +254,35 @@ object TorManager {
|
|||||||
AmberLog.e(TAG, "Failed to start built-in Tor", e)
|
AmberLog.e(TAG, "Failed to start built-in Tor", e)
|
||||||
torRuntime = null
|
torRuntime = null
|
||||||
_isRunning.value = false
|
_isRunning.value = false
|
||||||
|
updateStatus(TorStatus.Failed(e.message))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun restart(context: Context, scope: CoroutineScope) {
|
||||||
|
if (restarting) return
|
||||||
|
restarting = true
|
||||||
|
scope.launch(Dispatchers.IO) {
|
||||||
|
try {
|
||||||
|
AmberLog.i(TAG, "Restarting built-in Tor")
|
||||||
|
stop()
|
||||||
|
start(context, scope)
|
||||||
|
} finally {
|
||||||
|
restarting = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun stop() {
|
fun stop() {
|
||||||
val runtime = torRuntime ?: return
|
stopRequested = true
|
||||||
|
bootstrapped = false
|
||||||
|
val runtime = torRuntime
|
||||||
torRuntime = null
|
torRuntime = null
|
||||||
_isRunning.value = false
|
_isRunning.value = false
|
||||||
_socksPort.value = 0
|
_socksPort.value = 0
|
||||||
|
updateStatus(TorStatus.Stopped)
|
||||||
cancelNotification()
|
cancelNotification()
|
||||||
|
if (runtime == null) return
|
||||||
try {
|
try {
|
||||||
runBlocking(Dispatchers.IO) { runtime.stopDaemonAsync() }
|
runBlocking(Dispatchers.IO) { runtime.stopDaemonAsync() }
|
||||||
AmberLog.i(TAG, "Built-in Tor stopped")
|
AmberLog.i(TAG, "Built-in Tor stopped")
|
||||||
|
|||||||
@@ -43,6 +43,10 @@
|
|||||||
android:name=".service.StopServiceReceiver"
|
android:name=".service.StopServiceReceiver"
|
||||||
android:enabled="true"
|
android:enabled="true"
|
||||||
android:exported="false" />
|
android:exported="false" />
|
||||||
|
<receiver
|
||||||
|
android:name=".service.TorRestartReceiver"
|
||||||
|
android:enabled="true"
|
||||||
|
android:exported="false" />
|
||||||
<receiver
|
<receiver
|
||||||
android:name=".service.BootReceiver"
|
android:name=".service.BootReceiver"
|
||||||
android:enabled="true"
|
android:enabled="true"
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
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.models.TorMode
|
||||||
|
|
||||||
|
class TorRestartReceiver : BroadcastReceiver() {
|
||||||
|
override fun onReceive(context: Context, intent: Intent) {
|
||||||
|
if (Amber.instance.settings.torMode != TorMode.BUILTIN) return
|
||||||
|
TorManager.restart(context.applicationContext, Amber.instance.applicationIOScope)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
package com.greenart7c3.nostrsigner.service
|
||||||
|
|
||||||
|
sealed class TorStatus {
|
||||||
|
data object Stopped : TorStatus()
|
||||||
|
|
||||||
|
data class Connecting(val percentage: Int) : TorStatus()
|
||||||
|
|
||||||
|
data object Connected : TorStatus()
|
||||||
|
|
||||||
|
data class Failed(val message: String?) : TorStatus()
|
||||||
|
}
|
||||||
@@ -581,6 +581,9 @@
|
|||||||
<string name="tor_starting">Starting Tor\u2026</string>
|
<string name="tor_starting">Starting Tor\u2026</string>
|
||||||
<string name="tor_retrying">Retrying Tor connection\u2026</string>
|
<string name="tor_retrying">Retrying Tor connection\u2026</string>
|
||||||
<string name="tor_status_channel_description">Built-in Tor status notifications</string>
|
<string name="tor_status_channel_description">Built-in Tor status notifications</string>
|
||||||
|
<string name="tor_connecting">Connecting to Tor: %1$d%%</string>
|
||||||
|
<string name="tor_connection_failed">Tor connection failed</string>
|
||||||
|
<string name="tor_restart">Restart</string>
|
||||||
<string name="event_kind_443">KeyPackage</string>
|
<string name="event_kind_443">KeyPackage</string>
|
||||||
<string name="event_kind_10051">KeyPackage Relays List</string>
|
<string name="event_kind_10051">KeyPackage Relays List</string>
|
||||||
<string name="event_kind_444">Welcome</string>
|
<string name="event_kind_444">Welcome</string>
|
||||||
|
|||||||
@@ -13,6 +13,9 @@ object TorManager {
|
|||||||
private val _isRunning = MutableStateFlow(false)
|
private val _isRunning = MutableStateFlow(false)
|
||||||
val isRunning: StateFlow<Boolean> = _isRunning.asStateFlow()
|
val isRunning: StateFlow<Boolean> = _isRunning.asStateFlow()
|
||||||
|
|
||||||
|
private val _status = MutableStateFlow<TorStatus>(TorStatus.Stopped)
|
||||||
|
val status: StateFlow<TorStatus> = _status.asStateFlow()
|
||||||
|
|
||||||
@Suppress("UNUSED_PARAMETER")
|
@Suppress("UNUSED_PARAMETER")
|
||||||
fun init(context: Context) {
|
fun init(context: Context) {
|
||||||
// No-op in offline flavor
|
// No-op in offline flavor
|
||||||
@@ -23,6 +26,11 @@ object TorManager {
|
|||||||
// No-op in offline flavor
|
// No-op in offline flavor
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Suppress("UNUSED_PARAMETER")
|
||||||
|
fun restart(context: Context, scope: CoroutineScope) {
|
||||||
|
// No-op in offline flavor
|
||||||
|
}
|
||||||
|
|
||||||
fun stop() {
|
fun stop() {
|
||||||
// No-op in offline flavor
|
// No-op in offline flavor
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user