From c2de88a74014899a96ed74ccc988216dd284cd48 Mon Sep 17 00:00:00 2001 From: Kevin Boulongne Date: Tue, 19 Nov 2024 07:29:30 +0100 Subject: [PATCH 1/2] feat: Add Network module in Core2 --- Core2/Matomo/proguard-rules.pro | 2 +- Core2/Network/build.gradle.kts | 35 +++++ Core2/Network/consumer-rules.pro | 0 Core2/Network/proguard-rules.pro | 21 +++ Core2/Network/src/main/AndroidManifest.xml | 22 +++ .../infomaniak/network/NetworkAvailability.kt | 127 ++++++++++++++++++ app/build.gradle.kts | 1 + settings.gradle.kts | 1 + 8 files changed, 208 insertions(+), 1 deletion(-) create mode 100644 Core2/Network/build.gradle.kts create mode 100644 Core2/Network/consumer-rules.pro create mode 100644 Core2/Network/proguard-rules.pro create mode 100644 Core2/Network/src/main/AndroidManifest.xml create mode 100644 Core2/Network/src/main/java/com/infomaniak/network/NetworkAvailability.kt 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..67baddfd4 --- /dev/null +++ b/Core2/Network/build.gradle.kts @@ -0,0 +1,35 @@ +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(libs.androidx.core.ktx) + implementation(core2.sentry.android) +} 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..53ed43cf6 --- /dev/null +++ b/Core2/Network/src/main/java/com/infomaniak/network/NetworkAvailability.kt @@ -0,0 +1,127 @@ +/* + * 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 io.sentry.Sentry +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.channels.ProducerScope +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 + +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) + sendNetworkAvailability(networks) + } + } + } + + override fun onLost(network: Network) { + launch { + mutex.withLock { + networks.remove(network) + sendNetworkAvailability(networks) + } + } + } + } + + launch(ioDispatcher) { + send(getInitialNetworkAvailability(connectivityManager)) + } + + registerNetworkCallback(connectivityManager, callback) + + awaitClose { unregisterNetworkCallback(connectivityManager, callback) } + } + + private suspend fun ProducerScope.registerNetworkCallback( + connectivityManager: ConnectivityManager, + callback: NetworkCallback, + ) { + 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 + Sentry.captureException(exception) + send(false) + } + } + + private fun unregisterNetworkCallback(connectivityManager: ConnectivityManager, callback: NetworkCallback) { + runCatching { + connectivityManager.unregisterNetworkCallback(callback) + }.onFailure { exception -> + Sentry.captureException(exception) + } + } + + @Suppress("DEPRECATION") + 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 ProducerScope.sendNetworkAvailability(networks: List) { + launch(ioDispatcher) { send(hasAvailableNetwork(networks)) } + } + + 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) = mutex.withLock { networks.any(::hasInternetConnectivity) } + + companion object { + 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") From 574e5133ad631e9e1d87e608fc470e07bfc02972 Mon Sep 17 00:00:00 2001 From: Kevin Boulongne Date: Wed, 27 Nov 2024 08:55:20 +0100 Subject: [PATCH 2/2] fix: Update coroutines usage in NetworkAvailability --- Core2/Network/build.gradle.kts | 3 ++- .../infomaniak/network/NetworkAvailability.kt | 27 ++++++++++--------- 2 files changed, 16 insertions(+), 14 deletions(-) diff --git a/Core2/Network/build.gradle.kts b/Core2/Network/build.gradle.kts index 67baddfd4..5338bfe7b 100644 --- a/Core2/Network/build.gradle.kts +++ b/Core2/Network/build.gradle.kts @@ -30,6 +30,7 @@ android { } dependencies { + implementation(project(":Core2:Sentry")) + implementation(libs.androidx.core.ktx) - implementation(core2.sentry.android) } diff --git a/Core2/Network/src/main/java/com/infomaniak/network/NetworkAvailability.kt b/Core2/Network/src/main/java/com/infomaniak/network/NetworkAvailability.kt index 53ed43cf6..a7a5c7659 100644 --- a/Core2/Network/src/main/java/com/infomaniak/network/NetworkAvailability.kt +++ b/Core2/Network/src/main/java/com/infomaniak/network/NetworkAvailability.kt @@ -24,16 +24,17 @@ 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.ProducerScope 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) { @@ -49,7 +50,7 @@ class NetworkAvailability(private val context: Context, private val ioDispatcher launch { mutex.withLock { networks.add(network) - sendNetworkAvailability(networks) + send(element = hasAvailableNetwork(networks)) } } } @@ -58,7 +59,7 @@ class NetworkAvailability(private val context: Context, private val ioDispatcher launch { mutex.withLock { networks.remove(network) - sendNetworkAvailability(networks) + send(element = hasAvailableNetwork(networks)) } } } @@ -68,21 +69,23 @@ class NetworkAvailability(private val context: Context, private val ioDispatcher send(getInitialNetworkAvailability(connectivityManager)) } - registerNetworkCallback(connectivityManager, callback) + registerNetworkCallback(connectivityManager, callback, ::send) awaitClose { unregisterNetworkCallback(connectivityManager, callback) } } - private suspend fun ProducerScope.registerNetworkCallback( + 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 + // 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) } @@ -96,7 +99,6 @@ class NetworkAvailability(private val context: Context, private val ioDispatcher } } - @Suppress("DEPRECATION") private fun getInitialNetworkAvailability(connectivityManager: ConnectivityManager): Boolean { return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { connectivityManager.activeNetwork?.let(::hasInternetConnectivity) ?: false @@ -105,10 +107,6 @@ class NetworkAvailability(private val context: Context, private val ioDispatcher } } - private fun ProducerScope.sendNetworkAvailability(networks: List) { - launch(ioDispatcher) { send(hasAvailableNetwork(networks)) } - } - private fun networkRequestBuilder(): NetworkRequest { return NetworkRequest.Builder().apply { addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) @@ -119,9 +117,12 @@ class NetworkAvailability(private val context: Context, private val ioDispatcher network.getByName(ROOT_SERVER_URL) != null }.getOrDefault(false) - private suspend fun hasAvailableNetwork(networks: List) = mutex.withLock { networks.any(::hasInternetConnectivity) } + 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" } }