Commit 99d4a671 authored by Daniel Wolf's avatar Daniel Wolf
Browse files

Merge branch 'master' of https://git.frostnerd.com/PublicAndroidApps/smokescreen into translations

parents a4d06d7e 7805cb24
...@@ -25,13 +25,13 @@ android { ...@@ -25,13 +25,13 @@ android {
applicationId "com.frostnerd.smokescreen" applicationId "com.frostnerd.smokescreen"
minSdkVersion 21 minSdkVersion 21
targetSdkVersion 29 targetSdkVersion 29
versionCode 55 versionCode 57
versionName "1.2.1" versionName "1.2.2"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
buildConfigField("Boolean", "FROM_CI", String.valueOf(getSystemVariableOrDefault("CI_COMMIT_SHORT_SHA", "") != "")) buildConfigField("Boolean", "FROM_CI", String.valueOf(getSystemVariableOrDefault("CI_COMMIT_SHORT_SHA", "") != ""))
buildConfigField("String", "COMMIT_HASH", '"' + getGitCommitHash() + '"') buildConfigField("String", "COMMIT_HASH", '"' + getGitCommitHash() + '"')
buildConfigField("String", "SENTRY_DSN", '"' + getSystemVariableOrDefault("SENTRY_DSN", "dummy") + '"') buildConfigField("String", "SENTRY_DSN", '"https://fadeddb58abf408db50809922bf064cc@sentry.frostnerd.com:443/2"')
kapt { kapt {
arguments { arguments {
...@@ -124,7 +124,7 @@ dependencies { ...@@ -124,7 +124,7 @@ dependencies {
implementation 'com.frostnerd.utilskt:preferences:1.5.17' // https://git.frostnerd.com/AndroidUtils/preferenceskt implementation 'com.frostnerd.utilskt:preferences:1.5.17' // https://git.frostnerd.com/AndroidUtils/preferenceskt
implementation 'com.frostnerd.utilskt:navigationdraweractivity:1.3.29' // https://git.frostnerd.com/AndroidUtils/navigationdraweractivity implementation 'com.frostnerd.utilskt:navigationdraweractivity:1.3.29' // https://git.frostnerd.com/AndroidUtils/navigationdraweractivity
implementation 'com.frostnerd.utilskt:encrypteddnstunnelproxy:1.5.178' // https://git.frostnerd.com/AndroidUtils/encrypteddnstunnelproxy implementation 'com.frostnerd.utilskt:encrypteddnstunnelproxy:1.5.181' // https://git.frostnerd.com/AndroidUtils/encrypteddnstunnelproxy
implementation 'com.frostnerd.utilskt:general:1.0.19' // https://git.frostnerd.com/AndroidUtils/generalkt implementation 'com.frostnerd.utilskt:general:1.0.19' // https://git.frostnerd.com/AndroidUtils/generalkt
implementation 'com.frostnerd.utilskt:adapters:1.1.6' // https://git.frostnerd.com/AndroidUtils/Adapters implementation 'com.frostnerd.utilskt:adapters:1.1.6' // https://git.frostnerd.com/AndroidUtils/Adapters
......
...@@ -30,7 +30,10 @@ import com.frostnerd.smokescreen.util.LanguageContextWrapper ...@@ -30,7 +30,10 @@ import com.frostnerd.smokescreen.util.LanguageContextWrapper
class SettingsActivity : BaseActivity() { class SettingsActivity : BaseActivity() {
companion object { companion object {
private var category:Category? = null
fun showCategory(context: Context, category: Category) { fun showCategory(context: Context, category: Category) {
this.category = category
Intent(context, SettingsActivity::class.java).apply { Intent(context, SettingsActivity::class.java).apply {
putExtra("category", category) putExtra("category", category)
}.also { }.also {
...@@ -52,7 +55,7 @@ class SettingsActivity : BaseActivity() { ...@@ -52,7 +55,7 @@ class SettingsActivity : BaseActivity() {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
supportFragmentManager.beginTransaction().replace(android.R.id.content, SettingsFragment().apply { supportFragmentManager.beginTransaction().replace(android.R.id.content, SettingsFragment().apply {
arguments = Bundle().apply { arguments = Bundle().apply {
putSerializable("category", intent.getSerializableExtra("category")!!) putSerializable("category", intent.getSerializableExtra("category") ?: category ?: Category.GENERAL)
} }
}).commit() }).commit()
} }
......
...@@ -55,7 +55,11 @@ class BatteryOptimizationInfoDialog(context: Context) : ...@@ -55,7 +55,11 @@ class BatteryOptimizationInfoDialog(context: Context) :
context.getString(R.string.menu_settings) context.getString(R.string.menu_settings)
) { dialog, _ -> ) { dialog, _ ->
dialog.dismiss() dialog.dismiss()
context.startActivity(settingsIntent) try {
context.startActivity(settingsIntent)
} catch (e: SecurityException) {
Toast.makeText(context, R.string.error_cannot_open_settings, Toast.LENGTH_LONG).show()
}
} }
} else { } else {
setButton( setButton(
......
...@@ -286,14 +286,14 @@ class NewServerDialog( ...@@ -286,14 +286,14 @@ class NewServerDialog(
private fun addUrlTextWatcher(input: TextInputLayout, editText: TextInputEditText, emptyAllowed: Boolean) { private fun addUrlTextWatcher(input: TextInputLayout, editText: TextInputEditText, emptyAllowed: Boolean) {
editText.addTextChangedListener(object : TextWatcher { editText.addTextChangedListener(object : TextWatcher {
override fun afterTextChanged(s: Editable) { override fun afterTextChanged(s: Editable) {
val valid = var valid = (emptyAllowed && s.isBlank())
(emptyAllowed && s.isBlank()) || (dnsOverHttps && SERVER_URL_REGEX.matches(s.toString())) || (!dnsOverHttps && TLS_REGEX.matches( valid = valid || (!s.isBlank() && dnsOverHttps && SERVER_URL_REGEX.matches(s.toString()))
s.toString() valid = valid || (!s.isBlank() && !dnsOverHttps && TLS_REGEX.matches(s.toString()))
))
input.error = if (valid) { input.error = if (valid) {
null null
} else context.getString(R.string.error_invalid_url) } else if(dnsOverHttps) context.getString(R.string.error_invalid_url)
else context.getString(R.string.error_invalid_host)
} }
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) { override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {
......
...@@ -225,19 +225,19 @@ class DnsRuleFragment : Fragment() { ...@@ -225,19 +225,19 @@ class DnsRuleFragment : Fragment() {
notifyRulesChanged() notifyRulesChanged()
}, editSource = { hostSource -> }, editSource = { hostSource ->
NewHostSourceDialog(context!!, onSourceCreated = { newSource -> NewHostSourceDialog(context!!, onSourceCreated = { newSource ->
getDatabase().hostSourceDao().findById(hostSource.id)?.apply {
val currentSource = getDatabase().hostSourceDao().findById(hostSource.id)!!.apply {
this.name = newSource.name this.name = newSource.name
this.source = newSource.source this.source = newSource.source
this.whitelistSource = newSource.whitelistSource this.whitelistSource = newSource.whitelistSource
} }?.also {
getDatabase().hostSourceDao().update(currentSource) getDatabase().hostSourceDao().update(it)
val index = sourceAdapterList.indexOf(hostSource).takeIf { it >= 0 } ?: sourceAdapterList.indexOfFirst { it.id == hostSource.id } val index = sourceAdapterList.indexOf(hostSource).takeIf { it >= 0 } ?: sourceAdapterList.indexOfFirst { it.id == hostSource.id }
sourceAdapterList[index] = currentSource sourceAdapterList[index] = it
sourceRuleCount[currentSource] = sourceRuleCount[hostSource] sourceRuleCount[it] = sourceRuleCount[hostSource]
sourceRuleCount.remove(hostSource) sourceRuleCount.remove(hostSource)
sourceAdapter.notifyItemChanged(index) sourceAdapter.notifyItemChanged(index)
}
}, showFileChooser = { callback -> }, showFileChooser = { callback ->
fileChosenCallback = callback fileChosenCallback = callback
startActivityForResult(Intent(Intent.ACTION_OPEN_DOCUMENT).apply { startActivityForResult(Intent(Intent.ACTION_OPEN_DOCUMENT).apply {
......
...@@ -3,10 +3,7 @@ package com.frostnerd.smokescreen.service ...@@ -3,10 +3,7 @@ package com.frostnerd.smokescreen.service
import android.app.NotificationManager import android.app.NotificationManager
import android.app.PendingIntent import android.app.PendingIntent
import android.app.Service import android.app.Service
import android.content.BroadcastReceiver import android.content.*
import android.content.Context
import android.content.Intent
import android.content.SharedPreferences
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.net.* import android.net.*
import android.os.* import android.os.*
...@@ -98,6 +95,8 @@ class DnsVpnService : VpnService(), Runnable { ...@@ -98,6 +95,8 @@ class DnsVpnService : VpnService(), Runnable {
private var lastVPNStopTime:Long? = null private var lastVPNStopTime:Long? = null
private val coroutineScope:CoroutineContext = SupervisorJob() private val coroutineScope:CoroutineContext = SupervisorJob()
private var queryCount = 0 private var queryCount = 0
private var dnsCache:SimpleDnsCache? = null
private var localResolver:LocalResolver? = null
private val addressResolveScope:CoroutineScope by lazy { private val addressResolveScope:CoroutineScope by lazy {
CoroutineScope(newSingleThreadContext("service-resolve-retry")) CoroutineScope(newSingleThreadContext("service-resolve-retry"))
} }
...@@ -525,6 +524,15 @@ class DnsVpnService : VpnService(), Runnable { ...@@ -525,6 +524,15 @@ class DnsVpnService : VpnService(), Runnable {
return if (destroyed) Service.START_NOT_STICKY else Service.START_STICKY return if (destroyed) Service.START_NOT_STICKY else Service.START_STICKY
} }
override fun onTrimMemory(level: Int) {
if(level >= ComponentCallbacks2.TRIM_MEMORY_BACKGROUND ||
level == ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL ||
level == ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW) {
dnsCache?.clear()
localResolver?.cleanup()
}
}
private fun setServerConfiguration(intent: Intent?) { private fun setServerConfiguration(intent: Intent?) {
log("Updating server configuration..") log("Updating server configuration..")
userServerConfig = BackgroundVpnConfigureActivity.readServerInfoFromIntent(intent) userServerConfig = BackgroundVpnConfigureActivity.readServerInfoFromIntent(intent)
...@@ -734,6 +742,17 @@ class DnsVpnService : VpnService(), Runnable { ...@@ -734,6 +742,17 @@ class DnsVpnService : VpnService(), Runnable {
Handler(Looper.getMainLooper()).postDelayed({ Handler(Looper.getMainLooper()).postDelayed({
BackgroundVpnConfigureActivity.prepareVpn(this, userServerConfig) BackgroundVpnConfigureActivity.prepareVpn(this, userServerConfig)
}, 250) }, 250)
} else if(getPreferences().showNotificationOnRevoked){
NotificationCompat.Builder(this, Notifications.getHighPriorityChannelId(this)).apply {
setSmallIcon(R.drawable.ic_cloud_warn)
setContentTitle(getString(R.string.notification_service_revoked_title))
setContentText(getString(R.string.notification_service_revoked_message))
setContentIntent(PendingIntent.getActivity(this@DnsVpnService, RequestCodes.RESTART_AFTER_REVOKE, Intent(this@DnsVpnService, BackgroundVpnConfigureActivity::class.java), PendingIntent.FLAG_CANCEL_CURRENT))
setAutoCancel(true)
priority = NotificationCompat.PRIORITY_HIGH
}.build().also {
(getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager).notify(Notifications.ID_SERVICE_REVOKED, it)
}
} }
} }
...@@ -1020,12 +1039,15 @@ class DnsVpnService : VpnService(), Runnable { ...@@ -1020,12 +1039,15 @@ class DnsVpnService : VpnService(), Runnable {
} }
log("Creating DNS proxy with ${1 + handles.size} handles") log("Creating DNS proxy with ${1 + handles.size} handles")
dnsCache = createDnsCache()
localResolver = createLocalResolver()
dnsProxy = SmokeProxy( dnsProxy = SmokeProxy(
defaultHandle!!, defaultHandle!!,
handles + createProxyBypassHandlers(), handles + createProxyBypassHandlers(),
createDnsCache(), dnsCache,
createQueryLogger(), createQueryLogger(),
createLocalResolver() localResolver
) )
log("DnsProxy created, creating VPN proxy") log("DnsProxy created, creating VPN proxy")
vpnProxy = RetryingVPNTunnelProxy(dnsProxy!!, vpnService = this, coroutineScope = CoroutineScope( vpnProxy = RetryingVPNTunnelProxy(dnsProxy!!, vpnService = this, coroutineScope = CoroutineScope(
...@@ -1043,6 +1065,7 @@ class DnsVpnService : VpnService(), Runnable { ...@@ -1043,6 +1065,7 @@ class DnsVpnService : VpnService(), Runnable {
log("VPN proxy started.") log("VPN proxy started.")
currentTrafficStats = vpnProxy?.trafficStats currentTrafficStats = vpnProxy?.trafficStats
LocalBroadcastManager.getInstance(this).sendBroadcast(Intent(BROADCAST_VPN_ACTIVE)) LocalBroadcastManager.getInstance(this).sendBroadcast(Intent(BROADCAST_VPN_ACTIVE))
(getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager).cancel(Notifications.ID_SERVICE_REVOKED)
} }
private fun createQueryLogger(): QueryListener? { private fun createQueryLogger(): QueryListener? {
......
...@@ -36,6 +36,7 @@ class Notifications { ...@@ -36,6 +36,7 @@ class Notifications {
const val ID_DNSRULE_EXPORT_FINISHED = 7 const val ID_DNSRULE_EXPORT_FINISHED = 7
const val ID_PIN = 8 const val ID_PIN = 8
const val ID_SERVICE_KILLED = 9 const val ID_SERVICE_KILLED = 9
const val ID_SERVICE_REVOKED = 10
const val ID_VPN_RESTART = 999 const val ID_VPN_RESTART = 999
...@@ -104,6 +105,23 @@ class Notifications { ...@@ -104,6 +105,23 @@ class Notifications {
} }
return "pinchannel" return "pinchannel"
} }
fun getHighPriorityChannelId(context: Context):String {
if(android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
val channel = NotificationChannel(
"high_priority",
context.getString(R.string.notification_channel_high_priorty),
NotificationManager.IMPORTANCE_HIGH
)
channel.enableLights(true)
channel.enableVibration(true)
channel.description = context.getString(R.string.notification_channel_high_priorty)
channel.lockscreenVisibility = Notification.VISIBILITY_PUBLIC
channel.setBypassDnd(true)
(context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager).createNotificationChannel(channel)
}
return "high_priority"
}
} }
} }
...@@ -120,4 +138,5 @@ object RequestCodes { ...@@ -120,4 +138,5 @@ object RequestCodes {
val RULE_EXPORT_ABORT = 8 val RULE_EXPORT_ABORT = 8
val PIN_NOTIFICATION = 9 val PIN_NOTIFICATION = 9
val REQUEST_CODE_IGNORE_SERVICE_KILLED = 10 val REQUEST_CODE_IGNORE_SERVICE_KILLED = 10
val RESTART_AFTER_REVOKE = 11
} }
\ No newline at end of file
...@@ -182,6 +182,7 @@ class AppSettingsSharedPreferences(context: Context) : AppSettings, SimpleTypedP ...@@ -182,6 +182,7 @@ class AppSettingsSharedPreferences(context: Context) : AppSettings, SimpleTypedP
override var hideNotificationIcon: Boolean by booleanPref("hide_notification_icon", false) override var hideNotificationIcon: Boolean by booleanPref("hide_notification_icon", false)
override var allowPauseInNotification: Boolean by booleanPref("notification_allow_pause", true) override var allowPauseInNotification: Boolean by booleanPref("notification_allow_pause", true)
override var allowStopInNotification: Boolean by booleanPref("notification_allow_stop", true) override var allowStopInNotification: Boolean by booleanPref("notification_allow_stop", true)
var showNotificationOnRevoked:Boolean by booleanPref("show_vpn_revoked_notification", true)
override var enablePin:Boolean by booleanPref("enable_pin", false) override var enablePin:Boolean by booleanPref("enable_pin", false)
override var allowFingerprintForPin:Boolean by booleanPref("pin_allow_fingerprint", true) override var allowFingerprintForPin:Boolean by booleanPref("pin_allow_fingerprint", true)
......
...@@ -236,7 +236,13 @@ class DnsRuleResolver(context: Context) : LocalResolver(false) { ...@@ -236,7 +236,13 @@ class DnsRuleResolver(context: Context) : LocalResolver(false) {
} ?: throw IllegalStateException() } ?: throw IllegalStateException()
} }
override fun cleanup() {} override fun cleanup() {
cachedWildcardWhitelisted.clear()
cachedNonWildcardWhitelisted.clear()
cachedResolved.clear()
cachedWildcardResolved.clear()
cachedNonIncluded.clear()
}
// Handle CNAME Cloaking // Handle CNAME Cloaking
// Does not need to handle whitelist as the query has already been forwarded // Does not need to handle whitelist as the query has already been forwarded
......
...@@ -365,5 +365,14 @@ ...@@ -365,5 +365,14 @@
<item>@string/changelog_build_31_4</item> <item>@string/changelog_build_31_4</item>
</string-array> </string-array>
<string name="changelog_build_58_1">The primary host/url could be empty when adding a server</string>
<string name="changelog_build_58_2">A notification is now shown when Nebulo loses permission to the VPN</string>
<string-array name="changelog_build_58">
<item>Version 1.2.3 (Build 58)</item>
<item>@string/changelog_build_58_1</item>
<item>@string/changelog_build_58_2</item>
<item>@string/changelog_build_31_4</item>
</string-array>
<string name="changelog_build_55_4">The query log now loads the queries in chunks instead of all at once</string> <string name="changelog_build_55_4">The query log now loads the queries in chunks instead of all at once</string>
</resources> </resources>
...@@ -12,6 +12,9 @@ ...@@ -12,6 +12,9 @@
<string name="notification_channel_pin">Pin-related channel</string> <string name="notification_channel_pin">Pin-related channel</string>
<string name="notification_channel_pin_description">Channel used for notifications related to the pin, i.e. when it has to be provided</string> <string name="notification_channel_pin_description">Channel used for notifications related to the pin, i.e. when it has to be provided</string>
<string name="notification_channel_high_priorty">High priority</string>
<string name="notification_channel_high_priorty_description">Channel for high priority notifications for example when the app loses permission to the VPN</string>
<string name="notification_simple_text">Current server: %1$s</string> <string name="notification_simple_text">Current server: %1$s</string>
<string name="notification_main_text">Queries are sent to %1$s\n\n%2$d Apps are excluded from DoH/DoT\n%3$d entries in the cache</string> <string name="notification_main_text">Queries are sent to %1$s\n\n%2$d Apps are excluded from DoH/DoT\n%3$d entries in the cache</string>
<string name="notification_main_text_with_secondary">Queries are sent to %1$s and %2$s\n\n%3$d Apps are excluded from DoH/DoT\n%4$d entries in the cache</string> <string name="notification_main_text_with_secondary">Queries are sent to %1$s and %2$s\n\n%3$d Apps are excluded from DoH/DoT\n%4$d entries in the cache</string>
...@@ -51,4 +54,7 @@ ...@@ -51,4 +54,7 @@
<string name="notification_service_killed_title">Information about battery optimization</string> <string name="notification_service_killed_title">Information about battery optimization</string>
<string name="notification_service_killed_message">It seems like the app was previously stopped by the system. Click here to learn more.</string> <string name="notification_service_killed_message">It seems like the app was previously stopped by the system. Click here to learn more.</string>
<string name="notification_service_killed_ignore">Ignore</string> <string name="notification_service_killed_ignore">Ignore</string>
<string name="notification_service_revoked_title">VPN permission revoked by system</string>
<string name="notification_service_revoked_message">Click here to restart</string>
</resources> </resources>
\ No newline at end of file
...@@ -36,6 +36,12 @@ ...@@ -36,6 +36,12 @@
<string name="title_notification_allow_pause">Allow pausing</string> <string name="title_notification_allow_pause">Allow pausing</string>
<string name="summary_notification_allow_pause">Allow the app to be paused from the notification</string> <string name="summary_notification_allow_pause">Allow the app to be paused from the notification</string>
<string name="title_show_noconnection_notification">Notification on no connection</string>
<string name="summary_show_noconnection_notification">Show a notification when the app has no or a bad connection</string>
<string name="title_show_revoked_notification">Notification on permission lost</string>
<string name="summary_show_revoked_notification">Show a notification when Nebulo loses permission to the VPN</string>
<string name="preference_category_pin">PIN protection</string> <string name="preference_category_pin">PIN protection</string>
...@@ -134,9 +140,6 @@ ...@@ -134,9 +140,6 @@
<string name="title_pause_on_captiveportal">Pause on captive portal</string> <string name="title_pause_on_captiveportal">Pause on captive portal</string>
<string name="summary_pause_on_captiveportal">Don\'t use DoH if a captive portal is detected. It will automatically be resumed shortly after logging in.\nDisabling it might prevent you from opening the captive portal site to log in.</string> <string name="summary_pause_on_captiveportal">Don\'t use DoH if a captive portal is detected. It will automatically be resumed shortly after logging in.\nDisabling it might prevent you from opening the captive portal site to log in.</string>
<string name="title_show_noconnection_notification">Notification on no connection</string>
<string name="summary_show_noconnection_notification">Show a notification when the app has no or a bad connection</string>
<string name="preference_category_querylogging">Query logging</string> <string name="preference_category_querylogging">Query logging</string>
......
...@@ -18,9 +18,11 @@ ...@@ -18,9 +18,11 @@
<string name="all_close">Close</string> <string name="all_close">Close</string>
<string name="error_invalid_url">Please provide a valid url.</string> <string name="error_invalid_url">Please provide a valid url.</string>
<string name="error_invalid_host">Please provide a valid host.</string>
<string name="error_invalid_pin">Please provide your PIN.</string> <string name="error_invalid_pin">Please provide your PIN.</string>
<string name="error_invalid_servername">Please provide a name.</string> <string name="error_invalid_servername">Please provide a name.</string>
<string name="error_webview_missing">WebView package not found, is it currently updating or removed?</string> <string name="error_webview_missing">WebView package not found, is it currently updating or removed?</string>
<string name="error_cannot_open_settings">Cannot open system settings.</string>
<string name="main_dnssurveillance_infotext">Be aware that the DNS server you are connected to is able to record which sites you are visiting. Check the respective privacy statements for more information.</string> <string name="main_dnssurveillance_infotext">Be aware that the DNS server you are connected to is able to record which sites you are visiting. Check the respective privacy statements for more information.</string>
<string name="main_dnssurveillance_privacystatement">Privacy statement of %1s</string> <string name="main_dnssurveillance_privacystatement">Privacy statement of %1s</string>
......
...@@ -37,5 +37,11 @@ ...@@ -37,5 +37,11 @@
android:key="show_no_connection_notification" android:key="show_no_connection_notification"
android:summary="@string/summary_show_noconnection_notification" android:summary="@string/summary_show_noconnection_notification"
android:title="@string/title_show_noconnection_notification" /> android:title="@string/title_show_noconnection_notification" />
<CheckBoxPreference
android:defaultValue="true"
android:key="show_vpn_revoked_notification"
android:summary="@string/summary_show_revoked_notification"
android:title="@string/title_show_revoked_notification" />
</PreferenceCategory> </PreferenceCategory>
</PreferenceScreen> </PreferenceScreen>
\ No newline at end of file
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment