tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

commit 72d6fa46eb96a0c9644b8647789c7290c8637736
parent 24b753daa968aa6898a4ca47385eaa965b91e09b
Author: Dan Ballard <dan@mindstab.net>
Date:   Fri, 25 Aug 2023 16:08:13 -0700

TB 41878: [android] Add standalone Tor Bootstrap

Diffstat:
Mmobile/android/android-components/components/browser/engine-gecko/src/main/java/mozilla/components/browser/engine/gecko/GeckoEngine.kt | 2++
Mmobile/android/fenix/app/build.gradle | 11+++++++++++
Amobile/android/fenix/app/src/main/assets/common/torrc-defaults | 9+++++++++
Mmobile/android/fenix/app/src/main/java/org/mozilla/fenix/FenixApplication.kt | 4+++-
Mmobile/android/fenix/app/src/main/java/org/mozilla/fenix/HomeActivity.kt | 38+++++++++++++++++++++++++++++++-------
Mmobile/android/fenix/app/src/main/java/org/mozilla/fenix/IntentReceiverActivity.kt | 27++++++++++++++++++++++++++-
Mmobile/android/fenix/app/src/main/java/org/mozilla/fenix/components/Components.kt | 3+++
Amobile/android/fenix/app/src/main/java/org/mozilla/fenix/tor/TorBridgeTransports.kt | 48++++++++++++++++++++++++++++++++++++++++++++++++
Amobile/android/fenix/app/src/main/java/org/mozilla/fenix/tor/TorController.kt | 41+++++++++++++++++++++++++++++++++++++++++
Amobile/android/fenix/app/src/main/java/org/mozilla/fenix/tor/TorControllerGV.kt | 196+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Amobile/android/fenix/app/src/main/java/org/mozilla/fenix/tor/TorLog.kt | 10++++++++++
Mmobile/android/fenix/app/src/main/res/drawable/tor_bootstrap_background_gradient.xml | 6+++---
Mmobile/android/fenix/app/src/main/res/values/colors.xml | 2++
Mmobile/android/fenix/app/src/main/res/values/styles.xml | 3++-
14 files changed, 387 insertions(+), 13 deletions(-)

diff --git a/mobile/android/android-components/components/browser/engine-gecko/src/main/java/mozilla/components/browser/engine/gecko/GeckoEngine.kt b/mobile/android/android-components/components/browser/engine-gecko/src/main/java/mozilla/components/browser/engine/gecko/GeckoEngine.kt @@ -1877,6 +1877,8 @@ class GeckoEngine( installedExtension.registerTabHandler(webExtensionTabHandler, defaultSettings) onSuccess(installedExtension) } + + fun getTorIntegrationController() = runtime.getTorIntegrationController() } internal fun ContentBlockingController.LogEntry.BlockingData.hasBlockedCookies(): Boolean { diff --git a/mobile/android/fenix/app/build.gradle b/mobile/android/fenix/app/build.gradle @@ -329,6 +329,11 @@ android.applicationVariants.configureEach { variant -> System.setProperty("nimbusFml", gradle.mozconfig.substs.NIMBUS_FML) } + def disableTor = false + if (project.hasProperty("disableTor")) { + disableTor = project.getProperty("disableTor") + } + project.logger.debug("----------------------------------------------") project.logger.debug("Variant name: " + variant.name) project.logger.debug("Application ID: " + [variant.applicationId, variant.buildType.applicationIdSuffix].findAll().join()) @@ -336,6 +341,9 @@ android.applicationVariants.configureEach { variant -> project.logger.debug("Flavor: " + variant.flavorName) project.logger.debug("Telemetry enabled: " + !isDebugOrDCD) project.logger.debug("nimbusFml: " + System.getProperty("nimbusFml")) + project.logger.debug("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 @@ -754,6 +762,9 @@ dependencies { androidTestUtil libs.androidx.test.orchestrator lintChecks project(':components:tooling-lint') + + // Tor Expert Bundle + implementation files(gradle.mozconfig.substs.TOR_EXPERT_BUNDLE_AAR) } protobuf { diff --git a/mobile/android/fenix/app/src/main/assets/common/torrc-defaults b/mobile/android/fenix/app/src/main/assets/common/torrc-defaults @@ -0,0 +1,9 @@ +## torrc-defaults for Tor Browser for Android +AvoidDiskWrites 1 +# (stderr|stdout|syslog|file FILENAME). +Log notice stdout +CookieAuthentication 1 +DormantCanceledByStartup 1 +ClientTransportPlugin meek_lite,obfs2,obfs3,obfs4,scramblesuit,webtunnel exec ./libLyrebird.so +ClientTransportPlugin snowflake exec ./libLyrebird.so +ClientTransportPlugin conjure exec ./libConjure.so -registerURL https://registration.refraction.network/api diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/FenixApplication.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/FenixApplication.kt @@ -361,6 +361,8 @@ open class FenixApplication : LocaleAwareApplication(), Provider { CoroutineScope(IO).launch { components.useCases.wallpaperUseCases.fetchCurrentWallpaperUseCase.invoke() } + + components.torController.start() } @OptIn(DelicateCoroutinesApi::class) // GlobalScope usage @@ -976,7 +978,7 @@ open class FenixApplication : LocaleAwareApplication(), Provider { clipboardSuggestionsEnabled.set(settings.shouldShowClipboardSuggestions) searchShortcutsEnabled.set(settings.shouldShowSearchShortcuts) voiceSearchEnabled.set(settings.shouldShowVoiceSearch) - openLinksInAppEnabled.set(settings.openLinksInExternalApp) + // openLinksInAppEnabled.set(settings.openLinksInExternalApp) signedInSync.set(settings.signedInFxaAccount) isolatedContentProcessesEnabled.set(settings.isIsolatedProcessEnabled) appZygoteIsolatedContentProcessesEnabled.set(settings.isAppZygoteEnabled) diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/HomeActivity.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/HomeActivity.kt @@ -4,6 +4,7 @@ package org.mozilla.fenix +import android.annotation.SuppressLint import android.app.assist.AssistContent import android.app.PendingIntent import android.content.ComponentName @@ -170,6 +171,7 @@ import org.mozilla.fenix.tabstray.TabsTrayFragment import org.mozilla.fenix.theme.DefaultThemeManager import org.mozilla.fenix.theme.StatusBarColorManager import org.mozilla.fenix.theme.ThemeManager +import org.mozilla.fenix.tor.TorEvents import org.mozilla.fenix.utils.AccessibilityUtils.announcePrivateModeForAccessibility import org.mozilla.fenix.utils.Settings import org.mozilla.fenix.utils.changeAppLauncherIcon @@ -900,7 +902,26 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity { /** * Handles intents received when the activity is open. */ + @SuppressLint("MissingSuperCall") // super.onNewIntent is called in [onNewIntentInternal(intent)] final override fun onNewIntent(intent: Intent) { + if (intent.action == ACTION_MAIN || components.torController.isBootstrapped) { + onNewIntentInternal(intent) + } else { + // Wait until Tor is connected to handle intents from external apps for links, search, etc. + components.torController.registerTorListener(object : TorEvents { + override fun onTorConnected() { + components.torController.unregisterTorListener(this) + onNewIntentInternal(intent) + } + override fun onTorConnecting() { /* no-op */ } + override fun onTorStopped() { /* no-op */ } + override fun onTorStatusUpdate(entry: String?, status: String?, progress: Double?) { /* no-op */ } + }) + return + } + } + + private fun onNewIntentInternal(intent: Intent) { super.onNewIntent(intent) handleNewIntent(intent) startupPathProvider.onIntentReceived(intent) @@ -1303,11 +1324,11 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity { @VisibleForTesting internal fun navigateToHome(navController: NavController) { - if (this is ExternalAppBrowserActivity) { - return - } + // if (this is ExternalAppBrowserActivity) { + // return + // } - navController.navigate(NavGraphDirections.actionStartupHome()) + navController.navigate(NavGraphDirections.actionStartupTorbootstrap()) } final override fun attachBaseContext(base: Context) { @@ -1406,14 +1427,17 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity { /** * Indicates if the user should be redirected to the [BrowserFragment] or to the [HomeFragment], - * links from an external apps should always opened in the [BrowserFragment]. + * links from an external apps should always opened in the [BrowserFragment], + * unless Tor is not yet connected. */ @VisibleForTesting internal fun shouldStartOnHome(intent: Intent? = this.intent): Boolean { return components.strictMode.allowViolation(StrictMode::allowThreadDiskReads) { // We only want to open on home when users tap the app, - // we want to ignore other cases when the app gets open by users clicking on links. - getSettings().shouldStartOnHome() && intent?.action == ACTION_MAIN + // we want to ignore other cases when the app gets open by users clicking on links, + // unless Tor is not yet connected. + getSettings().shouldStartOnHome() && (intent?.action == ACTION_MAIN || + !components.torController.isBootstrapped) } } diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/IntentReceiverActivity.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/IntentReceiverActivity.kt @@ -6,11 +6,13 @@ package org.mozilla.fenix import android.app.Activity import android.content.Intent +import android.content.Intent.ACTION_MAIN import android.content.pm.PackageManager import android.net.Uri import android.os.Bundle import android.os.StrictMode import androidx.annotation.VisibleForTesting +import mozilla.components.browser.engine.gecko.GeckoEngine import mozilla.components.feature.intent.ext.sanitize import mozilla.components.feature.intent.processing.IntentProcessor import mozilla.components.feature.intent.processing.TabIntentProcessor.Companion.EXTRA_APP_LINK_LAUNCH_TYPE @@ -30,6 +32,8 @@ import org.mozilla.fenix.ext.settings import org.mozilla.fenix.perf.MarkersActivityLifecycleCallbacks import org.mozilla.fenix.perf.StartupTimeline import org.mozilla.fenix.shortcut.NewTabShortcutIntentProcessor +import org.mozilla.geckoview.TorAndroidIntegration.BootstrapStateChangeListener +import org.mozilla.geckoview.TorConnectStage /** * Processes incoming intents and sends them to the corresponding activity. @@ -62,7 +66,28 @@ class IntentReceiverActivity : Activity() { // the HomeActivity. val intent = intent?.let { Intent(it) } ?: Intent() intent.sanitize().stripUnwantedFlags() - processIntent(intent) + if (intent.action == ACTION_MAIN || components.torController.isBootstrapped) { + processIntent(intent) + } else { + // Wait until Tor is connected to handle intents from external apps for links, search, etc. + val engine = components.core.engine as GeckoEngine + engine.getTorIntegrationController().registerBootstrapStateChangeListener( + object : BootstrapStateChangeListener { + + override fun onBootstrapStageChange(stage: TorConnectStage) { + if (stage.isBootstrapped) { + engine.getTorIntegrationController().unregisterBootstrapStateChangeListener(this) + processIntent(intent) + } + } + + override fun onBootstrapProgress(progress: Double, hasWarnings: Boolean) {} + }) + + + // In the meantime, open the HomeActivity so the user can get connected. + processIntent(Intent()) + } components.core.engine.profiler?.addMarker( MarkersActivityLifecycleCallbacks.MARKER_NAME, diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/components/Components.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/components/Components.kt @@ -74,6 +74,7 @@ import org.mozilla.fenix.reviewprompt.ReviewPromptMiddleware import org.mozilla.fenix.settings.settingssearch.DefaultFenixSettingsIndexer import org.mozilla.fenix.termsofuse.TermsOfUseManager import org.mozilla.fenix.termsofuse.store.DefaultTermsOfUsePromptRepository +import org.mozilla.fenix.tor.TorControllerGV import org.mozilla.fenix.utils.Settings import org.mozilla.fenix.utils.isLargeScreenSize import org.mozilla.fenix.wifi.WifiConnectionMonitor @@ -356,6 +357,8 @@ class Components(private val context: Context) { val settingsIndexer by lazyMonitored { DefaultFenixSettingsIndexer(context) } + + val torController by lazyMonitored { TorControllerGV(context) } } /** diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/tor/TorBridgeTransports.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/tor/TorBridgeTransports.kt @@ -0,0 +1,48 @@ +/* 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.Parcelable +import androidx.annotation.StringRes +import kotlinx.parcelize.Parcelize +import org.mozilla.fenix.R + +@Parcelize +enum class TorBridgeTransportConfig( + @param:StringRes val preferenceKey: Int, + val transportName: String +) : Parcelable { + + BUILTIN_OBFS4( + preferenceKey = R.string.pref_key_tor_network_settings_bridge_config_builtin_bridge_obfs4, + transportName = "obfs4" + ), + BUILTIN_MEEK( + preferenceKey = R.string.pref_key_tor_network_settings_bridge_config_builtin_bridge_meek_azure, + transportName = "meek" + ), + BUILTIN_SNOWFLAKE( + preferenceKey = R.string.pref_key_tor_network_settings_bridge_config_builtin_bridge_snowflake, + transportName = "snowflake" + ), + USER_PROVIDED( + preferenceKey = R.string.pref_key_tor_network_settings_bridge_config_user_provided_bridge, + transportName = "user_provided" + ) +} + +object TorBridgeTransportConfigUtil { + fun getStringToBridgeTransport(bridge: String): TorBridgeTransportConfig { + return when (bridge) { + TorBridgeTransportConfig.BUILTIN_OBFS4.transportName -> + TorBridgeTransportConfig.BUILTIN_OBFS4 + TorBridgeTransportConfig.BUILTIN_MEEK.transportName -> + TorBridgeTransportConfig.BUILTIN_MEEK + TorBridgeTransportConfig.BUILTIN_SNOWFLAKE.transportName -> + TorBridgeTransportConfig.BUILTIN_SNOWFLAKE + else -> TorBridgeTransportConfig.USER_PROVIDED + } + } +} diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/tor/TorController.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/tor/TorController.kt @@ -0,0 +1,41 @@ +/* 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 androidx.lifecycle.LifecycleCoroutineScope + +// Callback for function to be run one time when the system is bootstrapped and then disregarded +interface RunOnceBootstrapped { + fun onBootstrapped() +} + +interface TorController { + val logEntries: MutableList<TorLog> + val isBootstrapped: Boolean + var bridgesEnabled: Boolean + var bridgeTransport: TorBridgeTransportConfig + var userProvidedBridges: String? + + fun start() + fun stop() + + // TorBrowserFeatures.install wants to register a callback for when tor bootstraps the first time + // so it can then check for noscript updates. + // Currently it needs to register it before TorAndroidIntegration is fully loaded, so this way + // they can register with TorController which will start streaming events from TAS when available + // and call them one time when the system is bootstrapped + // TODO: rewire the noscript update call in TorBrowserFeatures.install + // a) call TorBrowserFeatures.install from somewhere else (ex: move from Core.GeckoEngine.also + // to maybe FenixApplication.setupInMainProcessOnly + // dan: had trouble with this first time: + // https://gitlab.torproject.org/tpo/applications/tor-browser/-/merge_requests/1423#note_3191590 + // b) just move the call to `context.components.addonUpdater.update(NOSCRIPT_ID)` somewhere else + // that can use TorAndroidIntegration.BootstrapListener + fun registerRunOnceBootstrapped(rob: RunOnceBootstrapped) + fun unregisterRunOnceBootstrapped(rob: RunOnceBootstrapped) + + fun initiateTorBootstrap(lifecycleScope: LifecycleCoroutineScope? = null, withDebugLogging: Boolean = false) + fun stopTor() +} diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/tor/TorControllerGV.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/tor/TorControllerGV.kt @@ -0,0 +1,196 @@ +package org.mozilla.fenix.tor + + +import android.content.Context +import android.util.Log +import androidx.lifecycle.LifecycleCoroutineScope +import mozilla.components.browser.engine.gecko.GeckoEngine +import org.mozilla.fenix.ext.components +import org.mozilla.geckoview.TorAndroidIntegration +import org.mozilla.geckoview.TorAndroidIntegration.BootstrapStateChangeListener +import org.mozilla.geckoview.TorAndroidIntegration.TorLogListener +import org.mozilla.geckoview.TorConnectStage +import org.mozilla.geckoview.TorConnectStageName +import org.mozilla.geckoview.TorSettings +import org.mozilla.geckoview.TorSettings.BridgeBuiltinType +import org.mozilla.geckoview.TorSettings.BridgeSource + +class TorControllerGV( + private val context: Context, +) : TorController, BootstrapStateChangeListener, TorLogListener { + + private val TAG = "TorControllerGV" + + private var runOnceBootstrappedHandlers = mutableListOf<RunOnceBootstrapped>() + + override val isBootstrapped get() = + getTorIntegration().lastKnowStage.value?.name?.isBootstrapped ?: false + + private val entries = mutableListOf<TorLog>() + override val logEntries get() = entries + + private fun getTorIntegration(): TorAndroidIntegration { + return (context.components.core.engine as GeckoEngine).getTorIntegrationController() + } + + private fun getTorSettings(): TorSettings? { + return getTorIntegration().getSettings() + } + + // On a fresh install bridgeEnabled can be set to true without a valid bridgeSource + // having been selected. After first use this will not happen because last selected bridge + // will be remembered and reused. + // However, on first use, submitting this to TorSettings is an invalid state. + // TorSettings.sys.mjs's #cleanupSettings will remove a lone bridgeEnabled with no source + // selected. Therefore we check and don't call setSettings if bridgeSource isn't selected + // (when trying to enable). Disabeling is always valid. + private var _bridgesEnabled: Boolean? = null + override var bridgesEnabled: Boolean + get() { + return _bridgesEnabled ?: getTorSettings()?.bridgesEnabled ?: false + } + set(value) { + _bridgesEnabled = value + getTorSettings()?.let { + if (!value || it.bridgesSource != BridgeSource.Invalid) { + it.bridgesEnabled = value + getTorIntegration().setSettings(it) + } + } + } + + override var bridgeTransport: TorBridgeTransportConfig + get() { + return when (getTorSettings()?.bridgesSource) { + BridgeSource.BuiltIn -> { + when (getTorSettings()?.bridgesBuiltinType) { + BridgeBuiltinType.Obfs4 -> TorBridgeTransportConfig.BUILTIN_OBFS4 + BridgeBuiltinType.Meek -> TorBridgeTransportConfig.BUILTIN_MEEK + BridgeBuiltinType.Snowflake -> TorBridgeTransportConfig.BUILTIN_SNOWFLAKE + else -> TorBridgeTransportConfig.USER_PROVIDED + } + + } + + BridgeSource.UserProvided -> TorBridgeTransportConfig.USER_PROVIDED + else -> TorBridgeTransportConfig.USER_PROVIDED + } + } + set(value) { + getTorSettings()?.let { + it.bridgesEnabled = true + if (value == TorBridgeTransportConfig.USER_PROVIDED) { + // NOOP: all settings will be set in call to set userProvidedBridges and submited + // at the same time to clear TorSettings.sys.mjs #cleanupSettings + return + } else { + it.bridgesSource = BridgeSource.BuiltIn + val bbt: BridgeBuiltinType = when (value) { + TorBridgeTransportConfig.BUILTIN_OBFS4 -> BridgeBuiltinType.Obfs4 + TorBridgeTransportConfig.BUILTIN_MEEK -> BridgeBuiltinType.Meek + TorBridgeTransportConfig.BUILTIN_SNOWFLAKE -> BridgeBuiltinType.Snowflake + } + it.bridgesBuiltinType = bbt + } + getTorIntegration().setSettings(it) + } + } + + // Currently the UI takes a user provided string and sets this in one step so there is where we + // actually set it.bridgesSource = BridgeSource.UserProvided, not above, + // as TorSettings.sys.mjs #cleanupSettings could reject BridgeSource.UserProvided + // with no bridge strings + override var userProvidedBridges: String? + get() { + return getTorSettings()?.let { + if (it.bridgesSource == BridgeSource.UserProvided) { + return getTorSettings()?.bridgeBridgeStrings?.joinToString("\n") + } + return "" + } + } + set(value) { + getTorSettings()?.let { + Log.i(TAG, "setUserProvidedBridges: '$value'") + // Hack: we don't have validation so lets do something quick and dirty (each line has a length) + val userProvidedLines: Array<String> = value?.split("\n")?.filter { it.length > 4 }?.toTypedArray() ?: arrayOf<String>() + it.bridgesSource = BridgeSource.UserProvided + it.bridgeBridgeStrings = userProvidedLines + getTorIntegration().setSettings(it) + } + } + + override fun start() { + getTorIntegration().registerBootstrapStateChangeListener(this) + getTorIntegration().registerLogListener(this) + } + + override fun stop() { + getTorIntegration().unregisterBootstrapStateChangeListener(this) + getTorIntegration().unregisterLogListener(this) + } + + override fun onLog(type: String?, message: String?, timestamp: String?) { + synchronized(entries) { + entries.add(TorLog(type ?: "null", message ?: "null", timestamp ?: "null")) + } + } + + override fun registerRunOnceBootstrapped(rob: RunOnceBootstrapped) { + // TODO Remove need for this with tb-44002 + // it would be nice to have a short circuit run and don't add if already bootstrapped + // however this calls context.components.core.engine which tries to lazy load engine + // which causes a recursive loop. instead we should do the work in tb-44002 + // this is currently fine as there is a single use case for this called in + // TorBrowserFeatures that is at startup + //if (isBootstrapped) { + // rob.onBootstrapped() + // return + //} + synchronized(runOnceBootstrappedHandlers) { + if (runOnceBootstrappedHandlers.contains(rob)) { + return + } + runOnceBootstrappedHandlers.add(rob) + } + } + + override fun unregisterRunOnceBootstrapped(rob: RunOnceBootstrapped) { + synchronized(runOnceBootstrappedHandlers) { + if (!runOnceBootstrappedHandlers.contains(rob)) { + return + } + runOnceBootstrappedHandlers.remove(rob) + } + } + + override fun initiateTorBootstrap( + lifecycleScope: LifecycleCoroutineScope?, + withDebugLogging: Boolean, + ) { + getTorIntegration().beginBootstrap() + } + + override fun stopTor() { + getTorIntegration().cancelBootstrap() + } + + // TorEventsBootstrapStateChangeListener + override fun onBootstrapStageChange(stage: TorConnectStage) { + Log.d(TAG, "onBootstrapStageChange(stage = $stage)") + + if (stage.name == TorConnectStageName.Bootstrapped) { + synchronized(runOnceBootstrappedHandlers) { + runOnceBootstrappedHandlers.toList().forEach { + it.onBootstrapped() + runOnceBootstrappedHandlers.remove(it) + } + } + } + } + + // TorEventsBootstrapStateChangeListener + override fun onBootstrapProgress(progress: Double, hasWarnings: Boolean) { + Log.d(TAG, "onBootstrapProgress(progress = $progress, hasWarnings = $hasWarnings)") + } +} diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/tor/TorLog.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/tor/TorLog.kt @@ -0,0 +1,10 @@ +package org.mozilla.fenix.tor + +import androidx.compose.runtime.Stable + +@Stable +data class TorLog( + val type: String, + val text: String, + val timestamp: String, +) diff --git a/mobile/android/fenix/app/src/main/res/drawable/tor_bootstrap_background_gradient.xml b/mobile/android/fenix/app/src/main/res/drawable/tor_bootstrap_background_gradient.xml @@ -7,9 +7,9 @@ <shape> <gradient android:angle="225" - android:startColor="#FF7329A4" - android:centerColor="#FF3A3274" - android:endColor="#FF3A3274" + android:startColor="@color/backgroundGradientLight" + android:centerColor="@color/backgroundGradientDark" + android:endColor="@color/backgroundGradientDark" android:type="linear" /> </shape> </item> diff --git a/mobile/android/fenix/app/src/main/res/values/colors.xml b/mobile/android/fenix/app/src/main/res/values/colors.xml @@ -224,6 +224,8 @@ <color name="sync_disconnected_icon_fill_private_theme">@color/photonYellow70</color> <color name="sync_disconnected_background_private_theme">#5B5846</color> <color name="prompt_login_edit_text_cursor_color_private_theme">@color/photonViolet50</color> + <color name="backgroundGradientDark">#FF3A3274</color> + <color name="backgroundGradientLight">#FF7329A4</color> <!-- Normal theme colors for light mode --> <color name="accent_normal_theme">@color/photonInk20</color> diff --git a/mobile/android/fenix/app/src/main/res/values/styles.xml b/mobile/android/fenix/app/src/main/res/values/styles.xml @@ -24,7 +24,7 @@ <item name="android:statusBarColor">@android:color/transparent</item> <item name="android:navigationBarColor">?attr/colorSurface</item> <item name="android:windowTranslucentNavigation">false</item> - <item name="android:windowBackground">@color/fx_mobile_surface</item> + <item name="android:windowBackground">@color/backgroundGradientDark</item> <item name="android:colorEdgeEffect">@color/accent_normal_theme</item> <item name="android:colorAccent">@color/fx_mobile_text_color_primary</item> <item name="android:textColorPrimary">@color/state_list_text_color</item> @@ -447,6 +447,7 @@ <item name="fenixLogo">@drawable/ic_logo_wordmark_private</item> <item name="fenixWordmarkText">@drawable/ic_wordmark_text_private</item> <item name="fenixWordmarkLogo">@drawable/ic_wordmark_logo</item> + <item name="torBootstrapBackground">@drawable/tor_bootstrap_background_gradient</item> <item name="bottomBarBackground">@drawable/private_home_bottom_bar_background_gradient</item> <item name="bottomBarBackgroundTop">@drawable/private_home_bottom_bar_background_gradient_top</item> <item name="mozac_ic_private_mode_circle_fill_background_color">@color/photonWhite</item>