Skip to content
Snippets Groups Projects
Unverified Commit cf7ca790 authored by Matthew Finkel's avatar Matthew Finkel Committed by boklm
Browse files

Add Tor integration and UI

Bug 40001: Start Tor as part of the Fenix initialization

Bug 40028: Implement Tor Service controller

Bug 40028: Integrate Tor Controller into HomeFragment

Bug 40028: Implement Tor connect and logger screens

Bug 40028: Implement Tor Onboarding

Bug 40028: Implement new home screen

Bug 40028: Define bootstrapping events and Quick Start

Bug 40041: Implement Tor Network Settings

Bug 40041: Integrate Tor Network Settings
parent 68dc9160
Branches
Tags
1 merge request!120Rebase fenix patches to fenix v89.1.1
Showing
with 850 additions and 28 deletions
......@@ -19,6 +19,8 @@ default:
- 32GB
before_script:
- set -e
# Prepare our Debian environment.
- apt-get update -qq
- apt-get upgrade -qy
......@@ -47,6 +49,33 @@ default:
# Create local.properties file.
- echo "sdk.dir=$PWD" > local.properties
# Fetch tor library build dependencies
- wget --quiet https://people.torproject.org/~sysrqb/mirrors/tor-onion-proxy-library/0.0.3/android-release.aar
- wget --quiet https://people.torproject.org/~sysrqb/mirrors/tor-onion-proxy-library/0.0.3/universal-0.0.3.jar
- wget --quiet https://people.torproject.org/~sysrqb/mirrors/tor-android-service/1.0/jsocksAndroid-release.aar
- wget --quiet https://people.torproject.org/~sysrqb/mirrors/tor-android-service/1.0/service-release.aar
- wget --quiet https://people.torproject.org/~sysrqb/mirrors/tor/0.4.4.4-rc/tor_x86.tar.gz
- wget --quiet https://people.torproject.org/~sysrqb/mirrors/tor/0.4.4.4-rc/tor_x86_64.tar.gz
- wget --quiet https://people.torproject.org/~sysrqb/mirrors/tor/0.4.4.4-rc/tor_armv7.tar.gz
- wget --quiet https://people.torproject.org/~sysrqb/mirrors/tor/0.4.4.4-rc/tor_aarch64.tar.gz
- echo "4e2bd087df6128a31c598d297367c106762f539c25d71cc343aae25386e4bee9 android-release.aar" | sha256sum -c
- echo "0082d1d035a0808d03d7e1009398ed2c3e3ea4c51811198f6ff83ccda456036f universal-0.0.3.jar" | sha256sum -c
- echo "3dca44a48fdbd3f6c44f7ea335ae85fe542676d8e48c6438f84a2c852daf4f54 jsocksAndroid-release.aar" | sha256sum -c
- echo "d38967569af56d809f09f08b888962971149411832c3bc2b7b0b64a43ceb0dcd service-release.aar" | sha256sum -c
- echo "0faa344f658d2e38c7cc881f51c1d8658aac56801f7def1b75be72a8d3f90c54 tor_x86.tar.gz" | sha256sum -c
- echo "acf1d403ca12e3302d1150d2b5fba6585aa83d46db16f15ad33fae39645c5b5d tor_x86_64.tar.gz" | sha256sum -c
- echo "132cb40bbc15bd8f72abd15324705ab720f8b316a2fca84baaeb5db37f602c32 tor_armv7.tar.gz" | sha256sum -c
- echo "6c453c5f7566c87d422ef60750cba794d9616e9197114be5dc1194f75b55b04e tor_aarch64.tar.gz" | sha256sum -c
- mv android-release.aar universal-0.0.3.jar jsocksAndroid-release.aar service-release.aar app/
- tar -C app/src/main -xf tor_x86.tar.gz
- tar -C app/src/main -xf tor_x86_64.tar.gz
- tar -C app/src/main -xf tor_armv7.tar.gz
- tar -C app/src/main -xf tor_aarch64.tar.gz
- rm tor_x86.tar.gz tor_x86_64.tar.gz tor_armv7.tar.gz tor_aarch64.tar.gz
stages:
- build
- buildFenixProduction
......@@ -57,7 +86,7 @@ stages:
buildDebug:
stage: build
script:
- ./gradlew clean app:assembleDebug --stacktrace
- ./gradlew -PdisableTor=true clean app:assembleDebug --stacktrace
# Disable for now.
#artifacts:
......@@ -71,7 +100,7 @@ testLight:
# from config/pre-push-recommended.sh
# `tee` into a log file because resulting output is larger than 4 MB
# (4 MB is max log size)
- ./gradlew ktlint detekt assembleDebug assembleDebugAndroidTest testDebug | tee testLight_gradle.log
- ./gradlew -PdisableTor=true ktlint detekt assembleDebug assembleDebugAndroidTest testDebug | tee testLight_gradle.log
artifacts:
paths:
- testLight_gradle.log
......@@ -85,25 +114,25 @@ buildFenixProduction:
only:
- schedules
script:
- ./gradlew clean app:assembleNightly --stacktrace
- ./gradlew -PdisableTor=true clean app:assembleNightly --stacktrace
buildFennecBeta:
stage: buildFennecBeta
only:
- schedules
script:
- ./gradlew clean app:assembleBeta --stacktrace
- ./gradlew -PdisableTor=true clean app:assembleBeta --stacktrace
buildFennecProduction:
stage: buildFennecProduction
only:
- schedules
script:
- ./gradlew clean app:assembleRelease --stacktrace
- ./gradlew -PdisableTor=true clean app:assembleRelease --stacktrace
testAll:
stage: test
only:
- schedules
script:
- ./gradlew clean test
- ./gradlew -PdisableTor=true clean test
......@@ -252,12 +252,20 @@ android.applicationVariants.all { variant ->
def useReleaseVersioning = variant.buildType.buildConfigFields['USE_RELEASE_VERSIONING']?.value ?: false
def versionName = Config.releaseVersionName(project)
def disableTor = false
if (project.hasProperty("disableTor")) {
disableTor = project.getProperty("disableTor")
}
println("----------------------------------------------")
println("Variant name: " + variant.name)
println("Application ID: " + [variant.mergedFlavor.applicationId, variant.buildType.applicationIdSuffix].findAll().join())
println("Build type: " + variant.buildType.name)
println("Flavor: " + variant.flavorName)
println("Telemetry enabled: " + !isDebugOrDCD)
println("Tor is disabled: " + disableTor)
buildConfigField "boolean", "DISABLE_TOR", "$disableTor"
if (useReleaseVersioning) {
// The Google Play Store does not allow multiple APKs for the same app that all have the
......@@ -588,6 +596,18 @@ dependencies {
testImplementation "org.mozilla.telemetry:glean-forUnitTests:${project.ext.glean_version}"
lintChecks project(":mozilla-lint-rules")
// Tor Android Services Dependencies
implementation 'net.freehaven.tor.control:jtorctl:0.2'
implementation 'org.slf4j:slf4j-api:1.7.25'
implementation 'org.slf4j:slf4j-android:1.7.25'
// Tor Android Services.
implementation files('service-release.aar')
// Tor Onion Proxy Library.
implementation files('universal-0.0.3.jar')
implementation files('android-release.aar')
}
if (project.hasProperty("coverage")) {
......
......
......@@ -292,6 +292,13 @@
android:name="androidx.work.impl.WorkManagerInitializer"
android:authorities="${applicationId}.workmanager-init"
tools:node="remove" />
<!-- Define Orbotservice's TorService -->
<service
android:name="org.torproject.android.service.TorService"
android:enabled="true"
android:exported="false"
android:stopWithTask="true">
</service>
</application>
</manifest>
......@@ -63,6 +63,7 @@ import org.mozilla.fenix.session.VisibilityLifecycleCallback
import org.mozilla.fenix.telemetry.TelemetryLifecycleObserver
import org.mozilla.fenix.utils.BrowsersCache
import java.util.concurrent.TimeUnit
import org.torproject.android.service.util.Prefs
/**
*The main application class for Fenix. Records data to measure initialization performance.
......@@ -76,6 +77,8 @@ open class FenixApplication : LocaleAwareApplication(), Provider {
private val logger = Logger("FenixApplication")
var terminating = false
open val components by lazy { Components(this) }
var visibilityLifecycleCallback: VisibilityLifecycleCallback? = null
......@@ -114,6 +117,21 @@ open class FenixApplication : LocaleAwareApplication(), Provider {
PerfStartup.applicationOnCreate.stopAndAccumulate(completeMethodDurationTimerId)
}
fun isTerminating() = terminating
fun terminate() {
onTerminate()
System.exit(0)
}
override fun onTerminate() {
terminating = true
super.onTerminate()
components.torController.stop()
components.torController.stopTor()
}
protected open fun initializeGlean() {
val telemetryEnabled = settings().isTelemetryEnabled
......@@ -176,6 +194,11 @@ open class FenixApplication : LocaleAwareApplication(), Provider {
runBlockingIncrement { megazordSetup.await() }
}
}
GlobalScope.launch(Dispatchers.IO) {
// Give TAS the base Context
Prefs.setContext(applicationContext)
}
}
PerfStartup.appOnCreateToSetupInMain.measureNoInline {
......@@ -199,6 +222,7 @@ open class FenixApplication : LocaleAwareApplication(), Provider {
ProcessLifecycleOwner.get().lifecycle.addObserver(TelemetryLifecycleObserver(components.core.store))
components.appStartupTelemetry.onFenixApplicationOnCreate()
components.torController.start()
}
}
......
......
......@@ -145,6 +145,8 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity {
private var isToolbarInflated = false
private var isBeingRecreated = false
private val webExtensionPopupFeature by lazy {
WebExtensionPopupFeature(components.core.store, ::openPopup)
}
......@@ -180,6 +182,7 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity {
components.core.engine.profiler?.addMarker("Activity.onCreate", "HomeActivity")
components.strictMode.attachListenerToDisablePenaltyDeath(supportFragmentManager)
// There is disk read violations on some devices such as samsung and pixel for android 9/10
components.strictMode.resetAfter(StrictMode.allowThreadDiskReads()) {
// Theme setup should always be called before super.onCreate
......@@ -443,6 +446,14 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity {
)
privateNotificationObserver?.stop()
if (!isBeingRecreated && !(application as FenixApplication).isTerminating()) {
// We assume the Activity is being destroyed because the user
// swiped away the app on the Recent screen. When this happens,
// we assume the user expects the entire Application is destroyed
// and not only the top Activity/Task. Therefore we kill the
// underlying Application, as well.
(application as FenixApplication).terminate()
}
}
override fun onConfigurationChanged(newConfig: Configuration) {
......@@ -462,6 +473,8 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity {
message = "recreate()"
)
isBeingRecreated = true
super.recreate()
}
......
......
......@@ -32,6 +32,7 @@ import org.mozilla.fenix.perf.StartupActivityLog
import org.mozilla.fenix.perf.StartupStateProvider
import org.mozilla.fenix.perf.StrictModeManager
import org.mozilla.fenix.perf.lazyMonitored
import org.mozilla.fenix.tor.TorController
import org.mozilla.fenix.utils.ClipboardHandler
import org.mozilla.fenix.utils.Mockable
import org.mozilla.fenix.utils.Settings
......@@ -178,4 +179,5 @@ class Components(private val context: Context) {
val appStartReasonProvider by lazyMonitored { AppStartReasonProvider() }
val startupActivityLog by lazyMonitored { StartupActivityLog() }
val startupStateProvider by lazyMonitored { StartupStateProvider(startupActivityLog, appStartReasonProvider) }
val torController by lazyMonitored { TorController(context) }
}
......@@ -31,6 +31,8 @@ import androidx.constraintlayout.widget.ConstraintSet.PARENT_ID
import androidx.constraintlayout.widget.ConstraintSet.TOP
import androidx.coordinatorlayout.widget.CoordinatorLayout
import androidx.core.content.ContextCompat
import androidx.core.view.children
import androidx.core.view.doOnLayout
import androidx.core.view.isVisible
import androidx.core.view.updateLayoutParams
import androidx.fragment.app.Fragment
......@@ -47,8 +49,10 @@ import com.google.android.material.snackbar.Snackbar
import kotlinx.android.synthetic.main.fragment_home.*
import kotlinx.android.synthetic.main.fragment_home.view.bottomBarShadow
import kotlinx.android.synthetic.main.fragment_home.view.bottom_bar
import kotlinx.android.synthetic.main.fragment_home.view.exploreprivately
import kotlinx.android.synthetic.main.fragment_home.view.homeAppBar
import kotlinx.android.synthetic.main.fragment_home.view.menuButton
import kotlinx.android.synthetic.main.fragment_home.view.onion_pattern_image
import kotlinx.android.synthetic.main.fragment_home.view.sessionControlRecyclerView
import kotlinx.android.synthetic.main.fragment_home.view.tab_button
import kotlinx.android.synthetic.main.fragment_home.view.toolbar
......@@ -84,6 +88,7 @@ import mozilla.components.support.ktx.android.content.res.resolveAttribute
import mozilla.components.support.ktx.kotlinx.coroutines.flow.ifChanged
import mozilla.components.ui.tabcounter.TabCounterMenu
import org.mozilla.fenix.BrowserDirection
import org.mozilla.fenix.BuildConfig
import org.mozilla.fenix.Config
import org.mozilla.fenix.GleanMetrics.PerfStartup
import org.mozilla.fenix.HomeActivity
......@@ -120,6 +125,7 @@ import org.mozilla.fenix.onboarding.FenixOnboarding
import org.mozilla.fenix.settings.SupportUtils
import org.mozilla.fenix.settings.SupportUtils.SumoTopic.HELP
import org.mozilla.fenix.settings.deletebrowsingdata.deleteAndQuit
import org.mozilla.fenix.tor.bootstrap.TorQuickStart
import org.mozilla.fenix.theme.ThemeManager
import org.mozilla.fenix.utils.ToolbarPopupWindow
import org.mozilla.fenix.utils.allowUndo
......@@ -171,6 +177,7 @@ class HomeFragment : Fragment() {
private lateinit var currentMode: CurrentMode
private val topSitesFeature = ViewBoundFeatureWrapper<TopSitesFeature>()
private val torQuickStart by lazy { TorQuickStart(requireContext()) }
@VisibleForTesting
internal var getMenuButton: () -> MenuButton? = { menuButton }
......@@ -202,9 +209,21 @@ class HomeFragment : Fragment() {
val activity = activity as HomeActivity
val components = requireComponents
// Splits by full stops or commas and puts the parts in different lines.
// Ignoring separators at the end of the string, it is expected
// that there are at most two parts (e.g. "Explore. Privately.").
view.exploreprivately.text = view
.exploreprivately
.text
?.replace(" *([.,。।]) *".toRegex(), "$1\n")
?.trim()
currentMode = CurrentMode(
view.context,
onboarding,
torQuickStart,
!BuildConfig.DISABLE_TOR,
components.torController,
browsingModeManager,
::dispatchModeChanges
)
......@@ -263,7 +282,11 @@ class HomeFragment : Fragment() {
registerCollectionStorageObserver = ::registerCollectionStorageObserver,
showDeleteCollectionPrompt = ::showDeleteCollectionPrompt,
showTabTray = ::openTabTray,
handleSwipedItemDeletionCancel = ::handleSwipedItemDeletionCancel
handleSwipedItemDeletionCancel = ::handleSwipedItemDeletionCancel,
handleTorBootstrapConnect = ::handleTorBootstrapConnect,
cancelTorBootstrap = ::cancelTorBootstrap,
initiateTorBootstrap = ::initiateTorBootstrap,
openTorNetworkSettings = ::openTorNetworkSettings
)
)
......@@ -280,6 +303,8 @@ class HomeFragment : Fragment() {
appBarLayout = view.homeAppBar
activity.themeManager.applyStatusBarTheme(activity)
adjustHomeFragmentView(currentMode.getCurrentMode(), view)
showSessionControlView(view)
view
}
......@@ -361,6 +386,108 @@ class HomeFragment : Fragment() {
}
}
// This function should be paired with showSessionControlView()
@SuppressWarnings("ComplexMethod", "NestedBlockDepth", "LongMethod")
private fun adjustHomeFragmentView(mode: Mode, view: View?) {
view?.sessionControlRecyclerView?.apply {
visibility = View.INVISIBLE
}
if (mode == Mode.Bootstrap) {
view?.sessionControlRecyclerView?.apply {
setPadding(0, 0, 0, 0)
(layoutParams as ViewGroup.MarginLayoutParams).setMargins(0, 0, 0, 0)
}
view?.homeAppBar?.apply {
visibility = View.GONE
// Reset this as SCROLL in case it was previously set as NO_SCROLL after bootstrap
children.forEach {
(it.layoutParams as AppBarLayout.LayoutParams).scrollFlags =
AppBarLayout.LayoutParams.SCROLL_FLAG_SCROLL
}
}
view?.onion_pattern_image?.apply {
visibility = View.GONE
}
view?.toolbarLayout?.apply {
visibility = View.GONE
}
} else {
// Keep synchronized with xml layout (somehow).
view?.sessionControlRecyclerView?.apply {
setPadding(
SESSION_CONTROL_VIEW_PADDING,
SESSION_CONTROL_VIEW_PADDING,
SESSION_CONTROL_VIEW_PADDING,
SESSION_CONTROL_VIEW_PADDING
)
// Default margin until it is re-set below (either set immediately or after Layout)
(layoutParams as ViewGroup.MarginLayoutParams).setMargins(
0,
0,
0,
DEFAULT_ONBOARDING_FINISH_MARGIN
)
}
view?.toolbarLayout?.apply {
visibility = View.VISIBLE
// If the Layout rendering pass was completed, then we have a |height| value,
// if it wasn't completed then we have 0.
if (height == 0) {
// Set the bottom margin after the toolbar height is defined during Layout
doOnLayout {
val toolbarLayoutHeight = view.toolbarLayout.height
view.sessionControlRecyclerView?.apply {
(layoutParams as ViewGroup.MarginLayoutParams).setMargins(
0,
0,
0,
toolbarLayoutHeight - SESSION_CONTROL_VIEW_PADDING
)
}
}
} else {
view.sessionControlRecyclerView?.apply {
(layoutParams as ViewGroup.MarginLayoutParams).setMargins(
0,
0,
0,
height - SESSION_CONTROL_VIEW_PADDING
)
}
}
}
// Hide the onion pattern during Onboarding, too.
view?.onion_pattern_image?.apply {
visibility = if (onboarding.userHasBeenOnboarded()) {
View.VISIBLE
} else {
View.GONE
}
}
view?.homeAppBar?.apply {
visibility = View.VISIBLE
children.forEach {
(it.layoutParams as AppBarLayout.LayoutParams).scrollFlags =
if (onboarding.userHasBeenOnboarded()) {
AppBarLayout.LayoutParams.SCROLL_FLAG_NO_SCROLL
} else {
AppBarLayout.LayoutParams.SCROLL_FLAG_SCROLL
}
}
}
}
}
// This function should be paired with adjustHomeFragmentView()
private fun showSessionControlView(view: View?) {
view?.sessionControlRecyclerView?.apply {
visibility = View.VISIBLE
}
}
@Suppress("LongMethod", "ComplexMethod")
override fun onViewCreated(view: View, savedInstanceState: Bundle?) =
PerfStartup.homeFragmentOnViewCreated.measureNoInline {
......@@ -632,8 +759,13 @@ class HomeFragment : Fragment() {
}
private fun dispatchModeChanges(mode: Mode) {
if (mode != Mode.fromBrowsingMode(browsingModeManager.mode)) {
homeFragmentStore.dispatch(HomeFragmentAction.ModeChange(mode))
val localView = view
if (localView != null) {
adjustHomeFragmentView(mode, localView)
updateSessionControlView(localView)
showSessionControlView(localView)
}
}
......@@ -667,6 +799,11 @@ class HomeFragment : Fragment() {
}.show()
}
override fun onStop() {
super.onStop()
currentMode.unregisterTorListener()
}
override fun onResume() {
super.onResume()
if (browsingModeManager.mode == BrowsingMode.Private) {
......@@ -739,6 +876,11 @@ class HomeFragment : Fragment() {
mode = currentMode.getCurrentMode()
)
)
adjustHomeFragmentView(
currentMode.getCurrentMode(),
view
)
showSessionControlView(view)
}
}
......@@ -1067,6 +1209,25 @@ class HomeFragment : Fragment() {
view?.sessionControlRecyclerView?.adapter?.notifyDataSetChanged()
}
private fun handleTorBootstrapConnect() {
requireComponents.torController.onTorConnecting()
}
private fun cancelTorBootstrap() {
requireComponents.torController.stopTor()
}
private fun initiateTorBootstrap(withDebugLogging: Boolean = false) {
requireComponents.torController.initiateTorBootstrap(lifecycleScope, withDebugLogging)
}
private fun openTorNetworkSettings() {
val directions =
HomeFragmentDirections
.actionHomeFragmentToTorNetworkSettingsFragment()
findNavController().navigateBlockingForAsyncNavGraph(directions)
}
companion object {
const val ALL_NORMAL_TABS = "all_normal"
const val ALL_PRIVATE_TABS = "all_private"
......@@ -1085,5 +1246,9 @@ class HomeFragment : Fragment() {
private const val FADE_ANIM_DURATION = 150L
private const val CFR_WIDTH_DIVIDER = 1.7
private const val CFR_Y_OFFSET = -20
// Layout
private const val DEFAULT_ONBOARDING_FINISH_MARGIN = 60
private const val SESSION_CONTROL_VIEW_PADDING = 16
}
}
......@@ -10,6 +10,9 @@ import mozilla.components.concept.sync.AuthType
import mozilla.components.concept.sync.OAuthAccount
import mozilla.components.concept.sync.Profile
import mozilla.components.service.fxa.sharing.ShareableAccount
import org.mozilla.fenix.tor.TorController
import org.mozilla.fenix.tor.TorEvents
import org.mozilla.fenix.tor.bootstrap.TorQuickStart
import org.mozilla.fenix.browser.browsingmode.BrowsingMode
import org.mozilla.fenix.browser.browsingmode.BrowsingModeManager
import org.mozilla.fenix.ext.components
......@@ -22,6 +25,7 @@ sealed class Mode {
object Normal : Mode()
object Private : Mode()
data class Onboarding(val state: OnboardingState) : Mode()
object Bootstrap : Mode()
companion object {
fun fromBrowsingMode(browsingMode: BrowsingMode) = when (browsingMode) {
......@@ -43,16 +47,26 @@ sealed class OnboardingState {
object SignedIn : OnboardingState()
}
@SuppressWarnings("LongParameterList", "TooManyFunctions")
class CurrentMode(
private val context: Context,
private val onboarding: FenixOnboarding,
private val torQuickStart: TorQuickStart,
private val shouldStartTor: Boolean,
private val torController: TorController,
private val browsingModeManager: BrowsingModeManager,
private val dispatchModeChanges: (mode: Mode) -> Unit
) : AccountObserver {
) : AccountObserver, TorEvents {
private val accountManager by lazy { context.components.backgroundServices.accountManager }
fun getCurrentMode() = if (onboarding.userHasBeenOnboarded()) {
init {
torController.registerTorListener(this)
}
fun getCurrentMode() = if (shouldStartTor && (!torQuickStart.quickStartTor() && !torController.isBootstrapped)) {
Mode.Bootstrap
} else if (onboarding.userHasBeenOnboarded()) {
Mode.fromBrowsingMode(browsingModeManager.mode)
} else {
val account = accountManager.authenticatedAccount()
......@@ -70,6 +84,26 @@ class CurrentMode(
dispatchModeChanges(getCurrentMode())
}
@SuppressWarnings("EmptyFunctionBlock")
override fun onTorConnecting() {
}
override fun onTorConnected() {
dispatchModeChanges(getCurrentMode())
}
override fun onTorStopped() {
dispatchModeChanges(getCurrentMode())
}
@SuppressWarnings("EmptyFunctionBlock")
override fun onTorStatusUpdate(entry: String?, status: String?) {
}
fun unregisterTorListener() {
torController.unregisterTorListener(this)
}
override fun onAuthenticated(account: OAuthAccount, authType: AuthType) = emitModeChanges()
override fun onAuthenticationProblems() = emitModeChanges()
override fun onLoggedOut() = emitModeChanges()
......
......
......@@ -22,6 +22,7 @@ import org.mozilla.fenix.home.sessioncontrol.viewholders.CollectionHeaderViewHol
import org.mozilla.fenix.home.sessioncontrol.viewholders.CollectionViewHolder
import org.mozilla.fenix.home.sessioncontrol.viewholders.NoCollectionsMessageViewHolder
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.TopSitePagerViewHolder
import org.mozilla.fenix.home.sessioncontrol.viewholders.onboarding.ExperimentDefaultBrowserCardViewHolder
......@@ -36,6 +37,8 @@ import org.mozilla.fenix.home.sessioncontrol.viewholders.onboarding.OnboardingTh
import org.mozilla.fenix.home.sessioncontrol.viewholders.onboarding.OnboardingToolbarPositionPickerViewHolder
import org.mozilla.fenix.home.sessioncontrol.viewholders.onboarding.OnboardingTrackingProtectionViewHolder
import org.mozilla.fenix.home.sessioncontrol.viewholders.onboarding.OnboardingWhatsNewViewHolder
import org.mozilla.fenix.home.sessioncontrol.viewholders.onboarding.TorOnboardingSecurityLevelViewHolder
import org.mozilla.fenix.home.sessioncontrol.viewholders.onboarding.TorOnboardingDonateViewHolder
import org.mozilla.fenix.home.tips.ButtonTipViewHolder
import mozilla.components.feature.tab.collections.Tab as ComponentTab
......@@ -80,6 +83,8 @@ sealed class AdapterItem(@LayoutRes val viewType: Int) {
object PrivateBrowsingDescription : AdapterItem(PrivateBrowsingDescriptionViewHolder.LAYOUT_ID)
object NoCollectionsMessage : AdapterItem(NoCollectionsMessageViewHolder.LAYOUT_ID)
object TorBootstrap : AdapterItem(TorBootstrapPagerViewHolder.LAYOUT_ID)
object CollectionHeader : AdapterItem(CollectionHeaderViewHolder.LAYOUT_ID)
data class CollectionItem(
val collection: TabCollection,
......@@ -131,6 +136,9 @@ sealed class AdapterItem(@LayoutRes val viewType: Int) {
object OnboardingWhatsNew : AdapterItem(OnboardingWhatsNewViewHolder.LAYOUT_ID)
object TorOnboardingSecurityLevel : AdapterItem(TorOnboardingSecurityLevelViewHolder.LAYOUT_ID)
object TorOnboardingDonate : AdapterItem(TorOnboardingDonateViewHolder.LAYOUT_ID)
/**
* True if this item represents the same value as other. Used by [AdapterItemDiffCallback].
*/
......@@ -174,6 +182,11 @@ class SessionControlAdapter(
view,
interactor
)
TorBootstrapPagerViewHolder.LAYOUT_ID -> TorBootstrapPagerViewHolder(
view,
components,
interactor
)
NoCollectionsMessageViewHolder.LAYOUT_ID ->
NoCollectionsMessageViewHolder(
view,
......@@ -212,6 +225,14 @@ class SessionControlAdapter(
)
ExperimentDefaultBrowserCardViewHolder.LAYOUT_ID -> ExperimentDefaultBrowserCardViewHolder(view, interactor)
TorOnboardingSecurityLevelViewHolder.LAYOUT_ID -> TorOnboardingSecurityLevelViewHolder(
view,
interactor
)
TorOnboardingDonateViewHolder.LAYOUT_ID -> TorOnboardingDonateViewHolder(
view,
interactor
)
else -> throw IllegalStateException()
}
}
......
......
......@@ -124,6 +124,11 @@ interface SessionControlController {
*/
fun handleOpenSettingsClicked()
/**
* @see [OnboardingInteractor.onOpenSecurityLevelSettingsClicked]
*/
fun handleOpenSecurityLevelSettingsClicked()
/**
* @see [OnboardingInteractor.onWhatsNewGetAnswersClicked]
*/
......@@ -134,6 +139,11 @@ interface SessionControlController {
*/
fun handleReadPrivacyNoticeClicked()
/**
* @see [OnboardingInteractor.onDonateClicked]
*/
fun handleDonateClicked()
/**
* @see [CollectionInteractor.onToggleCollectionExpanded]
*/
......@@ -178,6 +188,31 @@ interface SessionControlController {
* @see [ExperimentCardInteractor.onCloseExperimentCardClicked]
*/
fun handleCloseExperimentCard()
/**
* @see [TorBootstrapInteractor.onTorBootstrapConnectClicked]
*/
fun handleTorBootstrapConnectClicked()
/**
* @see [TorBootstrapInteractor.onTorStopBootstrapping]
*/
fun handleTorStopBootstrapping()
/**
* @see [TorBootstrapInteractor.onTorStartBootstrapping]
*/
fun handleTorStartBootstrapping()
/**
* @see [TorBootstrapInteractor.onTorStartDebugBootstrapping]
*/
fun handleTorStartDebugBootstrapping()
/**
* @see [TorBootstrapInteractor.onTorBootstrapNetworkSettingsClicked]
*/
fun handleTorNetworkSettingsClicked()
}
@Suppress("TooManyFunctions", "LargeClass")
......@@ -206,7 +241,11 @@ class DefaultSessionControlController(
handleSwipedItemDeletionCancel: () -> Unit
) -> Unit,
private val showTabTray: () -> Unit,
private val handleSwipedItemDeletionCancel: () -> Unit
private val handleSwipedItemDeletionCancel: () -> Unit,
private val handleTorBootstrapConnect: () -> Unit,
private val initiateTorBootstrap: (Boolean) -> Unit,
private val cancelTorBootstrap: () -> Unit,
private val openTorNetworkSettings: () -> Unit
) : SessionControlController {
override fun handleCollectionAddTabTapped(collection: TabCollection) {
......@@ -459,6 +498,9 @@ class DefaultSessionControlController(
navController.nav(R.id.homeFragment, directions)
}
override fun handleOpenSecurityLevelSettingsClicked() {
}
override fun handleWhatsNewGetAnswersClicked() {
activity.openToBrowserAndLoad(
searchTermOrURL = SupportUtils.getWhatsNewUrl(activity),
......@@ -475,6 +517,14 @@ class DefaultSessionControlController(
)
}
override fun handleDonateClicked() {
activity.openToBrowserAndLoad(
searchTermOrURL = SupportUtils.DONATE_URL,
newTab = true,
from = BrowserDirection.FromHome
)
}
override fun handleToggleCollectionExpanded(collection: TabCollection, expand: Boolean) {
fragmentStore.dispatch(HomeFragmentAction.CollectionExpanded(collection, expand))
}
......@@ -576,4 +626,24 @@ class DefaultSessionControlController(
metrics.track(Event.CloseExperimentCardClicked)
fragmentStore.dispatch(HomeFragmentAction.RemoveSetDefaultBrowserCard)
}
override fun handleTorBootstrapConnectClicked() {
handleTorBootstrapConnect()
}
override fun handleTorStopBootstrapping() {
cancelTorBootstrap()
}
override fun handleTorStartBootstrapping() {
initiateTorBootstrap(false)
}
override fun handleTorStartDebugBootstrapping() {
initiateTorBootstrap(true)
}
override fun handleTorNetworkSettingsClicked() {
openTorNetworkSettings()
}
}
......@@ -132,6 +132,11 @@ interface OnboardingInteractor {
*/
fun onOpenSettingsClicked()
/**
* Hides the onboarding and navigates to Settings. Called when a user clicks on the "Open Security Settings" button.
*/
fun onOpenSecurityLevelSettingsClicked()
/**
* Opens a custom tab to what's new url. Called when a user clicks on the "Get answers here" link.
*/
......@@ -141,6 +146,11 @@ interface OnboardingInteractor {
* Opens a custom tab to privacy notice url. Called when a user clicks on the "read our privacy notice" button.
*/
fun onReadPrivacyNoticeClicked()
/**
* Opens a custom tab to privacy notice url. Called when a user clicks on the "read our privacy notice" button.
*/
fun onDonateClicked()
}
interface TipInteractor {
......@@ -150,6 +160,34 @@ interface TipInteractor {
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].
*/
......@@ -211,7 +249,7 @@ interface ExperimentCardInteractor {
class SessionControlInteractor(
private val controller: SessionControlController
) : CollectionInteractor, OnboardingInteractor, TopSiteInteractor, TipInteractor,
TabSessionInteractor, ToolbarInteractor, ExperimentCardInteractor {
TabSessionInteractor, ToolbarInteractor, ExperimentCardInteractor, TorBootstrapInteractor {
override fun onCollectionAddTabTapped(collection: TabCollection) {
controller.handleCollectionAddTabTapped(collection)
}
......@@ -264,6 +302,10 @@ class SessionControlInteractor(
controller.handleOpenSettingsClicked()
}
override fun onOpenSecurityLevelSettingsClicked() {
controller.handleOpenSecurityLevelSettingsClicked()
}
override fun onWhatsNewGetAnswersClicked() {
controller.handleWhatsNewGetAnswersClicked()
}
......@@ -272,6 +314,10 @@ class SessionControlInteractor(
controller.handleReadPrivacyNoticeClicked()
}
override fun onDonateClicked() {
controller.handleDonateClicked()
}
override fun onToggleCollectionExpanded(collection: TabCollection, expand: Boolean) {
controller.handleToggleCollectionExpanded(collection, expand)
}
......@@ -315,4 +361,24 @@ class SessionControlInteractor(
override fun onCloseExperimentCardClicked() {
controller.handleCloseExperimentCard()
}
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()
}
}
......@@ -72,6 +72,8 @@ private fun showCollections(
private fun privateModeAdapterItems() = listOf(AdapterItem.PrivateBrowsingDescription)
private fun bootstrapAdapterItems() = listOf(AdapterItem.TorBootstrap)
private fun onboardingAdapterItems(onboardingState: OnboardingState): List<AdapterItem> {
val items: MutableList<AdapterItem> = mutableListOf(AdapterItem.OnboardingHeader)
......@@ -109,6 +111,13 @@ private fun onboardingAdapterItems(onboardingState: OnboardingState): List<Adapt
return items
}
private fun torOnboardingAdapterItems() =
listOf(
AdapterItem.TorOnboardingSecurityLevel,
AdapterItem.TorOnboardingDonate,
AdapterItem.OnboardingFinish
)
private fun HomeFragmentState.toAdapterList(): List<AdapterItem> = when (mode) {
is Mode.Normal -> normalModeAdapterItems(
topSites,
......@@ -119,7 +128,8 @@ private fun HomeFragmentState.toAdapterList(): List<AdapterItem> = when (mode) {
showSetAsDefaultBrowserCard
)
is Mode.Private -> privateModeAdapterItems()
is Mode.Onboarding -> onboardingAdapterItems(mode.state)
is Mode.Onboarding -> torOnboardingAdapterItems()
is Mode.Bootstrap -> bootstrapAdapterItems()
}
private fun collectionTabItems(collection: TabCollection) =
......
......
......@@ -4,12 +4,10 @@
package org.mozilla.fenix.home.sessioncontrol.viewholders
import android.text.method.LinkMovementMethod
import android.view.View
import androidx.recyclerview.widget.RecyclerView
import kotlinx.android.synthetic.main.private_browsing_description.view.*
import org.mozilla.fenix.R
import org.mozilla.fenix.ext.addUnderline
import org.mozilla.fenix.home.sessioncontrol.TabSessionInteractor
class PrivateBrowsingDescriptionViewHolder(
......@@ -18,18 +16,18 @@ class PrivateBrowsingDescriptionViewHolder(
) : RecyclerView.ViewHolder(view) {
init {
val resources = view.resources
val appName = resources.getString(R.string.app_name)
view.private_session_description.text = resources.getString(
R.string.private_browsing_placeholder_description_2, appName
)
with(view.private_session_common_myths) {
movementMethod = LinkMovementMethod.getInstance()
addUnderline()
setOnClickListener {
interactor.onPrivateBrowsingLearnMoreClicked()
}
}
// val resources = view.resources
// val appName = resources.getString(R.string.app_name)
// view.private_session_description.text = resources.getString(
// R.string.private_browsing_placeholder_description_2, appName
// )
// with(view.private_session_common_myths) {
// movementMethod = LinkMovementMethod.getInstance()
// addUnderline()
// setOnClickListener {
// interactor.onPrivateBrowsingLearnMoreClicked()
// }
// }
}
companion object {
......
......
/* 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.onboarding
import android.view.View
import androidx.recyclerview.widget.RecyclerView
import kotlinx.android.synthetic.main.tor_onboarding_donate.view.*
import org.mozilla.fenix.R
import org.mozilla.fenix.home.sessioncontrol.OnboardingInteractor
class TorOnboardingDonateViewHolder(
view: View,
private val interactor: OnboardingInteractor
) : RecyclerView.ViewHolder(view) {
init {
view.donate_now_button.setOnClickListener {
interactor.onDonateClicked()
}
}
companion object {
const val LAYOUT_ID = R.layout.tor_onboarding_donate
}
}
/* 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.onboarding
import android.view.View
import androidx.recyclerview.widget.RecyclerView
import kotlinx.android.synthetic.main.tor_onboarding_security_level.view.*
import org.mozilla.fenix.R
import org.mozilla.fenix.home.sessioncontrol.OnboardingInteractor
import org.mozilla.fenix.onboarding.OnboardingRadioButton
import org.mozilla.fenix.utils.view.addToRadioGroup
class TorOnboardingSecurityLevelViewHolder(
view: View,
private val interactor: OnboardingInteractor
) : RecyclerView.ViewHolder(view) {
private var standardSecurityLevel: OnboardingRadioButton
private var saferSecurityLevel: OnboardingRadioButton
private var safestSecurityLevel: OnboardingRadioButton
init {
view.header_text.setOnboardingIcon(R.drawable.ic_onboarding_tracking_protection)
standardSecurityLevel = view.security_level_standard_option
saferSecurityLevel = view.security_level_safer_option
safestSecurityLevel = view.security_level_safest_option
view.description_text.text = view.context.getString(
R.string.tor_onboarding_security_level_description
)
view.open_settings_button.setOnClickListener {
interactor.onOpenSettingsClicked()
}
setupRadioGroup()
}
private fun setupRadioGroup() {
addToRadioGroup(standardSecurityLevel, saferSecurityLevel, safestSecurityLevel)
standardSecurityLevel.isChecked = true
safestSecurityLevel.isChecked = false
saferSecurityLevel.isChecked = false
standardSecurityLevel.onClickListener {
updateSecurityLevel()
}
saferSecurityLevel.onClickListener {
updateSecurityLevel()
}
safestSecurityLevel.onClickListener {
updateSecurityLevel()
}
}
private fun updateSecurityLevel() {
}
companion object {
const val LAYOUT_ID = R.layout.tor_onboarding_security_level
}
}
/* 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
}
}
......@@ -239,6 +239,9 @@ class SettingsFragment : PreferenceFragmentCompat() {
resources.getString(R.string.pref_key_search_settings) -> {
SettingsFragmentDirections.actionSettingsFragmentToSearchEngineFragment()
}
resources.getString(R.string.pref_key_tor_network_settings) -> {
SettingsFragmentDirections.actionSettingsFragmentToTorNetworkSettingsFragment()
}
resources.getString(R.string.pref_key_tracking_protection_settings) -> {
requireContext().metrics.track(Event.TrackingProtectionSettings)
SettingsFragmentDirections.actionSettingsFragmentToTrackingProtectionFragment()
......
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please to comment