Commit 6970b34f authored by clairehurst's avatar clairehurst 🌱 Committed by Pier Angelo Vendrame
Browse files

[android] Implement Android-native Connection Assist UI

parent 54622396
Loading
Loading
Loading
Loading
+2 −12
Original line number Diff line number Diff line
@@ -76,12 +76,7 @@ class SearchUseCases(
            flags: EngineSession.LoadUrlFlags = EngineSession.LoadUrlFlags.none(),
            additionalHeaders: Map<String, String>? = null,
        ) {
            var securityLevel: Int
            try {
                securityLevel = settings?.torSecurityLevel ?: 0
            } catch (e: UnsupportedSettingException) {
                securityLevel = 0
            }
            val securityLevel : Int = settings!!.torSecurityLevel
            val searchUrl = searchEngine?.let {
                searchEngine.buildSearchUrl(searchTerms, securityLevel)
            } ?: store.state.search.selectedOrDefaultSearchEngine?.buildSearchUrl(searchTerms, securityLevel)
@@ -172,12 +167,7 @@ class SearchUseCases(
            flags: EngineSession.LoadUrlFlags = EngineSession.LoadUrlFlags.none(),
            additionalHeaders: Map<String, String>? = null,
        ) {
            var securityLevel: Int
            try {
                securityLevel = settings?.torSecurityLevel ?: 0
            } catch (e: UnsupportedSettingException) {
                securityLevel = 0
            }
            val securityLevel : Int = settings!!.torSecurityLevel
            val searchUrl = searchEngine?.let {
                searchEngine.buildSearchUrl(searchTerms, securityLevel)
            } ?: store.state.search.selectedOrDefaultSearchEngine?.buildSearchUrl(searchTerms, securityLevel)
+9 −0
Original line number Diff line number Diff line
@@ -33,6 +33,15 @@ class NotificationsDelegate(
    var isRequestingPermission: Boolean = false
        private set

    /**
     * Defaults to true, normal behavior is to destroy the app when OnDestroy is called with isFinishing set to true
     *
     * A value of false indicates that the notification was just swiped away and the app should not shut down on it's behalf
     *
     * Workaround to make swiping the notification away not shutdown the app
     */
    var shouldShutDownWithOnDestroyWhenIsFinishing: Boolean = true

    @VisibleForTesting
    internal var permissionRequestsCount: Int = 0

+1 −0
Original line number Diff line number Diff line
@@ -44,4 +44,5 @@ enum class BrowserDirection(@IdRes val fragmentId: Int) {
    FromMenuDialogFragment(R.id.menuDialogFragment),
    FromWebCompatReporterFragment(R.id.webCompatReporterFragment),
    FromGleanDebugToolsFragment(R.id.gleanDebugToolsFragment),
    FromTorConnectionAssistFragment(R.id.torConnectionAssistFragment),
}
+109 −15
Original line number Diff line number Diff line
@@ -27,6 +27,7 @@ import android.view.ViewConfiguration
import android.view.ViewGroup
import android.view.WindowManager.LayoutParams.FLAG_SECURE
import androidx.activity.BackEventCompat
import androidx.activity.viewModels
import androidx.annotation.CallSuper
import androidx.annotation.IdRes
import androidx.annotation.RequiresApi
@@ -37,7 +38,6 @@ import androidx.compose.runtime.mutableStateOf
import androidx.core.app.NotificationManagerCompat
import androidx.core.net.toUri
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
import androidx.core.content.ContentProviderCompat.requireContext
import androidx.core.text.layoutDirection
import androidx.lifecycle.lifecycleScope
import androidx.navigation.NavController
@@ -85,6 +85,7 @@ import mozilla.components.support.utils.BrowsersCache
import mozilla.components.support.utils.BuildManufacturerChecker
import mozilla.components.support.utils.SafeIntent
import mozilla.components.support.utils.TorUtils
import mozilla.components.support.utils.ext.getParcelableExtraCompat
import mozilla.components.support.utils.toSafeIntent
import mozilla.components.support.webextensions.WebExtensionPopupObserver
import mozilla.telemetry.glean.private.NoExtras
@@ -100,7 +101,6 @@ import org.mozilla.fenix.browser.browsingmode.BrowsingMode
import org.mozilla.fenix.browser.browsingmode.BrowsingModeManager
import org.mozilla.fenix.browser.browsingmode.DefaultBrowsingModeManager
import org.mozilla.fenix.components.appstate.AppAction
import org.mozilla.fenix.components.appstate.AppAction.ShareAction
import org.mozilla.fenix.components.appstate.OrientationMode
import org.mozilla.fenix.components.metrics.BreadcrumbsRecorder
import org.mozilla.fenix.components.metrics.GrowthDataWorker
@@ -136,7 +136,6 @@ import org.mozilla.fenix.home.intent.OpenBrowserIntentProcessor
import org.mozilla.fenix.home.intent.OpenPasswordManagerIntentProcessor
import org.mozilla.fenix.home.intent.OpenRecentlyClosedIntentProcessor
import org.mozilla.fenix.home.intent.OpenSpecificTabIntentProcessor
import org.mozilla.fenix.home.intent.ReEngagementIntentProcessor
import org.mozilla.fenix.home.intent.SpeechProcessingIntentProcessor
import org.mozilla.fenix.home.intent.StartSearchIntentProcessor
import org.mozilla.fenix.library.bookmarks.DesktopFolders
@@ -165,12 +164,22 @@ 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.tor.TorConnectionAssistFragmentDirections
import org.mozilla.fenix.utils.Settings
import org.mozilla.fenix.utils.changeAppLauncherIcon
import java.lang.ref.WeakReference
import java.util.Locale

import mozilla.components.browser.engine.gecko.GeckoEngine
import org.mozilla.fenix.compose.core.Action
import org.mozilla.fenix.compose.snackbar.SnackbarState
import org.mozilla.fenix.compose.snackbar.Snackbar
import org.mozilla.fenix.tor.UrlQuickLoadViewModel
import org.mozilla.geckoview.TorAndroidIntegration
import org.mozilla.geckoview.TorAndroidIntegration.BootstrapStateChangeListener
import org.mozilla.geckoview.TorConnectStage
import kotlin.system.exitProcess

/**
 * The main activity of the application. The application is primarily a single Activity (this one)
 * with fragments switching out to display different views. The most important views shown here are the:
@@ -178,7 +187,7 @@ import java.util.Locale
 * - browser screen
 */
@SuppressWarnings("TooManyFunctions", "LargeClass", "LongMethod")
open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity {
open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity, TorAndroidIntegration.BootstrapStateChangeListener {
    @VisibleForTesting
    internal lateinit var binding: ActivityHomeBinding
    lateinit var themeManager: ThemeManager
@@ -314,6 +323,8 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity {

    private var dialog: RedirectDialogFragment? = null

    private val urlQuickLoadViewModel: UrlQuickLoadViewModel by viewModels()

    @Suppress("ComplexMethod")
    final override fun onCreate(savedInstanceState: Bundle?) {
        // DO NOT MOVE ANYTHING ABOVE THIS getProfilerTime CALL.
@@ -576,6 +587,14 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity {
            onBackPressedCallback = onBackPressedCallback,
        )

        if (settings().useHtmlConnectionUi) {
            val engine = components.core.engine
            if (engine is GeckoEngine) {
                val torIntegration = engine.getTorIntegrationController()
                torIntegration.registerBootstrapStateChangeListener(this)
            }
        }

        StartupTimeline.onActivityCreateEndHome(this) // DO NOT MOVE ANYTHING BELOW HERE.
    }

@@ -764,8 +783,10 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity {
    override fun onProvideAssistContent(outContent: AssistContent?) {
        super.onProvideAssistContent(outContent)
        val currentTabUrl = components.core.store.state.selectedTab?.content?.url
        if (components.core.store.state.selectedTab?.content?.private == false) {
            outContent?.webUri = currentTabUrl?.let { it.toUri() }
        }
    }

    @CallSuper
    override fun onDestroy() {
@@ -800,6 +821,16 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity {
            stopMediaSession()
        }

        if (applicationContext.components.notificationsDelegate.shouldShutDownWithOnDestroyWhenIsFinishing) {
            if (isFinishing) {
                shutDown()
            }
        } else {
            // We only want to not shut down when the notification is swiped away,
            // if we do not reset this value
            applicationContext.components.notificationsDelegate.shouldShutDownWithOnDestroyWhenIsFinishing = true
        }

        components.core.engine.profiler?.addMarker(
            MarkersActivityLifecycleCallbacks.MARKER_NAME,
            startTimeProfiler,
@@ -864,15 +895,21 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity {
            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)
            val torIntegration = (components.core.engine as GeckoEngine).getTorIntegrationController()
            torIntegration.registerBootstrapStateChangeListener(
                object : BootstrapStateChangeListener {

                    override fun onBootstrapStageChange(stage: TorConnectStage) {
                        if (stage.isBootstrapped) {
                            torIntegration.unregisterBootstrapStateChangeListener(this)
                            onNewIntentInternal(intent)
                        }
                override fun onTorConnecting() { /* no-op */ }
                override fun onTorStopped() { /* no-op */ }
                override fun onTorStatusUpdate(entry: String?, status: String?, progress: Double?) { /* no-op */ }
            })
                    }

                    override fun onBootstrapProgress(progress: Double, hasWarnings: Boolean) {}
                }
            )

            return
        }
    }
@@ -1269,6 +1306,30 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity {
        historyMetadata: HistoryMetadataKey? = null,
        additionalHeaders: Map<String, String>? = null,
    ) {
        if (!components.torController.isBootstrapped && !searchTermOrURL.startsWith("about:")) {
            Snackbar.make(
                snackBarParentView = binding.root,
                snackbarState = SnackbarState(
                    message = getString(R.string.connection_assist_connect_to_tor_before_opening_links),
                    duration = SnackbarState.Duration.Preset.Long,
                    action = Action(
                        label = getString(R.string.connection_assist_connect_to_tor_before_opening_links_confirmation),
                        onClick = {
                            urlQuickLoadViewModel.urlToLoadAfterConnecting.value = searchTermOrURL
                            urlQuickLoadViewModel.maybeBeginBootstrap()
                            if (navHost.navController.previousBackStackEntry?.destination?.id == R.id.torConnectionAssistFragment) {
                                supportFragmentManager.popBackStack()
                            } else {
                                navHost.navController.navigate(
                                    TorConnectionAssistFragmentDirections.actionConnectToTorBeforeOpeningLinks(),
                                )
                            }
                        },
                    ),
                ),
            ).show()
            return
        }
        openToBrowser(from, customTabSessionId)

        components.useCases.fenixBrowserUseCases.loadUrlOrSearch(
@@ -1312,7 +1373,16 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity {
        //     return
        // }

        navHost.navController.navigate(NavGraphDirections.actionStartupTorbootstrap())
        if (!settings().useHtmlConnectionUi) {
            navController.navigate(NavGraphDirections.actionStartupTorConnectionAssist())
        } else {
            navController.navigate(NavGraphDirections.actionStartupHome())
            openToBrowserAndLoad(
                searchTermOrURL = "about:torconnect",
                newTab = true,
                from = BrowserDirection.FromHome,
            )
        }
    }

    final override fun attachBaseContext(base: Context) {
@@ -1503,4 +1573,28 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity {
        // telemetry purposes.
        private const val PWA_RECENTLY_USED_THRESHOLD = DateUtils.DAY_IN_MILLIS * 30L
    }

    fun restartApplication() {
        startActivity(
            Intent(applicationContext, HomeActivity::class.java).addFlags(
                Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK,
            ),
        )
        shutDown()
    }

    fun shutDown() : Nothing {
        finishAndRemoveTask()
        exitProcess(0)
    }

    override fun onBootstrapStageChange(stage: TorConnectStage)  {
        if (stage.isBootstrapped) {
            if (settings().useHtmlConnectionUi) {
                components.useCases.tabsUseCases.removeAllTabs()
                navHost.navController.navigate(NavGraphDirections.actionStartupHome())
            }
        }
    }
    override fun onBootstrapProgress(progress: Double, hasWarnings: Boolean) = Unit
}
+22 −0
Original line number Diff line number Diff line
@@ -1865,6 +1865,10 @@ abstract class BaseBrowserFragment :
                resumeDownloadDialogState(selectedTab.id, context.components.core.store, context)
                @Suppress("DEPRECATION")
                it.announceForAccessibility(selectedTab.toDisplayTitle())
                if (getCurrentTab()?.content?.url == "about:torconnect") {
                    // FIXME: view is not available anymore.
                    // browserToolbarView.view.visibility = View.GONE
                }
            }
        } else {
            view?.let { view -> initializeUI(view) }
@@ -1890,6 +1894,8 @@ abstract class BaseBrowserFragment :
        BiometricAuthenticationManager.biometricAuthenticationNeededInfo.authenticationStatus =
            AuthenticationStatus.NOT_AUTHENTICATED

        handleBetaHtmlTorConnect()

        getSafeCurrentTab()?.id?.let {
            requireComponents.core.store.dispatch(
                ContentAction.UpdateExpandedToolbarStateAction(
@@ -1900,6 +1906,22 @@ abstract class BaseBrowserFragment :
        }
    }

    private fun handleBetaHtmlTorConnect() {
        val currentTab = getCurrentTab() ?: return
        if (currentTab.content.url == "about:torconnect") {
            if (!requireActivity().settings().useHtmlConnectionUi) {
                requireContext().components.useCases.tabsUseCases.removeTab(currentTab.id)
                (requireActivity() as HomeActivity).navigateToHome(findNavController())
            } else {
                // This just makes it not flash (be visible for a split second) before handleTabSelected() hides it again
                // FIXME: view is not available anymore.
                // browserToolbarView.view.visibility = View.GONE
            }
        } else if (currentTab.content.url == "about:tor") {
            requireContext().components.useCases.tabsUseCases.removeTab(currentTab.id)
        }
    }

    private fun evaluateMessagesForMicrosurvey(components: Components) =
        components.appStore.dispatch(MessagingAction.Evaluate(FenixMessageSurfaceId.MICROSURVEY))

Loading