Commit 5e60c209 authored by clairehurst's avatar clairehurst 🌱
Browse files

Implement Android-native Connection Assist UI

parent cd3473f6
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -41,4 +41,5 @@ enum class BrowserDirection(@IdRes val fragmentId: Int) {
    FromLoginDetailFragment(R.id.loginDetailFragment),
    FromTabsTray(R.id.tabsTrayFragment),
    FromRecentlyClosed(R.id.recentlyClosedFragment),
    FromTorConnectionAssistFragment(R.id.torConnectionAssistFragment)
}
+5 −0
Original line number Diff line number Diff line
@@ -157,6 +157,7 @@ import org.mozilla.fenix.tabstray.TabsTrayFragment
import org.mozilla.fenix.tabstray.TabsTrayFragmentDirections
import org.mozilla.fenix.theme.DefaultThemeManager
import org.mozilla.fenix.theme.ThemeManager
import org.mozilla.fenix.tor.TorConnectionAssistFragmentDirections
import org.mozilla.fenix.tor.TorEvents
import org.mozilla.fenix.trackingprotection.TrackingProtectionPanelDialogFragmentDirections
import org.mozilla.fenix.utils.BrowsersCache
@@ -1085,6 +1086,10 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity, TorIn
        BrowserDirection.FromStudiesFragment -> StudiesFragmentDirections.actionGlobalBrowser(
            customTabSessionId,
        )
        BrowserDirection.FromTorConnectionAssistFragment,
        -> TorConnectionAssistFragmentDirections.actionGlobalBrowser(
            customTabSessionId,
        )
    }

    /**
+131 −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.tor

import android.os.Build
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import androidx.navigation.fragment.findNavController
import kotlinx.coroutines.launch
import org.mozilla.fenix.R
import org.mozilla.fenix.databinding.FragmentTorConnectionAssistBinding
import org.mozilla.fenix.ext.hideToolbar

class TorConnectionAssistFragment : Fragment() {

    private var _binding: FragmentTorConnectionAssistBinding? = null
    private val binding get() = _binding!!

    private val viewModel: TorConnectionAssistViewModel by viewModels()

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?,
    ): View {
        _binding = FragmentTorConnectionAssistBinding.inflate(
            inflater, container, false,
        )

        return binding.root
    }

    override fun onResume() {
        super.onResume()
        hideToolbar()
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        lifecycleScope.launch {
            repeatOnLifecycle(Lifecycle.State.STARTED) {
                viewModel.torConnectState.collect {
                    when (it) {
                        TorConnectState.Initial -> showConfiguring()
                        TorConnectState.Configuring -> showConfiguring()
                        TorConnectState.AutoBootstrapping -> showBootstrapping()
                        TorConnectState.Bootstrapping -> showBootstrapping()
                        TorConnectState.Error -> TODO()
                        TorConnectState.Bootstrapped -> openHome()
                        TorConnectState.Disabled -> openHome()
                    }
                }
            }
        }

        viewModel.progress().observe(
            viewLifecycleOwner,
        ) { progress ->
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
                binding.torBootstrapProgressBar.setProgress(progress, true)
            } else {
                binding.torBootstrapProgressBar.progress = progress
            }
        }

        viewModel.quickconnectToggle().observe(
            viewLifecycleOwner
        ) {
            binding.quickstartSwitch.isChecked = it
        }

        binding.settingsButton.setOnClickListener {
            viewModel.cancelTorBootstrap()
            openSettings()
        }

        binding.torBootstrapConnectButton.setOnClickListener {
            viewModel.handleConnect(lifecycleScope = lifecycleScope)
        }

        binding.quickstartSwitch.setOnCheckedChangeListener { _, isChecked ->
            viewModel.handleQuickstartChecked(isChecked)
        }
    }

    private fun showConfiguring() {
        binding.torBootstrapConnectButton.visibility = View.VISIBLE
        binding.torBootstrapNetworkSettingsButton.text =
            getString(R.string.connection_assist_configure_connection_button)
        binding.torBootstrapNetworkSettingsButton.setOnClickListener {
            openTorNetworkSettings()
        }
    }

    private fun showBootstrapping() {
        binding.torBootstrapConnectButton.visibility = View.INVISIBLE
        binding.torBootstrapNetworkSettingsButton.text = getString(R.string.btn_cancel)
        binding.torBootstrapNetworkSettingsButton.setOnClickListener {
            viewModel.cancelTorBootstrap()
        }
    }

    private fun openSettings(preferenceToScrollTo: String? = null) {
        findNavController().navigate(
            TorConnectionAssistFragmentDirections
                .actionTorConnectionAssistFragmentToSettingsFragment(preferenceToScrollTo),
        )
    }

    private fun openTorNetworkSettings() {
        findNavController().navigate(
            TorConnectionAssistFragmentDirections
                .actionTorConnectionAssistFragmentToTorNetworkSettings(),
        )
    }

    private fun openHome() {
        findNavController().navigate(TorConnectionAssistFragmentDirections.actionStartupHome())
    }

}
+86 −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.tor

import android.app.Application
import android.util.Log
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.LifecycleCoroutineScope
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import org.mozilla.fenix.ext.components

class TorConnectionAssistViewModel(
    application: Application,
) : AndroidViewModel(application), TorEvents {

    private val TAG = "torConnectionAssistVM"
    private val components = getApplication<Application>().components

    private val _torController: TorControllerGV = components.torController as TorControllerGV

    private val _torConnectState = MutableStateFlow(TorConnectState.Initial)
    internal val torConnectState: StateFlow<TorConnectState> = _torConnectState

    init {
        _torController.registerTorListener(this)
    }

    private val _progress = MutableLiveData(0)
    fun progress(): LiveData<Int> {
        return _progress
    }

    private val _quickconnectToggle = MutableLiveData(_torController.quickstart)
    fun quickconnectToggle(): LiveData<Boolean> {
        return _quickconnectToggle
    }

    fun handleConnect(
        withDebugLogging: Boolean = false,
        lifecycleScope: LifecycleCoroutineScope? = null,
    ) {
        _torController.initiateTorBootstrap(
            withDebugLogging = withDebugLogging,
            lifecycleScope = lifecycleScope,
        )
    }

    fun handleQuickstartChecked(checked: Boolean) {
        _torController.quickstart = checked
    }

    fun cancelTorBootstrap() {
        _torController.stopTor()
        _torController.setTorStopped()
    }

    override fun onTorConnecting() {
        Log.d(TAG, "onTorConnecting()")
    }

    override fun onTorConnected() {
        Log.d(TAG, "onTorConnected()")
        _torController.unregisterTorListener(this)
        _torConnectState.value = _torController.lastKnownStatus
    }

    override fun onTorStatusUpdate(entry: String?, status: String?, progress: Double?) {
        Log.d(TAG, "onTorStatusUpdate($entry, $status, $progress)")
        if (progress != null) {
            _progress.value = progress.toInt()
        }
        _torConnectState.value = _torController.lastKnownStatus
    }

    override fun onTorStopped() {
        Log.d(TAG, "onTorStopped()")
        _progress.value = 0
        _torConnectState.value = _torController.lastKnownStatus
    }

}
+1 −0
Original line number Diff line number Diff line
@@ -49,6 +49,7 @@ interface TorController: TorEvents {
    var bridgesEnabled: Boolean
    var bridgeTransport: TorBridgeTransportConfig
    var userProvidedBridges: String?
    var quickstart: Boolean

    fun start()
    fun stop()
Loading