morgan pushed to branch tor-browser-128.5.0esr-14.5-1 at The Tor Project / Applications / Tor Browser
Commits: 938f751c by clairehurst at 2024-12-04T11:50:34-07:00 fixup! [android] Add Tor integration and UI
- - - - -
9 changed files:
- mobile/android/fenix/app/src/main/java/org/mozilla/fenix/HomeActivity.kt - mobile/android/fenix/app/src/main/java/org/mozilla/fenix/home/HomeFragment.kt - mobile/android/fenix/app/src/main/java/org/mozilla/fenix/settings/SettingsFragment.kt - mobile/android/fenix/app/src/main/java/org/mozilla/fenix/tor/TorConnectionAssistFragment.kt - mobile/android/fenix/app/src/main/java/org/mozilla/fenix/tor/TorConnectionAssistViewModel.kt - mobile/android/fenix/app/src/main/java/org/mozilla/fenix/tor/TorControllerGV.kt - mobile/android/fenix/app/src/main/res/layout/fenix_snackbar.xml - mobile/android/fenix/app/src/main/res/navigation/nav_graph.xml - mobile/android/fenix/app/src/main/res/values/torbrowser_strings.xml
Changes:
===================================== mobile/android/fenix/app/src/main/java/org/mozilla/fenix/HomeActivity.kt ===================================== @@ -25,6 +25,7 @@ import android.view.MotionEvent import android.view.View import android.view.ViewConfiguration import android.view.WindowManager.LayoutParams.FLAG_SECURE +import androidx.activity.viewModels import androidx.annotation.CallSuper import androidx.annotation.IdRes import androidx.annotation.RequiresApi @@ -32,7 +33,6 @@ import androidx.annotation.VisibleForTesting import androidx.appcompat.app.ActionBar import androidx.appcompat.widget.Toolbar import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen -import androidx.core.content.ContentProviderCompat.requireContext import androidx.lifecycle.lifecycleScope import androidx.navigation.NavController import androidx.navigation.fragment.NavHostFragment @@ -151,10 +151,10 @@ import org.mozilla.fenix.utils.Settings import java.lang.ref.WeakReference import java.util.Locale
-import androidx.navigation.fragment.findNavController import mozilla.components.browser.engine.gecko.GeckoEngine -import mozilla.components.browser.state.selector.findCustomTab +import org.mozilla.fenix.components.FenixSnackbar import org.mozilla.fenix.home.HomeFragment +import org.mozilla.fenix.tor.TorConnectionAssistViewModel import org.mozilla.geckoview.TorIntegrationAndroid
/** @@ -238,6 +238,8 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity, TorIn
private var dialog: RedirectDialogFragment? = null
+ private val torConnectionAssistViewModel: TorConnectionAssistViewModel by viewModels() + @Suppress("ComplexMethod") final override fun onCreate(savedInstanceState: Bundle?) { // DO NOT MOVE ANYTHING ABOVE THIS getProfilerTime CALL. @@ -1115,6 +1117,25 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity, TorIn historyMetadata: HistoryMetadataKey? = null, additionalHeaders: Map<String, String>? = null, ) { + if (!components.torController.isBootstrapped && !searchTermOrURL.startsWith("about:")) { + FenixSnackbar.make( + view = binding.root, + isDisplayedWithBrowserToolbar = true, + ) + .setText(getString(R.string.connection_assist_connect_to_tor_before_opening_links)) + .setAction(getString(R.string.connection_assist_connect_to_tor_before_opening_links_confirmation)) { + torConnectionAssistViewModel.handleConnect(searchTermOrURL) + if (navHost.navController.previousBackStackEntry?.destination?.id == R.id.torConnectionAssistFragment) { + supportFragmentManager.popBackStack() + } else { + navHost.navController.navigate( + TorConnectionAssistFragmentDirections.actionConnectToTorBeforeOpeningLinks() + ) + } + } + .show() + return + } openToBrowser(from, customTabSessionId) load( searchTermOrURL = searchTermOrURL, @@ -1434,8 +1455,10 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity, TorIn override fun onBootstrapStateChange(state: String) = Unit override fun onBootstrapProgress(progress: Double, hasWarnings: Boolean) = Unit override fun onBootstrapComplete() { - components.useCases.tabsUseCases.removeAllTabs() - navHost.navController.navigate(NavGraphDirections.actionStartupHome()) + if (settings().useHtmlConnectionUi) { + components.useCases.tabsUseCases.removeAllTabs() + navHost.navController.navigate(NavGraphDirections.actionStartupHome()) + } } override fun onBootstrapError(code: String?, message: String?, phase: String?, reason: String?) = Unit }
===================================== mobile/android/fenix/app/src/main/java/org/mozilla/fenix/home/HomeFragment.kt ===================================== @@ -160,6 +160,7 @@ import org.mozilla.fenix.tabstray.TabsTrayAccessPoint import org.mozilla.fenix.theme.FirefoxTheme import org.mozilla.fenix.tor.TorBootstrapFragmentDirections import org.mozilla.fenix.tor.TorBootstrapStatus +import org.mozilla.fenix.tor.TorConnectionAssistViewModel import org.mozilla.fenix.utils.Settings.Companion.TOP_SITES_PROVIDER_MAX_THRESHOLD import org.mozilla.fenix.utils.allowUndo import org.mozilla.fenix.wallpapers.Wallpaper @@ -179,6 +180,7 @@ class HomeFragment : Fragment(), UserInteractionHandler { private val binding get() = _binding!!
private val homeViewModel: HomeScreenViewModel by activityViewModels() + private val torConnectionAssistViewModel: TorConnectionAssistViewModel by activityViewModels()
private val snackbarAnchorView: View? get() = when (requireContext().settings().toolbarPosition) { @@ -899,6 +901,17 @@ class HomeFragment : Fragment(), UserInteractionHandler { view = view, )
+ torConnectionAssistViewModel.urlToLoadAfterConnecting.also { + if(!it.isNullOrBlank()){ + (requireActivity() as HomeActivity).openToBrowserAndLoad( + searchTermOrURL = it, + newTab = true, + from = BrowserDirection.FromHome, + ) + torConnectionAssistViewModel.urlToLoadAfterConnecting = null // Only load this url once + } + } + // DO NOT MOVE ANYTHING BELOW THIS addMarker CALL! requireComponents.core.engine.profiler?.addMarker( MarkersFragmentLifecycleCallbacks.MARKER_NAME,
===================================== mobile/android/fenix/app/src/main/java/org/mozilla/fenix/settings/SettingsFragment.kt ===================================== @@ -25,6 +25,7 @@ import androidx.lifecycle.Observer import androidx.lifecycle.lifecycleScope import androidx.navigation.NavDirections import androidx.navigation.findNavController +import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.navArgs import androidx.preference.Preference import androidx.preference.PreferenceFragmentCompat @@ -40,6 +41,7 @@ import mozilla.components.concept.sync.OAuthAccount import mozilla.components.concept.sync.Profile import mozilla.components.feature.addons.ui.AddonFilePicker import mozilla.components.service.glean.private.NoExtras +import mozilla.components.support.base.feature.UserInteractionHandler import mozilla.components.support.ktx.android.view.showKeyboard import mozilla.components.ui.widgets.withCenterAlignedButtons import org.mozilla.fenix.BrowserDirection @@ -77,7 +79,7 @@ import kotlin.system.exitProcess import org.mozilla.fenix.GleanMetrics.Settings as SettingsMetrics
@Suppress("LargeClass", "TooManyFunctions") -class SettingsFragment : PreferenceFragmentCompat() { +class SettingsFragment : PreferenceFragmentCompat(), UserInteractionHandler {
private val args by navArgs<SettingsFragmentArgs>() private lateinit var accountUiView: AccountUiView @@ -883,4 +885,18 @@ class SettingsFragment : PreferenceFragmentCompat() { private const val FXA_SYNC_OVERRIDE_EXIT_DELAY = 2000L private const val AMO_COLLECTION_OVERRIDE_EXIT_DELAY = 3000L } + + override fun onBackPressed(): Boolean { + // If tor is already bootstrapped, skip going back to [TorConnectionAssistFragment] and instead go directly to [HomeFragment] + if (requireComponents.torController.isBootstrapped) { + val navController = findNavController() + if (navController.previousBackStackEntry?.destination?.id == R.id.torConnectionAssistFragment) { + navController.navigate( + SettingsFragmentDirections.actionGlobalHomeFragment(), + ) + return true + } + } + return false + } }
===================================== mobile/android/fenix/app/src/main/java/org/mozilla/fenix/tor/TorConnectionAssistFragment.kt ===================================== @@ -19,7 +19,7 @@ import android.view.View import android.view.ViewGroup import androidx.appcompat.content.res.AppCompatResources import androidx.fragment.app.Fragment -import androidx.fragment.app.viewModels +import androidx.fragment.app.activityViewModels import androidx.lifecycle.Lifecycle import androidx.lifecycle.lifecycleScope import androidx.lifecycle.repeatOnLifecycle @@ -34,7 +34,7 @@ import org.mozilla.fenix.ext.hideToolbar class TorConnectionAssistFragment : Fragment(), UserInteractionHandler {
private val TAG = "TorConnectionAssistFrag" - private val viewModel: TorConnectionAssistViewModel by viewModels() + private val viewModel: TorConnectionAssistViewModel by activityViewModels() private var _binding: FragmentTorConnectionAssistBinding? = null private val binding get() = _binding!!
@@ -46,6 +46,11 @@ class TorConnectionAssistFragment : Fragment(), UserInteractionHandler { _binding = FragmentTorConnectionAssistBinding.inflate( inflater, container, false, ) + viewLifecycleOwner.lifecycleScope.launch { + repeatOnLifecycle(Lifecycle.State.STARTED) { + viewModel.collectLastKnownStatus() + } + }
viewLifecycleOwner.lifecycleScope.launch { repeatOnLifecycle(Lifecycle.State.STARTED) { @@ -62,7 +67,6 @@ class TorConnectionAssistFragment : Fragment(), UserInteractionHandler { override fun onResume() { super.onResume() hideToolbar() - viewModel.handleTorConnectStateToScreen() // Covers the case where the app is backgrounded when the bootstrap finishes }
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { @@ -71,11 +75,7 @@ class TorConnectionAssistFragment : Fragment(), UserInteractionHandler { viewModel.progress().observe( viewLifecycleOwner, ) { progress -> - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { - binding.torBootstrapProgressBar.setProgress(progress, true) - } else { - binding.torBootstrapProgressBar.progress = progress - } + setProgressBarCompat(progress) }
viewModel.quickstartToggle().observe( @@ -95,6 +95,14 @@ class TorConnectionAssistFragment : Fragment(), UserInteractionHandler {
}
+ private fun setProgressBarCompat(progress: Int) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + binding.torBootstrapProgressBar.setProgress(progress, true) + } else { + binding.torBootstrapProgressBar.progress = progress + } + } + private fun showScreen(screen: ConnectAssistUiState) { setProgressBar(screen) setSettingsButton(screen) @@ -269,7 +277,7 @@ class TorConnectionAssistFragment : Fragment(), UserInteractionHandler {
private fun openHome() { Log.d(TAG, "openHome()") - findNavController().navigate(TorConnectionAssistFragmentDirections.actionStartupHome()) + viewModel.openHome(findNavController()) }
private fun openSettings(preferenceToScrollTo: String? = null) {
===================================== mobile/android/fenix/app/src/main/java/org/mozilla/fenix/tor/TorConnectionAssistViewModel.kt ===================================== @@ -10,6 +10,7 @@ import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.LifecycleCoroutineScope import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData +import androidx.navigation.NavController import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import org.mozilla.fenix.ext.components @@ -44,20 +45,25 @@ class TorConnectionAssistViewModel( }
init { - Log.d(TAG, "initiating TorConnectionAssistViewModel") + Log.d(TAG, "initiating TorConnectionAssistViewModel $this") _torController.registerTorListener(this) - handleTorConnectStateToScreen() // should cover the case of when we have an onBootStrapStateChange() event before this is initialized, which lead to being stuck on the splash screen }
- private fun handleConnect( + var urlToLoadAfterConnecting: String? = null + + fun handleConnect( + urlToLoadAfterConnecting: String? = null, withDebugLogging: Boolean = false, lifecycleScope: LifecycleCoroutineScope? = null, ) { - Log.d(TAG, "handleConnect initiatingTorBootstrap with lifecycleScope = $lifecycleScope") - _torController.initiateTorBootstrap( - withDebugLogging = withDebugLogging, - lifecycleScope = lifecycleScope, - ) + this.urlToLoadAfterConnecting = urlToLoadAfterConnecting + if (_torController.lastKnownStatus.value.isOff()) { + Log.d(TAG, "handleConnect() triggered, initiatingTorBootstrap") + _torController.initiateTorBootstrap( + withDebugLogging = withDebugLogging, + lifecycleScope = lifecycleScope, + ) + } }
fun handleQuickstartChecked(checked: Boolean) { @@ -96,18 +102,19 @@ class TorConnectionAssistViewModel( _progress.value = progress.toInt() }
- handleTorConnectStateToScreen() }
- fun handleTorConnectStateToScreen() { - when (_torController.lastKnownStatus) { - TorConnectState.Initial -> _torConnectScreen.value = ConnectAssistUiState.Splash - TorConnectState.Configuring -> handleConfiguring() - TorConnectState.AutoBootstrapping -> handleBootstrap() - TorConnectState.Bootstrapping -> handleBootstrap() - TorConnectState.Bootstrapped -> _shouldOpenHome.value = true - TorConnectState.Disabled -> _shouldOpenHome.value = true - TorConnectState.Error -> handleError() + suspend fun collectLastKnownStatus() { + _torController.lastKnownStatus.collect { + when (it) { + TorConnectState.Initial -> _torConnectScreen.value = ConnectAssistUiState.Splash + TorConnectState.Configuring -> handleConfiguring() + TorConnectState.AutoBootstrapping -> handleBootstrap() + TorConnectState.Bootstrapping -> handleBootstrap() + TorConnectState.Bootstrapped -> _shouldOpenHome.value = true + TorConnectState.Disabled -> _shouldOpenHome.value = true + TorConnectState.Error -> handleError() + } } }
@@ -254,4 +261,10 @@ class TorConnectionAssistViewModel( } return true } + + fun openHome(navController: NavController) { + navController.navigate( + TorConnectionAssistFragmentDirections.actionHome(), + ) + } }
===================================== mobile/android/fenix/app/src/main/java/org/mozilla/fenix/tor/TorControllerGV.kt ===================================== @@ -4,6 +4,8 @@ package org.mozilla.fenix.tor import android.content.Context import android.util.Log import androidx.lifecycle.LifecycleCoroutineScope +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow import mozilla.components.browser.engine.gecko.GeckoEngine import org.mozilla.fenix.ext.components import org.mozilla.geckoview.TorIntegrationAndroid @@ -54,20 +56,22 @@ class TorControllerGV( private var torListeners = mutableListOf<TorEvents>() private var torLogListeners = mutableListOf<TorLogs>()
- internal var lastKnownStatus = TorConnectState.Initial + private val _lastKnownStatus = MutableStateFlow(TorConnectState.Initial) + internal val lastKnownStatus: StateFlow<TorConnectState> = _lastKnownStatus + internal var lastKnownError: TorError? = null private var wasTorBootstrapped = false private var isTorRestarting = false
private var isTorBootstrapped = false - get() = ((lastKnownStatus.isStarted()) && wasTorBootstrapped) + get() = ((_lastKnownStatus.value.isStarted()) && wasTorBootstrapped)
private val entries = mutableListOf<Pair<String?, String?>>() override val logEntries get() = entries - override val isStarting get() = lastKnownStatus.isStarting() + override val isStarting get() = _lastKnownStatus.value.isStarting() override val isRestarting get() = isTorRestarting override val isBootstrapped get() = isTorBootstrapped - override val isConnected get() = (lastKnownStatus.isStarted() && !isTorRestarting) + override val isConnected get() = (_lastKnownStatus.value.isStarted() && !isTorRestarting)
override var quickstart: Boolean get() { @@ -267,13 +271,13 @@ class TorControllerGV( }
override fun setTorStopped() { - lastKnownStatus = TorConnectState.Configuring + _lastKnownStatus.value = TorConnectState.Configuring onTorStatusUpdate(null, lastKnownStatus.toString(), 0.0) onTorStopped() }
override fun restartTor() { - if (!lastKnownStatus.isStarted() && wasTorBootstrapped) { + if (!_lastKnownStatus.value.isStarted() && wasTorBootstrapped) { // If we aren't started, but we were previously bootstrapped, // then we handle a "restart" request as a "start" restart initiateTorBootstrap() @@ -321,42 +325,22 @@ class TorControllerGV( } }
- if (lastKnownStatus.isOff() && newState.isStarting()) { + if (_lastKnownStatus.value.isOff() && newState.isStarting()) { isTorRestarting = false }
- lastKnownStatus = newState + _lastKnownStatus.value = newState onTorStatusUpdate(null, newStateVal, null) }
// TorEventsBootstrapStateChangeListener override fun onBootstrapProgress(progress: Double, hasWarnings: Boolean) { - Log.d(TAG, "onBootstrapProgress($progress, $hasWarnings)") - // TODO: onBootstrapProgress should only be used to change the shown - // bootstrap percentage or a Tor log option during a "Bootstrapping" - // stage. - // The progress value should not be used to change the `lastKnownStatus` - // value or determine if a bootstrap has started or completed. The - // TorConnectStage should be used instead. - if (progress == 100.0) { - lastKnownStatus = TorConnectState.Bootstrapped - wasTorBootstrapped = true - onTorConnected() - } else if (lastKnownStatus == TorConnectState.Bootstrapping) { - onTorConnecting() - } - onTorStatusUpdate("", lastKnownStatus.toTorStatus().status, progress) + Log.d(TAG, "onBootstrapProgress(progress = $progress, hasWarnings = $hasWarnings)") + onTorStatusUpdate("", _lastKnownStatus.value.toTorStatus().status, progress) }
// TorEventsBootstrapStateChangeListener - override fun onBootstrapComplete() { - // TODO: There should be no need to respond to the BootstrapComplete - // event if we are already handling TorConnectStage.Bootstrapped. - // In particular, `lastKnownStatus` and onTorConnected should be set in - // response to a change in TorConnectStage instead. - lastKnownStatus = TorConnectState.Bootstrapped - this.onTorConnected() - } + override fun onBootstrapComplete() = Unit
// TorEventsBootstrapStateChangeListener override fun onBootstrapError(code: String?, message: String?, phase: String?, reason: String?) {
===================================== mobile/android/fenix/app/src/main/res/layout/fenix_snackbar.xml ===================================== @@ -21,6 +21,12 @@ android:paddingStart="16dp" android:paddingEnd="16dp">
+ <!-- + TextView below changed as part of tor-browser#43229 to match the designs + https://www.figma.com/design/vXrWeiV2IlKx5IIZVLtxBX/Android-Components?node-id=1807-3117&t=8Gc1mpPYPQCLMYH2-1 screenshot shown here + Line spacing eyeballed from screenshot here + https://gitlab.torproject.org/tpo/applications/tor-browser/-/merge_requests/1275#note_3125666 + --> <TextView android:id="@+id/snackbar_text" android:layout_width="0dp" @@ -29,8 +35,9 @@ android:letterSpacing="0.05" android:minHeight="46dp" android:maxLines="2" - android:paddingTop="8dp" - android:paddingBottom="8dp" + android:paddingTop="12dp" + android:paddingBottom="12dp" + android:lineSpacingExtra="8sp" android:textAlignment="textStart" android:textColor="@color/photonWhite" android:textSize="18sp"
===================================== mobile/android/fenix/app/src/main/res/navigation/nav_graph.xml ===================================== @@ -27,6 +27,12 @@ app:popUpTo="@id/startupFragment" app:popUpToInclusive="true" />
+ <action + android:id="@+id/action_connect_to_tor_before_opening_links" + app:destination="@+id/torConnectionAssistFragment" + app:popUpTo="@id/torConnectionAssistFragment" + app:popUpToInclusive="true"/> + <action android:id="@+id/action_global_home" app:destination="@id/homeFragment" @@ -264,7 +270,7 @@ <fragment android:id="@+id/torConnectionAssistFragment" android:name="org.mozilla.fenix.tor.TorConnectionAssistFragment" - tools:layout="@layout/fragment_home"> + tools:layout="@layout/fragment_tor_connection_assist"> <action android:id="@+id/action_home" app:destination="@id/homeFragment"
===================================== mobile/android/fenix/app/src/main/res/values/torbrowser_strings.xml ===================================== @@ -149,4 +149,31 @@ <string name="connection_assist_final_error_troubleshoot_connection_link">troubleshooting your connection</string> <!-- Connection assist. --> <string name="connection_assist_final_error_learn_more_link">Learn more</string> + + <!-- Connection assist. Description for a shown "Snackbar" (special popup notification) with an action to connect --> + <string name="connection_assist_connect_to_tor_before_opening_links">Connect to Tor before opening links</string> + <!-- Connection assist. Confirmation button for a shown "Snackbar" (special popup notification) that has a previously mentioned description. Automatically shown in ALL CAPS if available, regardless of the localized string --> + <string name="connection_assist_connect_to_tor_before_opening_links_confirmation">CONNECT</string> + + <!-- 2024 YEC. --> + <string name="YEC_2024_right_to_speak">You have a right to SPEAK without uninvited listeners.</string> + <!-- 2024 YEC. --> + <string name="YEC_2024_right_to_BROWSE">You have a right to BROWSE without being watched.</string> + <!-- 2024 YEC. --> + <string name="YEC_2024_right_to_SEARCH">You have a right to SEARCH without being followed.</string> + + <!-- 2024 YEC. --> + <string name="YEC_2024_donation_encouragement">Join the thousands of Tor supporters building an internet powered by privacy. Make a donation today.</string> + + <!-- 2024 YEC. --> + <string name="YEC_2024_donation_match_text">Through December 31, your gift will be matched, up to $300,000!</string> + + <!-- 2024 YEC. %1$s is the app name "Tor Browser". Since this will only ever show on release, it will always be "Tor Browser" (and not "Tor Browser Alpha" for instance) --> + <string name="YEC_2024_tor_browser_for_android_will_always_be_free_no_donation_required">%1$s for Android will always be free to use—no donation is required to use this app.</string> + + <!-- 2024 YEC. Accessible name for the "X" button. --> + <string name="YEC_2024_close">Close</string> + + <!-- 2024 YEC. --> + <string name="YEC_2024_donate_now">Donate now</string> </resources>
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser/-/commit/938f751c...