Commit 3282ebed authored by Daniel Wolf's avatar Daniel Wolf
Browse files

Translated using Weblate (Dutch)

Currently translated at 96.4% (27 of 28 strings)

Translation: Nebulo/General strings
Translate-URL: https://weblate.frostnerd.com/projects/nebulo/nebulo/nl/
parent 9d214e30
......@@ -54,6 +54,7 @@ A list of some extraordinary people who contributed to this project:
- App icon and notification icon by [RKBDI](http://dribbble.com/rkbdi).
- Turkish translation by Kemal Oktay Aktoğan
- Russian translation by [bruleto](https://t.me/bruleto)
- Dutch translation by Bas Koedijk
<br/>
<br/>
......
......@@ -83,37 +83,34 @@ android {
}
dependencies {
def room_version = "2.1.0-alpha06"
def room_version = "2.1.0"
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.2.1'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.2.1'
implementation 'com.frostnerd.utilskt:preferences:1.5.8'
implementation 'com.frostnerd.utilskt:navigationdraweractivity:1.3.15'
implementation 'com.frostnerd.utilskt:encrypteddnstunnelproxy:1.5.98'
implementation 'com.frostnerd.utilskt:general:1.0.14'
implementation 'com.frostnerd.utilskt:preferences:1.5.9'
implementation 'com.frostnerd.utilskt:navigationdraweractivity:1.3.17'
implementation 'com.frostnerd.utilskt:encrypteddnstunnelproxy:1.5.106'
implementation 'com.frostnerd.utilskt:general:1.0.15'
implementation 'com.frostnerd.utils:materialedittext:1.0.20'
implementation 'com.frostnerd.utils:design:1.0.17'
implementation 'com.frostnerd.utilskt:networking:1.0.2'
implementation 'com.frostnerd.utilskt:adapters:1.0.5'
implementation 'com.frostnerd.utilskt:networking:1.0.3'
implementation 'com.frostnerd.utilskt:adapters:1.0.6'
implementation 'androidx.appcompat:appcompat:1.0.2'
implementation "androidx.preference:preference:1.0.0"
implementation 'androidx.appcompat:appcompat:1.1.0-beta01'
implementation "androidx.preference:preference:1.1.0-beta01"
implementation "androidx.room:room-runtime:2.1.0-beta01"
implementation "androidx.room:room-runtime:$room_version"
kapt "androidx.room:room-compiler:$room_version"
implementation "androidx.room:room-coroutines:2.1.0-alpha04"
testImplementation "androidx.room:room-testing:$room_version"
implementation 'com.github.PhilJay:MPAndroidChart:v3.1.0-alpha'
implementation 'io.sentry:sentry-android:1.7.16'
implementation 'io.sentry:sentry-android:1.7.23'
implementation 'com.github.anrwatchdog:anrwatchdog:1.4.0'
implementation 'androidx.constraintlayout:constraintlayout:2.0.0-beta1'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test:runner:1.1.1'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1'
androidTestImplementation 'androidx.test:runner:1.2.0'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
}
File mode changed from 100644 to 100755
File mode changed from 100644 to 100755
File mode changed from 100644 to 100755
File mode changed from 100644 to 100755
......@@ -17,7 +17,7 @@
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:roundIcon="@mipmap/ic_launcher"
android:supportsRtl="true"
android:theme="@style/AppTheme_Mono"
tools:ignore="GoogleAppIndexingWarning"
......@@ -170,6 +170,11 @@
<action android:name="com.twofortyfouram.locale.intent.action.EDIT_SETTING"/>
</intent-filter>
</activity>
<activity
android:name=".activity.SpeedTestActivity"
android:theme="@style/AppTheme_Mono"
android:label="@string/activity_label_speed_test"
/>
<service
android:name=".service.DnsVpnService"
......
package com.frostnerd.smokescreen.activity
import android.graphics.Color
import android.graphics.Rect
import android.os.Bundle
import android.view.View
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.frostnerd.cacheadapter.AdapterBuilder
import com.frostnerd.design.DesignUtil
import com.frostnerd.dnstunnelproxy.DnsServerInformation
import com.frostnerd.encrypteddnstunnelproxy.AbstractHttpsDNSHandle
import com.frostnerd.encrypteddnstunnelproxy.HttpsDnsServerInformation
import com.frostnerd.encrypteddnstunnelproxy.tls.AbstractTLSDnsHandle
import com.frostnerd.lifecyclemanagement.BaseActivity
import com.frostnerd.lifecyclemanagement.BaseViewHolder
import com.frostnerd.lifecyclemanagement.launchWithLifecylce
import com.frostnerd.smokescreen.R
import com.frostnerd.smokescreen.getPreferences
import com.frostnerd.smokescreen.showInfoTextDialog
import com.frostnerd.smokescreen.util.speedtest.DnsSpeedTest
import kotlinx.android.synthetic.main.activity_speedtest.*
import kotlinx.android.synthetic.main.item_dns_speed.view.*
import kotlinx.coroutines.Job
/*
* Copyright (C) 2019 Daniel Wolf (Ch4t4r)
*
* 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 <http://www.gnu.org/licenses/>.
*
* You can contact the developer at daniel.wolf@frostnerd.com.
*/
class SpeedTestActivity : BaseActivity() {
private var testRunning = false
private var wasStartedBefore = false
private var testJob: Job? = null
private var testResults:MutableList<SpeedTest>? = null
private var listAdapter:RecyclerView.Adapter<*>? = null
private var prepareListJob:Job? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_speedtest)
setSupportActionBar(toolBar)
startTest.setOnClickListener {
startTest()
startTest.isEnabled = false
abort.visibility = View.VISIBLE
info.visibility = View.GONE
}
abort.setOnClickListener {
abort.visibility = View.GONE
testJob?.cancel()
testJob = null
startTest.isEnabled = true
testRunning = false
info.visibility = View.VISIBLE
startTest.text = getString(R.string.window_speedtest_runtest)
}
info.setOnClickListener {
if(testResults != null) {
val dotCount = testResults!!.count { it.server !is HttpsDnsServerInformation }
val dotReachable = testResults!!.count { it.server !is HttpsDnsServerInformation && it.latency != null}
val dotNotReachable = dotCount - dotReachable
val dohCount = testResults!!.size - dotCount
val dohReachable = testResults!!.count { it.server is HttpsDnsServerInformation && it.latency != null}
val dohNotReachable = dohCount - dohReachable
val avgLatency = testResults!!.sumBy { it.latency ?: 0 }/testResults!!.size
val fastestServer = testResults!!.minBy { it.latency ?: Integer.MAX_VALUE}
val slowestServer = testResults!!.maxBy { it.latency ?: 0}
showInfoTextDialog(this,
getString(R.string.dialog_speedresult_title),
getString(R.string.dialog_speedresult_message,
testResults!!.size,
dotReachable,
dotNotReachable,
dohReachable,
dohNotReachable,
avgLatency,
fastestServer?.server?.name ?: "-",
slowestServer?.server?.name ?: "-"
))
}
}
serverList.layoutManager = LinearLayoutManager(this)
serverList.addItemDecoration(SpaceItemDecorator())
prepareList()
}
private fun prepareList() {
prepareListJob = launchWithLifecylce(true) {
val dnsServers = AbstractTLSDnsHandle.KNOWN_DNS_SERVERS.values +
AbstractHttpsDNSHandle.KNOWN_DNS_SERVERS.values +
getPreferences().userServers.map {
it.serverInformation
}
val testResults = dnsServers.map {
SpeedTest(it, null)
}.toMutableList()
this@SpeedTestActivity.testResults = testResults
val showUseServerDialog = { test:SpeedTest ->
showInfoTextDialog(this@SpeedTestActivity,
getString(R.string.dialog_speedtest_useserver_title),
getString(R.string.dialog_speedtest_useserver_message,
test.server.name,
testResults.indexOf(test) + 1,
testResults.size,
test.latency!!
),
getString(R.string.all_yes) to { dialog, _ ->
getPreferences().dnsServerConfig = test.server
dialog.dismiss()
}, getString(R.string.all_no) to { dialog, _ ->
dialog.dismiss()
}, null)
}
listAdapter = AdapterBuilder.withViewHolder({ SpeedViewHolder(it, showUseServerDialog) }) {
viewBuilder = { parent, _ ->
layoutInflater.inflate(R.layout.item_dns_speed, parent, false)
}
getItemCount = {
testResults.size
}
bindView = { viewHolder, position ->
viewHolder.display(testResults[position])
}
}.build()
runOnUiThread {
serverList.adapter = listAdapter
}
prepareListJob = null
}
}
private fun startTest() {
if(wasStartedBefore) prepareList()
testJob = launchWithLifecylce(false) {
prepareListJob?.join()
testRunning = true
wasStartedBefore = true
val testsLeft = testResults!!.shuffled()
var cnt = 0
startTest.text = "0/${testsLeft.size}"
testsLeft.forEach {
if(testJob?.isCancelled == false) {
it.started = true
val res = DnsSpeedTest(it.server, 500, 750).runTest(3)
if (res != null) it.latency = res
else it.error = true
testResults!!.sortBy {
it.latency ?: Integer.MAX_VALUE
}
runOnUiThread {
cnt++
listAdapter!!.notifyDataSetChanged()
startTest.text = "$cnt/${testResults!!.size}"
}
}
}
if(testJob?.isCancelled == false)runOnUiThread {
startTest.isEnabled = true
abort.visibility = View.GONE
startTest.text = getString(R.string.window_speedtest_runtest)
testRunning = false
testJob = null
info.visibility = View.VISIBLE
}
}
}
override fun getConfiguration(): Configuration {
return Configuration.withDefaults()
}
private inner class SpeedViewHolder(view: View, private val showUseServerDialog:(SpeedTest) -> Any) : BaseViewHolder(view) {
val name = view.name
val servers = view.servers
val progress = view.progress
val latency = view.latency
val serverType = view.serverType
val nameWrap = view.nameWrap
private var defaultTextColor = latency.currentTextColor
fun display(speedTest: SpeedTest) {
if(speedTest.latency != null) {
val listener:(View) ->Unit = { showUseServerDialog(speedTest) }
itemView.setOnClickListener(listener)
nameWrap.setOnClickListener(listener)
} else {
itemView.setOnClickListener(null)
nameWrap.setOnClickListener(null)
}
name.text = speedTest.server.name
servers.text = buildString {
speedTest.server.servers.forEach {
append(it.address.formatToString())
append("\n")
}
}
serverType.text = if(speedTest.server is HttpsDnsServerInformation) getString(R.string.tasker_mode_doh)
else getString(R.string.tasker_mode_dot)
if (speedTest.latency == null) {
when {
speedTest.error -> {
latency.text = "- ms"
latency.setTextColor(Color.RED)
progress.visibility = View.INVISIBLE
latency.visibility = View.VISIBLE
}
speedTest.started -> {
progress.visibility = View.VISIBLE
latency.visibility = View.INVISIBLE
}
else -> {
latency.text = "? ms"
latency.visibility = View.VISIBLE
progress.visibility = View.INVISIBLE
latency.setTextColor(defaultTextColor)
}
}
} else {
latency.text = "${speedTest.latency} ms"
latency.setTextColor(defaultTextColor)
progress.visibility = View.INVISIBLE
latency.visibility = View.VISIBLE
}
}
override fun destroy() {}
}
private class SpeedTest(val server: DnsServerInformation<*>, var latency: Int?) {
var error: Boolean = false
var started:Boolean = false
}
private inner class SpaceItemDecorator() : RecyclerView.ItemDecoration() {
private val decorationHeight: Int = DesignUtil.dpToPixels(12f, this@SpeedTestActivity).toInt()
override fun getItemOffsets(outRect: Rect, view: View, parent: RecyclerView, state: RecyclerView.State) {
super.getItemOffsets(outRect, view, parent, state)
val itemPosition = parent.getChildAdapterPosition(view)
val totalCount = parent.adapter!!.itemCount
if (itemPosition >= 0 && itemPosition < totalCount - 1) {
outRect.bottom = decorationHeight
}
}
}
}
\ No newline at end of file
......@@ -18,6 +18,7 @@ import androidx.fragment.app.Fragment
import com.frostnerd.dnstunnelproxy.DnsServerInformation
import com.frostnerd.general.service.isServiceRunning
import com.frostnerd.smokescreen.R
import com.frostnerd.smokescreen.activity.SpeedTestActivity
import com.frostnerd.smokescreen.dialog.ServerChoosalDialog
import com.frostnerd.smokescreen.getPreferences
import com.frostnerd.smokescreen.registerLocalReceiver
......@@ -77,6 +78,9 @@ class MainFragment : Fragment() {
}
updateVpnIndicators()
}
speedTest.setOnClickListener {
startActivity(Intent(context!!, SpeedTestActivity::class.java))
}
vpnStateReceiver = requireContext().registerLocalReceiver(
listOf(
DnsVpnService.BROADCAST_VPN_ACTIVE,
......
......@@ -14,6 +14,7 @@ import androidx.appcompat.app.AlertDialog
import androidx.core.content.FileProvider
import androidx.preference.CheckBoxPreference
import androidx.preference.EditTextPreference
import androidx.preference.Preference
import androidx.preference.PreferenceFragmentCompat
import com.frostnerd.design.dialogs.LoadingDialog
import com.frostnerd.general.isInt
......@@ -93,6 +94,10 @@ class SettingsFragment : PreferenceFragmentCompat() {
)
}
fun findPreference(key:String): Preference {
return super.findPreference<Preference>(key)!!
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
log("Fragment created")
......
......@@ -328,10 +328,12 @@ class DnsVpnService : VpnService(), Runnable {
destroy(false)
pauseNotificationAction?.title = getString(R.string.all_resume)
pauseNotificationAction?.icon = R.drawable.ic_stat_resume
notificationBuilder.setSmallIcon(R.drawable.ic_notification_paused)
} else {
recreateVpn(false, null)
pauseNotificationAction?.title = getString(R.string.all_pause)
pauseNotificationAction?.icon = R.drawable.ic_stat_pause
notificationBuilder.setSmallIcon(R.drawable.ic_mainnotification)
}
updateNotification()
}
......@@ -753,7 +755,7 @@ class DnsVpnService : VpnService(), Runnable {
dnsProxy = SmokeProxy(handle, createProxyBypassHandlers(), createDnsCache(), createQueryLogger())
log("DnsProxy created, creating VPN proxy")
vpnProxy = VPNTunnelProxy(dnsProxy!!, vpnService = this, coroutineScope = CoroutineScope(
newFixedThreadPoolContext(3, "proxy-pool")), logger = object:com.frostnerd.vpntunnelproxy.Logger {
newFixedThreadPoolContext(1, "proxy-pool")), logger = object:com.frostnerd.vpntunnelproxy.Logger {
override fun logException(ex: Exception, terminal: Boolean, level: Level) {
if(terminal) log(ex)
else log(Logger.stacktraceToString(ex), "VPN-LIBRARY, $level")
......
......@@ -221,7 +221,7 @@ class AppSettingsSharedPreferences(context: Context) : AppSettings, SimpleTypedP
override var userServers: Set<UserServerConfiguration> by cache(UserServerConfigurationPreference(
"user_servers"
) { mutableSetOf() }, cacheControl)
override var catchKnownDnsServers: Boolean by booleanPref("catch_known_servers", false)
override var catchKnownDnsServers: Boolean by booleanPref("catch_known_servers", true)
override var dummyDnsAddressIpv4: String by stringPref("dummy_dns_ipv4", "203.0.113.244")
override var dummyDnsAddressIpv6: String by stringPref("dummy_dns_ipv6", "fd21:c5ea:169d:fff1:3418:d688:36c5:e8c2")
override val defaultBypassPackages: Set<String> by cache(restrictedCollection(
......
package com.frostnerd.smokescreen.util.speedtest
import androidx.annotation.IntRange
import com.frostnerd.dnstunnelproxy.DnsServerInformation
import com.frostnerd.dnstunnelproxy.UpstreamAddress
import com.frostnerd.encrypteddnstunnelproxy.*
import com.frostnerd.encrypteddnstunnelproxy.tls.TLSUpstreamAddress
import okhttp3.*
import org.minidns.dnsmessage.DnsMessage
import org.minidns.dnsmessage.Question
import org.minidns.record.Record
import java.io.DataInputStream
import java.io.DataOutputStream
import java.lang.Exception
import java.net.*
import java.time.Duration
import java.time.temporal.TemporalUnit
import java.util.concurrent.TimeUnit
import javax.net.ssl.SSLSocketFactory
import kotlin.random.Random
/*
* Copyright (C) 2019 Daniel Wolf (Ch4t4r)
*
* 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 <http://www.gnu.org/licenses/>.
*
* You can contact the developer at daniel.wolf@frostnerd.com.
*/
class DnsSpeedTest(val server: DnsServerInformation<*>, val connectTimeout: Int = 2500, val readTimeout:Int = 1500) {
private val httpClient by lazy {
OkHttpClient.Builder()
.dns(httpsDnsClient)
.connectTimeout(3, TimeUnit.SECONDS)
.readTimeout(readTimeout.toLong(), TimeUnit.MILLISECONDS)
.build()
}
private val httpsDnsClient by lazy {
PinnedDns((server as HttpsDnsServerInformation).serverConfigurations.values.map {
it.urlCreator.address
})
}
companion object {
val testDomains = listOf("google.com", "frostnerd.com", "amazon.com", "youtube.com", "github.com",
"stackoverflow.com", "stackexchange.com", "spotify.com", "material.io", "reddit.com", "android.com")
}
/**
* @param passes The amount of requests to make
* @return The average response time (in ms)
*/
fun runTest(@IntRange(from = 1) passes: Int): Int? {
println("Running test for ${server.name}")
var ttl = 0
for (i in 0 until passes) {
if (server is HttpsDnsServerInformation) {
server.serverConfigurations.values.forEach {
ttl += testHttps(it) ?: 0
}
} else {
(server as DnsServerInformation<TLSUpstreamAddress>).servers.forEach {
ttl += testTls(it.address) ?: 0
}
}
}
return (ttl / passes).let {
if (it <= 0) null else it
}
}
private fun testHttps(config: ServerConfiguration): Int? {
val msg = createTestDnsPacket()
val url: URL = config.urlCreator.createUrl(msg, config.urlCreator.address)
val requestBuilder = Request.Builder().url(url)
if (config.requestHasBody) {
val body = config.bodyCreator!!.createBody(msg, config.urlCreator.address)
if (body != null) {
requestBuilder.header("Content-Type", config.contentType)
requestBuilder.post(RequestBody.create(body.mediaType, body.rawBody))
} else {
return null
}
}
var response:Response? = null
try {
val start = System.currentTimeMillis()
response = httpClient.newCall(requestBuilder.build()).execute()
if(!response.isSuccessful) return null
val body = response.body() ?: return null
val bytes = body.bytes()
val time = (System.currentTimeMillis() - start).toInt()
if (bytes.size < 17) {
return null
} else if(!testResponse(DnsMessage(bytes))) {
return null
}
return time
} catch (ex: Exception) {
return null
} finally {
if(response?.body() != null) response.close()
}
}
private fun testTls(address: TLSUpstreamAddress): Int? {
val addr =
address.addressCreator.resolveOrGetResultOrNull(retryIfError = true, runResolveNow = true) ?: return null
var socket: Socket? = null
try {
socket = SSLSocketFactory.getDefault().createSocket()
val msg = createTestDnsPacket()
val start = System.currentTimeMillis()
socket.connect(InetSocketAddress(addr[0], address.port), connectTimeout)
socket.soTimeout = readTimeout
val data: ByteArray = msg.toArray()
val outputStream = DataOutputStream(socket.getOutputStream())
val size = data.size
val arr: ByteArray = byteArrayOf(((size shr 8) and 0xFF).toByte(), (size and 0xFF).toByte())
outputStream.write(arr)
outputStream.write(data)
outputStream.flush()
val inStream = DataInputStream(socket.getInputStream())
val readData = ByteArray(inStream.readUnsignedShort())
inStream.read(readData)
val time = (System.currentTimeMillis() - start).toInt()