- Tor Cleanup:
- Only starts Tor when the "Internal" option is selected on the privacy settings - Adds a mutex to make sure tor only starts once. - Fixes bug when setting port before the service is running - Moves tor start and stop to Compose actions, instead of activity - Adds a 5 second delay before disconnecting Tor on the App's onPause
This commit is contained in:
@@ -103,10 +103,21 @@ class ServiceManager(
|
|||||||
if (myAccount != null) {
|
if (myAccount != null) {
|
||||||
when (myAccount.settings.torSettings.torType.value) {
|
when (myAccount.settings.torSettings.torType.value) {
|
||||||
TorType.INTERNAL -> {
|
TorType.INTERNAL -> {
|
||||||
Log.d("TorManager", "Relays Service Connected ${TorManager.socksPort()}")
|
// Tor's lib will automatically set this port.
|
||||||
HttpClientManager.setDefaultProxyOnPort(TorManager.socksPort())
|
if (TorManager.isSocksReady()) {
|
||||||
|
HttpClientManager.setDefaultProxyOnPort(TorManager.socksPort())
|
||||||
|
} else {
|
||||||
|
HttpClientManager.setProxyNotReady()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
TorType.EXTERNAL -> {
|
||||||
|
val port = myAccount.settings.torSettings.externalSocksPort.value
|
||||||
|
if (port > 0) {
|
||||||
|
HttpClientManager.setDefaultProxyOnPort(port)
|
||||||
|
} else {
|
||||||
|
HttpClientManager.setProxyNotReady()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
TorType.EXTERNAL -> HttpClientManager.setDefaultProxyOnPort(myAccount.settings.torSettings.externalSocksPort.value)
|
|
||||||
else -> HttpClientManager.setDefaultProxy(null)
|
else -> HttpClientManager.setDefaultProxy(null)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -36,7 +36,6 @@ import com.vitorpamplona.amethyst.commons.richtext.RichTextParser
|
|||||||
import com.vitorpamplona.amethyst.service.NostrLnZapPaymentResponseDataSource
|
import com.vitorpamplona.amethyst.service.NostrLnZapPaymentResponseDataSource
|
||||||
import com.vitorpamplona.amethyst.service.checkNotInMainThread
|
import com.vitorpamplona.amethyst.service.checkNotInMainThread
|
||||||
import com.vitorpamplona.amethyst.service.location.LocationState
|
import com.vitorpamplona.amethyst.service.location.LocationState
|
||||||
import com.vitorpamplona.amethyst.service.okhttp.HttpClientManager
|
|
||||||
import com.vitorpamplona.amethyst.service.uploads.FileHeader
|
import com.vitorpamplona.amethyst.service.uploads.FileHeader
|
||||||
import com.vitorpamplona.amethyst.tryAndWait
|
import com.vitorpamplona.amethyst.tryAndWait
|
||||||
import com.vitorpamplona.amethyst.ui.actions.mediaServers.DEFAULT_MEDIA_SERVERS
|
import com.vitorpamplona.amethyst.ui.actions.mediaServers.DEFAULT_MEDIA_SERVERS
|
||||||
@@ -3648,12 +3647,6 @@ class Account(
|
|||||||
|
|
||||||
fun markDonatedInThisVersion() = settings.markDonatedInThisVersion(BuildConfig.VERSION_NAME)
|
fun markDonatedInThisVersion() = settings.markDonatedInThisVersion(BuildConfig.VERSION_NAME)
|
||||||
|
|
||||||
fun httpClientForCoil() = HttpClientManager.getHttpClient(shouldUseTorForImageDownload())
|
|
||||||
|
|
||||||
fun httpClientForRelay(dirtyUrl: String) = HttpClientManager.getHttpClient(shouldUseTorForDirty(dirtyUrl))
|
|
||||||
|
|
||||||
fun httpClientForPreviewUrl(url: String) = HttpClientManager.getHttpClient(shouldUseTorForPreviewUrl(url))
|
|
||||||
|
|
||||||
fun shouldUseTorForImageDownload() =
|
fun shouldUseTorForImageDownload() =
|
||||||
when (settings.torSettings.torType.value) {
|
when (settings.torSettings.torType.value) {
|
||||||
TorType.OFF -> false
|
TorType.OFF -> false
|
||||||
|
|||||||
+10
-2
@@ -20,6 +20,7 @@
|
|||||||
*/
|
*/
|
||||||
package com.vitorpamplona.amethyst.service.okhttp
|
package com.vitorpamplona.amethyst.service.okhttp
|
||||||
|
|
||||||
|
import android.R.attr.port
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import com.vitorpamplona.quartz.nip17Dm.files.encryption.NostrCipher
|
import com.vitorpamplona.quartz.nip17Dm.files.encryption.NostrCipher
|
||||||
import okhttp3.OkHttpClient
|
import okhttp3.OkHttpClient
|
||||||
@@ -35,6 +36,8 @@ object HttpClientManager {
|
|||||||
.followSslRedirects(true)
|
.followSslRedirects(true)
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
|
val DEFAULT_TOR_PROXY = Proxy(Proxy.Type.SOCKS, InetSocketAddress("127.0.0.1", 9050))
|
||||||
|
|
||||||
val DEFAULT_TIMEOUT_ON_WIFI: Duration = Duration.ofSeconds(10L)
|
val DEFAULT_TIMEOUT_ON_WIFI: Duration = Duration.ofSeconds(10L)
|
||||||
val DEFAULT_TIMEOUT_ON_MOBILE: Duration = Duration.ofSeconds(30L)
|
val DEFAULT_TIMEOUT_ON_MOBILE: Duration = Duration.ofSeconds(30L)
|
||||||
|
|
||||||
@@ -43,13 +46,13 @@ object HttpClientManager {
|
|||||||
private var defaultHttpClientWithoutProxy: OkHttpClient? = null
|
private var defaultHttpClientWithoutProxy: OkHttpClient? = null
|
||||||
private var userAgent: String = "Amethyst"
|
private var userAgent: String = "Amethyst"
|
||||||
|
|
||||||
private var currentProxy: Proxy? = null
|
private var currentProxy: Proxy? = DEFAULT_TOR_PROXY
|
||||||
|
|
||||||
private val cache = EncryptionKeyCache()
|
private val cache = EncryptionKeyCache()
|
||||||
|
|
||||||
fun setDefaultProxy(proxy: Proxy?) {
|
fun setDefaultProxy(proxy: Proxy?) {
|
||||||
if (currentProxy != proxy) {
|
if (currentProxy != proxy) {
|
||||||
Log.d("HttpClient", "Changing proxy to: ${proxy != null}")
|
Log.d("HttpClient", "Changing proxy to: $proxy")
|
||||||
currentProxy = proxy
|
currentProxy = proxy
|
||||||
|
|
||||||
// recreates singleton
|
// recreates singleton
|
||||||
@@ -117,6 +120,11 @@ object HttpClientManager {
|
|||||||
defaultHttpClientWithoutProxy!!
|
defaultHttpClientWithoutProxy!!
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun setProxyNotReady() {
|
||||||
|
// this blocks all connections unless Orbot is live.
|
||||||
|
setDefaultProxy(DEFAULT_TOR_PROXY)
|
||||||
|
}
|
||||||
|
|
||||||
fun setDefaultProxyOnPort(port: Int) {
|
fun setDefaultProxyOnPort(port: Int) {
|
||||||
setDefaultProxy(Proxy(Proxy.Type.SOCKS, InetSocketAddress("127.0.0.1", port)))
|
setDefaultProxy(Proxy(Proxy.Type.SOCKS, InetSocketAddress("127.0.0.1", port)))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,7 +46,6 @@ import com.vitorpamplona.amethyst.ui.navigation.Route
|
|||||||
import com.vitorpamplona.amethyst.ui.screen.AccountScreen
|
import com.vitorpamplona.amethyst.ui.screen.AccountScreen
|
||||||
import com.vitorpamplona.amethyst.ui.screen.AccountStateViewModel
|
import com.vitorpamplona.amethyst.ui.screen.AccountStateViewModel
|
||||||
import com.vitorpamplona.amethyst.ui.theme.AmethystTheme
|
import com.vitorpamplona.amethyst.ui.theme.AmethystTheme
|
||||||
import com.vitorpamplona.amethyst.ui.tor.TorManager
|
|
||||||
import com.vitorpamplona.quartz.nip04Dm.messages.PrivateDmEvent
|
import com.vitorpamplona.quartz.nip04Dm.messages.PrivateDmEvent
|
||||||
import com.vitorpamplona.quartz.nip19Bech32.Nip19Parser
|
import com.vitorpamplona.quartz.nip19Bech32.Nip19Parser
|
||||||
import com.vitorpamplona.quartz.nip19Bech32.entities.NAddress
|
import com.vitorpamplona.quartz.nip19Bech32.entities.NAddress
|
||||||
@@ -112,8 +111,6 @@ class MainActivity : AppCompatActivity() {
|
|||||||
checkLanguage(locales.get(0).language)
|
checkLanguage(locales.get(0).language)
|
||||||
}
|
}
|
||||||
|
|
||||||
TorManager.startTor(this)
|
|
||||||
|
|
||||||
Log.d("Lifetime Event", "MainActivity.onResume")
|
Log.d("Lifetime Event", "MainActivity.onResume")
|
||||||
|
|
||||||
// starts muted every time
|
// starts muted every time
|
||||||
@@ -160,8 +157,6 @@ class MainActivity : AppCompatActivity() {
|
|||||||
(getSystemService(ConnectivityManager::class.java) as ConnectivityManager)
|
(getSystemService(ConnectivityManager::class.java) as ConnectivityManager)
|
||||||
.unregisterNetworkCallback(networkCallback)
|
.unregisterNetworkCallback(networkCallback)
|
||||||
|
|
||||||
TorManager.stopTor(this)
|
|
||||||
|
|
||||||
super.onPause()
|
super.onPause()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+7
-2
@@ -50,6 +50,7 @@ import androidx.compose.material3.ButtonDefaults
|
|||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.OutlinedButton
|
import androidx.compose.material3.OutlinedButton
|
||||||
|
import androidx.compose.material3.OutlinedTextFieldDefaults.contentPadding
|
||||||
import androidx.compose.material3.Surface
|
import androidx.compose.material3.Surface
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
@@ -246,7 +247,9 @@ private fun DialogContent(
|
|||||||
val writeStoragePermissionState =
|
val writeStoragePermissionState =
|
||||||
rememberPermissionState(Manifest.permission.WRITE_EXTERNAL_STORAGE) { isGranted ->
|
rememberPermissionState(Manifest.permission.WRITE_EXTERNAL_STORAGE) { isGranted ->
|
||||||
if (isGranted) {
|
if (isGranted) {
|
||||||
saveMediaToGallery(myContent, localContext, accountViewModel)
|
scope.launch {
|
||||||
|
saveMediaToGallery(myContent, localContext, accountViewModel)
|
||||||
|
}
|
||||||
scope.launch {
|
scope.launch {
|
||||||
Toast
|
Toast
|
||||||
.makeText(
|
.makeText(
|
||||||
@@ -264,7 +267,9 @@ private fun DialogContent(
|
|||||||
Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q ||
|
Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q ||
|
||||||
writeStoragePermissionState.status.isGranted
|
writeStoragePermissionState.status.isGranted
|
||||||
) {
|
) {
|
||||||
saveMediaToGallery(myContent, localContext, accountViewModel)
|
scope.launch {
|
||||||
|
saveMediaToGallery(myContent, localContext, accountViewModel)
|
||||||
|
}
|
||||||
scope.launch {
|
scope.launch {
|
||||||
Toast
|
Toast
|
||||||
.makeText(
|
.makeText(
|
||||||
|
|||||||
@@ -39,13 +39,16 @@ import androidx.compose.runtime.CompositionLocalProvider
|
|||||||
import androidx.compose.runtime.DisposableEffect
|
import androidx.compose.runtime.DisposableEffect
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.platform.LocalLifecycleOwner
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.lifecycle.Lifecycle
|
import androidx.lifecycle.Lifecycle
|
||||||
import androidx.lifecycle.LifecycleEventObserver
|
import androidx.lifecycle.LifecycleEventObserver
|
||||||
import androidx.lifecycle.ViewModelStore
|
import androidx.lifecycle.ViewModelStore
|
||||||
import androidx.lifecycle.ViewModelStoreOwner
|
import androidx.lifecycle.ViewModelStoreOwner
|
||||||
|
import androidx.lifecycle.compose.LocalLifecycleOwner
|
||||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
import androidx.lifecycle.viewmodel.compose.LocalViewModelStoreOwner
|
import androidx.lifecycle.viewmodel.compose.LocalViewModelStoreOwner
|
||||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
@@ -58,8 +61,13 @@ import com.vitorpamplona.amethyst.ui.navigation.AppNavigation
|
|||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedOff.LoginOrSignupScreen
|
import com.vitorpamplona.amethyst.ui.screen.loggedOff.LoginOrSignupScreen
|
||||||
import com.vitorpamplona.amethyst.ui.stringRes
|
import com.vitorpamplona.amethyst.ui.stringRes
|
||||||
|
import com.vitorpamplona.amethyst.ui.tor.TorManager
|
||||||
|
import com.vitorpamplona.amethyst.ui.tor.TorType
|
||||||
import com.vitorpamplona.quartz.nip55AndroidSigner.NostrSignerExternal
|
import com.vitorpamplona.quartz.nip55AndroidSigner.NostrSignerExternal
|
||||||
import kotlinx.coroutines.CancellationException
|
import kotlinx.coroutines.CancellationException
|
||||||
|
import kotlinx.coroutines.Job
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun AccountScreen(
|
fun AccountScreen(
|
||||||
@@ -136,6 +144,8 @@ fun LoggedInPage(
|
|||||||
accountViewModel.restartServices()
|
accountViewModel.restartServices()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ManageTorInstance(accountViewModel)
|
||||||
|
|
||||||
ListenToExternalSignerIfNeeded(accountViewModel)
|
ListenToExternalSignerIfNeeded(accountViewModel)
|
||||||
|
|
||||||
AppNavigation(
|
AppNavigation(
|
||||||
@@ -145,6 +155,55 @@ fun LoggedInPage(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun ManageTorInstance(accountViewModel: AccountViewModel) {
|
||||||
|
val torSettings by accountViewModel.account.settings.torSettings.torType
|
||||||
|
.collectAsStateWithLifecycle()
|
||||||
|
if (torSettings == TorType.INTERNAL) {
|
||||||
|
ManageTorInstanceInner(accountViewModel)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun ManageTorInstanceInner(accountViewModel: AccountViewModel) {
|
||||||
|
val context = LocalContext.current.applicationContext
|
||||||
|
val lifeCycleOwner = LocalLifecycleOwner.current
|
||||||
|
|
||||||
|
val scope = rememberCoroutineScope()
|
||||||
|
var job = remember<Job?> { null }
|
||||||
|
|
||||||
|
DisposableEffect(key1 = accountViewModel) {
|
||||||
|
job?.cancel()
|
||||||
|
job = null
|
||||||
|
TorManager.startTorIfNotAlreadyOn(context)
|
||||||
|
|
||||||
|
val observer =
|
||||||
|
LifecycleEventObserver { _, event ->
|
||||||
|
when (event) {
|
||||||
|
Lifecycle.Event.ON_RESUME -> {
|
||||||
|
job?.cancel()
|
||||||
|
job = null
|
||||||
|
TorManager.startTorIfNotAlreadyOn(context)
|
||||||
|
}
|
||||||
|
Lifecycle.Event.ON_PAUSE -> {
|
||||||
|
job =
|
||||||
|
scope.launch {
|
||||||
|
delay(5000) // 5 seconds
|
||||||
|
TorManager.stopTor(context)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else -> {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lifeCycleOwner.lifecycle.addObserver(observer)
|
||||||
|
onDispose {
|
||||||
|
lifeCycleOwner.lifecycle.removeObserver(observer)
|
||||||
|
TorManager.stopTor(context)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun ListenToExternalSignerIfNeeded(accountViewModel: AccountViewModel) {
|
private fun ListenToExternalSignerIfNeeded(accountViewModel: AccountViewModel) {
|
||||||
if (accountViewModel.account.signer is NostrSignerExternal) {
|
if (accountViewModel.account.signer is NostrSignerExternal) {
|
||||||
|
|||||||
@@ -30,20 +30,40 @@ import androidx.appcompat.app.AppCompatActivity.BIND_AUTO_CREATE
|
|||||||
import com.vitorpamplona.amethyst.service.okhttp.HttpClientManager
|
import com.vitorpamplona.amethyst.service.okhttp.HttpClientManager
|
||||||
import org.torproject.jni.TorService
|
import org.torproject.jni.TorService
|
||||||
import org.torproject.jni.TorService.LocalBinder
|
import org.torproject.jni.TorService.LocalBinder
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean
|
||||||
|
|
||||||
object TorManager {
|
object TorManager {
|
||||||
|
var runningIntent: Intent? = null
|
||||||
var torService: TorService? = null
|
var torService: TorService? = null
|
||||||
|
|
||||||
|
// To make sure we don't start two services.
|
||||||
|
var isConnectingMutex = AtomicBoolean(false)
|
||||||
|
|
||||||
fun startTorIfNotAlreadyOn(ctx: Context) {
|
fun startTorIfNotAlreadyOn(ctx: Context) {
|
||||||
if (torService == null) {
|
if (runningIntent == null && isConnectingMutex.compareAndSet(false, true)) {
|
||||||
startTor(ctx)
|
try {
|
||||||
|
startTor(ctx)
|
||||||
|
} finally {
|
||||||
|
isConnectingMutex.set(false)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun startTor(ctx: Context) {
|
fun stopTor(ctx: Context) {
|
||||||
|
Log.d("TorManager", "Stopping Tor Service")
|
||||||
|
runningIntent?.let {
|
||||||
|
ctx.stopService(runningIntent)
|
||||||
|
}
|
||||||
|
runningIntent = null
|
||||||
|
torService = null
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun startTor(ctx: Context) {
|
||||||
Log.d("TorManager", "Binding Tor Service")
|
Log.d("TorManager", "Binding Tor Service")
|
||||||
|
val currentIntent = Intent(ctx, TorService::class.java)
|
||||||
|
runningIntent = currentIntent
|
||||||
ctx.bindService(
|
ctx.bindService(
|
||||||
Intent(ctx, TorService::class.java),
|
currentIntent,
|
||||||
object : ServiceConnection {
|
object : ServiceConnection {
|
||||||
override fun onServiceConnected(
|
override fun onServiceConnected(
|
||||||
name: ComponentName,
|
name: ComponentName,
|
||||||
@@ -66,6 +86,7 @@ object TorManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onServiceDisconnected(name: ComponentName) {
|
override fun onServiceDisconnected(name: ComponentName) {
|
||||||
|
runningIntent = null
|
||||||
torService = null
|
torService = null
|
||||||
Log.d("TorManager", "Tor Service Disconected")
|
Log.d("TorManager", "Tor Service Disconected")
|
||||||
}
|
}
|
||||||
@@ -74,12 +95,6 @@ object TorManager {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun stopTor(ctx: Context) {
|
|
||||||
Log.d("TorManager", "Stopping Tor Service")
|
|
||||||
torService = null
|
|
||||||
ctx.stopService(Intent(ctx, TorService::class.java))
|
|
||||||
}
|
|
||||||
|
|
||||||
fun isSocksReady() =
|
fun isSocksReady() =
|
||||||
torService?.let {
|
torService?.let {
|
||||||
it.socksPort > 0
|
it.socksPort > 0
|
||||||
|
|||||||
Reference in New Issue
Block a user