GitLab is used only for code review, issue tracking and project management. Canonical locations for source code are still https://gitweb.torproject.org/ https://git.torproject.org/ and git-rw.torproject.org.

Commit 232247fd authored by Matthew Finkel's avatar Matthew Finkel
Browse files

Bug 40028: Implement Tor connect and logger screens

parent f39de0b6
...@@ -253,7 +253,8 @@ class HomeFragment : Fragment() { ...@@ -253,7 +253,8 @@ class HomeFragment : Fragment() {
handleSwipedItemDeletionCancel = ::handleSwipedItemDeletionCancel, handleSwipedItemDeletionCancel = ::handleSwipedItemDeletionCancel,
handleTorBootstrapConnect = ::handleTorBootstrapConnect, handleTorBootstrapConnect = ::handleTorBootstrapConnect,
cancelTorBootstrap = ::cancelTorBootstrap, cancelTorBootstrap = ::cancelTorBootstrap,
initiateTorBootstrap = ::initiateTorBootstrap initiateTorBootstrap = ::initiateTorBootstrap,
openTorNetworkSettings = ::openTorNetworkSettings
) )
) )
...@@ -1062,6 +1063,9 @@ class HomeFragment : Fragment() { ...@@ -1062,6 +1063,9 @@ class HomeFragment : Fragment() {
requireComponents.torController.initiateTorBootstrap(lifecycleScope, withDebugLogging) requireComponents.torController.initiateTorBootstrap(lifecycleScope, withDebugLogging)
} }
private fun openTorNetworkSettings() {
}
companion object { companion object {
const val ALL_NORMAL_TABS = "all_normal" const val ALL_NORMAL_TABS = "all_normal"
const val ALL_PRIVATE_TABS = "all_private" const val ALL_PRIVATE_TABS = "all_private"
......
...@@ -21,6 +21,7 @@ import org.mozilla.fenix.home.sessioncontrol.viewholders.CollectionHeaderViewHol ...@@ -21,6 +21,7 @@ import org.mozilla.fenix.home.sessioncontrol.viewholders.CollectionHeaderViewHol
import org.mozilla.fenix.home.sessioncontrol.viewholders.CollectionViewHolder import org.mozilla.fenix.home.sessioncontrol.viewholders.CollectionViewHolder
import org.mozilla.fenix.home.sessioncontrol.viewholders.NoCollectionsMessageViewHolder import org.mozilla.fenix.home.sessioncontrol.viewholders.NoCollectionsMessageViewHolder
import org.mozilla.fenix.home.sessioncontrol.viewholders.PrivateBrowsingDescriptionViewHolder import org.mozilla.fenix.home.sessioncontrol.viewholders.PrivateBrowsingDescriptionViewHolder
import org.mozilla.fenix.home.sessioncontrol.viewholders.TorBootstrapPagerViewHolder
import org.mozilla.fenix.home.sessioncontrol.viewholders.TabInCollectionViewHolder import org.mozilla.fenix.home.sessioncontrol.viewholders.TabInCollectionViewHolder
import org.mozilla.fenix.home.sessioncontrol.viewholders.TopSitePagerViewHolder import org.mozilla.fenix.home.sessioncontrol.viewholders.TopSitePagerViewHolder
import org.mozilla.fenix.home.sessioncontrol.viewholders.onboarding.OnboardingAutomaticSignInViewHolder import org.mozilla.fenix.home.sessioncontrol.viewholders.onboarding.OnboardingAutomaticSignInViewHolder
...@@ -60,6 +61,8 @@ sealed class AdapterItem(@LayoutRes val viewType: Int) { ...@@ -60,6 +61,8 @@ sealed class AdapterItem(@LayoutRes val viewType: Int) {
object PrivateBrowsingDescription : AdapterItem(PrivateBrowsingDescriptionViewHolder.LAYOUT_ID) object PrivateBrowsingDescription : AdapterItem(PrivateBrowsingDescriptionViewHolder.LAYOUT_ID)
object NoCollectionsMessage : AdapterItem(NoCollectionsMessageViewHolder.LAYOUT_ID) object NoCollectionsMessage : AdapterItem(NoCollectionsMessageViewHolder.LAYOUT_ID)
object TorBootstrap : AdapterItem(TorBootstrapPagerViewHolder.LAYOUT_ID)
object CollectionHeader : AdapterItem(CollectionHeaderViewHolder.LAYOUT_ID) object CollectionHeader : AdapterItem(CollectionHeaderViewHolder.LAYOUT_ID)
data class CollectionItem( data class CollectionItem(
val collection: TabCollection, val collection: TabCollection,
...@@ -152,6 +155,11 @@ class SessionControlAdapter( ...@@ -152,6 +155,11 @@ class SessionControlAdapter(
view, view,
interactor interactor
) )
TorBootstrapPagerViewHolder.LAYOUT_ID -> TorBootstrapPagerViewHolder(
view,
components,
interactor
)
NoCollectionsMessageViewHolder.LAYOUT_ID -> NoCollectionsMessageViewHolder.LAYOUT_ID ->
NoCollectionsMessageViewHolder( NoCollectionsMessageViewHolder(
view, view,
......
...@@ -156,9 +156,9 @@ interface SessionControlController { ...@@ -156,9 +156,9 @@ interface SessionControlController {
fun handleTorBootstrapConnectClicked() fun handleTorBootstrapConnectClicked()
/** /**
* @see [TorBootstrapInteractor.onTorBootstrapConnectingClicked] * @see [TorBootstrapInteractor.onTorStopBootstrapping]
*/ */
fun handleTorBootstrapConnectingClicked() fun handleTorStopBootstrapping()
/** /**
* @see [TorBootstrapInteractor.onTorStartBootstrapping] * @see [TorBootstrapInteractor.onTorStartBootstrapping]
...@@ -169,6 +169,11 @@ interface SessionControlController { ...@@ -169,6 +169,11 @@ interface SessionControlController {
* @see [TorBootstrapInteractor.onTorStartDebugBootstrapping] * @see [TorBootstrapInteractor.onTorStartDebugBootstrapping]
*/ */
fun handleTorStartDebugBootstrapping() fun handleTorStartDebugBootstrapping()
/**
* @see [TorBootstrapInteractor.onTorBootstrapNetworkSettingsClicked]
*/
fun handleTorNetworkSettingsClicked()
} }
@Suppress("TooManyFunctions", "LargeClass") @Suppress("TooManyFunctions", "LargeClass")
...@@ -196,7 +201,8 @@ class DefaultSessionControlController( ...@@ -196,7 +201,8 @@ class DefaultSessionControlController(
private val handleSwipedItemDeletionCancel: () -> Unit, private val handleSwipedItemDeletionCancel: () -> Unit,
private val handleTorBootstrapConnect: () -> Unit, private val handleTorBootstrapConnect: () -> Unit,
private val initiateTorBootstrap: (Boolean) -> Unit, private val initiateTorBootstrap: (Boolean) -> Unit,
private val cancelTorBootstrap: () -> Unit private val cancelTorBootstrap: () -> Unit,
private val openTorNetworkSettings: () -> Unit
) : SessionControlController { ) : SessionControlController {
override fun handleCollectionAddTabTapped(collection: TabCollection) { override fun handleCollectionAddTabTapped(collection: TabCollection) {
...@@ -462,7 +468,7 @@ class DefaultSessionControlController( ...@@ -462,7 +468,7 @@ class DefaultSessionControlController(
handleTorBootstrapConnect() handleTorBootstrapConnect()
} }
override fun handleTorBootstrapConnectingClicked() { override fun handleTorStopBootstrapping() {
cancelTorBootstrap() cancelTorBootstrap()
} }
...@@ -473,4 +479,8 @@ class DefaultSessionControlController( ...@@ -473,4 +479,8 @@ class DefaultSessionControlController(
override fun handleTorStartDebugBootstrapping() { override fun handleTorStartDebugBootstrapping() {
initiateTorBootstrap(true) initiateTorBootstrap(true)
} }
override fun handleTorNetworkSettingsClicked() {
openTorNetworkSettings()
}
} }
...@@ -144,6 +144,34 @@ interface TipInteractor { ...@@ -144,6 +144,34 @@ interface TipInteractor {
fun onCloseTip(tip: Tip) fun onCloseTip(tip: Tip)
} }
interface TorBootstrapInteractor {
/**
* Initiates Tor bootstrapping. Called when a user clicks on the "Connect" button.
*/
fun onTorBootstrapConnectClicked()
/**
* Initiates Tor bootstrapping. Called when a user clicks on the "Connect" button.
*/
fun onTorStartBootstrapping()
/**
* Stop Tor bootstrapping. Called when a user clicks on the "settings" cog/button.
*/
fun onTorStopBootstrapping()
/**
* Initiates Tor bootstrapping with debug logging. Called when bootstrapping fails with
* the control.txt file not existing.
*/
fun onTorStartDebugBootstrapping()
/**
* Open Tor Network Settings preference screen
*/
fun onTorBootstrapNetworkSettingsClicked()
}
/** /**
* Interface for top site related actions in the [SessionControlInteractor]. * Interface for top site related actions in the [SessionControlInteractor].
*/ */
...@@ -181,7 +209,7 @@ interface TopSiteInteractor { ...@@ -181,7 +209,7 @@ interface TopSiteInteractor {
class SessionControlInteractor( class SessionControlInteractor(
private val controller: SessionControlController private val controller: SessionControlController
) : CollectionInteractor, OnboardingInteractor, TopSiteInteractor, TipInteractor, ) : CollectionInteractor, OnboardingInteractor, TopSiteInteractor, TipInteractor,
TabSessionInteractor, ToolbarInteractor { TabSessionInteractor, ToolbarInteractor, TorBootstrapInteractor {
override fun onCollectionAddTabTapped(collection: TabCollection) { override fun onCollectionAddTabTapped(collection: TabCollection) {
controller.handleCollectionAddTabTapped(collection) controller.handleCollectionAddTabTapped(collection)
} }
...@@ -265,4 +293,24 @@ class SessionControlInteractor( ...@@ -265,4 +293,24 @@ class SessionControlInteractor(
override fun onRemoveCollectionsPlaceholder() { override fun onRemoveCollectionsPlaceholder() {
controller.handleRemoveCollectionsPlaceholder() controller.handleRemoveCollectionsPlaceholder()
} }
override fun onTorBootstrapConnectClicked() {
controller.handleTorBootstrapConnectClicked()
}
override fun onTorStopBootstrapping() {
controller.handleTorStopBootstrapping()
}
override fun onTorStartBootstrapping() {
controller.handleTorStartBootstrapping()
}
override fun onTorStartDebugBootstrapping() {
controller.handleTorStartDebugBootstrapping()
}
override fun onTorBootstrapNetworkSettingsClicked() {
controller.handleTorNetworkSettingsClicked()
}
} }
...@@ -68,6 +68,8 @@ private fun showCollections( ...@@ -68,6 +68,8 @@ private fun showCollections(
private fun privateModeAdapterItems() = listOf(AdapterItem.PrivateBrowsingDescription) private fun privateModeAdapterItems() = listOf(AdapterItem.PrivateBrowsingDescription)
private fun bootstrapAdapterItems() = listOf(AdapterItem.TorBootstrap)
private fun onboardingAdapterItems(onboardingState: OnboardingState): List<AdapterItem> { private fun onboardingAdapterItems(onboardingState: OnboardingState): List<AdapterItem> {
val items: MutableList<AdapterItem> = mutableListOf(AdapterItem.OnboardingHeader) val items: MutableList<AdapterItem> = mutableListOf(AdapterItem.OnboardingHeader)
...@@ -117,6 +119,7 @@ private fun HomeFragmentState.toAdapterList(): List<AdapterItem> = when (mode) { ...@@ -117,6 +119,7 @@ private fun HomeFragmentState.toAdapterList(): List<AdapterItem> = when (mode) {
) )
is Mode.Private -> privateModeAdapterItems() is Mode.Private -> privateModeAdapterItems()
is Mode.Onboarding -> onboardingAdapterItems(mode.state) is Mode.Onboarding -> onboardingAdapterItems(mode.state)
is Mode.Bootstrap -> bootstrapAdapterItems()
} }
private fun collectionTabItems(collection: TabCollection) = private fun collectionTabItems(collection: TabCollection) =
......
/* 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.home.sessioncontrol.viewholders
import android.view.View
import androidx.appcompat.widget.SwitchCompat
import androidx.recyclerview.widget.RecyclerView
import kotlinx.android.synthetic.main.tor_bootstrap_connect.view.*
import org.mozilla.fenix.R
import org.mozilla.fenix.tor.TorEvents
import org.mozilla.fenix.tor.bootstrap.TorQuickStart
import org.mozilla.fenix.components.Components
import org.mozilla.fenix.home.sessioncontrol.TorBootstrapInteractor
class TorBootstrapConnectViewHolder(
private val view: View,
private val components: Components,
private val interactor: TorBootstrapInteractor
) : RecyclerView.ViewHolder(view), TorEvents {
init {
val torQuickStart = TorQuickStart(view.context)
setQuickStartDescription(view, torQuickStart)
with(view.quick_start_toggle as SwitchCompat) {
setOnCheckedChangeListener { _, isChecked ->
torQuickStart.setQuickStartTor(isChecked)
setQuickStartDescription(view, torQuickStart)
}
isChecked = torQuickStart.quickStartTor()
}
with(view.tor_bootstrap_network_settings_button) {
setOnClickListener {
interactor.onTorStopBootstrapping()
interactor.onTorBootstrapNetworkSettingsClicked()
with(view.tor_bootstrap_progress) {
visibility = View.INVISIBLE
}
with(view.tor_bootstrap_connect_button) {
visibility = View.VISIBLE
}
}
}
with(view.tor_bootstrap_connect_button) {
setOnClickListener {
interactor.onTorBootstrapConnectClicked()
interactor.onTorStartBootstrapping()
visibility = View.INVISIBLE
with(view.tor_bootstrap_progress) {
visibility = View.VISIBLE
}
}
}
components.torController.registerTorListener(this)
}
private fun setQuickStartDescription(view: View, torQuickStart: TorQuickStart) {
val resources = view.context.resources
val appName = resources.getString(R.string.app_name)
if (torQuickStart.quickStartTor()) {
view.tor_bootstrap_quick_start_description.text = resources.getString(
R.string.tor_bootstrap_quick_start_enabled, appName
)
} else {
view.tor_bootstrap_quick_start_description.text = resources.getString(
R.string.tor_bootstrap_quick_start_disabled
)
}
}
@SuppressWarnings("EmptyFunctionBlock")
override fun onTorConnecting() {
}
override fun onTorConnected() {
components.torController.unregisterTorListener(this)
}
@SuppressWarnings("EmptyFunctionBlock")
override fun onTorStopped() {
}
override fun onTorStatusUpdate(entry: String?, status: String?) {
if (entry == null) return
view.tor_bootstrap_status_message.text = entry
if (entry.startsWith(BOOTSTRAPPED_PREFIX)) {
val percentIdx = entry.indexOf("%")
val percent = entry.substring(
BOOTSTRAPPED_PREFIX.length,
percentIdx
)
with(view.tor_bootstrap_progress) {
progress = percent.toInt()
}
}
}
companion object {
const val LAYOUT_ID = R.layout.tor_bootstrap_connect
const val BOOTSTRAPPED_PREFIX = "NOTICE: Bootstrapped "
}
}
/* 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.home.sessioncontrol.viewholders
import android.text.method.ScrollingMovementMethod
import android.view.View
import androidx.recyclerview.widget.RecyclerView
import kotlinx.android.synthetic.main.tor_bootstrap_logger.view.*
import org.mozilla.fenix.R
import org.mozilla.fenix.tor.TorEvents
import org.mozilla.fenix.components.Components
class TorBootstrapLoggerViewHolder(
private val view: View,
private val components: Components
) : RecyclerView.ViewHolder(view), TorEvents {
private var entries = mutableListOf<String>()
init {
components.torController.registerTorListener(this)
val currentEntries = components.torController.logEntries
.filter { it.first != null }
.filter { !(it.first!!.startsWith("Circuit") && it.second == "ON") }
// Keep synchronized with format in onTorStatusUpdate
.flatMap { listOf("(${it.second}) '${it.first}'") }
val entriesLen = currentEntries.size
val subListOffset = if (entriesLen > MAX_NEW_ENTRIES) MAX_NEW_ENTRIES else entriesLen
entries = currentEntries.subList((entriesLen - subListOffset), entriesLen) as MutableList<String>
val initLog = "---------------" + view.resources.getString(R.string.tor_initializing_log) + "---------------"
entries.add(0, initLog)
with(view.tor_bootstrap_log_entries) {
movementMethod = ScrollingMovementMethod()
text = formatLogEntries(entries)
}
}
private fun formatLogEntries(entries: List<String>) = entries.joinToString("\n")
@SuppressWarnings("EmptyFunctionBlock")
override fun onTorConnecting() {
}
override fun onTorConnected() {
components.torController.unregisterTorListener(this)
}
@SuppressWarnings("EmptyFunctionBlock")
override fun onTorStopped() {
}
override fun onTorStatusUpdate(entry: String?, status: String?) {
if (status == null || entry == null) return
if (status == "ON" && entry.startsWith("Circuit")) return
if (entries.size > MAX_LINES) {
entries = entries.drop(1) as MutableList<String>
}
entries.add("($status) '$entry'")
view.tor_bootstrap_log_entries.text = formatLogEntries(entries)
}
companion object {
const val LAYOUT_ID = R.layout.tor_bootstrap_logger
const val MAX_NEW_ENTRIES = 24
const val MAX_LINES = 25
}
}
/* 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.home.sessioncontrol.viewholders
import android.view.View
import androidx.recyclerview.widget.RecyclerView
import kotlinx.android.synthetic.main.tor_bootstrap_pager.view.*
import org.mozilla.fenix.R
import org.mozilla.fenix.components.Components
import org.mozilla.fenix.home.sessioncontrol.TorBootstrapInteractor
import org.mozilla.fenix.home.sessioncontrol.viewholders.torbootstrap.BootstrapPagerAdapter
class TorBootstrapPagerViewHolder(
view: View,
components: Components,
interactor: TorBootstrapInteractor
) : RecyclerView.ViewHolder(view) {
private val bootstrapPagerAdapter = BootstrapPagerAdapter(components, interactor)
init {
view.bootstrap_pager.apply {
adapter = bootstrapPagerAdapter
}
}
companion object {
const val LAYOUT_ID = R.layout.tor_bootstrap_pager
}
}
/* 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.home.sessioncontrol.viewholders.torbootstrap
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.RecyclerView.ViewHolder
import org.mozilla.fenix.components.Components
import org.mozilla.fenix.home.sessioncontrol.TorBootstrapInteractor
import org.mozilla.fenix.home.sessioncontrol.viewholders.TorBootstrapConnectViewHolder
import org.mozilla.fenix.home.sessioncontrol.viewholders.TorBootstrapLoggerViewHolder
class BootstrapPagerAdapter(
private val components: Components,
private val interactor: TorBootstrapInteractor
) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
if (viewType == BOOTSTRAP_UI_PAGE_TYPE) {
val viewDVH = LayoutInflater.from(parent.context)
.inflate(TorBootstrapConnectViewHolder.LAYOUT_ID, parent, false)
return TorBootstrapConnectViewHolder(viewDVH, components, interactor)
} else {
val viewLVH = LayoutInflater.from(parent.context)
.inflate(TorBootstrapLoggerViewHolder.LAYOUT_ID, parent, false)
return TorBootstrapLoggerViewHolder(viewLVH, components)
}
}
@SuppressWarnings("EmptyFunctionBlock")
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
}
override fun getItemViewType(position: Int): Int = position
override fun getItemCount(): Int = BOOTSTRAP_PAGE_COUNT
companion object {
const val BOOTSTRAP_UI_PAGE_TYPE = 0
const val BOOTSTRAP_LOG_PAGE_TYPE = 1
const val BOOTSTRAP_PAGE_COUNT = 2
}
}
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:width="302dp"
android:height="263dp"
android:viewportWidth="302"
android:viewportHeight="263">
<path
android:pathData="M279.49,222.64l-94.22,-0l-0,-79.61l94.22,-0z"
android:strokeWidth="1"
android:fillType="nonZero"
android:strokeColor="#00000000">
<aapt:attr name="android:fillColor">
<gradient
android:startY="203.70642"
android:startX="231.9"
android:endY="160.04314"
android:endX="232.90999"
android:type="linear">
<item android:offset="0.08" android:color="#FF7E4696"/>
<item android:offset="0.39" android:color="#9B7E4696"/>
<item android:offset="0.85" android:color="#007E4696"/>
</gradient>
</aapt:attr>
</path>
<path
android:pathData="M112.74,217.05l-102.32,-0l-0,-102.32l102.32,-0z"
android:strokeWidth="1"
android:fillType="nonZero"
android:strokeColor="#00000000">
<aapt:attr name="android:fillColor">
<gradient
android:startY="203.36"
android:startX="60.96"
android:endY="124.990005"
android:endX="62.249996"
android:type="linear">
<item android:offset="0.08" android:color="#FF00D9B5"/>
<item android:offset="0.3" android:color="#BA00D9B5"/>
<item android:offset="0.8" android:color="#1100D9B5"/>
<item android:offset="0.85" android:color="#0000D9B5"/>
</gradient>
</aapt:attr>
</path>
<path