summaryrefslogtreecommitdiffhomepage
path: root/android/lib/common/src/main
diff options
context:
space:
mode:
authorAlbin <albin@mullvad.net>2023-07-27 13:56:12 +0200
committerAlbin <albin@mullvad.net>2023-07-27 13:56:12 +0200
commit7a1c9dba446651f06b00c80178407b34120cede9 (patch)
tree72328e2b0966106e46583ff2b2a539f3b8cf27db /android/lib/common/src/main
parent6d57800a24833e53c5cc161d9afb4dffe4381365 (diff)
parentea51297f2a3bbac44351147df78ae4f3ff2bc6e8 (diff)
downloadmullvadvpn-7a1c9dba446651f06b00c80178407b34120cede9.tar.xz
mullvadvpn-7a1c9dba446651f06b00c80178407b34120cede9.zip
Merge branch 'move-tile-service-to-its-own-module-droid-225'
Diffstat (limited to 'android/lib/common/src/main')
-rw-r--r--android/lib/common/src/main/AndroidManifest.xml4
-rw-r--r--android/lib/common/src/main/kotlin/net/mullvad/mullvadvpn/lib/common/util/CommonFlowUtils.kt47
-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/SdkUtils.kt38
4 files changed, 134 insertions, 0 deletions
diff --git a/android/lib/common/src/main/AndroidManifest.xml b/android/lib/common/src/main/AndroidManifest.xml
new file mode 100644
index 0000000000..d2bb18d6f3
--- /dev/null
+++ b/android/lib/common/src/main/AndroidManifest.xml
@@ -0,0 +1,4 @@
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools">
+ <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
+</manifest>
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
new file mode 100644
index 0000000000..42f0663967
--- /dev/null
+++ b/android/lib/common/src/main/kotlin/net/mullvad/mullvadvpn/lib/common/util/CommonFlowUtils.kt
@@ -0,0 +1,47 @@
+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
+
+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.")
+ }
+ }
+ }
+}
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
new file mode 100644
index 0000000000..7fc37a752c
--- /dev/null
+++ b/android/lib/common/src/main/kotlin/net/mullvad/mullvadvpn/lib/common/util/DispatchingFlow.kt
@@ -0,0 +1,45 @@
+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/SdkUtils.kt b/android/lib/common/src/main/kotlin/net/mullvad/mullvadvpn/lib/common/util/SdkUtils.kt
new file mode 100644
index 0000000000..11394ca8dd
--- /dev/null
+++ b/android/lib/common/src/main/kotlin/net/mullvad/mullvadvpn/lib/common/util/SdkUtils.kt
@@ -0,0 +1,38 @@
+package net.mullvad.mullvadvpn.lib.common.util
+
+import android.Manifest
+import android.app.PendingIntent
+import android.content.Context
+import android.content.pm.PackageInfo
+import android.content.pm.PackageManager
+import android.os.Build
+import android.service.quicksettings.Tile
+
+object SdkUtils {
+ fun getSupportedPendingIntentFlags(): Int {
+ return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
+ PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_MUTABLE
+ } else {
+ PendingIntent.FLAG_UPDATE_CURRENT
+ }
+ }
+
+ fun Context.isNotificationPermissionGranted(): Boolean {
+ return (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) ||
+ checkSelfPermission(Manifest.permission.POST_NOTIFICATIONS) ==
+ PackageManager.PERMISSION_GRANTED
+ }
+
+ fun Tile.setSubtitleIfSupported(subtitleText: CharSequence) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
+ this.subtitle = subtitleText
+ }
+ }
+
+ fun PackageManager.getInstalledPackagesList(flags: Int = 0): List<PackageInfo> =
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
+ getInstalledPackages(PackageManager.PackageInfoFlags.of(flags.toLong()))
+ } else {
+ @Suppress("DEPRECATION") getInstalledPackages(flags)
+ }
+}