Commit b6e4aa30 authored by Matthew Finkel's avatar Matthew Finkel
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 7a91fd47
......@@ -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
......@@ -217,12 +217,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
......@@ -535,6 +543,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")) {
......
......@@ -279,6 +279,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>
......@@ -49,6 +49,7 @@ import org.mozilla.fenix.push.WebPushEngineIntegration
import org.mozilla.fenix.session.PerformanceActivityLifecycleCallbacks
import org.mozilla.fenix.session.VisibilityLifecycleCallback
import org.mozilla.fenix.utils.BrowsersCache
import org.torproject.android.service.util.Prefs
/**
*The main application class for Fenix. Records data to measure initialization performance.
......@@ -62,6 +63,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
......@@ -91,6 +94,21 @@ open class FenixApplication : LocaleAwareApplication(), Provider {
setupInMainProcessOnly()
}
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
......@@ -139,6 +157,11 @@ open class FenixApplication : LocaleAwareApplication(), Provider {
if (!megazordSetup.isCompleted) {
runBlockingIncrement { megazordSetup.await() }
}
GlobalScope.launch(Dispatchers.IO) {
// Give TAS the base Context
Prefs.setContext(applicationContext)
}
}
setupLeakCanary()
......@@ -157,6 +180,7 @@ open class FenixApplication : LocaleAwareApplication(), Provider {
initVisualCompletenessQueueAndQueueTasks()
components.appStartupTelemetry.onFenixApplicationOnCreate()
components.torController.start()
}
private fun restoreDownloads() {
......
......@@ -129,6 +129,8 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity {
private var isToolbarInflated = false
private var isBeingRecreated = false
private val webExtensionPopupFeature by lazy {
WebExtensionPopupFeature(components.core.store, ::openPopup)
}
......@@ -154,6 +156,7 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity {
final override fun onCreate(savedInstanceState: Bundle?) {
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()) {
super.onCreate(savedInstanceState)
......@@ -369,6 +372,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) {
......@@ -388,6 +399,8 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity {
message = "recreate()"
)
isBeingRecreated = true
super.recreate()
}
......
......@@ -23,6 +23,7 @@ import org.mozilla.fenix.perf.StrictModeManager
import org.mozilla.fenix.components.metrics.AppStartupTelemetry
import org.mozilla.fenix.ext.settings
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
......@@ -143,4 +144,6 @@ class Components(private val context: Context) {
FenixReviewSettings(settings)
)
}
val torController by lazyMonitored { TorController(context) }
}
......@@ -28,6 +28,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
......@@ -39,6 +41,7 @@ import androidx.navigation.fragment.navArgs
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.RecyclerView.SCROLL_STATE_IDLE
import com.google.android.material.appbar.AppBarLayout
import com.google.android.material.snackbar.Snackbar
import kotlinx.android.synthetic.main.fragment_home.*
import kotlinx.android.synthetic.main.fragment_home.view.*
......@@ -69,6 +72,7 @@ import mozilla.components.lib.state.ext.consumeFrom
import mozilla.components.support.base.feature.ViewBoundFeatureWrapper
import mozilla.components.support.ktx.android.content.res.resolveAttribute
import org.mozilla.fenix.BrowserDirection
import org.mozilla.fenix.BuildConfig
import org.mozilla.fenix.HomeActivity
import org.mozilla.fenix.R
import org.mozilla.fenix.browser.BrowserAnimator.Companion.getToolbarNavOptions
......@@ -98,6 +102,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.FragmentPreDrawManager
import org.mozilla.fenix.utils.ToolbarPopupWindow
......@@ -159,6 +164,7 @@ class HomeFragment : Fragment() {
private lateinit var currentMode: CurrentMode
private val topSitesFeature = ViewBoundFeatureWrapper<TopSitesFeature>()
private val torQuickStart by lazy { TorQuickStart(requireContext()) }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
......@@ -181,9 +187,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
)
......@@ -237,7 +255,11 @@ class HomeFragment : Fragment() {
registerCollectionStorageObserver = ::registerCollectionStorageObserver,
showDeleteCollectionPrompt = ::showDeleteCollectionPrompt,
showTabTray = ::openTabTray,
handleSwipedItemDeletionCancel = ::handleSwipedItemDeletionCancel
handleSwipedItemDeletionCancel = ::handleSwipedItemDeletionCancel,
handleTorBootstrapConnect = ::handleTorBootstrapConnect,
cancelTorBootstrap = ::cancelTorBootstrap,
initiateTorBootstrap = ::initiateTorBootstrap,
openTorNetworkSettings = ::openTorNetworkSettings
)
)
......@@ -252,6 +274,10 @@ class HomeFragment : Fragment() {
updateSessionControlView(view)
activity.themeManager.applyStatusBarTheme(activity)
adjustHomeFragmentView(currentMode.getCurrentMode(), view)
showSessionControlView(view)
return view
}
......@@ -323,6 +349,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?) {
super.onViewCreated(view, savedInstanceState)
......@@ -579,8 +707,13 @@ class HomeFragment : Fragment() {
}
private fun dispatchModeChanges(mode: Mode) {
if (mode != Mode.fromBrowsingMode(browsingModeManager.mode)) {
homeFragmentStore.dispatch(HomeFragmentAction.ModeChange(mode))
homeFragmentStore.dispatch(HomeFragmentAction.ModeChange(mode))
val localView = view
if (localView != null) {
adjustHomeFragmentView(mode, localView)
updateSessionControlView(localView)
showSessionControlView(localView)
}
}
......@@ -618,6 +751,8 @@ class HomeFragment : Fragment() {
super.onStop()
homeViewModel.layoutManagerState =
sessionControlView!!.view.layoutManager?.onSaveInstanceState()
currentMode.unregisterTorListener()
}
override fun onResume() {
......@@ -689,6 +824,11 @@ class HomeFragment : Fragment() {
mode = currentMode.getCurrentMode()
)
)
adjustHomeFragmentView(
currentMode.getCurrentMode(),
view
)
showSessionControlView(view)
}
}
......@@ -945,6 +1085,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().navigate(directions)
}
companion object {
const val ALL_NORMAL_TABS = "all_normal"
const val ALL_PRIVATE_TABS = "all_private"
......@@ -959,5 +1118,9 @@ class HomeFragment : Fragment() {
private const val ANIM_SNACKBAR_DELAY = 100L
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.OnboardingAutomaticSignInViewHolder
......@@ -35,6 +36,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