summaryrefslogtreecommitdiffhomepage
path: root/android/lib/common/src
diff options
context:
space:
mode:
authorDavid Göransson <david.goransson@mullvad.net>2024-05-29 17:18:29 +0200
committerDavid Göransson <david.goransson@mullvad.net>2024-05-29 17:18:29 +0200
commitad90145a5d86d8c1e6e70f2238f11edf5e50f8d8 (patch)
tree9d085bc81caed9409e3a4360490c06c2da4fbba8 /android/lib/common/src
parent8e14a8d4287af66a57a98db79d3ac320c2dad4a1 (diff)
parent767b97eda756f4ec4e67fb5fa2ae664277291e8f (diff)
downloadmullvadvpn-ad90145a5d86d8c1e6e70f2238f11edf5e50f8d8.tar.xz
mullvadvpn-ad90145a5d86d8c1e6e70f2238f11edf5e50f8d8.zip
Merge branch 'android-grpc'
Diffstat (limited to 'android/lib/common/src')
-rw-r--r--android/lib/common/src/main/kotlin/net/mullvad/mullvadvpn/lib/common/constant/ClassNames.kt (renamed from android/lib/common/src/main/kotlin/net/mullvad/mullvadvpn/lib/common/constant/ClassesAndActions.kt)7
-rw-r--r--android/lib/common/src/main/kotlin/net/mullvad/mullvadvpn/lib/common/constant/IntentActions.kt6
-rw-r--r--android/lib/common/src/main/kotlin/net/mullvad/mullvadvpn/lib/common/constant/LogTag.kt3
-rw-r--r--android/lib/common/src/main/kotlin/net/mullvad/mullvadvpn/lib/common/util/CommonFlowUtils.kt46
-rw-r--r--android/lib/common/src/main/kotlin/net/mullvad/mullvadvpn/lib/common/util/ContextExtensions.kt15
-rw-r--r--android/lib/common/src/main/kotlin/net/mullvad/mullvadvpn/lib/common/util/DispatchingFlow.kt45
-rw-r--r--android/lib/common/src/main/kotlin/net/mullvad/mullvadvpn/lib/common/util/ErrorStateExtension.kt10
-rw-r--r--android/lib/common/src/main/kotlin/net/mullvad/mullvadvpn/lib/common/util/Intermittent.kt87
-rw-r--r--android/lib/common/src/main/kotlin/net/mullvad/mullvadvpn/lib/common/util/JobTracker.kt91
9 files changed, 29 insertions, 281 deletions
diff --git a/android/lib/common/src/main/kotlin/net/mullvad/mullvadvpn/lib/common/constant/ClassesAndActions.kt b/android/lib/common/src/main/kotlin/net/mullvad/mullvadvpn/lib/common/constant/ClassNames.kt
index 09210ffa03..1636bbd46f 100644
--- a/android/lib/common/src/main/kotlin/net/mullvad/mullvadvpn/lib/common/constant/ClassesAndActions.kt
+++ b/android/lib/common/src/main/kotlin/net/mullvad/mullvadvpn/lib/common/constant/ClassNames.kt
@@ -2,13 +2,8 @@ package net.mullvad.mullvadvpn.lib.common.constant
// Do not use in cases where the application id is expected since the application id will differ
// between different builds.
-private const val MULLVAD_PACKAGE_NAME = "net.mullvad.mullvadvpn"
+internal const val MULLVAD_PACKAGE_NAME = "net.mullvad.mullvadvpn"
// Classes
const val MAIN_ACTIVITY_CLASS = "$MULLVAD_PACKAGE_NAME.ui.MainActivity"
const val VPN_SERVICE_CLASS = "$MULLVAD_PACKAGE_NAME.service.MullvadVpnService"
-
-// Actions
-const val KEY_CONNECT_ACTION = "$MULLVAD_PACKAGE_NAME.connect_action"
-const val KEY_DISCONNECT_ACTION = "$MULLVAD_PACKAGE_NAME.disconnect_action"
-const val KEY_QUIT_ACTION = "$MULLVAD_PACKAGE_NAME.quit_action"
diff --git a/android/lib/common/src/main/kotlin/net/mullvad/mullvadvpn/lib/common/constant/IntentActions.kt b/android/lib/common/src/main/kotlin/net/mullvad/mullvadvpn/lib/common/constant/IntentActions.kt
new file mode 100644
index 0000000000..ea420f2d0a
--- /dev/null
+++ b/android/lib/common/src/main/kotlin/net/mullvad/mullvadvpn/lib/common/constant/IntentActions.kt
@@ -0,0 +1,6 @@
+package net.mullvad.mullvadvpn.lib.common.constant
+
+// Actions
+const val KEY_CONNECT_ACTION = "$MULLVAD_PACKAGE_NAME.connect_action"
+const val KEY_DISCONNECT_ACTION = "$MULLVAD_PACKAGE_NAME.disconnect_action"
+const val KEY_REQUEST_VPN_PERMISSION = "$MULLVAD_PACKAGE_NAME.request_vpn_permission"
diff --git a/android/lib/common/src/main/kotlin/net/mullvad/mullvadvpn/lib/common/constant/LogTag.kt b/android/lib/common/src/main/kotlin/net/mullvad/mullvadvpn/lib/common/constant/LogTag.kt
new file mode 100644
index 0000000000..d2ae3f1871
--- /dev/null
+++ b/android/lib/common/src/main/kotlin/net/mullvad/mullvadvpn/lib/common/constant/LogTag.kt
@@ -0,0 +1,3 @@
+package net.mullvad.mullvadvpn.lib.common.constant
+
+const val TAG = "mullvad"
diff --git a/android/lib/common/src/main/kotlin/net/mullvad/mullvadvpn/lib/common/util/CommonFlowUtils.kt b/android/lib/common/src/main/kotlin/net/mullvad/mullvadvpn/lib/common/util/CommonFlowUtils.kt
index 42f0663967..bf94c80778 100644
--- a/android/lib/common/src/main/kotlin/net/mullvad/mullvadvpn/lib/common/util/CommonFlowUtils.kt
+++ b/android/lib/common/src/main/kotlin/net/mullvad/mullvadvpn/lib/common/util/CommonFlowUtils.kt
@@ -1,47 +1,9 @@
package net.mullvad.mullvadvpn.lib.common.util
-import android.content.ComponentName
-import android.content.Context
-import android.content.Intent
-import android.content.ServiceConnection
-import android.os.IBinder
-import android.util.Log
-import kotlin.coroutines.EmptyCoroutineContext
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.channels.SendChannel
-import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.callbackFlow
-import net.mullvad.mullvadvpn.model.ServiceResult
+import kotlinx.coroutines.flow.firstOrNull
+import kotlinx.coroutines.withTimeoutOrNull
-fun <T> SendChannel<T>.safeOffer(element: T): Boolean {
- return runCatching { trySend(element).isSuccess }.getOrDefault(false)
-}
-
-fun Context.bindServiceFlow(intent: Intent, flags: Int = 0): Flow<ServiceResult> = callbackFlow {
- val connectionCallback =
- object : ServiceConnection {
- override fun onServiceConnected(className: ComponentName, binder: IBinder) {
- safeOffer(ServiceResult(binder))
- }
-
- override fun onServiceDisconnected(className: ComponentName) {
- safeOffer(ServiceResult.NOT_CONNECTED)
- bindService(intent, this, flags)
- }
- }
-
- bindService(intent, connectionCallback, flags)
-
- awaitClose {
- safeOffer(ServiceResult.NOT_CONNECTED)
-
- Dispatchers.Default.dispatch(EmptyCoroutineContext) {
- try {
- unbindService(connectionCallback)
- } catch (e: IllegalArgumentException) {
- Log.e("mullvad", "Cannot unbind as no binding exists.")
- }
- }
- }
+suspend fun <T> Flow<T>.firstOrNullWithTimeout(timeMillis: Long): T? {
+ return withTimeoutOrNull(timeMillis) { firstOrNull() }
}
diff --git a/android/lib/common/src/main/kotlin/net/mullvad/mullvadvpn/lib/common/util/ContextExtensions.kt b/android/lib/common/src/main/kotlin/net/mullvad/mullvadvpn/lib/common/util/ContextExtensions.kt
index 8ef70dad92..d714dae327 100644
--- a/android/lib/common/src/main/kotlin/net/mullvad/mullvadvpn/lib/common/util/ContextExtensions.kt
+++ b/android/lib/common/src/main/kotlin/net/mullvad/mullvadvpn/lib/common/util/ContextExtensions.kt
@@ -4,15 +4,20 @@ import android.content.Context
import android.content.Intent
import android.net.Uri
import android.provider.Settings
-import net.mullvad.mullvadvpn.lib.common.R
import net.mullvad.mullvadvpn.lib.common.util.SdkUtils.getInstalledPackagesList
+import net.mullvad.mullvadvpn.lib.model.WebsiteAuthToken
private const val ALWAYS_ON_VPN_APP = "always_on_vpn_app"
-fun Context.openAccountPageInBrowser(authToken: String) {
- startActivity(
- Intent(Intent.ACTION_VIEW, Uri.parse(getString(R.string.account_url) + "?token=$authToken"))
- )
+fun createAccountUri(accountUri: String, websiteAuthToken: WebsiteAuthToken?): Uri {
+ val urlString = buildString {
+ append(accountUri)
+ if (websiteAuthToken != null) {
+ append("?token=")
+ append(websiteAuthToken.value)
+ }
+ }
+ return Uri.parse(urlString)
}
fun Context.getAlwaysOnVpnAppName(): String? {
diff --git a/android/lib/common/src/main/kotlin/net/mullvad/mullvadvpn/lib/common/util/DispatchingFlow.kt b/android/lib/common/src/main/kotlin/net/mullvad/mullvadvpn/lib/common/util/DispatchingFlow.kt
deleted file mode 100644
index 7fc37a752c..0000000000
--- a/android/lib/common/src/main/kotlin/net/mullvad/mullvadvpn/lib/common/util/DispatchingFlow.kt
+++ /dev/null
@@ -1,45 +0,0 @@
-package net.mullvad.mullvadvpn.lib.common.util
-
-import java.util.concurrent.ConcurrentHashMap
-import kotlin.reflect.KClass
-import kotlinx.coroutines.InternalCoroutinesApi
-import kotlinx.coroutines.channels.Channel
-import kotlinx.coroutines.channels.ClosedSendChannelException
-import kotlinx.coroutines.channels.SendChannel
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.FlowCollector
-import kotlinx.coroutines.flow.consumeAsFlow
-
-class DispatchingFlow<T : Any>(private val upstream: Flow<T>) : Flow<T> {
- private val subscribers = ConcurrentHashMap<KClass<out T>, SendChannel<T>>()
-
- fun <V : T> subscribe(variant: KClass<V>, capacity: Int = Channel.CONFLATED): Flow<V> {
- val channel = Channel<V>(capacity)
-
- // This is safe because `collect` will only send to this channel if the instance class is V
- @Suppress("UNCHECKED_CAST")
- subscribers[variant] = channel as SendChannel<T>
-
- return channel.consumeAsFlow()
- }
-
- fun <V : T> unsubscribe(variant: KClass<V>) = subscribers.remove(variant)
-
- @InternalCoroutinesApi
- override suspend fun collect(collector: FlowCollector<T>) {
- upstream.collect { event ->
- try {
- subscribers[event::class]?.send(event)
- } catch (closedException: ClosedSendChannelException) {
- subscribers.remove(event::class)
- }
-
- collector.emit(event)
- }
-
- subscribers.clear()
- }
-}
-
-fun <T : Any> Flow<T>.dispatchTo(configureSubscribers: DispatchingFlow<T>.() -> Unit) =
- DispatchingFlow(this).also(configureSubscribers)
diff --git a/android/lib/common/src/main/kotlin/net/mullvad/mullvadvpn/lib/common/util/ErrorStateExtension.kt b/android/lib/common/src/main/kotlin/net/mullvad/mullvadvpn/lib/common/util/ErrorStateExtension.kt
index f906ee8f6d..2c9554a842 100644
--- a/android/lib/common/src/main/kotlin/net/mullvad/mullvadvpn/lib/common/util/ErrorStateExtension.kt
+++ b/android/lib/common/src/main/kotlin/net/mullvad/mullvadvpn/lib/common/util/ErrorStateExtension.kt
@@ -2,9 +2,9 @@ package net.mullvad.mullvadvpn.lib.common.util
import android.content.Context
import net.mullvad.mullvadvpn.lib.common.R
-import net.mullvad.talpid.tunnel.ErrorState
-import net.mullvad.talpid.tunnel.ErrorStateCause
-import net.mullvad.talpid.tunnel.ParameterGenerationError
+import net.mullvad.mullvadvpn.lib.model.ErrorState
+import net.mullvad.mullvadvpn.lib.model.ErrorStateCause
+import net.mullvad.mullvadvpn.lib.model.ParameterGenerationError
import net.mullvad.talpid.util.addressString
fun ErrorState.getErrorNotificationResources(context: Context): ErrorNotificationMessage {
@@ -48,8 +48,8 @@ fun ErrorStateCause.errorMessageId(): Int {
is ErrorStateCause.InvalidDnsServers -> R.string.invalid_dns_servers
is ErrorStateCause.AuthFailed -> R.string.auth_failed
is ErrorStateCause.Ipv6Unavailable -> R.string.ipv6_unavailable
- is ErrorStateCause.SetFirewallPolicyError -> R.string.set_firewall_policy_error
- is ErrorStateCause.SetDnsError -> R.string.set_dns_error
+ is ErrorStateCause.FirewallPolicyError -> R.string.set_firewall_policy_error
+ is ErrorStateCause.DnsError -> R.string.set_dns_error
is ErrorStateCause.StartTunnelError -> R.string.start_tunnel_error
is ErrorStateCause.IsOffline -> R.string.is_offline
is ErrorStateCause.TunnelParameterError -> {
diff --git a/android/lib/common/src/main/kotlin/net/mullvad/mullvadvpn/lib/common/util/Intermittent.kt b/android/lib/common/src/main/kotlin/net/mullvad/mullvadvpn/lib/common/util/Intermittent.kt
deleted file mode 100644
index 448d96778f..0000000000
--- a/android/lib/common/src/main/kotlin/net/mullvad/mullvadvpn/lib/common/util/Intermittent.kt
+++ /dev/null
@@ -1,87 +0,0 @@
-package net.mullvad.mullvadvpn.lib.common.util
-
-import kotlin.properties.Delegates.observable
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.GlobalScope
-import kotlinx.coroutines.Job
-import kotlinx.coroutines.launch
-import kotlinx.coroutines.sync.Mutex
-import kotlinx.coroutines.sync.Semaphore
-import kotlinx.coroutines.sync.withLock
-import kotlinx.coroutines.sync.withPermit
-import net.mullvad.talpid.util.EventNotifier
-
-// Wrapper to allow awaiting for intermittent values.
-//
-// Wraps a property that is changed from time to time and that can become unavailable (null). This
-// behaves in a way similar to `CompletableDeferred`, but the value can be set and reset multiple
-// times.
-//
-// Calling `await` will either provide the value if it's available, or suspend until it becomes
-// available and then return it.
-//
-// Calling `update` will set the internal value after it guarantees that no other coroutine is
-// currently reading the value (through a permit from the semaphore). After the value is set, it
-// provides a permit to the semaphore so that suspended coroutines can use the new value.
-//
-// Extra initialization can be done on the intermittent value when it becomes available and before
-// it is provided to the awaiting coroutines, through the use of listener callbacks. These are
-// called after the value is updated but before it is made available to the coroutines.
-class Intermittent<T> {
- private val notifier = EventNotifier<T?>(null)
- private val semaphore = Semaphore(1, 1)
- private val writeLock = Mutex()
-
- private var updateJob: Job? = null
- private var value by notifier.notifiable()
-
- // When the internal value is updated, listeners can be notified before the awaiting coroutines
- // resume execution. This allows performing any extra initialization before the value is made
- // available for usage.
- fun registerListener(id: Any, listener: (T?) -> Unit) = notifier.subscribe(id, listener)
-
- fun unregisterListener(id: Any) = notifier.unsubscribe(id)
-
- suspend fun await(): T {
- return semaphore.withPermit { value!! }
- }
-
- suspend fun update(newValue: T?) {
- writeLock.withLock {
- if (newValue != value) {
- if (value != null) {
- semaphore.acquire()
- }
-
- // This will trigger the listeners to run before the awaiting coroutines resume
- value = newValue
-
- if (newValue != null) {
- semaphore.release()
- }
- }
- }
- }
-
- // Helper method that spawns a coroutine to update the value.
- fun spawnUpdate(newValue: T?) {
- synchronized(this@Intermittent) {
- val previousUpdate = updateJob
-
- updateJob =
- GlobalScope.launch(Dispatchers.Default) {
- previousUpdate?.join()
- update(newValue)
- }
- }
- }
-
- // Helper method that provides a simple way to change the wrapped value.
- // The method returns a property delegate that will spawn a coroutine to update the wrapped
- // value every time the property is written to.
- fun source() = observable<T?>(null) { _, _, newValue -> spawnUpdate(newValue) }
-
- fun onDestroy() {
- notifier.unsubscribeAll()
- }
-}
diff --git a/android/lib/common/src/main/kotlin/net/mullvad/mullvadvpn/lib/common/util/JobTracker.kt b/android/lib/common/src/main/kotlin/net/mullvad/mullvadvpn/lib/common/util/JobTracker.kt
deleted file mode 100644
index edb76ed4ae..0000000000
--- a/android/lib/common/src/main/kotlin/net/mullvad/mullvadvpn/lib/common/util/JobTracker.kt
+++ /dev/null
@@ -1,91 +0,0 @@
-package net.mullvad.mullvadvpn.lib.common.util
-
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.GlobalScope
-import kotlinx.coroutines.Job
-import kotlinx.coroutines.async
-import kotlinx.coroutines.launch
-
-class JobTracker {
- private val jobs = HashMap<Long, Job>()
- private val reaperJobs = HashMap<Long, Job>()
- private val namedJobs = HashMap<String, Long>()
-
- private var jobIdCounter = 0L
-
- fun newJob(job: Job): Long {
- synchronized(jobs) {
- val jobId = jobIdCounter
-
- jobIdCounter += 1
-
- jobs.put(jobId, job)
-
- reaperJobs.put(
- jobId,
- GlobalScope.launch(Dispatchers.Default) {
- job.join()
-
- synchronized(jobs) { jobs.remove(jobId) }
- }
- )
-
- return jobId
- }
- }
-
- fun newJob(name: String, job: Job): Long {
- synchronized(namedJobs) {
- cancelJob(name)
-
- val newJobId = newJob(job)
-
- namedJobs.put(name, newJobId)
-
- return newJobId
- }
- }
-
- fun newBackgroundJob(name: String, jobBody: suspend () -> Unit): Long {
- return newJob(name, GlobalScope.launch(Dispatchers.Default) { jobBody() })
- }
-
- fun newUiJob(name: String, jobBody: suspend () -> Unit): Long {
- return newJob(name, GlobalScope.launch(Dispatchers.Main) { jobBody() })
- }
-
- suspend fun <T> runOnBackground(jobBody: suspend () -> T): T {
- val job = GlobalScope.async(Dispatchers.Default) { jobBody() }
-
- newJob(job)
-
- return job.await()
- }
-
- fun cancelJob(name: String) {
- synchronized(namedJobs) { namedJobs.remove(name)?.let { oldJobId -> cancelJob(oldJobId) } }
- }
-
- fun cancelJob(jobId: Long) {
- synchronized(jobs) {
- jobs.remove(jobId)?.cancel()
- reaperJobs.remove(jobId)?.cancel()
- }
- }
-
- fun cancelAllJobs() {
- synchronized(jobs) {
- for (job in jobs.values) {
- job.cancel()
- }
-
- for (job in reaperJobs.values) {
- job.cancel()
- }
-
- jobs.clear()
- reaperJobs.clear()
- namedJobs.clear()
- }
- }
-}