Commit b7c849b0 authored by Matthew Finkel's avatar Matthew Finkel Committed by Pier Angelo Vendrame
Browse files

TB 40041 [android]: Implement Tor Network Settings

Originally, fenix#40041.
parent 68b6b20e
Loading
Loading
Loading
Loading
+0 −4
Original line number Diff line number Diff line
@@ -21,7 +21,6 @@ class OpenLinksInAppsFragment : PreferenceFragmentCompat(), SystemInsetsPaddedFr
    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)
@@ -39,9 +38,6 @@ class OpenLinksInAppsFragment : PreferenceFragmentCompat(), SystemInsetsPaddedFr
        showToolbar(getString(R.string.preferences_open_links_in_apps))

        setupPreferences()
        args.preferenceToScrollTo?.let {
            scrollToPreferenceWithHighlight(it)
        }
    }

    private fun setupPreferences() {
+77 −8
Original line number Diff line number Diff line
@@ -10,6 +10,7 @@ import android.content.Intent
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
@@ -33,6 +34,7 @@ import androidx.preference.SwitchPreferenceCompat
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
@@ -62,6 +64,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
@@ -84,6 +87,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 java.lang.ref.WeakReference
import kotlin.system.exitProcess
@@ -108,6 +112,8 @@ class SettingsFragment : PreferenceFragmentCompat(), SystemInsetsPaddedFragment
    private val snackbarBinding = ViewBoundFeatureWrapper<SnackbarBinding>()
    private val dateTimeProvider: DateTimeProvider by lazy { DefaultDateTimeProvider() }

    private val quickstartViewModel: QuickstartViewModel by activityViewModels()

    @VisibleForTesting
    internal val accountObserver = object : AccountObserver {
        private fun updateAccountUi(profile: Profile? = null) {
@@ -206,8 +212,10 @@ class SettingsFragment : PreferenceFragmentCompat(), SystemInsetsPaddedFragment
    }

    override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
        requireContext().components.strictMode.allowViolation(StrictMode::allowThreadDiskReads) {
            setPreferencesFromResource(R.xml.preferences, rootKey)
        }
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
@@ -272,7 +280,11 @@ class SettingsFragment : PreferenceFragmentCompat(), SystemInsetsPaddedFragment
        )

        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 {
            scrollToPreferenceWithHighlight(it)
@@ -336,9 +348,9 @@ class SettingsFragment : PreferenceFragmentCompat(), SystemInsetsPaddedFragment
//            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)

@@ -539,9 +551,9 @@ class SettingsFragment : PreferenceFragmentCompat(), SystemInsetsPaddedFragment
                SettingsFragmentDirections.actionSettingsFragmentToRemoteImprovementsFragment()
            }

            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()
@@ -692,6 +704,7 @@ class SettingsFragment : PreferenceFragmentCompat(), SystemInsetsPaddedFragment
        setupTrackingProtectionPreference(settings)
        setupDnsOverHttpsPreference(settings)
        setupEmailMaskPreference(settings, requireComponents)
        setupConnectionPreferences()
    }

    private val setToDefaultPromptRequestLauncher: ActivityResultLauncher<Intent> =
@@ -738,6 +751,11 @@ class SettingsFragment : PreferenceFragmentCompat(), SystemInsetsPaddedFragment
        }
    }

    private fun RecyclerView.disableHidingAnimation() {
        this.setItemAnimator(null)
        this.setLayoutAnimation(null)
    }

    @VisibleForTesting
    internal fun setupAmoCollectionOverridePreference(
        settings: Settings,
@@ -844,6 +862,57 @@ class SettingsFragment : PreferenceFragmentCompat(), SystemInsetsPaddedFragment
        }
    }

    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<SwitchPreferenceCompat>(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 {
                @Suppress("DEPRECATION")
                (requireActivity() as HomeActivity).openToBrowserAndLoad(
                    searchTermOrURL = "about:config",
                    from = BrowserDirection.FromSettings,
                    newTab = true,
                )
                true
            }
        }
    }

    @VisibleForTesting
    internal fun setupEmailMaskPreference(settings: Settings, components: Components) {
        findPreference<Preference>(getPreferenceKey(R.string.pref_key_email_masks))?.let {
+152 −0
Original line number Diff line number Diff line
/* 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)
            )
        }
    }
}
+0 −1
Original line number Diff line number Diff line
@@ -347,7 +347,6 @@ class DefaultFenixSettingsIndexer(
            PreferenceFileInformation.DefaultSearchEnginePreferences,
            PreferenceFileInformation.DownloadsSettingsPreferences,
            PreferenceFileInformation.HomePreferences,
            PreferenceFileInformation.OpenLinksInAppsPreferences,
            PreferenceFileInformation.PrivateBrowsingPreferences,
            PreferenceFileInformation.SearchSettingsPreferences,
            PreferenceFileInformation.SiteSettingsPreferences,
+0 −10
Original line number Diff line number Diff line
@@ -93,16 +93,6 @@ sealed class PreferenceFileInformation(
        fragmentId = R.id.homeSettingsFragment,
    )

    /**
     * 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.
     */
Loading