Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Unverified Commit 1c419cd7 authored by Sunik Kupfer's avatar Sunik Kupfer Committed by Ricki Hirner
Browse files

Add setting to ignore VPNs at connection detection (bitfireAT/davx5#356)



* Add setting to ignore VPNs at connection detection

* Minor changes

- move methods to ConnectionUtils to keep SyncWorker class compact
- always use "ignore VPNs" as Boolean
- other minor changes

* Show ignore VPNs setting only below api lvl 23

* Change strings

---------

Co-authored-by: default avatarRicki Hirner <hirner@bitfire.at>
parent 62cca293
Loading
Loading
Loading
Loading
+5 −2
Original line number Diff line number Diff line
@@ -18,6 +18,7 @@ import androidx.work.workDataOf
import at.bitfire.davdroid.R
import at.bitfire.davdroid.TestUtils.workScheduledOrRunningOrSuccessful
import at.bitfire.davdroid.db.Credentials
import at.bitfire.davdroid.network.ConnectionUtils
import at.bitfire.davdroid.settings.AccountSettings
import at.bitfire.davdroid.ui.NotificationUtils
import dagger.hilt.android.testing.HiltAndroidRule
@@ -96,8 +97,9 @@ class SyncWorkerTest {
        val accountSettings = AccountSettings(context, account)
        accountSettings.setSyncWiFiOnly(true)

        mockkObject(ConnectionUtils)
        every { ConnectionUtils.wifiAvailable(any()) } returns true
        mockkObject(SyncWorker.Companion)
        every { SyncWorker.Companion.wifiAvailable(any()) } returns true
        every { SyncWorker.Companion.correctWifiSsid(any(), any()) } returns true

        assertTrue(SyncWorker.wifiConditionsMet(context, accountSettings))
@@ -108,8 +110,9 @@ class SyncWorkerTest {
        val accountSettings = AccountSettings(context, account)
        accountSettings.setSyncWiFiOnly(true)

        mockkObject(ConnectionUtils)
        every { ConnectionUtils.wifiAvailable(any()) } returns false
        mockkObject(SyncWorker.Companion)
        every { SyncWorker.Companion.wifiAvailable(any()) } returns false
        every { SyncWorker.Companion.correctWifiSsid(any(), any()) } returns true

        assertFalse(SyncWorker.wifiConditionsMet(context, accountSettings))
+74 −0
Original line number Diff line number Diff line
/***************************************************************************************************
 * Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details.
 **************************************************************************************************/

package at.bitfire.davdroid.network

import android.content.Context
import android.net.ConnectivityManager
import android.net.NetworkCapabilities
import androidx.annotation.RequiresApi
import androidx.core.content.getSystemService
import at.bitfire.davdroid.log.Logger
import java.util.logging.Level

object ConnectionUtils {

    /**
     * Checks whether we are connected to working WiFi
     */
    internal fun wifiAvailable(context: Context): Boolean {
        val connectivityManager = context.getSystemService<ConnectivityManager>()!!
        connectivityManager.allNetworks.forEach { network ->
            connectivityManager.getNetworkCapabilities(network)?.let { capabilities ->
                if (capabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) &&
                    capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED))
                    return true
            }
        }
        return false
    }

    /**
     * Checks whether we are connected to the Internet.
     *
     * On API 26+ devices, if a VPN is used, WorkManager might start the SyncWorker without an
     * internet connection (because NET_CAPABILITY_VALIDATED is always set for VPN connections).
     * To prevent the start without internet access, we don't check for VPN connections by default
     * (by using [NetworkCapabilities.NET_CAPABILITY_NOT_VPN]).
     *
     * However in special occasions (when syncing over a VPN without validated Internet on the
     * underlying connection) we do not want to exclude VPNs.
     *
     * @param ignoreVpns *true* filters VPN connections in the Internet check; *false* allows them as valid connection
     * @return whether we are connected to the Internet
     */
    @RequiresApi(23)
    internal fun internetAvailable(context: Context, ignoreVpns: Boolean): Boolean {
        val connectivityManager = context.getSystemService<ConnectivityManager>()!!
        return connectivityManager.allNetworks.any { network ->
            Logger.log.log(Level.FINE, "Looking for validated Internet", connectivityManager.getNetworkInfo(network))

            connectivityManager.getNetworkCapabilities(network)?.let { capabilities ->
                if (!capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)) {
                    Logger.log.fine("Missing network capability: INTERNET")
                    return false
                }

                if (!capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED)) {
                    Logger.log.fine("Missing network capability: VALIDATED")
                    return false
                }

                if (ignoreVpns)
                    if (!capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN)) {
                        Logger.log.fine("Missing network capability: NOT_VPN")
                        return false
                    }

                /* return */ true
            } ?: false
        }
    }

}
 No newline at end of file
+24 −13
Original line number Diff line number Diff line
@@ -70,6 +70,7 @@ class AccountSettings(

        const val KEY_WIFI_ONLY = "wifi_only"               // sync on WiFi only (default: false)
        const val KEY_WIFI_ONLY_SSIDS = "wifi_only_ssids"   // restrict sync to specific WiFi SSIDs
        const val KEY_IGNORE_VPNS = "ignore_vpns"           // ignore vpns at connection detection

        /** Time range limitation to the past [in days]. Values:
         *
@@ -343,6 +344,16 @@ class AccountSettings(
    fun setSyncWifiOnlySSIDs(ssids: List<String>?) =
        accountManager.setAndVerifyUserData(account, KEY_WIFI_ONLY_SSIDS, StringUtils.trimToNull(ssids?.joinToString(",")))

    fun getIgnoreVpns(): Boolean =
        when (accountManager.getUserData(account, KEY_IGNORE_VPNS)) {
            null -> settings.getBoolean(KEY_IGNORE_VPNS)
            "0" -> false
            else -> true
        }

    fun setIgnoreVpns(ignoreVpns: Boolean) =
        accountManager.setAndVerifyUserData(account, KEY_IGNORE_VPNS, if (ignoreVpns) "1" else "0")

    /**
     * Updates the periodic sync worker of an authority according to
     *
+2 −1
Original line number Diff line number Diff line
@@ -20,7 +20,8 @@ class DefaultsProvider(

    override val booleanDefaults = mutableMapOf(
        Pair(Settings.DISTRUST_SYSTEM_CERTIFICATES, false),
        Pair(Settings.FORCE_READ_ONLY_ADDRESSBOOKS, false)
        Pair(Settings.FORCE_READ_ONLY_ADDRESSBOOKS, false),
        Pair(Settings.IGNORE_VPN_NETWORK_CAPABILITY, true)
    )

    override val intDefaults = mapOf(
+7 −1
Original line number Diff line number Diff line
@@ -22,7 +22,13 @@ object Settings {
    const val PROXY_PORT = "proxy_port"         // Integer

    /**
     * Default sync interval (long), in seconds.
     * Whether to ignore VPNs at internet connection detection, true by default because VPN connections
     * seem to include "VALIDATED" by default even without actual internet connection
     */
    const val IGNORE_VPN_NETWORK_CAPABILITY = "ignore_vpns"         // Boolean

    /**
     * Default sync interval (Long), in seconds.
     * Used to initialize an account.
     */
    const val DEFAULT_SYNC_INTERVAL = "default_sync_interval"
Loading