AppSettings.kt 14.1 KB
Newer Older
Daniel Wolf's avatar
Daniel Wolf committed
1
2
3
package com.frostnerd.smokescreen.util.preferences

import android.content.Context
4
import android.content.SharedPreferences
Daniel Wolf's avatar
Daniel Wolf committed
5
import com.frostnerd.dnstunnelproxy.DnsServerInformation
Daniel Wolf's avatar
Daniel Wolf committed
6
import com.frostnerd.encrypteddnstunnelproxy.AbstractHttpsDNSHandle
Daniel Wolf's avatar
Daniel Wolf committed
7
8
import com.frostnerd.preferenceskt.restrictedpreferences.restrictedCollection
import com.frostnerd.preferenceskt.typedpreferences.SimpleTypedPreferences
Daniel Wolf's avatar
Daniel Wolf committed
9
import com.frostnerd.preferenceskt.typedpreferences.buildMigration
10
11
import com.frostnerd.preferenceskt.typedpreferences.cache.ExpirationCacheControl
import com.frostnerd.preferenceskt.typedpreferences.cache.buildCacheStrategy
12
import com.frostnerd.preferenceskt.typedpreferences.types.*
Daniel Wolf's avatar
Daniel Wolf committed
13
import com.frostnerd.smokescreen.BuildConfig
14
import com.frostnerd.smokescreen.dialog.HostSourceRefreshDialog
15
import java.text.NumberFormat
16
import java.util.*
Daniel Wolf's avatar
Daniel Wolf committed
17

Daniel Wolf's avatar
Daniel Wolf committed
18
19
/*
 * Copyright (C) 2019 Daniel Wolf (Ch4t4r)
Daniel Wolf's avatar
Daniel Wolf committed
20
 *
Daniel Wolf's avatar
Daniel Wolf committed
21
22
23
24
25
26
27
28
29
30
31
32
33
34
 * 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.
Daniel Wolf's avatar
Daniel Wolf committed
35
36
37
38
 */
interface AppSettings {
    companion object {
        internal var instance: AppSettingsSharedPreferences? = null
39
40
41
        val isReleaseVersion = BuildConfig.VERSION_NAME.let {
            !it.contains("alpha", true) && !it.contains("beta", true)
        }
Daniel Wolf's avatar
Daniel Wolf committed
42
43
44
        @Suppress("unused")
        val isBetaVersion = BuildConfig.VERSION_NAME.contains("beta", true)
        val isAlphaVersion = BuildConfig.VERSION_NAME.contains("alpha", true)
Daniel Wolf's avatar
Daniel Wolf committed
45
    }
46
47
    val cacheControl:ExpirationCacheControl

48
49
50

    var catchKnownDnsServers: Boolean
    val defaultBypassPackages: Set<String>
Daniel Wolf's avatar
Daniel Wolf committed
51
    var dnsServerConfig: DnsServerInformation<*>
52
    var userServers: Set<UserServerConfiguration>
53
    var crashReportingConsent:Boolean
54
    var crashReportingConsentAsked:Boolean
55
    var crashReportingUUID:String
56
    var lastLogId:Int
57

58
59
60
    // ###### Settings (in order)
    // No Category
    var theme: Theme
61
    var startAppOnBoot: Boolean
62
63
    var startAppAfterUpdate: Boolean
    var userBypassPackages: MutableSet<String>
64
    var isBypassBlacklist: Boolean
65

66
    // Notification category
67
68
    var showNotificationOnLockscreen: Boolean
    var hideNotificationIcon: Boolean
69
70
    var allowPauseInNotification:Boolean
    var allowStopInNotification:Boolean
71

72
73
74
    // Pin category
    var enablePin:Boolean
    var allowFingerprintForPin:Boolean
75
    var pin: String
76

77
78
    // Cache category
    var useDnsCache: Boolean
79
80
    var keepDnsCacheAcrossLaunches: Boolean
    var maxCacheSize: Int
81
    var useDefaultDnsCacheTime: Boolean
82
    var minimumCacheTime:Int
83
    var customDnsCacheTime: Int
84
    var nxDomainCacheTime:Int
85
86

    // Logging category
87
    var loggingEnabled: Boolean
88

89
90

    // IP category
91
92
    var enableIpv6: Boolean
    var enableIpv4: Boolean
93
    var forceIpv6: Boolean
94
    var forceIpv4: Boolean
95
96
97
98
99
100
    var allowIpv6Traffic:Boolean
    var allowIpv4Traffic:Boolean

    // Network category
    var disallowOtherVpns: Boolean

101
    var bypassSearchdomains: Boolean
102
    var pauseOnCaptivePortal:Boolean
103
104

    // Query logging category
105
    var queryLoggingEnabled: Boolean
106
107
    // ###### End of settings

108

109
    var hasRatedApp: Boolean
110
111
    var previousInstalledVersion:Int // Maintained in ChangelogDialog
    var showChangelog:Boolean // Maintained in ChangelogDialog
112
    var exportedQueryCount:Int
113
114
    var totalAppLaunches:Int
    var askedForGroupJoin:Boolean
115

Daniel Wolf's avatar
Daniel Wolf committed
116
    fun addUserServerConfiguration(info:DnsServerInformation<*>):UserServerConfiguration {
117
118
119
120
121
122
123
124
125
126
127
        var max = 0
        for (server in userServers) {
            if (server.id >= max) max = server.id + 1
        }
        val mutableServers = userServers.toMutableSet()
        val config = UserServerConfiguration(max, info)
        mutableServers.add(config)
        userServers = mutableServers
        return config
    }

Daniel Wolf's avatar
Daniel Wolf committed
128
    fun addUserServerConfiguration(infos:List<DnsServerInformation<*>>) {
129
130
131
132
133
134
135
136
137
138
139
140
        var max = 0
        for (server in userServers) {
            if (server.id >= max) max = server.id + 1
        }
        val mutableServers = userServers.toMutableSet()
        for (info in infos) {
            val config = UserServerConfiguration(max++, info)
            mutableServers.add(config)
        }
        userServers = mutableServers
    }

141
142
143
144
145
146
147
148
    fun removeUserServerConfiguration(config:UserServerConfiguration) {
        val mutableServers = userServers.toMutableSet()
        val iterator = mutableServers.iterator()
        for (configuration in iterator) {
            if(config.id == configuration.id) iterator.remove()
        }
        userServers = mutableServers
    }
Daniel Wolf's avatar
Daniel Wolf committed
149

Daniel Wolf's avatar
Daniel Wolf committed
150
    fun shouldShowAppIntro() = totalAppLaunches == 0 && BuildConfig.SHOW_INTRO
Daniel Wolf's avatar
Daniel Wolf committed
151
152
}

Daniel Wolf's avatar
Daniel Wolf committed
153
class AppSettingsSharedPreferences(context: Context) : AppSettings, SimpleTypedPreferences(context, version = 1, migrate = migration) {
154
155
156
157
158
    override val cacheControl: ExpirationCacheControl = ExpirationCacheControl(buildCacheStrategy {
        this.allKeys {
            neverExpires()
        }
    })
159
160
161
    init {
        filterForeignKeys = false
    }
162

163
164
    var lastCrashTimeStamp:Long? by longPref("last_crash_timestamp")

165
    override var hasRatedApp: Boolean by booleanPref("has_rated_app", false)
166
    var hasAskedRateApp:Boolean by booleanPref("asked_rate_app", false)
167
    override var previousInstalledVersion:Int by nonOptionalOf(intPref("previous_version"),true, BuildConfig.VERSION_CODE)
168
    override var showChangelog:Boolean by booleanPref("show_changelog", true)
169
    override var exportedQueryCount:Int by intPref("exported_query_count", 0)
170
    override var crashReportingConsent: Boolean by booleanPref("sentry_consent", false)
171
    override var crashReportingConsentAsked: Boolean by booleanPref("sentry_consent_asked", false)
172
    override var crashReportingUUID: String by nonOptionalOf(stringPref("sentry_id"), true, UUID.randomUUID().toString())
173
174
    override var totalAppLaunches: Int by intPref("total_app_launches", 0)
    override var askedForGroupJoin: Boolean by booleanPref("asked_group_join", false)
175
    override var lastLogId: Int by intPref("last_log_id", 0)
176

177
    var language:String by stringPref("language", "auto")
Daniel Wolf's avatar
Daniel Wolf committed
178
    @Suppress("MemberVisibilityCanBePrivate")
179
180
181
182
183
184
185
186
187
    val languageAsLocale: Locale
        get() {
            if(language == "auto") return Locale.getDefault()
            val country = language.split("_").getOrNull(1)
            return if (country != null) Locale(
                language.split("_")[0],
                country
            ) else Locale(language)
        }
Daniel Wolf's avatar
Daniel Wolf committed
188
    @Suppress("MemberVisibilityCanBePrivate")
189
190
191
    val numberFormatter by lazy(mode = LazyThreadSafetyMode.NONE) {
        NumberFormat.getInstance(languageAsLocale)
    }
192
    override var theme: Theme by ThemePreference("theme", Theme.MONO)
193
    override var startAppOnBoot: Boolean by booleanPref("start_on_boot", true)
194
    override var startAppAfterUpdate: Boolean by booleanPref("start_after_update", true)
195
    override var userBypassPackages by mutableStringSetPref("user_bypass_packages", mutableSetOf("com.android.vending", "ch.threema.app.work", "ch.threema.app"))
196
    override var isBypassBlacklist: Boolean by booleanPref("user_bypass_blacklist", true)
197
    var fallbackDns: DnsServerInformation<*>? by cache(DnsServerInformationPreference("fallback_dns_server"), cacheControl)
198

199
    override var showNotificationOnLockscreen: Boolean by booleanPref("show_notification_on_lockscreen", true)
200
    var simpleNotification:Boolean by booleanPref("simple_notification", false)
201
    override var hideNotificationIcon: Boolean by booleanPref("hide_notification_icon", false)
202
203
    override var allowPauseInNotification: Boolean by booleanPref("notification_allow_pause", true)
    override var allowStopInNotification: Boolean by booleanPref("notification_allow_stop", true)
204
    var showNotificationOnRevoked:Boolean by booleanPref("show_vpn_revoked_notification", true)
205

206
207
    override var enablePin:Boolean by booleanPref("enable_pin", false)
    override var allowFingerprintForPin:Boolean by booleanPref("pin_allow_fingerprint", true)
208
    override var pin: String by stringPref("pin", "1234")
209
    override var useDnsCache: Boolean by booleanPref("dnscache_enabled", false)
210
    override var keepDnsCacheAcrossLaunches: Boolean by booleanPref("dnscache_keepacrosslaunches", false)
211
    override var maxCacheSize: Int by stringBasedIntPref("dnscache_maxsize", 1000)
212
    override var useDefaultDnsCacheTime: Boolean by booleanPref("dnscache_use_default_time", true)
213
    override var minimumCacheTime: Int by stringBasedIntPref("dnscache_minimum_time", 10)
214
    override var customDnsCacheTime: Int by stringBasedIntPref("dnscache_custom_time", 100)
215
    override var nxDomainCacheTime: Int by stringBasedIntPref("dnscache_nxdomain_cachetime", 1800)
216
217
    override var loggingEnabled: Boolean by booleanPref(
        "logging_enabled",
218
        AppSettings.isAlphaVersion || BuildConfig.DEBUG
219
    )
Daniel Wolf's avatar
Daniel Wolf committed
220
    fun shouldLogDnsQueriesToConsole():Boolean = loggingEnabled && (!AppSettings.isReleaseVersion || advancedLogging || BuildConfig.DEBUG)
221
222
223
224
    var advancedLogging:Boolean by booleanPref(
        "advanced_logging",
        false
    )
225
226
227
228
    private var oldCrashreportSetting: Boolean by booleanPref("enable_sentry", false)
    var crashreportingType: Crashreporting by CrashReportingPreferenceWithDefault("crashreporting_type") {
        if (oldCrashreportSetting) Crashreporting.FULL else Crashreporting.MINIMAL
    }
229

230
231
232
233
    override var enableIpv6: Boolean by booleanPref("ipv6_enabled", true)
    override var enableIpv4: Boolean by booleanPref("ipv4_enabled", true)
    override var forceIpv6: Boolean by booleanPref("force_ipv6", false)
    override var forceIpv4: Boolean by booleanPref("force_ipv4", false)
234
235
236
237
238
    override var allowIpv4Traffic: Boolean by booleanPref("allow_ipv4_traffic", true)
    override var allowIpv6Traffic: Boolean by booleanPref("allow_ipv6_traffic", true)


    override var disallowOtherVpns: Boolean by booleanPref("disallow_other_vpns", false)
239
    var restartVpnOnNetworkChange:Boolean by booleanPref("restart_vpn_networkchange", false)
240
    override var bypassSearchdomains: Boolean by booleanPref("bypass_searchdomains", true)
241
    override var pauseOnCaptivePortal: Boolean by booleanPref("pause_on_captive_portal", true)
242
    var compareDnsSpeedsAtLaunch: Boolean by booleanPref("compare_dns_speeds", true)
243
    var mapQueryRefusedToHostBlock:Boolean by booleanPref("map_query_refused", true)
244

245
246
    override var queryLoggingEnabled: Boolean by booleanPref("log_dns_queries", false)

247
    override var userServers: Set<UserServerConfiguration> by cache(UserServerConfigurationPreference(
248
        "user_servers"
249
    ) { mutableSetOf() }, cacheControl)
250
    override var catchKnownDnsServers: Boolean by booleanPref("catch_known_servers", true)
251
    override val defaultBypassPackages: Set<String> by cache(restrictedCollection(
252
253
        stringSetPref(
            "default_bypass_packages",
254
            hashSetOf(BuildConfig.APPLICATION_ID)
255
256
        )
    ) {
Daniel Wolf's avatar
Daniel Wolf committed
257
        shouldContain(BuildConfig.APPLICATION_ID)
258
    }, cacheControl)
259
260
    override var dnsServerConfig: DnsServerInformation<*> by cache(DnsServerInformationPreference("dns_server_config"), cacheControl)
        .toNonOptionalPreference(true) {
Daniel Wolf's avatar
Daniel Wolf committed
261
262
            AbstractHttpsDNSHandle.waitUntilKnownServersArePopulated(2500) { knownServers ->
                knownServers.getValue(3) // Quad9
263
            }
264
        }
265

266
    var customHostsEnabled:Boolean by booleanPref("custom_hosts", true)
267
    var dnsRulesEnabled:Boolean by booleanPref("dns_rules_enabled", false)
268
    var hostSourcesVersion:Int by intPref("dns_rules_sources_version", 0)
269
270
271

    var removedDefaultDoTServers:Set<Int> by intPref<SharedPreferences>("removed_dohserver_id").toSetPreference(emptySet())
    var removedDefaultDoHServers:Set<Int> by intPref<SharedPreferences>("removed_dotserver_id").toSetPreference(emptySet())
Daniel Wolf's avatar
Daniel Wolf committed
272
    var removedDefaultDoQServers:Set<Int> by intPref<SharedPreferences>("removed_doqserver_id").toSetPreference(emptySet())
273
274

    var vpnServiceState:VpnServiceState by enumPref("vpn_service_state", VpnServiceState.STOPPED)
275
    var ignoreServiceKilled:Boolean by booleanPref("ignore_service_killed", false)
276
    var vpnLaunchLastVersion:Int by intPref("vpn_last_version", 43)
277
278
279

    var automaticHostRefresh:Boolean by booleanPref("automatic_host_refresh", false)
    var automaticHostRefreshWifiOnly:Boolean by booleanPref("automatic_host_refresh_wifi_only", true)
280
281
    var automaticHostRefreshTimeUnit:HostSourceRefreshDialog.TimeUnit by enumPref("automatic_host_refresh_timeunit", HostSourceRefreshDialog.TimeUnit.HOURS)
    var automaticHostRefreshTimeAmount:Int by intPref("automatic_host_refresh_timeamount", 12)
282
283

    var vpnInformationShown:Boolean by booleanPref("vpn_information_shown", false)
284
285

    var runWithoutVpn:Boolean by booleanPref("run_without_vpn", false)
286
    var nonVPNUseLanIP:Boolean by booleanPref("nonvpn_use_lanip", false)
Daniel Wolf's avatar
Daniel Wolf committed
287
    var dnsServerModePort:Int by stringBasedIntPref("non_vpn_server_port", 11053)
288
    var nonVpnUseIptables:Boolean by booleanPref("nonvpn_use_iptables", false)
289
    var lastIptablesRedirectAddress:String? by stringPref("nonvpn_iptables_last_address")
290
    var lastIptablesRedirectAddressIPv6:String? by stringPref("nonvpn_iptables_last_address_ipv6")
291
    var iptablesModeDisableIpv6:Boolean by booleanPref("nonvpn_iptables_disable_ipv6", false)
292
293

    var enableConnectionWatchDog:Boolean by booleanPref("connection_watchdog", true)
Daniel Wolf's avatar
Daniel Wolf committed
294
295

    var holdUpdateUntil:Long? by longPref("hold_update_until")
Daniel Wolf's avatar
Daniel Wolf committed
296
297
298
}

fun AppSettings.Companion.fromSharedPreferences(context: Context): AppSettingsSharedPreferences {
299
    return if (instance == null) {
Daniel Wolf's avatar
Daniel Wolf committed
300
301
302
303
304
305
        instance =
                AppSettingsSharedPreferences(context)
        instance!!
    } else {
        instance!!
    }
Daniel Wolf's avatar
Daniel Wolf committed
306
307
308
}

private val migration = buildMigration {
309
    initialMigration { _, _, _ ->
Daniel Wolf's avatar
Daniel Wolf committed
310
311

    }
312
313
314
315
}

enum class Crashreporting(val value:String) {
    FULL("full"), MINIMAL("minimal"), OFF("off")
Daniel Wolf's avatar
Daniel Wolf committed
316
}