commit cbc07d746f9216716d594a9be041b14480bd36ea parent 72d6fa46eb96a0c9644b8647789c7290c8637736 Author: Matthew Finkel <sysrqb@torproject.org> Date: Fri, 18 Sep 2020 01:57:44 +0000 TB 40041 [android]: Implement Tor Network Settings Originally, fenix#40041. Diffstat:
16 files changed, 661 insertions(+), 49 deletions(-)
diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/settings/OpenLinksInAppsFragment.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/settings/OpenLinksInAppsFragment.kt @@ -20,7 +20,6 @@ class OpenLinksInAppsFragment : PreferenceFragmentCompat() { private lateinit var radioAlways: RadioButtonPreference private lateinit var radioAskBeforeOpening: RadioButtonPreference private lateinit var radioNever: RadioButtonPreference - private val args by navArgs<OpenLinksInAppsFragmentArgs>() override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { setPreferencesFromResource(R.xml.open_links_in_apps_preferences, rootKey) @@ -38,9 +37,6 @@ class OpenLinksInAppsFragment : PreferenceFragmentCompat() { showToolbar(getString(R.string.preferences_open_links_in_apps)) setupPreferences() - args.preferenceToScrollTo?.let { - scrollToPreference(it) - } } private fun setupPreferences() { diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/settings/SettingsFragment.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/settings/SettingsFragment.kt @@ -9,6 +9,7 @@ import android.content.DialogInterface import android.os.Bundle import android.os.Handler import android.os.Looper +import android.os.StrictMode import android.util.Log import android.view.LayoutInflater import android.view.View @@ -30,6 +31,7 @@ import androidx.preference.SwitchPreference import androidx.recyclerview.widget.RecyclerView import com.google.android.material.dialog.MaterialAlertDialogBuilder import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay import kotlinx.coroutines.launch import mozilla.components.browser.state.state.selectedOrDefaultSearchEngine @@ -54,6 +56,7 @@ import org.mozilla.fenix.GleanMetrics.TrackingProtection import org.mozilla.fenix.GleanMetrics.Translations import org.mozilla.fenix.HomeActivity import org.mozilla.fenix.R +import org.mozilla.fenix.ReleaseChannel import org.mozilla.fenix.components.Components import org.mozilla.fenix.components.accounts.FenixFxAEntryPoint import org.mozilla.fenix.databinding.AmoCollectionOverrideDialogBinding @@ -73,6 +76,7 @@ import org.mozilla.fenix.settings.account.AccountUiView import org.mozilla.fenix.snackbar.FenixSnackbarDelegate import org.mozilla.fenix.snackbar.SnackbarBinding import org.mozilla.fenix.tor.TorSecurityLevel +import org.mozilla.fenix.tor.QuickstartViewModel import org.mozilla.fenix.utils.Settings import kotlin.system.exitProcess import org.mozilla.fenix.GleanMetrics.Settings as SettingsMetrics @@ -89,6 +93,8 @@ class SettingsFragment : PreferenceFragmentCompat() { } private val snackbarBinding = ViewBoundFeatureWrapper<SnackbarBinding>() + private val quickstartViewModel: QuickstartViewModel by activityViewModels() + @VisibleForTesting internal val accountObserver = object : AccountObserver { private fun updateAccountUi(profile: Profile? = null) { @@ -180,7 +186,9 @@ class SettingsFragment : PreferenceFragmentCompat() { } override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { - setPreferencesFromResource(R.xml.preferences, rootKey) + requireContext().components.strictMode.allowViolation(StrictMode::allowThreadDiskReads) { + setPreferencesFromResource(R.xml.preferences, rootKey) + } } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { @@ -246,7 +254,11 @@ class SettingsFragment : PreferenceFragmentCompat() { ) requireView().findViewById<RecyclerView>(R.id.recycler_view) - ?.hideInitialScrollBar(viewLifecycleOwner.lifecycleScope) + .also { + it?.hideInitialScrollBar(viewLifecycleOwner.lifecycleScope) + // Prevent disabled settings from having a collapsing animation on open + it?.disableHidingAnimation() + } args.preferenceToScrollTo?.let { scrollToPreference(it) @@ -310,9 +322,9 @@ class SettingsFragment : PreferenceFragmentCompat() { // getString(R.string.preferences_credit_cards_2) // } - val openLinksInAppsSettingsPreference = - requirePreference<Preference>(R.string.pref_key_open_links_in_apps) - openLinksInAppsSettingsPreference.summary = settings.getOpenLinksInAppsString() + // val openLinksInAppsSettingsPreference = + // requirePreference<Preference>(R.string.pref_key_open_links_in_apps) + // openLinksInAppsSettingsPreference.summary = settings.getOpenLinksInAppsString() setupPreferences(settings) @@ -493,9 +505,9 @@ class SettingsFragment : PreferenceFragmentCompat() { SettingsFragmentDirections.actionSettingsFragmentToLinkSharingFragment() } - resources.getString(R.string.pref_key_open_links_in_apps) -> { - SettingsFragmentDirections.actionSettingsFragmentToOpenLinksInAppsFragment() - } + // resources.getString(R.string.pref_key_open_links_in_apps) -> { + // SettingsFragmentDirections.actionSettingsFragmentToOpenLinksInAppsFragment() + // } resources.getString(R.string.pref_key_downloads) -> { SettingsFragmentDirections.actionSettingsFragmentToOpenDownloadsSettingsFragment() @@ -628,6 +640,7 @@ class SettingsFragment : PreferenceFragmentCompat() { setupHomepagePreference(settings) setupTrackingProtectionPreference(settings) setupDnsOverHttpsPreference(settings) + setupConnectionPreferences() } /** @@ -662,6 +675,11 @@ class SettingsFragment : PreferenceFragmentCompat() { } } + private fun RecyclerView.disableHidingAnimation() { + this.setItemAnimator(null) + this.setLayoutAnimation(null) + } + @VisibleForTesting internal fun setupAmoCollectionOverridePreference( settings: Settings, @@ -768,6 +786,56 @@ class SettingsFragment : PreferenceFragmentCompat() { } } + internal fun setupConnectionPreferences() { + // will be needed for phase2 + //val torController = requireContext().components.torController + + requirePreference<Preference>(R.string.pref_key_tor_network_settings_bridge_config).apply { + setOnPreferenceClickListener { + val directions = + SettingsFragmentDirections + .actionSettingsFragmentToTorBridgeConfigFragment() + requireView().findNavController().navigate(directions) + true + } + } + + requirePreference<SwitchPreference>(R.string.pref_key_quick_start).apply { + isChecked = quickstartViewModel.quickstart().value == true + setOnPreferenceClickListener { + quickstartViewModel.quickstartSet( + isChecked, + ) + true + } + } + + requirePreference<Preference>(R.string.pref_key_use_html_connection_ui).apply { + onPreferenceChangeListener = object : SharedPreferenceUpdater() {} + isVisible = Config.channel != ReleaseChannel.Release + } + + requirePreference<Preference>(R.string.pref_key_tor_logs).apply { + setOnPreferenceClickListener { + val directions = + SettingsFragmentDirections.actionSettingsFragmentToTorLogsFragment() + requireView().findNavController().navigate(directions) + true + } + } + requirePreference<Preference>(R.string.pref_key_about_config_shortcut).apply { + isVisible = requireContext().settings().showSecretDebugMenuThisSession || Config.channel == ReleaseChannel.Debug + setOnPreferenceClickListener { + (requireActivity() as HomeActivity).openToBrowserAndLoad( + searchTermOrURL = "about:config", + from = BrowserDirection.FromSettings, + newTab = true, + ) + true + } + } + } + @VisibleForTesting internal fun setupCookieBannerPreference(settings: Settings) { FxNimbus.features.cookieBanners.recordExposure() diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/settings/TorBridgeConfigFragment.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/settings/TorBridgeConfigFragment.kt @@ -0,0 +1,152 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package org.mozilla.fenix.settings + +import android.os.Bundle +import androidx.preference.EditTextPreference +import androidx.preference.Preference +import androidx.preference.PreferenceFragmentCompat +import androidx.preference.SwitchPreference +import org.mozilla.fenix.Config +import org.mozilla.fenix.R +import org.mozilla.fenix.ext.components +import org.mozilla.fenix.ext.settings +import org.mozilla.fenix.ext.showToolbar +import org.mozilla.fenix.tor.TorBridgeTransportConfig +import org.mozilla.fenix.utils.view.addToRadioGroup +import org.mozilla.fenix.utils.view.GroupableRadioButton +import org.mozilla.fenix.utils.view.uncheckAll + +/** + * Displays the toggle for enabling bridges, options for built-in pluggable transports, and an additional + * preference for configuring a user-provided bridge. + */ +@Suppress("SpreadOperator") +class TorBridgeConfigFragment : PreferenceFragmentCompat() { + private val builtinBridgeRadioGroups = mutableListOf<GroupableRadioButton>() + private var previousTransportConfig: TorBridgeTransportConfig? = null + + override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { + setPreferencesFromResource(R.xml.tor_bridge_config_preferences, rootKey) + + // Initialize radio button group for built-in bridge transport types + val radioObfs4 = bindBridgeTransportRadio(TorBridgeTransportConfig.BUILTIN_OBFS4) + val radioMeek = bindBridgeTransportRadio(TorBridgeTransportConfig.BUILTIN_MEEK) + val radioSnowflake = bindBridgeTransportRadio(TorBridgeTransportConfig.BUILTIN_SNOWFLAKE) + + builtinBridgeRadioGroups.addAll(mutableListOf(radioObfs4, radioMeek, radioSnowflake)) + + // `*` is Kotlin's "spread" operator, for expanding an Array as a vararg. + addToRadioGroup(*builtinBridgeRadioGroups.toTypedArray()) + } + + override fun onResume() { + super.onResume() + + showToolbar(getString(R.string.preferences_tor_network_settings_bridge_config)) + + val bridgesEnabled = requireContext().components.torController.bridgesEnabled + + val prefBridgeConfig = + requirePreference<SwitchPreference>(R.string.pref_key_tor_network_settings_bridge_config_toggle) + prefBridgeConfig.apply { + isChecked = bridgesEnabled + setOnPreferenceChangeListener<Boolean> { preference, enabled -> + preference.context.components.torController.bridgesEnabled = enabled + updateCurrentConfiguredBridgePref(preference) + true + } + } + + val userProvidedBridges = requirePreference<EditTextPreference>( + R.string.pref_key_tor_network_settings_bridge_config_user_provided_bridge + ) + userProvidedBridges.apply { + setOnPreferenceChangeListener<String> { preference, userProvidedBridge -> + builtinBridgeRadioGroups.uncheckAll() + + preference.context.components.torController.bridgeTransport = TorBridgeTransportConfig.USER_PROVIDED + preference.context.components.torController.userProvidedBridges = userProvidedBridge + updateCurrentConfiguredBridgePref(preference) + true + } + val userProvidedBridge: String? = context.components.torController.userProvidedBridges + if (userProvidedBridge != null) { + setText(userProvidedBridge) + } + } + + val currentBridgeType = prefBridgeConfig.context.components.torController.bridgeTransport + // Cache the current configured transport type + previousTransportConfig = currentBridgeType + builtinBridgeRadioGroups.uncheckAll() + if (currentBridgeType != TorBridgeTransportConfig.USER_PROVIDED) { + val bridgeRadioButton = requirePreference<RadioButtonPreference>(currentBridgeType.preferenceKey) + bridgeRadioButton.setCheckedWithoutClickListener(true) + } + + updateCurrentConfiguredBridgePref(prefBridgeConfig) + } + + private fun bindBridgeTransportRadio( + bridge: TorBridgeTransportConfig + ): RadioButtonPreference { + val radio = requirePreference<RadioButtonPreference>(bridge.preferenceKey) + + radio.apply { + setOnPreferenceChangeListener<Boolean> { preference, isChecked -> + if (isChecked && (previousTransportConfig!! != bridge)) { + preference.context.components.torController.bridgeTransport = bridge + previousTransportConfig = bridge + updateCurrentConfiguredBridgePref(preference) + } + true + } + } + + return radio + } + + private fun setCurrentBridgeLabel(currentBridgePref: Preference?, bridge: String) { + currentBridgePref?.apply { + title = getString( + R + .string + .preferences_tor_network_settings_bridge_config_current_bridge, + bridge + ) + } + } + + private fun updateCurrentConfiguredBridgePref(preference: Preference) { + val currentBridge: Preference? = + findPreference( + getString( + R.string.pref_key_tor_network_settings_bridge_config_current_bridge + ) + ) + + val enabled = requireContext().components.torController.bridgesEnabled + + if (enabled) { + val configuredBridge = preference.context.components.torController.bridgeTransport + var bridges = when (configuredBridge) { + TorBridgeTransportConfig.USER_PROVIDED -> + preference.context.components.torController.userProvidedBridges + else -> configuredBridge.transportName + } + + if (bridges == null) { + bridges = "not known" + } + setCurrentBridgeLabel(currentBridge, bridges) + } else { + setCurrentBridgeLabel( + currentBridge, + getString(R.string.tor_network_settings_bridge_not_configured) + ) + } + } +} diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/settings/settingssearch/DefaultFenixSettingsIndexer.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/settings/settingssearch/DefaultFenixSettingsIndexer.kt @@ -309,7 +309,6 @@ class DefaultFenixSettingsIndexer(private val context: Context) : SettingsIndexe PreferenceFileInformation.DefaultSearchEnginePreferences, PreferenceFileInformation.DownloadsSettingsPreferences, PreferenceFileInformation.HomePreferences, - PreferenceFileInformation.OpenLinksInAppsPreferences, PreferenceFileInformation.PrivateBrowsingPreferences, PreferenceFileInformation.SearchSettingsPreferences, PreferenceFileInformation.SiteSettingsPreferences, diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/settings/settingssearch/PreferenceFileInformation.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/settings/settingssearch/PreferenceFileInformation.kt @@ -94,16 +94,6 @@ sealed class PreferenceFileInformation( ) /** - * Represents the "Open Links in Apps" settings screen. - */ - object OpenLinksInAppsPreferences : PreferenceFileInformation( - xmlResourceId = R.xml.open_links_in_apps_preferences, - topBreadcrumbResourceId = R.string.preferences_open_links_in_apps, - categoryHeaderResourceId = R.string.preferences_category_advanced, - fragmentId = R.id.openLinksInAppsFragment, - ) - - /** * Represents the "Private Browsing" settings screen. */ object PrivateBrowsingPreferences : PreferenceFileInformation( diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/tor/TorLogsComposeFragment.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/tor/TorLogsComposeFragment.kt @@ -0,0 +1,138 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package org.mozilla.fenix.tor + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.text.selection.DisableSelection +import androidx.compose.foundation.text.selection.SelectionContainer +import androidx.compose.foundation.verticalScroll +import androidx.compose.material3.FloatingActionButton +import androidx.compose.material3.Icon +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.DisposableEffect +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.Stable +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.ComposeView +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.unit.dp +import androidx.fragment.app.Fragment +import androidx.fragment.app.viewModels +import androidx.lifecycle.Observer +import androidx.lifecycle.compose.LocalLifecycleOwner +import mozilla.components.ui.colors.PhotonColors +import org.mozilla.fenix.R + +class TorLogsComposeFragment : Fragment() { + private val viewModel: TorLogsViewModel by viewModels() + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle?, + ): View { + return ComposeView(requireContext()).apply { + setContent { + Scaffold( + floatingActionButton = { CopyLogsButton() }, + content = { TorLogs(paddingValues = it) }, + ) + } + } + } + + @Composable + private fun TorLogs(paddingValues: PaddingValues) { + val torLogsState = remember { mutableStateOf<List<TorLog>>(emptyList()) } + val lifecycleOwner = LocalLifecycleOwner.current + val scrollState = rememberScrollState() + + DisposableEffect(viewModel.torLogs(), lifecycleOwner) { + val observer = Observer<List<TorLog>> { logs -> + torLogsState.value = logs + } + viewModel.torLogs().observe(lifecycleOwner, observer) + onDispose { + viewModel.torLogs().removeObserver(observer) + } + } + + val torLogs = torLogsState.value + + LaunchedEffect(torLogs) { + scrollState.animateScrollTo(scrollState.maxValue) + } + + SelectionContainer { + Column( + // Column instead of LazyColumn so that you can select all the logs, and not just one "screen" at a time + // The logs won't be too big so loading them all instead of just whats visible shouldn't be a big deal + modifier = Modifier + .fillMaxSize() + .verticalScroll(scrollState) + .padding(paddingValues) + .background(PhotonColors.Ink50), // Standard background color + ) { + for (log in torLogs) { + LogRow(log = log) + } + } + } + } + + @Composable + @Stable + private fun LogRow(log: TorLog, modifier: Modifier = Modifier) { + Column( + modifier + .fillMaxWidth() + .padding( + start = 16.dp, + end = 16.dp, + bottom = 16.dp, + ), + ) { + Text( + text = log.timestamp, + color = PhotonColors.LightGrey40, + modifier = modifier + .padding(bottom = 4.dp), + ) + Text( + text = "[${log.type}] " + log.text, + color = PhotonColors.LightGrey05, + ) + } + } + + @Composable + private fun CopyLogsButton() { + FloatingActionButton( + onClick = { viewModel.copyAllLogsToClipboard() }, + content = { + Icon( + painter = painterResource(id = R.drawable.ic_copy), + contentDescription = getString(R.string.share_copy_link_to_clipboard), + ) + }, + containerColor = PhotonColors.Violet50, // Same color as connect button + contentColor = PhotonColors.LightGrey05, + ) + } +} diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/tor/TorLogsViewModel.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/tor/TorLogsViewModel.kt @@ -0,0 +1,88 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package org.mozilla.fenix.tor + +import android.app.Application +import android.content.ClipData +import android.content.ClipboardManager +import android.content.Context +import android.os.Build +import android.widget.Toast +import androidx.lifecycle.AndroidViewModel +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData +import mozilla.components.browser.engine.gecko.GeckoEngine +import org.mozilla.fenix.R +import org.mozilla.fenix.ext.components +import org.mozilla.geckoview.TorAndroidIntegration.TorLogListener + +class TorLogsViewModel(application: Application) : AndroidViewModel(application), TorLogListener { + private val torController = application.components.torController + private val engine = application.components.core.engine as GeckoEngine + private val torAndroidIntegration = engine.getTorIntegrationController() + private val clipboardManager = + application.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager + + private val _torLogs: MutableLiveData<List<TorLog>> = MutableLiveData(mutableListOf()) + + fun torLogs(): LiveData<List<TorLog>> { + return _torLogs + } + + private fun addLog(log: TorLog) { + _torLogs.value = _torLogs.value?.plus(log) ?: return + } + + init { + setupClipboardListener() + torAndroidIntegration.registerLogListener(this) + val currentEntries = torController.logEntries + for (log in currentEntries) { + addLog(log) + } + } + + override fun onLog(type: String?, message: String?, timestamp: String?) { + addLog(TorLog(type ?: "null", message ?: "null", timestamp ?: "null")) + } + + override fun onCleared() { + super.onCleared() + torAndroidIntegration.unregisterLogListener(this) + } + + private fun setupClipboardListener() { + clipboardManager.addPrimaryClipChangedListener { + // Only show a toast for Android 12 and lower. + // https://developer.android.com/develop/ui/views/touch-and-input/copy-paste#duplicate-notifications + if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.S_V2) { + Toast.makeText( + getApplication<Application>().applicationContext, + getApplication<Application>().getString(R.string.toast_copy_link_to_clipboard), // "Copied to clipboard" already translated + Toast.LENGTH_SHORT, + ).show() + } + } + } + + fun copyAllLogsToClipboard() { + clipboardManager.setPrimaryClip( + ClipData.newPlainText( + getApplication<Application>().getString(R.string.preferences_tor_logs), + getAllTorLogs(), + ), + ) + } + + private fun getAllTorLogs(): String { + var ret = "" + for (log in torLogs().value + // FIXME: It was default_error_msg, but I don't understand where this came in the first place? + ?: return getApplication<Application>().getString(R.string.preferences_tor_logs)) { + ret += "${log.timestamp} [${log.type}] ${log.text}\n" + } + return ret + } +} diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/utils/Settings.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/utils/Settings.kt @@ -1077,6 +1077,7 @@ class Settings( /** * Get the display string for the current open links in apps setting */ + /* fun getOpenLinksInAppsString(): String = when (openLinksInExternalApp) { appContext.getString(R.string.pref_key_open_links_in_apps_always) -> { @@ -1093,6 +1094,7 @@ class Settings( appContext.getString(R.string.preferences_open_links_in_apps_never) } } + */ var shouldUseDarkTheme by booleanPreference( appContext.getPreferenceKey(R.string.pref_key_dark_theme), @@ -1841,26 +1843,31 @@ class Settings( /** * Check to see if we should open the link in an external app */ + @Suppress("UNUSED_PARAMETER") fun shouldOpenLinksInApp(isCustomTab: Boolean = false): Boolean { - return when (openLinksInExternalApp) { + return false + /*return when (openLinksInExternalApp) { appContext.getString(R.string.pref_key_open_links_in_apps_always) -> true appContext.getString(R.string.pref_key_open_links_in_apps_ask) -> true // Some applications will not work if custom tab never open links in apps, return true if it's custom tab appContext.getString(R.string.pref_key_open_links_in_apps_never) -> isCustomTab else -> false - } + }*/ } /** * Check to see if we need to prompt the user if the link can be opened in an external app */ fun shouldPromptOpenLinksInApp(): Boolean { + return true + /* return when (openLinksInExternalApp) { appContext.getString(R.string.pref_key_open_links_in_apps_always) -> false appContext.getString(R.string.pref_key_open_links_in_apps_ask) -> true appContext.getString(R.string.pref_key_open_links_in_apps_never) -> true else -> true } + */ } var openLinksInExternalApp by stringPreference( @@ -2920,4 +2927,9 @@ class Settings( appContext.getPreferenceKey(R.string.pref_key_downloads_default_location), default = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).path, ) + + var useHtmlConnectionUi by booleanPreference( + key = appContext.getPreferenceKey(R.string.pref_key_use_html_connection_ui), + default = false, + ) } diff --git a/mobile/android/fenix/app/src/main/res/drawable/ic_tor_config_bridge.xml b/mobile/android/fenix/app/src/main/res/drawable/ic_tor_config_bridge.xml @@ -0,0 +1,9 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="18dp" + android:height="18dp" + android:viewportWidth="24" + android:viewportHeight="24"> + <path + android:pathData="M22.7,19l-9.1,-9.1c0.9,-2.3 0.4,-5 -1.5,-6.9 -2,-2 -5,-2.4 -7.4,-1.3L9,6 6,9 1.6,4.7C0.4,7.1 0.9,10.1 2.9,12.1c1.9,1.9 4.6,2.4 6.9,1.5l9.1,9.1c0.4,0.4 1,0.4 1.4,0l2.3,-2.3c0.5,-0.4 0.5,-1.1 0.1,-1.4z" + android:fillColor="#ffffff"/> +</vector> diff --git a/mobile/android/fenix/app/src/main/res/drawable/ic_tor_network_settings.xml b/mobile/android/fenix/app/src/main/res/drawable/ic_tor_network_settings.xml @@ -0,0 +1,14 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24" + android:viewportHeight="24"> + <path + android:pathData="M12,0.75C18.2132,0.75 23.25,5.7865 23.25,11.9997C23.25,18.2132 18.2132,23.25 12,23.25C5.7865,23.25 0.75,18.2132 0.75,11.9997C0.75,5.7865 5.7865,0.75 12,0.75ZM12.0231,2.7958L12.0231,4.1596C16.3429,4.172 19.8408,7.677 19.8408,11.9997C19.8408,16.3227 16.3429,19.8277 12.0231,19.8401L12.0231,19.8401L12.0231,21.2039C17.0961,21.1914 21.2042,17.0758 21.2042,11.9997C21.2042,6.9239 17.0961,2.8086 12.0231,2.7958L12.0231,2.7958ZM12.0231,6.2057L12.0231,7.5691C14.4596,7.5819 16.4318,9.56 16.4318,11.9997C16.4318,14.4397 14.4596,16.4178 12.0231,16.4306L12.0231,16.4306L12.0231,17.794C15.2131,17.7816 17.7956,15.1928 17.7956,11.9997C17.7956,8.8069 15.2131,6.2181 12.0231,6.2057L12.0231,6.2057ZM12.0231,9.6146L12.0231,14.3851C13.3301,14.3726 14.3863,13.3101 14.3863,11.9997C14.3863,10.6896 13.3301,9.6271 12.0231,9.6146L12.0231,9.6146Z" + android:strokeAlpha="0.8" + android:strokeWidth="1" + android:fillColor="?attr/textPrimary" + android:fillType="evenOdd" + android:strokeColor="#00000000" + android:fillAlpha="0.8"/> +</vector> diff --git a/mobile/android/fenix/app/src/main/res/layout/preference_quick_start.xml b/mobile/android/fenix/app/src/main/res/layout/preference_quick_start.xml @@ -0,0 +1,13 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- This Source Code Form is subject to the terms of the Mozilla Public + - License, v. 2.0. If a copy of the MPL was not distributed with this + - file, You can obtain one at http://mozilla.org/MPL/2.0/. --> + +<com.google.android.material.switchmaterial.SwitchMaterial xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/switch_widget" + android:layout_width="wrap_content" + android:layout_height="match_parent" + android:clickable="false" + android:focusable="false" + android:gravity="center_vertical" + android:orientation="vertical" /> diff --git a/mobile/android/fenix/app/src/main/res/navigation/nav_graph.xml b/mobile/android/fenix/app/src/main/res/navigation/nav_graph.xml @@ -706,6 +706,20 @@ app:popEnterAnim="@anim/slide_in_left" app:popExitAnim="@anim/slide_out_right" /> <action + android:id="@+id/action_settingsFragment_to_torBridgeConfigFragment" + app:destination="@id/torBridgeConfigFragment" + app:enterAnim="@anim/slide_in_right" + app:exitAnim="@anim/slide_out_left" + app:popEnterAnim="@anim/slide_in_left" + app:popExitAnim="@anim/slide_out_right" /> + <action + android:id="@+id/action_settingsFragment_to_torLogsFragment" + app:destination="@+id/torLogsFragment" + app:enterAnim="@anim/slide_in_right" + app:exitAnim="@anim/slide_out_left" + app:popEnterAnim="@anim/slide_in_left" + app:popExitAnim="@anim/slide_out_right" /> + <action android:id="@+id/action_settingsFragment_to_trackingProtectionFragment" app:destination="@id/trackingProtectionFragment" app:enterAnim="@anim/slide_in_right" @@ -754,6 +768,7 @@ app:exitAnim="@anim/slide_out_left" app:popEnterAnim="@anim/slide_in_left" app:popExitAnim="@anim/slide_out_right" /> + <!-- <action android:id="@+id/action_settingsFragment_to_openLinksInAppsFragment" app:destination="@id/openLinksInAppsFragment" @@ -761,6 +776,7 @@ app:exitAnim="@anim/slide_out_left" app:popEnterAnim="@anim/slide_in_left" app:popExitAnim="@anim/slide_out_right" /> + --> <action android:id="@+id/action_settingsFragment_to_openDownloadsSettingsFragment" app:destination="@id/openDownloadsSettingsFragment" @@ -1047,8 +1063,11 @@ <fragment android:id="@+id/torBridgeConfigFragment" android:name="org.mozilla.fenix.settings.TorBridgeConfigFragment" - android:label="@string/preferences_tor_network_settings_bridge_config" - tools:layout="@layout/fragment_tor_bridge_config" /> + android:label="@string/preferences_tor_network_settings_bridge_config" /> + <fragment + android:id="@+id/torLogsFragment" + android:name="org.mozilla.fenix.tor.TorLogsComposeFragment" + android:label="@string/preferences_tor_logs" /> <fragment android:id="@+id/trackingProtectionFragment" android:name="org.mozilla.fenix.settings.TrackingProtectionFragment"> @@ -1072,6 +1091,7 @@ app:popEnterAnim="@anim/slide_in_left" app:popExitAnim="@anim/slide_out_right" /> </fragment> + <!-- <fragment android:id="@+id/openLinksInAppsFragment" android:name="org.mozilla.fenix.settings.OpenLinksInAppsFragment" > @@ -1081,6 +1101,7 @@ app:argType="string" app:nullable="true" /> </fragment> + --> <fragment android:id="@+id/openDownloadsSettingsFragment" android:name="org.mozilla.fenix.settings.downloads.DownloadsSettingsFragment"> diff --git a/mobile/android/fenix/app/src/main/res/values/preference_keys.xml b/mobile/android/fenix/app/src/main/res/values/preference_keys.xml @@ -14,6 +14,9 @@ <string name="pref_key_accessibility" translatable="false">pref_key_accessibility</string> <string name="pref_key_accessibility_auto_size" translatable="false">pref_key_accessibility_auto_size</string> <string name="pref_key_accessibility_font_scale" translatable="false">pref_key_accessibility_font_scale</string> + <string name="pref_key_privacy" translatable="false">pref_key_privacy</string> + <string name="pref_key_connection" translatable="false">pref_key_connection</string> + <string name="pref_key_quick_start" translatable="false">pref_key_quick_start</string> <string name="pref_key_accessibility_force_enable_zoom" translatable="false">pref_key_accessibility_force_enable_zoom</string> <string name="pref_key_advanced" translatable="false">pref_key_advanced</string> <string name="pref_key_firefox_labs" translatable="false">pref_key_firefox_labs</string> @@ -540,5 +543,25 @@ <string name="pref_key_tor_security_level_safer_option" translatable="false">pref_key_tor_security_level_safer_option</string> <string name="pref_key_tor_security_level_safest_option" translatable="false">pref_key_tor_security_level_safest_option</string> + <string name="pref_key_tor_network_settings_explanation" translatable="false">pref_key_tor_network_settings_explanation</string> + <string name="pref_key_tor_network_settings_bridge_config" translatable="false">pref_key_tor_network_settings_bridge_config</string> + <string name="pref_key_tor_logs" translatable="false">pref_key_tor_logs</string> + <string name="pref_key_use_html_connection_ui" translatable="false">pref_key_use_html_connection_ui</string> <!-- Changing the pref_key should reset it to off for users that had it enabled --> + <string name="pref_key_about_config_shortcut" translatable="false">pref_key_about_config_shortcut</string> + <string name="pref_key_tor_network_settings_bridge_config_explanation" translatable="false">pref_key_tor_network_settings_bridge_config_explanation</string> + <string name="pref_key_tor_network_settings_bridge_config_toggle" translatable="false">pref_key_tor_network_settings_bridge_config_toggle</string> + <string name="pref_key_tor_network_settings_bridge_config_builtin_bridge_obfs4" translatable="false">pref_key_tor_network_settings_bridge_config_builtin_bridge_obfs4</string> + <!-- NOTE: The "meek_azure" part is a legacy name for simply "meek", kept + - only to avoid a preference migration. See tor-browser#44068 --> + <string name="pref_key_tor_network_settings_bridge_config_builtin_bridge_meek_azure" translatable="false">pref_key_tor_network_settings_bridge_config_builtin_bridge_meek_azure</string> + <string name="pref_key_tor_network_settings_bridge_config_builtin_bridge_snowflake" translatable="false">pref_key_tor_network_settings_bridge_config_builtin_bridge_snowflake</string> + <string name="pref_key_tor_network_settings_bridge_config_user_provided_bridge" translatable="false">pref_key_tor_network_settings_bridge_config_user_provided_bridge</string> + <string name="pref_key_tor_network_settings_bridge_config_user_provided_bridge_preference" translatable="false">pref_key_tor_network_settings_bridge_config_user_provided_bridge_preference</string> + <string name="pref_key_tor_network_settings_bridge_config_current_bridge" translatable="false">pref_key_tor_network_settings_bridge_config_current_bridge</string> + <string name="pref_key_tor_network_settings_status">pref_key_tor_network_settings_status</string> + <string name="pref_key_tor_network_settings_tor_ready">pref_key_tor_network_settings_tor_ready</string> + <string name="pref_key_tor_network_settings_state">pref_key_tor_network_settings_state</string> + <string name="pref_key_tor_network_settings_bridges_enabled">pref_key_tor_network_settings_bridges_enabled</string> + <string name="pref_key_spoof_english" translatable="false">pref_key_spoof_english</string> </resources> diff --git a/mobile/android/fenix/app/src/main/res/xml/preferences.xml b/mobile/android/fenix/app/src/main/res/xml/preferences.xml @@ -158,6 +158,54 @@ </androidx.preference.PreferenceCategory> + <!-- title="@string/preferences_category_advanced" + key="@string/pref_key_advanced"--> + <PreferenceCategory + android:title="@string/preferences_tor_connection_settings_title" + android:key="@string/pref_key_connection" + android:layout="@layout/preference_category_no_icon_style"> + + <Preference + android:key="@string/pref_key_tor_network_settings_explanation" + app:iconSpaceReserved="false" + android:title="@string/preferences_tor_network_settings_explanation" /> + + <Preference + android:key="@string/pref_key_tor_network_settings_bridge_config" + app:iconSpaceReserved="false" + android:title="@string/preferences_tor_network_settings_bridge_config" + android:summary="@string/preferences_tor_network_settings_bridge_config_description" /> + + <SwitchPreference + android:key="@string/pref_key_quick_start" + android:title="@string/tor_connect_automatically_label" + app:iconSpaceReserved="false" /> + + <SwitchPreference + android:defaultValue="false" + android:key="@string/pref_key_use_html_connection_ui" + android:summary="Recommended only for debugging" + android:title="Enable HTML connection UI" + app:iconSpaceReserved="false" /> + + <Preference + android:key="@string/pref_key_tor_logs" + app:iconSpaceReserved="false" + android:title="@string/preferences_tor_logs" + android:summary="@string/preferences_tor_logs_description" /> + + <Preference + android:key="@string/pref_key_about_config_shortcut" + app:iconSpaceReserved="false" + android:title="about:config"/> + + + <!-- Auto connect --> + + <!-- Tor Logs --> + + </PreferenceCategory> + <PreferenceCategory android:title="@string/preferences_category_advanced" android:key="@string/pref_key_advanced" @@ -179,6 +227,7 @@ app:iconSpaceReserved="false" android:title="@string/preferences_customize_extension_collection" /> + <!-- <androidx.preference.Preference android:key="@string/pref_key_link_sharing" app:iconSpaceReserved="false" @@ -189,6 +238,7 @@ android:key="@string/pref_key_open_links_in_apps" android:title="@string/preferences_open_links_in_apps" app:iconSpaceReserved="false" /> + --> <androidx.preference.Preference android:key="@string/pref_key_downloads" diff --git a/mobile/android/fenix/app/src/main/res/xml/tor_bridge_config_preferences.xml b/mobile/android/fenix/app/src/main/res/xml/tor_bridge_config_preferences.xml @@ -0,0 +1,39 @@ +<?xml version="1.0" encoding="utf-8"?><!-- This Source Code Form is subject to the terms of the Mozilla Public + - License, v. 2.0. If a copy of the MPL was not distributed with this + - file, You can obtain one at http://mozilla.org/MPL/2.0/. --> +<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto"> + <androidx.preference.Preference + android:key="@string/pref_key_tor_network_settings_bridge_config_explanation" + android:summary="@string/preferences_tor_network_settings_bridge_config_explanation" + app:allowDividerBelow="false" /> + <SwitchPreference + android:defaultValue="false" + android:key="@string/pref_key_tor_network_settings_bridge_config_toggle" + android:title="@string/preferences_tor_network_settings_bridge_config_toggle" + android:summary="@string/preferences_tor_network_settings_bridge_config_toggle_description" /> + <org.mozilla.fenix.settings.RadioButtonPreference + android:defaultValue="false" + android:dependency="@string/pref_key_tor_network_settings_bridge_config_toggle" + android:key="@string/pref_key_tor_network_settings_bridge_config_builtin_bridge_obfs4" + android:title="@string/preferences_tor_network_settings_bridge_config_builtin_bridge_obfs4" /> + <org.mozilla.fenix.settings.RadioButtonPreference + android:defaultValue="false" + android:dependency="@string/pref_key_tor_network_settings_bridge_config_toggle" + android:key="@string/pref_key_tor_network_settings_bridge_config_builtin_bridge_meek_azure" + android:title="@string/preferences_tor_network_settings_bridge_config_builtin_bridge_meek" /> + <org.mozilla.fenix.settings.RadioButtonPreference + android:defaultValue="false" + android:dependency="@string/pref_key_tor_network_settings_bridge_config_toggle" + android:key="@string/pref_key_tor_network_settings_bridge_config_builtin_bridge_snowflake" + android:title="@string/preferences_tor_network_settings_bridge_config_builtin_bridge_snowflake2" /> + <EditTextPreference + android:dependency="@string/pref_key_tor_network_settings_bridge_config_toggle" + android:key="@string/pref_key_tor_network_settings_bridge_config_user_provided_bridge" + android:title="@string/preferences_tor_network_settings_bridge_config_user_provided_bridge" + android:summary="@string/preferences_tor_network_settings_bridge_config_user_provided_bridge_description" /> + <Preference + android:dependency="@string/pref_key_tor_network_settings_bridge_config_toggle" + android:key="@string/pref_key_tor_network_settings_bridge_config_current_bridge" + android:title="@string/preferences_tor_network_settings_bridge_config_current_bridge" /> +</androidx.preference.PreferenceScreen> diff --git a/mobile/android/fenix/app/src/test/java/org/mozilla/fenix/utils/SettingsTest.kt b/mobile/android/fenix/app/src/test/java/org/mozilla/fenix/utils/SettingsTest.kt @@ -984,28 +984,28 @@ class SettingsTest { assertFalse(settings.shouldUseHttpsOnlyInPrivateTabsOnly) } - @Test - fun `GIVEN open links in apps setting THEN return the correct display string`() { - settings.openLinksInExternalApp = "pref_key_open_links_in_apps_always" - settings.lastKnownMode = BrowsingMode.Normal - assertEquals(settings.getOpenLinksInAppsString(), "Always") - - settings.openLinksInExternalApp = "pref_key_open_links_in_apps_ask" - assertEquals(settings.getOpenLinksInAppsString(), "Ask before opening") - - settings.openLinksInExternalApp = "pref_key_open_links_in_apps_never" - assertEquals(settings.getOpenLinksInAppsString(), "Never") - - settings.openLinksInExternalApp = "pref_key_open_links_in_apps_always" - settings.lastKnownMode = BrowsingMode.Private - assertEquals(settings.getOpenLinksInAppsString(), "Ask before opening") - - settings.openLinksInExternalApp = "pref_key_open_links_in_apps_ask" - assertEquals(settings.getOpenLinksInAppsString(), "Ask before opening") - - settings.openLinksInExternalApp = "pref_key_open_links_in_apps_never" - assertEquals(settings.getOpenLinksInAppsString(), "Never") - } +// @Test +// fun `GIVEN open links in apps setting THEN return the correct display string`() { +// settings.openLinksInExternalApp = "pref_key_open_links_in_apps_always" +// settings.lastKnownMode = BrowsingMode.Normal +// assertEquals(settings.getOpenLinksInAppsString(), "Always") +// +// settings.openLinksInExternalApp = "pref_key_open_links_in_apps_ask" +// assertEquals(settings.getOpenLinksInAppsString(), "Ask before opening") +// +// settings.openLinksInExternalApp = "pref_key_open_links_in_apps_never" +// assertEquals(settings.getOpenLinksInAppsString(), "Never") +// +// settings.openLinksInExternalApp = "pref_key_open_links_in_apps_always" +// settings.lastKnownMode = BrowsingMode.Private +// assertEquals(settings.getOpenLinksInAppsString(), "Ask before opening") +// +// settings.openLinksInExternalApp = "pref_key_open_links_in_apps_ask" +// assertEquals(settings.getOpenLinksInAppsString(), "Ask before opening") +// +// settings.openLinksInExternalApp = "pref_key_open_links_in_apps_never" +// assertEquals(settings.getOpenLinksInAppsString(), "Never") +// } @Test fun `GIVEN a written integer value for pref_key_search_widget_installed WHEN reading searchWidgetInstalled THEN do not throw a ClassCastException`() {