diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 5525eeb9faeca1190920d7d6cc01d5f80c85afe6..b8864cd8bce737c41e397d87eeb7c0926e1db3dc 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -3,6 +3,8 @@
xmlns:tools="http://schemas.android.com/tools"
android:sharedUserId="android.uid.system">
+
+
+
@@ -68,6 +70,8 @@
+
+
{
+ if (isAdminActive(context)) {
+ val mainUI = MainUI(context)
+ mainUI.setPrivateDns(DnsManager.FORCE_UPDATE)
+ }
+ }
else -> super.onReceive(context, intent)
}
}
@@ -81,6 +88,7 @@ class DeviceAdmin : DeviceAdminReceiver() {
val mainUI = MainUI(context)
mainUI.setDefaultRestrictions()
mainUI.setDefaultMessages()
+ context.startService(Intent(context, DnsCheckService::class.java))
}
}
diff --git a/app/src/main/java/foundation/e/parentalcontrol/DnsCheckService.kt b/app/src/main/java/foundation/e/parentalcontrol/DnsCheckService.kt
new file mode 100644
index 0000000000000000000000000000000000000000..b8e0ad7176da74624fd08fef0a29238e9816356f
--- /dev/null
+++ b/app/src/main/java/foundation/e/parentalcontrol/DnsCheckService.kt
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2024 MURENA SAS
+ *
+ * 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 foundation.e.parentalcontrol
+
+import android.app.Service
+import android.content.Context
+import android.content.Intent
+import android.net.ConnectivityManager
+import android.net.Network
+import android.os.IBinder
+import android.util.Log
+import foundation.e.parentalcontrol.utils.Constants
+import foundation.e.parentalcontrol.utils.PrefsUtils
+import foundation.e.parentalcontrol.utils.SystemUtils
+import java.net.InetSocketAddress
+import java.net.Socket
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
+
+class DnsCheckService : Service() {
+ private val tag = "DnsCheckService"
+ private val dnsPort = 53
+
+ private lateinit var connectivityManager: ConnectivityManager
+ private lateinit var networkCallback: ConnectivityManager.NetworkCallback
+
+ override fun onCreate() {
+ super.onCreate()
+
+ // Register the network callback
+ networkCallback =
+ object : ConnectivityManager.NetworkCallback() {
+ override fun onAvailable(network: Network) {
+ CoroutineScope(Dispatchers.IO).launch {
+ delay(5000) // Wait for 5 seconds
+ if (SystemUtils.isNetworkAvailable(this@DnsCheckService)) {
+ val activeDns =
+ when {
+ isDnsReachable(Constants.DEFAULT_DNS_SERVER) ->
+ Constants.DEFAULT_DNS_SERVER
+ isDnsReachable(Constants.FALLBACK_DNS_SERVER) ->
+ Constants.FALLBACK_DNS_SERVER
+ else -> Constants.DEFAULT_DNS_SERVER
+ }
+
+ Log.d(tag, "Active DNS server: $activeDns")
+ resetPrivateDns(activeDns)
+ } else {
+ Log.d(tag, "Internet is not working, no DNS check performed.")
+ resetPrivateDns(Constants.DEFAULT_DNS_SERVER)
+ }
+ }
+ }
+
+ override fun onLost(network: Network) {
+ Log.d(tag, "Network lost, stopping DNS checks.")
+ }
+ }
+
+ connectivityManager = getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
+ connectivityManager.registerDefaultNetworkCallback(networkCallback)
+
+ PrefsUtils.init(this)
+ }
+
+ override fun onDestroy() {
+ super.onDestroy()
+ connectivityManager.unregisterNetworkCallback(networkCallback)
+ }
+
+ override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int = START_STICKY
+
+ private fun resetPrivateDns(dnsHost: String) {
+ PrefsUtils.setDefaultPrivateDns(dnsHost)
+ val intent = Intent(this, DeviceAdmin::class.java)
+ intent.action = Constants.RESET_PRIVATE_DNS
+ this.sendBroadcast(intent)
+ }
+
+ private suspend fun isDnsReachable(host: String, port: Int = dnsPort): Boolean {
+ return withContext(Dispatchers.IO) {
+ try {
+ Socket().use { socket ->
+ val socketAddress = InetSocketAddress(host, port)
+ socket.connect(socketAddress, 2000) // 2-second timeout
+ true
+ }
+ } catch (e: Exception) {
+ e.printStackTrace()
+ false
+ }
+ }
+ }
+
+ override fun onBind(intent: Intent?): IBinder? = null
+}
diff --git a/app/src/main/java/foundation/e/parentalcontrol/data/DnsManager.kt b/app/src/main/java/foundation/e/parentalcontrol/data/DnsManager.kt
new file mode 100644
index 0000000000000000000000000000000000000000..3423df88f872c27a42e33939f51009166b676553
--- /dev/null
+++ b/app/src/main/java/foundation/e/parentalcontrol/data/DnsManager.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2024 MURENA SAS
+ *
+ * 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 foundation.e.parentalcontrol.data
+
+import androidx.annotation.IntDef
+
+class DnsManager {
+
+ companion object {
+ const val DEFAULT = 0
+ const val FORCE_UPDATE = 1
+ }
+
+ @IntDef(DEFAULT, FORCE_UPDATE) @Retention(AnnotationRetention.SOURCE) annotation class DnsMode
+}
diff --git a/app/src/main/java/foundation/e/parentalcontrol/ui/view/MainUI.kt b/app/src/main/java/foundation/e/parentalcontrol/ui/view/MainUI.kt
index b980069d36ca5744df44a677a809c91d2ff033a9..fbff7d813579d9e37e72ea4ba51098dd78ec3dc6 100644
--- a/app/src/main/java/foundation/e/parentalcontrol/ui/view/MainUI.kt
+++ b/app/src/main/java/foundation/e/parentalcontrol/ui/view/MainUI.kt
@@ -36,6 +36,7 @@ import com.android.net.module.util.ConnectivitySettingsUtils
import foundation.e.parentalcontrol.BuildConfig
import foundation.e.parentalcontrol.DeviceAdmin
import foundation.e.parentalcontrol.R
+import foundation.e.parentalcontrol.data.DnsManager
import foundation.e.parentalcontrol.data.LoginStatus
import foundation.e.parentalcontrol.providers.AppLoungeData
import foundation.e.parentalcontrol.ui.buttons.ToggleWithText
@@ -154,10 +155,12 @@ class MainUI(context: Context) {
}
}
- private fun setPrivateDns() {
+ fun setPrivateDns(@DnsManager.DnsMode mode: Int = DnsManager.DEFAULT) {
+ if (!dA.isAdminActive(mContext)) return
+
// Set default private dns
- if (!isThisRestrictionSet(dnsSettingsRestriction)) {
- val cloudflareDnsHostName = mContext.getString(R.string.family_cloudflare_dns_com)
+ if (!isThisRestrictionSet(dnsSettingsRestriction) || mode == DnsManager.FORCE_UPDATE) {
+ val defaultDnsHostName = PrefsUtils.getDefaultPrivateDns()
val currentDnsHostMode =
Settings.Global.getString(
mContext.contentResolver,
@@ -184,7 +187,7 @@ class MainUI(context: Context) {
Settings.Global.putString(
mContext.contentResolver,
ConnectivitySettingsUtils.PRIVATE_DNS_SPECIFIER,
- cloudflareDnsHostName
+ defaultDnsHostName
)
}
@@ -300,30 +303,30 @@ class MainUI(context: Context) {
}
)
- val cloudflareDnsHostName = stringResource(R.string.family_cloudflare_dns_com)
- val cloudflareToggleState = remember {
+ val defaultDnsHostName = Constants.DEFAULT_DNS_SERVER
+ val defaultToggleState = remember {
val isHostname =
Settings.Global.getString(
mContext.contentResolver,
ConnectivitySettingsUtils.PRIVATE_DNS_MODE
) == ConnectivitySettingsUtils.PRIVATE_DNS_MODE_PROVIDER_HOSTNAME_STRING
- val isCloudflareDns =
+ val isDefaultDns =
Settings.Global.getString(
mContext.contentResolver,
ConnectivitySettingsUtils.PRIVATE_DNS_SPECIFIER
- ) == cloudflareDnsHostName
- mutableStateOf(isHostname && isCloudflareDns && dnsSettingsToggleState)
+ ) == defaultDnsHostName
+ mutableStateOf(isHostname && isDefaultDns && dnsSettingsToggleState)
}
ToggleWithText(
- text = stringResource(R.string.block_malware_and_adult_content_with_cloudflare),
- isChecked = cloudflareToggleState.value,
+ text = stringResource(R.string.block_malware_and_adult_content_with_dns),
+ isChecked = defaultToggleState.value,
onCheckedChange = { isActive ->
if (isActive) {
setPrivateDns()
} else {
removePrivateDns()
}
- cloudflareToggleState.value = isActive
+ defaultToggleState.value = isActive
}
)
diff --git a/app/src/main/java/foundation/e/parentalcontrol/utils/Constants.kt b/app/src/main/java/foundation/e/parentalcontrol/utils/Constants.kt
index 320e47b76c9a712fba7885afae6977dc5e379e34..4197c71e4d97dc6e41bd9c979f7f1e9ee3eda53f 100644
--- a/app/src/main/java/foundation/e/parentalcontrol/utils/Constants.kt
+++ b/app/src/main/java/foundation/e/parentalcontrol/utils/Constants.kt
@@ -25,8 +25,10 @@ object Constants {
const val PREF_AGE = "age"
const val PREF_DNS_HOST_MODE = "dns_host_mode"
const val PREF_DNS_HOST_NAME = "dns_host_name"
+ const val PREF_DEFAULT_DNS_HOST_NAME = "dns_default_host_name"
const val RESTART_SERVICE = "foundation.e.parental_control.RESTART_SERVICE"
+ const val RESET_PRIVATE_DNS = "foundation.e.parental_control.RESET_PRIVATE_DNS"
const val AUTHORITY = "foundation.e.parentalcontrol.provider"
const val APP_LOUNGE_PKG = "foundation.e.apps"
@@ -34,4 +36,7 @@ object Constants {
const val REQUEST_GPLAY_LOGIN = "request_gplay_login"
const val ACTION_PARENTAL_CONTROL_APP_LOUNGE_LOGIN =
"foundation.e.parentalcontrol.action.APP_LOUNGE_LOGIN"
+
+ const val DEFAULT_DNS_SERVER = "all.dns.mullvad.net"
+ const val FALLBACK_DNS_SERVER = "kids.dns0.eu"
}
diff --git a/app/src/main/java/foundation/e/parentalcontrol/utils/PrefsUtils.kt b/app/src/main/java/foundation/e/parentalcontrol/utils/PrefsUtils.kt
index b57d62fb6fb3f9ad7407f0d9bf8da0828584ca44..a53ff8a266d0ac7e289be219e430a565ec7146d8 100644
--- a/app/src/main/java/foundation/e/parentalcontrol/utils/PrefsUtils.kt
+++ b/app/src/main/java/foundation/e/parentalcontrol/utils/PrefsUtils.kt
@@ -80,6 +80,17 @@ object PrefsUtils {
return Gson().fromJson(json, type)
}
+ fun getDefaultPrivateDns(): String? {
+ return sharedPreferences.getString(
+ Constants.PREF_DEFAULT_DNS_HOST_NAME,
+ Constants.DEFAULT_DNS_SERVER
+ )
+ }
+
+ fun setDefaultPrivateDns(dns: String) {
+ getEdit().apply { putString(Constants.PREF_DEFAULT_DNS_HOST_NAME, dns) }.apply()
+ }
+
fun clearPassword() {
val editPref = sharedPreferences.edit()
editPref.remove(Constants.PREF_PIN_SET)
diff --git a/app/src/main/java/foundation/e/parentalcontrol/utils/SystemUtils.kt b/app/src/main/java/foundation/e/parentalcontrol/utils/SystemUtils.kt
index bd16f9b7de880987ca4adb19647daedc81e7e4b5..7284ecd2b15db987bb4169507de5986291106823 100644
--- a/app/src/main/java/foundation/e/parentalcontrol/utils/SystemUtils.kt
+++ b/app/src/main/java/foundation/e/parentalcontrol/utils/SystemUtils.kt
@@ -21,6 +21,9 @@ import android.annotation.SuppressLint
import android.content.Context
import android.content.pm.ApplicationInfo
import android.content.pm.PackageManager
+import android.net.ConnectivityManager
+import android.net.Network
+import android.net.NetworkCapabilities
import android.os.SystemProperties
import android.os.UserHandle
import android.provider.Settings
@@ -73,4 +76,22 @@ object SystemUtils {
(app.flags and ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) == 0
}
}
+
+ fun isNetworkAvailable(context: Context): Boolean {
+ val connectivityManager = context.getSystemService(ConnectivityManager::class.java)
+ val activeNetwork: Network? = connectivityManager.activeNetwork
+ val networkCapabilities = connectivityManager.getNetworkCapabilities(activeNetwork)
+ if (
+ networkCapabilities != null &&
+ networkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) &&
+ networkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED)
+ ) {
+ return networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) ||
+ networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET) ||
+ networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_USB) ||
+ networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_VPN) ||
+ networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)
+ }
+ return false
+ }
}
diff --git a/app/src/main/res/values/config.xml b/app/src/main/res/values/config.xml
index 939211153c1e27b96581e79e0002641e53fa6480..cb554895fb71aeb2a7cc1eb98c97877e698e71f7 100644
--- a/app/src/main/res/values/config.xml
+++ b/app/src/main/res/values/config.xml
@@ -1,5 +1,4 @@
- family.cloudflare-dns.com
https://doc.e.foundation/support-topics/parental-control
\ No newline at end of file
diff --git a/app/src/main/res/values/debug_strings.xml b/app/src/main/res/values/debug_strings.xml
index 56d0346259bf3ff91d5464ffe99b5e484d682244..9c2f0b65ca2d45258ea0741c6ba8da60e0c1394c 100644
--- a/app/src/main/res/values/debug_strings.xml
+++ b/app/src/main/res/values/debug_strings.xml
@@ -6,6 +6,6 @@
Disable factory reset device
Disable multiple users
Disable apps rated for adults
- Block malware and adult content with cloudflare
+ Block malware and adult content with dns
Parental Control restarted
\ No newline at end of file