Repo created

This commit is contained in:
Fr4nz D13trich 2025-11-22 13:56:56 +01:00
parent 75dc487a7a
commit 39c29d175b
6317 changed files with 388324 additions and 2 deletions

View file

@ -0,0 +1,17 @@
plugins {
id(ThunderbirdPlugins.Library.android)
}
android {
namespace = "net.thunderbird.core.android.network"
}
dependencies {
api(projects.core.common)
implementation(projects.core.logging.api)
implementation(projects.core.logging.implLegacy)
testImplementation(projects.core.testing)
testImplementation(libs.robolectric)
}

View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
</manifest>

View file

@ -0,0 +1,25 @@
package net.thunderbird.core.android.network
import android.os.Build
import android.net.ConnectivityManager as SystemConnectivityManager
interface ConnectivityManager {
fun start()
fun stop()
fun isNetworkAvailable(): Boolean
fun addListener(listener: ConnectivityChangeListener)
fun removeListener(listener: ConnectivityChangeListener)
}
interface ConnectivityChangeListener {
fun onConnectivityChanged()
fun onConnectivityLost()
}
internal fun ConnectivityManager(systemConnectivityManager: SystemConnectivityManager): ConnectivityManager {
return when {
Build.VERSION.SDK_INT >= Build.VERSION_CODES.N -> ConnectivityManagerApi24(systemConnectivityManager)
Build.VERSION.SDK_INT >= Build.VERSION_CODES.M -> ConnectivityManagerApi23(systemConnectivityManager)
else -> ConnectivityManagerApi21(systemConnectivityManager)
}
}

View file

@ -0,0 +1,66 @@
package net.thunderbird.core.android.network
import android.net.ConnectivityManager.NetworkCallback
import android.net.Network
import android.net.NetworkRequest
import net.thunderbird.core.logging.legacy.Log
import android.net.ConnectivityManager as SystemConnectivityManager
@Suppress("DEPRECATION")
internal class ConnectivityManagerApi21(
private val systemConnectivityManager: SystemConnectivityManager,
) : ConnectivityManagerBase() {
private var isRunning = false
private var lastNetworkType: Int? = null
private var wasConnected: Boolean? = null
private val networkCallback = object : NetworkCallback() {
override fun onAvailable(network: Network) {
Log.v("Network available: $network")
notifyIfConnectivityHasChanged()
}
override fun onLost(network: Network) {
Log.v("Network lost: $network")
notifyIfConnectivityHasChanged()
}
private fun notifyIfConnectivityHasChanged() {
val networkType = systemConnectivityManager.activeNetworkInfo?.type
val isConnected = isNetworkAvailable()
synchronized(this@ConnectivityManagerApi21) {
if (networkType != lastNetworkType || isConnected != wasConnected) {
lastNetworkType = networkType
wasConnected = isConnected
if (isConnected) {
notifyOnConnectivityChanged()
} else {
notifyOnConnectivityLost()
}
}
}
}
}
@Synchronized
override fun start() {
if (!isRunning) {
isRunning = true
val networkRequest = NetworkRequest.Builder().build()
systemConnectivityManager.registerNetworkCallback(networkRequest, networkCallback)
}
}
@Synchronized
override fun stop() {
if (isRunning) {
isRunning = false
systemConnectivityManager.unregisterNetworkCallback(networkCallback)
}
}
override fun isNetworkAvailable(): Boolean = systemConnectivityManager.activeNetworkInfo?.isConnected == true
}

View file

@ -0,0 +1,73 @@
package net.thunderbird.core.android.network
import android.net.ConnectivityManager.NetworkCallback
import android.net.Network
import android.net.NetworkCapabilities
import android.net.NetworkRequest
import android.os.Build
import androidx.annotation.RequiresApi
import net.thunderbird.core.logging.legacy.Log
import android.net.ConnectivityManager as SystemConnectivityManager
@RequiresApi(Build.VERSION_CODES.M)
internal class ConnectivityManagerApi23(
private val systemConnectivityManager: SystemConnectivityManager,
) : ConnectivityManagerBase() {
private var isRunning = false
private var lastActiveNetwork: Network? = null
private var wasConnected: Boolean? = null
private val networkCallback = object : NetworkCallback() {
override fun onAvailable(network: Network) {
Log.v("Network available: $network")
notifyIfActiveNetworkOrConnectivityHasChanged()
}
override fun onLost(network: Network) {
Log.v("Network lost: $network")
notifyIfActiveNetworkOrConnectivityHasChanged()
}
private fun notifyIfActiveNetworkOrConnectivityHasChanged() {
val activeNetwork = systemConnectivityManager.activeNetwork
val isConnected = isNetworkAvailable()
synchronized(this@ConnectivityManagerApi23) {
if (activeNetwork != lastActiveNetwork || isConnected != wasConnected) {
lastActiveNetwork = activeNetwork
wasConnected = isConnected
if (isConnected) {
notifyOnConnectivityChanged()
} else {
notifyOnConnectivityLost()
}
}
}
}
}
@Synchronized
override fun start() {
if (!isRunning) {
isRunning = true
val networkRequest = NetworkRequest.Builder().build()
systemConnectivityManager.registerNetworkCallback(networkRequest, networkCallback)
}
}
@Synchronized
override fun stop() {
if (isRunning) {
isRunning = false
systemConnectivityManager.unregisterNetworkCallback(networkCallback)
}
}
override fun isNetworkAvailable(): Boolean {
val activeNetwork = systemConnectivityManager.activeNetwork ?: return false
val networkCapabilities = systemConnectivityManager.getNetworkCapabilities(activeNetwork)
return networkCapabilities?.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) == true
}
}

View file

@ -0,0 +1,65 @@
package net.thunderbird.core.android.network
import android.net.ConnectivityManager.NetworkCallback
import android.net.Network
import android.net.NetworkCapabilities
import android.os.Build
import androidx.annotation.RequiresApi
import net.thunderbird.core.logging.legacy.Log
import android.net.ConnectivityManager as SystemConnectivityManager
@RequiresApi(Build.VERSION_CODES.N)
internal class ConnectivityManagerApi24(
private val systemConnectivityManager: SystemConnectivityManager,
) : ConnectivityManagerBase() {
private var isRunning = false
private var isNetworkAvailable: Boolean? = null
private val networkCallback = object : NetworkCallback() {
override fun onAvailable(network: Network) {
Log.v("Network available: $network")
synchronized(this@ConnectivityManagerApi24) {
isNetworkAvailable = true
notifyOnConnectivityChanged()
}
}
override fun onLost(network: Network) {
Log.v("Network lost: $network")
synchronized(this@ConnectivityManagerApi24) {
isNetworkAvailable = false
notifyOnConnectivityLost()
}
}
}
@Synchronized
override fun start() {
if (!isRunning) {
isRunning = true
systemConnectivityManager.registerDefaultNetworkCallback(networkCallback)
}
}
@Synchronized
override fun stop() {
if (isRunning) {
isRunning = false
systemConnectivityManager.unregisterNetworkCallback(networkCallback)
}
}
override fun isNetworkAvailable(): Boolean {
return synchronized(this) { isNetworkAvailable } ?: isNetworkAvailableSynchronous()
}
// Sometimes this will return 'true' even though networkCallback has already received onLost().
// That's why isNetworkAvailable() prefers the state derived from the callbacks over this method.
private fun isNetworkAvailableSynchronous(): Boolean {
val activeNetwork = systemConnectivityManager.activeNetwork ?: return false
val networkCapabilities = systemConnectivityManager.getNetworkCapabilities(activeNetwork)
return networkCapabilities?.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) == true
}
}

View file

@ -0,0 +1,31 @@
package net.thunderbird.core.android.network
import java.util.concurrent.CopyOnWriteArraySet
internal abstract class ConnectivityManagerBase : ConnectivityManager {
private val listeners = CopyOnWriteArraySet<ConnectivityChangeListener>()
@Synchronized
override fun addListener(listener: ConnectivityChangeListener) {
listeners.add(listener)
}
@Synchronized
override fun removeListener(listener: ConnectivityChangeListener) {
listeners.remove(listener)
}
@Synchronized
protected fun notifyOnConnectivityChanged() {
for (listener in listeners) {
listener.onConnectivityChanged()
}
}
@Synchronized
protected fun notifyOnConnectivityLost() {
for (listener in listeners) {
listener.onConnectivityLost()
}
}
}

View file

@ -0,0 +1,10 @@
package net.thunderbird.core.android.network
import android.content.Context
import org.koin.dsl.module
import android.net.ConnectivityManager as SystemConnectivityManager
val coreAndroidNetworkModule = module {
single { get<Context>().getSystemService(Context.CONNECTIVITY_SERVICE) as SystemConnectivityManager }
single { ConnectivityManager(systemConnectivityManager = get()) }
}