Commits:
-
1f3d54f3
by clairehurst at 2025-06-04T12:43:41+02:00
fixup! TB 40026 [android]: Implement Security Level settings on Android.
TB 43786: Add new UX flow for changing security level (Android)
-
4c264ede
by clairehurst at 2025-06-04T12:43:41+02:00
fixup! [android] Implement Android-native Connection Assist UI
TB 43786: Add new UX flow for changing security level (Android)
-
80613d52
by clairehurst at 2025-06-04T12:43:42+02:00
fixup! [android] TBA strings
TB 43786: Add new UX flow for changing security level (Android)
18 changed files:
Changes:
mobile/android/android-components/components/browser/engine-gecko/src/main/java/mozilla/components/browser/engine/gecko/GeckoEngine.kt
| ... |
... |
@@ -1335,9 +1335,7 @@ class GeckoEngine( |
|
1335
|
1335
|
override var torSecurityLevel: Int
|
|
1336
|
1336
|
get() = runtime.settings.torSecurityLevel
|
|
1337
|
1337
|
set(value) {
|
|
1338
|
|
- value.let {
|
|
1339
|
|
- runtime.settings.torSecurityLevel = it
|
|
1340
|
|
- }
|
|
|
1338
|
+ runtime.settings.torSecurityLevel = value
|
|
1341
|
1339
|
}
|
|
1342
|
1340
|
|
|
1343
|
1341
|
override var spoofEnglish: Boolean
|
mobile/android/android-components/components/concept/engine/src/main/java/mozilla/components/concept/engine/Settings.kt
| ... |
... |
@@ -254,6 +254,12 @@ abstract class Settings { |
|
254
|
254
|
|
|
255
|
255
|
/**
|
|
256
|
256
|
* Setting to control the current security level
|
|
|
257
|
+ *
|
|
|
258
|
+ * 4 -> STANDARD
|
|
|
259
|
+ *
|
|
|
260
|
+ * 2 -> SAFER
|
|
|
261
|
+ *
|
|
|
262
|
+ * 1 -> SAFEST
|
|
257
|
263
|
*/
|
|
258
|
264
|
open var torSecurityLevel: Int by UnsupportedSetting()
|
|
259
|
265
|
|
mobile/android/android-components/components/feature/search/src/main/java/mozilla/components/feature/search/SearchUseCases.kt
| ... |
... |
@@ -76,12 +76,7 @@ class SearchUseCases( |
|
76
|
76
|
flags: EngineSession.LoadUrlFlags = EngineSession.LoadUrlFlags.none(),
|
|
77
|
77
|
additionalHeaders: Map<String, String>? = null,
|
|
78
|
78
|
) {
|
|
79
|
|
- var securityLevel: Int
|
|
80
|
|
- try {
|
|
81
|
|
- securityLevel = settings?.torSecurityLevel ?: 0
|
|
82
|
|
- } catch (e: UnsupportedSettingException) {
|
|
83
|
|
- securityLevel = 0
|
|
84
|
|
- }
|
|
|
79
|
+ val securityLevel : Int = settings!!.torSecurityLevel
|
|
85
|
80
|
val searchUrl = searchEngine?.let {
|
|
86
|
81
|
searchEngine.buildSearchUrl(searchTerms, securityLevel)
|
|
87
|
82
|
} ?: store.state.search.selectedOrDefaultSearchEngine?.buildSearchUrl(searchTerms, securityLevel)
|
| ... |
... |
@@ -172,12 +167,7 @@ class SearchUseCases( |
|
172
|
167
|
flags: EngineSession.LoadUrlFlags = EngineSession.LoadUrlFlags.none(),
|
|
173
|
168
|
additionalHeaders: Map<String, String>? = null,
|
|
174
|
169
|
) {
|
|
175
|
|
- var securityLevel: Int
|
|
176
|
|
- try {
|
|
177
|
|
- securityLevel = settings?.torSecurityLevel ?: 0
|
|
178
|
|
- } catch (e: UnsupportedSettingException) {
|
|
179
|
|
- securityLevel = 0
|
|
180
|
|
- }
|
|
|
170
|
+ val securityLevel : Int = settings!!.torSecurityLevel
|
|
181
|
171
|
val searchUrl = searchEngine?.let {
|
|
182
|
172
|
searchEngine.buildSearchUrl(searchTerms, securityLevel)
|
|
183
|
173
|
} ?: store.state.search.selectedOrDefaultSearchEngine?.buildSearchUrl(searchTerms, securityLevel)
|
mobile/android/fenix/app/src/main/java/org/mozilla/fenix/HomeActivity.kt
| ... |
... |
@@ -1439,6 +1439,15 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity, TorAn |
|
1439
|
1439
|
private const val PWA_RECENTLY_USED_THRESHOLD = DateUtils.DAY_IN_MILLIS * 30L
|
|
1440
|
1440
|
}
|
|
1441
|
1441
|
|
|
|
1442
|
+ fun restartApplication() {
|
|
|
1443
|
+ startActivity(
|
|
|
1444
|
+ Intent(applicationContext, HomeActivity::class.java).addFlags(
|
|
|
1445
|
+ Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK,
|
|
|
1446
|
+ ),
|
|
|
1447
|
+ )
|
|
|
1448
|
+ shutDown()
|
|
|
1449
|
+ }
|
|
|
1450
|
+
|
|
1442
|
1451
|
fun shutDown() : Nothing {
|
|
1443
|
1452
|
finishAndRemoveTask()
|
|
1444
|
1453
|
exitProcess(0)
|
mobile/android/fenix/app/src/main/java/org/mozilla/fenix/components/Core.kt
| ... |
... |
@@ -157,7 +157,7 @@ class Core( |
|
157
|
157
|
cookieBannerHandlingGlobalRules = context.settings().shouldEnableCookieBannerGlobalRules,
|
|
158
|
158
|
cookieBannerHandlingGlobalRulesSubFrames = context.settings().shouldEnableCookieBannerGlobalRulesSubFrame,
|
|
159
|
159
|
emailTrackerBlockingPrivateBrowsing = false,
|
|
160
|
|
- torSecurityLevel = context.settings().torSecurityLevel().intRepresentation,
|
|
|
160
|
+ torSecurityLevel = context.settings().torSecurityLevel,
|
|
161
|
161
|
spoofEnglish = context.settings().spoofEnglish,
|
|
162
|
162
|
)
|
|
163
|
163
|
|
mobile/android/fenix/app/src/main/java/org/mozilla/fenix/settings/SettingsFragment.kt
| ... |
... |
@@ -13,6 +13,7 @@ import android.os.Build |
|
13
|
13
|
import android.os.Bundle
|
|
14
|
14
|
import android.os.Handler
|
|
15
|
15
|
import android.os.Looper
|
|
|
16
|
+import android.util.Log
|
|
16
|
17
|
import android.view.LayoutInflater
|
|
17
|
18
|
import android.view.WindowManager
|
|
18
|
19
|
import android.widget.Toast
|
| ... |
... |
@@ -67,7 +68,7 @@ import org.mozilla.fenix.ext.showToolbar |
|
67
|
68
|
import org.mozilla.fenix.nimbus.FxNimbus
|
|
68
|
69
|
import org.mozilla.fenix.perf.ProfilerViewModel
|
|
69
|
70
|
import org.mozilla.fenix.settings.account.AccountUiView
|
|
70
|
|
-import org.mozilla.fenix.tor.SecurityLevel
|
|
|
71
|
+import org.mozilla.fenix.tor.TorSecurityLevel
|
|
71
|
72
|
import org.mozilla.fenix.tor.QuickstartViewModel
|
|
72
|
73
|
import org.mozilla.fenix.utils.Settings
|
|
73
|
74
|
import kotlin.system.exitProcess
|
| ... |
... |
@@ -353,7 +354,7 @@ class SettingsFragment : PreferenceFragmentCompat(), UserInteractionHandler { |
|
353
|
354
|
SettingsFragmentDirections.actionSettingsFragmentToPrivateBrowsingFragment()
|
|
354
|
355
|
}
|
|
355
|
356
|
|
|
356
|
|
- resources.getString(R.string.pref_key_tor_security_level_settings) -> {
|
|
|
357
|
+ resources.getString(R.string.pref_key_tor_security_level) -> {
|
|
357
|
358
|
SettingsFragmentDirections.actionSettingsFragmentToTorSecurityLevelFragment()
|
|
358
|
359
|
}
|
|
359
|
360
|
|
| ... |
... |
@@ -852,14 +853,14 @@ class SettingsFragment : PreferenceFragmentCompat(), UserInteractionHandler { |
|
852
|
853
|
@VisibleForTesting
|
|
853
|
854
|
internal fun setupSecurityLevelPreference() {
|
|
854
|
855
|
val securityLevelPreference =
|
|
855
|
|
- requirePreference<Preference>(R.string.pref_key_tor_security_level_settings)
|
|
856
|
|
- securityLevelPreference.summary = context?.settings()?.torSecurityLevel()?.let {
|
|
857
|
|
- when (it) {
|
|
858
|
|
- SecurityLevel.STANDARD -> getString(R.string.tor_security_level_standard_option)
|
|
859
|
|
- SecurityLevel.SAFER -> getString(R.string.tor_security_level_safer_option)
|
|
860
|
|
- SecurityLevel.SAFEST -> getString(R.string.tor_security_level_safest_option)
|
|
|
856
|
+ requirePreference<Preference>(R.string.pref_key_tor_security_level)
|
|
|
857
|
+ securityLevelPreference.summary =
|
|
|
858
|
+ when (requireContext().settings().torSecurityLevel) {
|
|
|
859
|
+ TorSecurityLevel.STANDARD.level -> getString(R.string.tor_security_level_standard)
|
|
|
860
|
+ TorSecurityLevel.SAFER.level -> getString(R.string.tor_security_level_safer)
|
|
|
861
|
+ TorSecurityLevel.SAFEST.level -> getString(R.string.tor_security_level_safest)
|
|
|
862
|
+ else -> throw Exception("Unexpected TorSecurityLevel of ${requireContext().settings().torSecurityLevel}")
|
|
861
|
863
|
}
|
|
862
|
|
- }
|
|
863
|
864
|
}
|
|
864
|
865
|
|
|
865
|
866
|
@VisibleForTesting
|
mobile/android/fenix/app/src/main/java/org/mozilla/fenix/settings/TorSecurityLevelFragment.kt
deleted
|
1
|
|
-/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
2
|
|
- * License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
3
|
|
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
4
|
|
-
|
|
5
|
|
-package org.mozilla.fenix.settings
|
|
6
|
|
-
|
|
7
|
|
-import android.os.Bundle
|
|
8
|
|
-import androidx.preference.PreferenceFragmentCompat
|
|
9
|
|
-import org.mozilla.fenix.R
|
|
10
|
|
-import org.mozilla.fenix.ext.components
|
|
11
|
|
-import org.mozilla.fenix.ext.settings
|
|
12
|
|
-import org.mozilla.fenix.ext.showToolbar
|
|
13
|
|
-import org.mozilla.fenix.tor.SecurityLevel
|
|
14
|
|
-import org.mozilla.fenix.tor.SecurityLevelUtil
|
|
15
|
|
-import org.mozilla.fenix.utils.view.GroupableRadioButton
|
|
16
|
|
-import org.mozilla.fenix.utils.view.addToRadioGroup
|
|
17
|
|
-import org.mozilla.fenix.utils.view.uncheckAll
|
|
18
|
|
-
|
|
19
|
|
-/**
|
|
20
|
|
- * Lets the user choose their security level
|
|
21
|
|
- */
|
|
22
|
|
-@Suppress("SpreadOperator")
|
|
23
|
|
-class TorSecurityLevelFragment : PreferenceFragmentCompat() {
|
|
24
|
|
- private val securityLevelRadioGroups = mutableListOf<GroupableRadioButton>()
|
|
25
|
|
- private var previousSecurityLevel: SecurityLevel? = null
|
|
26
|
|
-
|
|
27
|
|
- override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
|
28
|
|
- setPreferencesFromResource(R.xml.tor_security_level_preferences, rootKey)
|
|
29
|
|
-
|
|
30
|
|
- val currentLevel: SecurityLevel? = context?.components?.let {
|
|
31
|
|
- try {
|
|
32
|
|
- SecurityLevelUtil.getSecurityLevelFromInt(
|
|
33
|
|
- it.core.engine.settings.torSecurityLevel
|
|
34
|
|
- )
|
|
35
|
|
- } catch (e: IllegalStateException) {
|
|
36
|
|
- // The default state is 0. If we get an invalid state then
|
|
37
|
|
- // default to Standard (4).
|
|
38
|
|
- SecurityLevel.STANDARD
|
|
39
|
|
- }
|
|
40
|
|
- }
|
|
41
|
|
-
|
|
42
|
|
- if (currentLevel == null) {
|
|
43
|
|
- throw IllegalStateException("context or Components is null.")
|
|
44
|
|
- }
|
|
45
|
|
-
|
|
46
|
|
- val radioSafer = bindSecurityLevelRadio(SecurityLevel.SAFER)
|
|
47
|
|
- val radioSafest = bindSecurityLevelRadio(SecurityLevel.SAFEST)
|
|
48
|
|
- val radioStandard = bindSecurityLevelRadio(SecurityLevel.STANDARD)
|
|
49
|
|
-
|
|
50
|
|
- securityLevelRadioGroups.addAll(mutableListOf(radioSafer, radioSafest, radioStandard))
|
|
51
|
|
- // `*` is Kotlin's "spread" operator, for expanding an Array as a vararg.
|
|
52
|
|
- addToRadioGroup(*securityLevelRadioGroups.toTypedArray())
|
|
53
|
|
-
|
|
54
|
|
- securityLevelRadioGroups.uncheckAll()
|
|
55
|
|
- val securityLevelRadioButton = requirePreference<RadioButtonPreference>(currentLevel.preferenceKey)
|
|
56
|
|
- // Cache this for later comparison in the OnPreferenceChangeListener
|
|
57
|
|
- previousSecurityLevel = currentLevel
|
|
58
|
|
- securityLevelRadioButton.setCheckedWithoutClickListener(true)
|
|
59
|
|
- }
|
|
60
|
|
-
|
|
61
|
|
- private fun bindSecurityLevelRadio(
|
|
62
|
|
- level: SecurityLevel
|
|
63
|
|
- ): RadioButtonPreference {
|
|
64
|
|
- val radio = requirePreference<RadioButtonPreference>(level.preferenceKey)
|
|
65
|
|
-
|
|
66
|
|
- radio.summary = getString(level.contentDescriptionRes)
|
|
67
|
|
-
|
|
68
|
|
- radio.apply {
|
|
69
|
|
- setOnPreferenceChangeListener<Boolean> { preference, isChecked ->
|
|
70
|
|
- if (isChecked && (previousSecurityLevel!! != level)) {
|
|
71
|
|
- preference.context.components.core.engine.settings.torSecurityLevel =
|
|
72
|
|
- level.intRepresentation
|
|
73
|
|
- previousSecurityLevel = level
|
|
74
|
|
- }
|
|
75
|
|
- true
|
|
76
|
|
- }
|
|
77
|
|
- }
|
|
78
|
|
-
|
|
79
|
|
- return radio
|
|
80
|
|
- }
|
|
81
|
|
-} |
mobile/android/fenix/app/src/main/java/org/mozilla/fenix/tor/SecurityLevel.kt
deleted
|
1
|
|
-/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
2
|
|
- * License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
3
|
|
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
4
|
|
-
|
|
5
|
|
-package org.mozilla.fenix.tor
|
|
6
|
|
-
|
|
7
|
|
-import android.os.Parcelable
|
|
8
|
|
-import androidx.annotation.StringRes
|
|
9
|
|
-import kotlinx.parcelize.Parcelize
|
|
10
|
|
-import org.mozilla.fenix.R
|
|
11
|
|
-
|
|
12
|
|
-
|
|
13
|
|
-const val SECURITY_LEVEL_STANDARD = 4
|
|
14
|
|
-const val SECURITY_LEVEL_SAFER = 2
|
|
15
|
|
-const val SECURITY_LEVEL_SAFEST = 1
|
|
16
|
|
-
|
|
17
|
|
-@Parcelize
|
|
18
|
|
-enum class SecurityLevel(
|
|
19
|
|
- @StringRes val preferenceKey: Int,
|
|
20
|
|
- @StringRes val contentDescriptionRes: Int,
|
|
21
|
|
- val intRepresentation: Int
|
|
22
|
|
-) : Parcelable {
|
|
23
|
|
-
|
|
24
|
|
- STANDARD(
|
|
25
|
|
- preferenceKey = R.string.pref_key_tor_security_level_standard_option,
|
|
26
|
|
- contentDescriptionRes = R.string.tor_security_level_standard_description,
|
|
27
|
|
- intRepresentation = SECURITY_LEVEL_STANDARD
|
|
28
|
|
- ),
|
|
29
|
|
- SAFER(
|
|
30
|
|
- preferenceKey = R.string.pref_key_tor_security_level_safer_option,
|
|
31
|
|
- contentDescriptionRes = R.string.tor_security_level_safer_description,
|
|
32
|
|
- intRepresentation = SECURITY_LEVEL_SAFER
|
|
33
|
|
- ),
|
|
34
|
|
- SAFEST(
|
|
35
|
|
- preferenceKey = R.string.pref_key_tor_security_level_safest_option,
|
|
36
|
|
- contentDescriptionRes = R.string.tor_security_level_safest_description,
|
|
37
|
|
- intRepresentation = SECURITY_LEVEL_SAFEST
|
|
38
|
|
- );
|
|
39
|
|
-
|
|
40
|
|
-
|
|
41
|
|
-
|
|
42
|
|
-}
|
|
43
|
|
-
|
|
44
|
|
-object SecurityLevelUtil {
|
|
45
|
|
- fun getSecurityLevelFromInt(level: Int): SecurityLevel {
|
|
46
|
|
- return when (level) {
|
|
47
|
|
- SECURITY_LEVEL_STANDARD -> SecurityLevel.STANDARD
|
|
48
|
|
- SECURITY_LEVEL_SAFER -> SecurityLevel.SAFER
|
|
49
|
|
- SECURITY_LEVEL_SAFEST -> SecurityLevel.SAFEST
|
|
50
|
|
- else -> throw IllegalStateException("Security Level $level is not valid")
|
|
51
|
|
- }
|
|
52
|
|
- }
|
|
53
|
|
-} |
mobile/android/fenix/app/src/main/java/org/mozilla/fenix/tor/TorConnectionAssistFragment.kt
| ... |
... |
@@ -343,7 +343,7 @@ class TorConnectionAssistFragment : Fragment(), UserInteractionHandler { |
|
343
|
343
|
if (screen.torBootstrapButton2ShouldOpenSettings) {
|
|
344
|
344
|
openTorConnectionSettings()
|
|
345
|
345
|
} else if (screen.torBootstrapButton2ShouldRestartApp) {
|
|
346
|
|
- restartApplication()
|
|
|
346
|
+ (requireActivity() as HomeActivity).restartApplication()
|
|
347
|
347
|
} else {
|
|
348
|
348
|
torConnectionAssistViewModel.cancelTorBootstrap()
|
|
349
|
349
|
}
|
| ... |
... |
@@ -404,15 +404,6 @@ class TorConnectionAssistFragment : Fragment(), UserInteractionHandler { |
|
404
|
404
|
openSettings(requireContext().getString(R.string.pref_key_connection))
|
|
405
|
405
|
}
|
|
406
|
406
|
|
|
407
|
|
- private fun restartApplication() {
|
|
408
|
|
- startActivity(
|
|
409
|
|
- Intent(requireContext(), HomeActivity::class.java).addFlags(
|
|
410
|
|
- Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK,
|
|
411
|
|
- ),
|
|
412
|
|
- )
|
|
413
|
|
- Runtime.getRuntime().exit(0)
|
|
414
|
|
- }
|
|
415
|
|
-
|
|
416
|
407
|
override fun onBackPressed(): Boolean {
|
|
417
|
408
|
torConnectionAssistViewModel.handleBackButtonPressed(requireActivity() as HomeActivity)
|
|
418
|
409
|
return true
|
mobile/android/fenix/app/src/main/java/org/mozilla/fenix/tor/TorSecurityLevel.kt
|
|
1
|
+/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
|
2
|
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
|
3
|
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
4
|
+
|
|
|
5
|
+package org.mozilla.fenix.tor
|
|
|
6
|
+
|
|
|
7
|
+import android.os.Parcelable
|
|
|
8
|
+import kotlinx.parcelize.Parcelize
|
|
|
9
|
+
|
|
|
10
|
+@Parcelize
|
|
|
11
|
+enum class TorSecurityLevel(val level: Int) : Parcelable {
|
|
|
12
|
+ STANDARD(4), SAFER(2), SAFEST(1)
|
|
|
13
|
+} |
mobile/android/fenix/app/src/main/java/org/mozilla/fenix/tor/TorSecurityLevelFragment.kt
|
|
1
|
+/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
|
2
|
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
|
3
|
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
4
|
+
|
|
|
5
|
+package org.mozilla.fenix.tor
|
|
|
6
|
+
|
|
|
7
|
+import android.content.Context
|
|
|
8
|
+import android.os.Bundle
|
|
|
9
|
+import android.util.Log
|
|
|
10
|
+import android.view.LayoutInflater
|
|
|
11
|
+import android.view.View
|
|
|
12
|
+import android.view.ViewGroup
|
|
|
13
|
+import android.widget.Toast
|
|
|
14
|
+import androidx.appcompat.content.res.AppCompatResources
|
|
|
15
|
+import androidx.fragment.app.Fragment
|
|
|
16
|
+import org.mozilla.fenix.HomeActivity
|
|
|
17
|
+import org.mozilla.fenix.R
|
|
|
18
|
+import org.mozilla.fenix.ext.components
|
|
|
19
|
+import org.mozilla.fenix.databinding.FragmentTorSecurityLevelPreferencesBinding
|
|
|
20
|
+import androidx.core.content.edit
|
|
|
21
|
+
|
|
|
22
|
+class TorSecurityLevelFragment : Fragment() {
|
|
|
23
|
+ private var _binding: FragmentTorSecurityLevelPreferencesBinding? = null
|
|
|
24
|
+ private val binding get() = _binding!!
|
|
|
25
|
+
|
|
|
26
|
+ private val tag = "TorSecurityLevelFrag"
|
|
|
27
|
+
|
|
|
28
|
+ override fun onCreateView(
|
|
|
29
|
+ inflater: LayoutInflater,
|
|
|
30
|
+ container: ViewGroup?,
|
|
|
31
|
+ savedInstanceState: Bundle?,
|
|
|
32
|
+ ): View {
|
|
|
33
|
+ _binding = FragmentTorSecurityLevelPreferencesBinding.inflate(
|
|
|
34
|
+ inflater, container, false,
|
|
|
35
|
+ )
|
|
|
36
|
+
|
|
|
37
|
+ return binding.root
|
|
|
38
|
+ }
|
|
|
39
|
+
|
|
|
40
|
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
|
|
41
|
+ super.onViewCreated(view, savedInstanceState)
|
|
|
42
|
+
|
|
|
43
|
+ binding.description.text = getString(R.string.tor_security_level_warning, getString(R.string.app_name))
|
|
|
44
|
+
|
|
|
45
|
+ updateSaveAndRestartButtonUI()
|
|
|
46
|
+
|
|
|
47
|
+ val currentLevel: Int = requireContext().components.core.engine.settings.torSecurityLevel
|
|
|
48
|
+
|
|
|
49
|
+ when (currentLevel) {
|
|
|
50
|
+ TorSecurityLevel.STANDARD.level -> {
|
|
|
51
|
+ binding.standardPreference.text =
|
|
|
52
|
+ getString(R.string.tor_security_level_standard_current_level)
|
|
|
53
|
+ binding.securityLevelRadioGroup.check(binding.standardPreference.id)
|
|
|
54
|
+ }
|
|
|
55
|
+
|
|
|
56
|
+ TorSecurityLevel.SAFER.level -> {
|
|
|
57
|
+ binding.saferPreference.text =
|
|
|
58
|
+ getString(R.string.tor_security_level_safer_current_level)
|
|
|
59
|
+ binding.securityLevelRadioGroup.check(binding.saferPreference.id)
|
|
|
60
|
+ }
|
|
|
61
|
+
|
|
|
62
|
+ TorSecurityLevel.SAFEST.level -> {
|
|
|
63
|
+ binding.safestPreference.text =
|
|
|
64
|
+ getString(R.string.tor_security_level_safest_current_level)
|
|
|
65
|
+ binding.securityLevelRadioGroup.check(binding.safestPreference.id)
|
|
|
66
|
+ }
|
|
|
67
|
+ }
|
|
|
68
|
+
|
|
|
69
|
+ binding.securityLevelRadioGroup.setOnCheckedChangeListener { _, checkedId ->
|
|
|
70
|
+ binding.saveAndRestartButton.isEnabled = when (checkedId) {
|
|
|
71
|
+ binding.standardPreference.id -> currentLevel != TorSecurityLevel.STANDARD.level
|
|
|
72
|
+ binding.saferPreference.id -> currentLevel != TorSecurityLevel.SAFER.level
|
|
|
73
|
+ binding.safestPreference.id -> currentLevel != TorSecurityLevel.SAFEST.level
|
|
|
74
|
+ else -> throw Exception("unexpected checkedID of $checkedId")
|
|
|
75
|
+ }
|
|
|
76
|
+
|
|
|
77
|
+ updateSaveAndRestartButtonUI()
|
|
|
78
|
+ }
|
|
|
79
|
+
|
|
|
80
|
+ binding.saveAndRestartButton.setOnClickListener {
|
|
|
81
|
+
|
|
|
82
|
+ Toast.makeText(
|
|
|
83
|
+ requireContext(),
|
|
|
84
|
+ R.string.tor_security_level_restarting,
|
|
|
85
|
+ Toast.LENGTH_SHORT,
|
|
|
86
|
+ ).show()
|
|
|
87
|
+
|
|
|
88
|
+ val selectedSecurityLevel: Int =
|
|
|
89
|
+ when (binding.securityLevelRadioGroup.checkedRadioButtonId) {
|
|
|
90
|
+ binding.standardPreference.id -> TorSecurityLevel.STANDARD.level
|
|
|
91
|
+ binding.saferPreference.id -> TorSecurityLevel.SAFER.level
|
|
|
92
|
+ binding.safestPreference.id -> TorSecurityLevel.SAFEST.level
|
|
|
93
|
+ else -> throw Exception("Unexpected checkedRadioButtonId of ${binding.securityLevelRadioGroup.checkedRadioButtonId}")
|
|
|
94
|
+ }
|
|
|
95
|
+
|
|
|
96
|
+ requireContext().components.core.geckoRuntime.settings.torSecurityLevel = selectedSecurityLevel
|
|
|
97
|
+
|
|
|
98
|
+ requireActivity().getSharedPreferences("fenix_preferences", Context.MODE_PRIVATE).edit(
|
|
|
99
|
+ commit = true,
|
|
|
100
|
+ ) {
|
|
|
101
|
+ putInt(
|
|
|
102
|
+ requireContext().getString(R.string.pref_key_tor_security_level),
|
|
|
103
|
+ selectedSecurityLevel,
|
|
|
104
|
+ )
|
|
|
105
|
+ }
|
|
|
106
|
+
|
|
|
107
|
+ Thread.sleep(1000)
|
|
|
108
|
+
|
|
|
109
|
+ (requireActivity() as HomeActivity).restartApplication()
|
|
|
110
|
+ }
|
|
|
111
|
+
|
|
|
112
|
+ binding.cancelButton.setOnClickListener {
|
|
|
113
|
+ requireActivity().onBackPressed()
|
|
|
114
|
+ }
|
|
|
115
|
+ }
|
|
|
116
|
+
|
|
|
117
|
+ private fun updateSaveAndRestartButtonUI() {
|
|
|
118
|
+ binding.saveAndRestartButton.apply {
|
|
|
119
|
+ if (binding.saveAndRestartButton.isEnabled) {
|
|
|
120
|
+ backgroundTintList = AppCompatResources.getColorStateList(
|
|
|
121
|
+ requireContext(),
|
|
|
122
|
+ R.color.connect_button_purple,
|
|
|
123
|
+ )
|
|
|
124
|
+ setTextColor(
|
|
|
125
|
+ AppCompatResources.getColorStateList(
|
|
|
126
|
+ requireContext(),
|
|
|
127
|
+ R.color.photonLightGrey05,
|
|
|
128
|
+ ),
|
|
|
129
|
+ )
|
|
|
130
|
+ } else {
|
|
|
131
|
+ backgroundTintList = AppCompatResources.getColorStateList(
|
|
|
132
|
+ requireContext(),
|
|
|
133
|
+ R.color.disabled_connect_button_purple,
|
|
|
134
|
+ )
|
|
|
135
|
+ setTextColor(
|
|
|
136
|
+ AppCompatResources.getColorStateList(
|
|
|
137
|
+ requireContext(),
|
|
|
138
|
+ R.color.disabled_text_gray_purple,
|
|
|
139
|
+ ),
|
|
|
140
|
+ )
|
|
|
141
|
+ }
|
|
|
142
|
+ }
|
|
|
143
|
+ }
|
|
|
144
|
+} |
mobile/android/fenix/app/src/main/java/org/mozilla/fenix/utils/Settings.kt
| ... |
... |
@@ -52,7 +52,8 @@ import org.mozilla.fenix.settings.registerOnSharedPreferenceChangeListener |
|
52
|
52
|
import org.mozilla.fenix.settings.sitepermissions.AUTOPLAY_BLOCK_ALL
|
|
53
|
53
|
import org.mozilla.fenix.settings.sitepermissions.AUTOPLAY_BLOCK_AUDIBLE
|
|
54
|
54
|
import org.mozilla.fenix.wallpapers.Wallpaper
|
|
55
|
|
-import org.mozilla.fenix.tor.SecurityLevel
|
|
|
55
|
+import org.mozilla.fenix.settings.SettingsFragment
|
|
|
56
|
+import org.mozilla.fenix.tor.TorSecurityLevel
|
|
56
|
57
|
import java.security.InvalidParameterException
|
|
57
|
58
|
import java.util.UUID
|
|
58
|
59
|
|
| ... |
... |
@@ -299,31 +300,55 @@ class Settings(private val appContext: Context) : PreferencesHolder { |
|
299
|
300
|
false,
|
|
300
|
301
|
)
|
|
301
|
302
|
|
|
302
|
|
- var standardSecurityLevel by booleanPreference(
|
|
303
|
|
- appContext.getPreferenceKey(SecurityLevel.STANDARD.preferenceKey),
|
|
304
|
|
- default = true
|
|
|
303
|
+ private var oldStandardSecurityLevel by booleanPreference(
|
|
|
304
|
+ appContext.getPreferenceKey(R.string.pref_key_tor_security_level_standard_option),
|
|
|
305
|
+ default = false
|
|
305
|
306
|
)
|
|
306
|
307
|
|
|
307
|
|
- var saferSecurityLevel by booleanPreference(
|
|
308
|
|
- appContext.getPreferenceKey(SecurityLevel.SAFER.preferenceKey),
|
|
|
308
|
+ private var oldSaferSecurityLevel by booleanPreference(
|
|
|
309
|
+ appContext.getPreferenceKey(R.string.pref_key_tor_security_level_safer_option),
|
|
309
|
310
|
default = false
|
|
310
|
311
|
)
|
|
311
|
312
|
|
|
312
|
|
- var safestSecurityLevel by booleanPreference(
|
|
313
|
|
- appContext.getPreferenceKey(SecurityLevel.SAFEST.preferenceKey),
|
|
|
313
|
+ private var oldSafestSecurityLevel by booleanPreference(
|
|
|
314
|
+ appContext.getPreferenceKey(R.string.pref_key_tor_security_level_safest_option),
|
|
314
|
315
|
default = false
|
|
315
|
316
|
)
|
|
316
|
317
|
|
|
317
|
|
- // torSecurityLevel is defined as the first |true| preference,
|
|
318
|
|
- // beginning at the safest level.
|
|
319
|
|
- // If multiple preferences are true, then that is a bug and the
|
|
320
|
|
- // highest |true| security level is chosen.
|
|
321
|
|
- // Standard is the default level.
|
|
322
|
|
- fun torSecurityLevel(): SecurityLevel = when {
|
|
323
|
|
- safestSecurityLevel -> SecurityLevel.SAFEST
|
|
324
|
|
- saferSecurityLevel -> SecurityLevel.SAFER
|
|
325
|
|
- standardSecurityLevel -> SecurityLevel.STANDARD
|
|
326
|
|
- else -> SecurityLevel.STANDARD
|
|
|
318
|
+ /**
|
|
|
319
|
+ * Backing property that should used only for the [SettingsFragment] UI
|
|
|
320
|
+ *
|
|
|
321
|
+ * 4 -> STANDARD
|
|
|
322
|
+ *
|
|
|
323
|
+ * 2 -> SAFER
|
|
|
324
|
+ *
|
|
|
325
|
+ * 1 -> SAFEST
|
|
|
326
|
+ */
|
|
|
327
|
+ var torSecurityLevel by intPreference(
|
|
|
328
|
+ appContext.getPreferenceKey(R.string.pref_key_tor_security_level),
|
|
|
329
|
+ migrateTorSecurityLevel() ?: 4,
|
|
|
330
|
+ )
|
|
|
331
|
+
|
|
|
332
|
+ /**
|
|
|
333
|
+ * Remove in 15.0 release.
|
|
|
334
|
+ */
|
|
|
335
|
+ private fun migrateTorSecurityLevel(): Int? {
|
|
|
336
|
+ return when {
|
|
|
337
|
+ oldSafestSecurityLevel -> {
|
|
|
338
|
+ TorSecurityLevel.SAFEST.level
|
|
|
339
|
+ }
|
|
|
340
|
+ oldSaferSecurityLevel -> {
|
|
|
341
|
+ TorSecurityLevel.SAFER.level
|
|
|
342
|
+ }
|
|
|
343
|
+ oldStandardSecurityLevel -> {
|
|
|
344
|
+ TorSecurityLevel.STANDARD.level
|
|
|
345
|
+ }
|
|
|
346
|
+ else -> null
|
|
|
347
|
+ }.also {
|
|
|
348
|
+ oldSafestSecurityLevel = false
|
|
|
349
|
+ oldSaferSecurityLevel = false
|
|
|
350
|
+ oldStandardSecurityLevel = false
|
|
|
351
|
+ }
|
|
327
|
352
|
}
|
|
328
|
353
|
|
|
329
|
354
|
var spoofEnglish by booleanPreference(
|
mobile/android/fenix/app/src/main/res/layout/fragment_tor_security_level_preferences.xml
|
|
1
|
+<?xml version="1.0" encoding="utf-8"?><!-- This Source Code Form is subject to the terms of the Mozilla Public
|
|
|
2
|
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
|
3
|
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
|
|
|
4
|
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
|
|
5
|
+ xmlns:app="http://schemas.android.com/apk/res-auto"
|
|
|
6
|
+ android:layout_width="match_parent"
|
|
|
7
|
+ android:layout_height="match_parent">
|
|
|
8
|
+
|
|
|
9
|
+ <TextView
|
|
|
10
|
+ android:id="@+id/description"
|
|
|
11
|
+ android:layout_width="match_parent"
|
|
|
12
|
+ android:layout_height="wrap_content"
|
|
|
13
|
+ android:lineSpacingExtra="6dp"
|
|
|
14
|
+ android:paddingHorizontal="24dp"
|
|
|
15
|
+ android:paddingVertical="16dp"
|
|
|
16
|
+ android:text="@string/tor_security_level_warning"
|
|
|
17
|
+ android:textColor="@color/photonLightGrey40"
|
|
|
18
|
+ android:textSize="14sp"
|
|
|
19
|
+ app:layout_constraintTop_toTopOf="parent" />
|
|
|
20
|
+
|
|
|
21
|
+ <RadioGroup
|
|
|
22
|
+ android:id="@+id/security_level_radio_group"
|
|
|
23
|
+ android:layout_width="match_parent"
|
|
|
24
|
+ android:layout_height="wrap_content"
|
|
|
25
|
+ android:layout_marginHorizontal="16dp"
|
|
|
26
|
+ app:layout_constraintTop_toBottomOf="@+id/description">
|
|
|
27
|
+
|
|
|
28
|
+ <RadioButton
|
|
|
29
|
+ android:id="@+id/standard_preference"
|
|
|
30
|
+ android:layout_width="match_parent"
|
|
|
31
|
+ android:layout_height="wrap_content"
|
|
|
32
|
+ android:text="@string/tor_security_level_standard"
|
|
|
33
|
+ android:textColor="@color/photonLightGrey05"
|
|
|
34
|
+ android:textSize="16sp" />
|
|
|
35
|
+
|
|
|
36
|
+ <TextView
|
|
|
37
|
+ android:id="@+id/standard_preference_description"
|
|
|
38
|
+ android:layout_width="match_parent"
|
|
|
39
|
+ android:layout_height="wrap_content"
|
|
|
40
|
+ android:layout_marginStart="32dp"
|
|
|
41
|
+ android:text="@string/tor_security_level_standard_description"
|
|
|
42
|
+ android:textColor="@color/photonLightGrey40" />
|
|
|
43
|
+
|
|
|
44
|
+ <RadioButton
|
|
|
45
|
+ android:id="@+id/safer_preference"
|
|
|
46
|
+ android:layout_width="match_parent"
|
|
|
47
|
+ android:layout_height="wrap_content"
|
|
|
48
|
+ android:text="@string/tor_security_level_safer"
|
|
|
49
|
+ android:textColor="@color/photonLightGrey05"
|
|
|
50
|
+ android:textSize="16sp" />
|
|
|
51
|
+
|
|
|
52
|
+ <TextView
|
|
|
53
|
+ android:id="@+id/safer_preference_description"
|
|
|
54
|
+ android:layout_width="match_parent"
|
|
|
55
|
+ android:layout_height="wrap_content"
|
|
|
56
|
+ android:layout_marginStart="32dp"
|
|
|
57
|
+ android:text="@string/tor_security_level_safer_description"
|
|
|
58
|
+ android:textColor="@color/photonLightGrey40" />
|
|
|
59
|
+
|
|
|
60
|
+ <RadioButton
|
|
|
61
|
+ android:id="@+id/safest_preference"
|
|
|
62
|
+ android:layout_width="match_parent"
|
|
|
63
|
+ android:layout_height="wrap_content"
|
|
|
64
|
+ android:text="@string/tor_security_level_safest"
|
|
|
65
|
+ android:textColor="@color/photonLightGrey05"
|
|
|
66
|
+ android:textSize="16sp" />
|
|
|
67
|
+
|
|
|
68
|
+ <TextView
|
|
|
69
|
+ android:id="@+id/safest_preference_description"
|
|
|
70
|
+ android:layout_width="match_parent"
|
|
|
71
|
+ android:layout_height="wrap_content"
|
|
|
72
|
+ android:layout_marginStart="32dp"
|
|
|
73
|
+ android:text="@string/tor_security_level_safest_description"
|
|
|
74
|
+ android:textColor="@color/photonLightGrey40" />
|
|
|
75
|
+ </RadioGroup>
|
|
|
76
|
+
|
|
|
77
|
+ <Button
|
|
|
78
|
+ android:id="@+id/save_and_restart_button"
|
|
|
79
|
+ android:layout_width="wrap_content"
|
|
|
80
|
+ android:layout_height="wrap_content"
|
|
|
81
|
+ android:layout_marginStart="24dp"
|
|
|
82
|
+ android:layout_marginEnd="24dp"
|
|
|
83
|
+ android:layout_marginBottom="8dp"
|
|
|
84
|
+ android:background="">"@drawable/rounded_corners"
|
|
|
85
|
+ android:backgroundTint="@color/connect_button_purple"
|
|
|
86
|
+ android:enabled="false"
|
|
|
87
|
+ android:minWidth="360dp"
|
|
|
88
|
+ android:text="@string/tor_security_level_save_and_restart_tor_browser"
|
|
|
89
|
+ android:textAlignment="center"
|
|
|
90
|
+ android:textAllCaps="false"
|
|
|
91
|
+ android:textColor="@color/photonLightGrey05"
|
|
|
92
|
+ android:textSize="14sp"
|
|
|
93
|
+ android:textStyle="bold"
|
|
|
94
|
+ app:layout_constraintBottom_toTopOf="@id/cancel_button"
|
|
|
95
|
+ app:layout_constraintEnd_toEndOf="parent"
|
|
|
96
|
+ app:layout_constraintStart_toStartOf="parent" />
|
|
|
97
|
+
|
|
|
98
|
+ <Button
|
|
|
99
|
+ android:id="@+id/cancel_button"
|
|
|
100
|
+ android:layout_width="wrap_content"
|
|
|
101
|
+ android:layout_height="wrap_content"
|
|
|
102
|
+ android:layout_marginStart="24dp"
|
|
|
103
|
+ android:layout_marginEnd="24dp"
|
|
|
104
|
+ android:layout_marginBottom="8dp"
|
|
|
105
|
+ android:background="">"@drawable/rounded_corners"
|
|
|
106
|
+ android:backgroundTint="@color/configure_connection_button_white"
|
|
|
107
|
+ android:minWidth="360dp"
|
|
|
108
|
+ android:text="@string/btn_cancel"
|
|
|
109
|
+ android:textAlignment="center"
|
|
|
110
|
+ android:textAllCaps="false"
|
|
|
111
|
+ android:textColor="@color/photonDarkGrey90"
|
|
|
112
|
+ android:textSize="14sp"
|
|
|
113
|
+ android:textStyle="bold"
|
|
|
114
|
+ app:layout_constraintBottom_toBottomOf="parent"
|
|
|
115
|
+ app:layout_constraintEnd_toEndOf="parent"
|
|
|
116
|
+ app:layout_constraintStart_toStartOf="parent" />
|
|
|
117
|
+</androidx.constraintlayout.widget.ConstraintLayout> |
mobile/android/fenix/app/src/main/res/navigation/nav_graph.xml
| ... |
... |
@@ -907,7 +907,7 @@ |
|
907
|
907
|
android:label="@string/preferences_customize" />
|
|
908
|
908
|
<fragment
|
|
909
|
909
|
android:id="@+id/torSecurityLevelFragment"
|
|
910
|
|
- android:name="org.mozilla.fenix.settings.TorSecurityLevelFragment"
|
|
|
910
|
+ android:name="org.mozilla.fenix.tor.TorSecurityLevelFragment"
|
|
911
|
911
|
android:label="@string/preferences_tor_security_level_options" />
|
|
912
|
912
|
<fragment
|
|
913
|
913
|
android:id="@+id/privateBrowsingFragment"
|
mobile/android/fenix/app/src/main/res/values/preference_keys.xml
| ... |
... |
@@ -406,6 +406,9 @@ |
|
406
|
406
|
<string name="pref_key_https_everywhere_removed" translatable="false">pref_key_https_everywhere_removed</string>
|
|
407
|
407
|
|
|
408
|
408
|
<!-- Security Level Settings -->
|
|
|
409
|
+ <string name="pref_key_tor_security_level" translatable="false">pref_key_tor_security_level</string>
|
|
|
410
|
+
|
|
|
411
|
+ <!-- Deprecated Security Level Settings -->
|
|
409
|
412
|
<string name="pref_key_tor_security_level_settings" translatable="false">pref_key_tor_security_level_settings</string>
|
|
410
|
413
|
<string name="pref_key_tor_security_level_standard_option" translatable="false">pref_key_tor_security_level_standard_option</string>
|
|
411
|
414
|
<string name="pref_key_tor_security_level_safer_option" translatable="false">pref_key_tor_security_level_safer_option</string>
|
mobile/android/fenix/app/src/main/res/values/torbrowser_strings.xml
| ... |
... |
@@ -36,12 +36,19 @@ |
|
36
|
36
|
<string name="preferences_tor_security_level_options">Security Level</string>
|
|
37
|
37
|
|
|
38
|
38
|
<!-- Description of security levels -->
|
|
39
|
|
- <string name="tor_security_level_standard_option">Standard</string>
|
|
|
39
|
+ <!-- %s will be replaced with the localised application name, such as "Tor Browser". -->
|
|
|
40
|
+ <string name="tor_security_level_warning">You will need to restart %s to apply any changes. This will close all tabs.</string>
|
|
|
41
|
+ <string name="tor_security_level_standard">Standard</string>
|
|
|
42
|
+ <string name="tor_security_level_standard_current_level">Standard (current level)</string>
|
|
40
|
43
|
<string name="tor_security_level_standard_description">All Tor Browser and website features are enabled.</string>
|
|
41
|
|
- <string name="tor_security_level_safer_option">Safer</string>
|
|
|
44
|
+ <string name="tor_security_level_safer">Safer</string>
|
|
|
45
|
+ <string name="tor_security_level_safer_current_level">Safer (current level)</string>
|
|
42
|
46
|
<string name="tor_security_level_safer_description">Disable website features that are often dangerous, causing some sites to lose functionality.</string>
|
|
43
|
|
- <string name="tor_security_level_safest_option">Safest</string>
|
|
|
47
|
+ <string name="tor_security_level_safest">Safest</string>
|
|
|
48
|
+ <string name="tor_security_level_safest_current_level">Safest (current level)</string>
|
|
44
|
49
|
<string name="tor_security_level_safest_description">Only allow website features required for static sites and basic services. These changes affect images, media, and scripts.</string>
|
|
|
50
|
+ <string name="tor_security_level_save_and_restart_tor_browser">Save and restart</string>
|
|
|
51
|
+ <string name="tor_security_level_restarting">Restarting</string>
|
|
45
|
52
|
|
|
46
|
53
|
<string name="btn_cancel">Cancel</string>
|
|
47
|
54
|
|
mobile/android/fenix/app/src/main/res/xml/preferences.xml
| ... |
... |
@@ -103,7 +103,7 @@ |
|
103
|
103
|
android:layout="@layout/preference_category_no_icon_style">
|
|
104
|
104
|
|
|
105
|
105
|
<androidx.preference.Preference
|
|
106
|
|
- android:key="@string/pref_key_tor_security_level_settings"
|
|
|
106
|
+ android:key="@string/pref_key_tor_security_level"
|
|
107
|
107
|
app:iconSpaceReserved="false"
|
|
108
|
108
|
android:title="@string/preferences_tor_security_level_options" />
|
|
109
|
109
|
|
mobile/android/fenix/app/src/main/res/xml/tor_security_level_preferences.xml
deleted
|
1
|
|
-<?xml version="1.0" encoding="utf-8"?><!-- This Source Code Form is subject to the terms of the Mozilla Public
|
|
2
|
|
- - License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
3
|
|
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
|
|
4
|
|
-<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
|
|
5
|
|
- xmlns:app="http://schemas.android.com/apk/res-auto">
|
|
6
|
|
- <org.mozilla.fenix.settings.RadioButtonPreference
|
|
7
|
|
- android:defaultValue="true"
|
|
8
|
|
- android:key="@string/pref_key_tor_security_level_standard_option"
|
|
9
|
|
- android:summary="@string/tor_security_level_standard_description"
|
|
10
|
|
- android:title="@string/tor_security_level_standard_option" />
|
|
11
|
|
- <org.mozilla.fenix.settings.RadioButtonPreference
|
|
12
|
|
- android:defaultValue="false"
|
|
13
|
|
- android:key="@string/pref_key_tor_security_level_safer_option"
|
|
14
|
|
- android:summary="@string/tor_security_level_safer_description"
|
|
15
|
|
- android:title="@string/tor_security_level_safer_option" />
|
|
16
|
|
- <org.mozilla.fenix.settings.RadioButtonPreference
|
|
17
|
|
- android:defaultValue="false"
|
|
18
|
|
- android:key="@string/pref_key_tor_security_level_safest_option"
|
|
19
|
|
- android:summary="@string/tor_security_level_safest_description"
|
|
20
|
|
- android:title="@string/tor_security_level_safest_option" />
|
|
21
|
|
-</androidx.preference.PreferenceScreen> |
|