diff --git a/Core2/Matomo/proguard-rules.pro b/Core2/Matomo/proguard-rules.pro
index f1b424510..481bb4348 100644
--- a/Core2/Matomo/proguard-rules.pro
+++ b/Core2/Matomo/proguard-rules.pro
@@ -18,4 +18,4 @@
# If you keep the line number information, uncomment this to
# hide the original source file name.
-#-renamesourcefileattribute SourceFile
+#-renamesourcefileattribute SourceFile
\ No newline at end of file
diff --git a/Core2/Network/build.gradle.kts b/Core2/Network/build.gradle.kts
new file mode 100644
index 000000000..5338bfe7b
--- /dev/null
+++ b/Core2/Network/build.gradle.kts
@@ -0,0 +1,36 @@
+plugins {
+ alias(libs.plugins.android.library)
+ alias(libs.plugins.kotlin.android)
+}
+
+android {
+ namespace = "com.infomaniak.network"
+ compileSdk = 34
+
+ defaultConfig {
+ minSdk = 24
+
+ testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
+ consumerProguardFiles("consumer-rules.pro")
+ }
+
+ buildTypes {
+ release {
+ isMinifyEnabled = false
+ proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
+ }
+ }
+ compileOptions {
+ sourceCompatibility = JavaVersion.VERSION_1_8
+ targetCompatibility = JavaVersion.VERSION_1_8
+ }
+ kotlinOptions {
+ jvmTarget = "1.8"
+ }
+}
+
+dependencies {
+ implementation(project(":Core2:Sentry"))
+
+ implementation(libs.androidx.core.ktx)
+}
diff --git a/Core2/Network/consumer-rules.pro b/Core2/Network/consumer-rules.pro
new file mode 100644
index 000000000..e69de29bb
diff --git a/Core2/Network/proguard-rules.pro b/Core2/Network/proguard-rules.pro
new file mode 100644
index 000000000..481bb4348
--- /dev/null
+++ b/Core2/Network/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
\ No newline at end of file
diff --git a/Core2/Network/src/main/AndroidManifest.xml b/Core2/Network/src/main/AndroidManifest.xml
new file mode 100644
index 000000000..15aa53ac3
--- /dev/null
+++ b/Core2/Network/src/main/AndroidManifest.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
diff --git a/Core2/Network/src/main/java/com/infomaniak/network/NetworkAvailability.kt b/Core2/Network/src/main/java/com/infomaniak/network/NetworkAvailability.kt
new file mode 100644
index 000000000..a7a5c7659
--- /dev/null
+++ b/Core2/Network/src/main/java/com/infomaniak/network/NetworkAvailability.kt
@@ -0,0 +1,128 @@
+/*
+ * Infomaniak Core - Android
+ * Copyright (C) 2024 Infomaniak Network SA
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+package com.infomaniak.network
+
+import android.content.Context
+import android.net.ConnectivityManager
+import android.net.ConnectivityManager.NetworkCallback
+import android.net.Network
+import android.net.NetworkCapabilities
+import android.net.NetworkRequest
+import android.os.Build
+import com.infomaniak.sentry.SentryLog
+import io.sentry.Sentry
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.callbackFlow
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.sync.Mutex
+import kotlinx.coroutines.sync.withLock
+import kotlinx.coroutines.withContext
+
+class NetworkAvailability(private val context: Context, private val ioDispatcher: CoroutineDispatcher = Dispatchers.IO) {
+
+ private val connectivityManager by lazy { context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager }
+ private val mutex = Mutex()
+
+ val isNetworkAvailable: Flow = callbackFlow {
+ val networks = mutableListOf()
+
+ val callback = object : NetworkCallback() {
+
+ override fun onAvailable(network: Network) {
+ launch {
+ mutex.withLock {
+ networks.add(network)
+ send(element = hasAvailableNetwork(networks))
+ }
+ }
+ }
+
+ override fun onLost(network: Network) {
+ launch {
+ mutex.withLock {
+ networks.remove(network)
+ send(element = hasAvailableNetwork(networks))
+ }
+ }
+ }
+ }
+
+ launch(ioDispatcher) {
+ send(getInitialNetworkAvailability(connectivityManager))
+ }
+
+ registerNetworkCallback(connectivityManager, callback, ::send)
+
+ awaitClose { unregisterNetworkCallback(connectivityManager, callback) }
+ }
+
+ private suspend fun registerNetworkCallback(
+ connectivityManager: ConnectivityManager,
+ callback: NetworkCallback,
+ send: suspend (Boolean) -> Unit,
+ ) {
+ runCatching {
+ connectivityManager.registerNetworkCallback(networkRequestBuilder(), callback)
+ }.onFailure { exception ->
+ // Fix potential Exception thrown by ConnectivityManager on Android 11.
+ // Already fixed in Android S and above.
+ // https://issuetracker.google.com/issues/175055271
+ SentryLog.e(TAG, "Android 11 exception", exception)
+ Sentry.captureException(exception)
+ send(false)
+ }
+ }
+
+ private fun unregisterNetworkCallback(connectivityManager: ConnectivityManager, callback: NetworkCallback) {
+ runCatching {
+ connectivityManager.unregisterNetworkCallback(callback)
+ }.onFailure { exception ->
+ Sentry.captureException(exception)
+ }
+ }
+
+ private fun getInitialNetworkAvailability(connectivityManager: ConnectivityManager): Boolean {
+ return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+ connectivityManager.activeNetwork?.let(::hasInternetConnectivity) ?: false
+ } else {
+ connectivityManager.activeNetworkInfo?.isConnected ?: false
+ }
+ }
+
+ private fun networkRequestBuilder(): NetworkRequest {
+ return NetworkRequest.Builder().apply {
+ addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
+ }.build()
+ }
+
+ private fun hasInternetConnectivity(network: Network) = runCatching {
+ network.getByName(ROOT_SERVER_URL) != null
+ }.getOrDefault(false)
+
+ private suspend fun hasAvailableNetwork(networks: List) = withContext(ioDispatcher) {
+ networks.any(::hasInternetConnectivity)
+ }
+
+ companion object {
+ private val TAG = NetworkAvailability::class.java.simpleName
+ private const val ROOT_SERVER_URL = "a.root-servers.net"
+ }
+}
diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index 4f20591e2..112f8c80e 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -84,6 +84,7 @@ dependencies {
implementation(project(":Core2"))
implementation(project(":Core2:Sentry"))
implementation(project(":Core2:Matomo"))
+ implementation(project(":Core2:Network"))
implementation(project(":FileTypes"))
implementation(kotlin("reflect"))
diff --git a/settings.gradle.kts b/settings.gradle.kts
index 57b8ebe0e..5e9ac91e7 100644
--- a/settings.gradle.kts
+++ b/settings.gradle.kts
@@ -32,4 +32,5 @@ include(":app")
include(":Core2")
include(":Core2:Sentry")
include(":Core2:Matomo")
+include(":Core2:Network")
include(":FileTypes")