GitLab is used only for code review, issue tracking and project management. Canonical locations for source code are still https://gitweb.torproject.org/ https://git.torproject.org/ and git-rw.torproject.org.

Commit 7010238f authored by Alex Catarineu's avatar Alex Catarineu Committed by Matthew Finkel
Browse files

Modify UI/UX

Bug 40015: Modify Home menu

Bug 40016: Hide unwanted Settings

Bug 40016: Modify Default toolbar menu

Bug 40016: Add Donate settings button

Bug 40016: Move Allow Screenshots under Advanced

Bug 40016: Don't install WebCompat webext

Bug 40016: Don't onboard Search Suggestions

Bug 40094: Do not use MasterPasswordTipProvider in HomeFragment

Bug 40095: Hide "Sign in to sync" in bookmarks

Bug 40031: Hide Mozilla-specific items on About page

Bug 40032: Set usesCleartextTraffic as false

Bug 40063: Do not sort search engines alphabetically

Bug 34378: Port external helper app prompting

With the corresponding android-components patch, this allows all `startActivity`
that may open external apps to be replaced by `TorUtils.startActivityPrompt`.

Bug 34403: Disable Normal mode by default

Bug 40087: Implement a switch for english locale spoofing

Bug 40144: Hide Download Manager
parent 6a647f16
......@@ -27,7 +27,7 @@
android:roundIcon="@mipmap/ic_launcher"
android:supportsRtl="true"
android:theme="@style/NormalTheme"
android:usesCleartextTraffic="true"
android:usesCleartextTraffic="false"
tools:ignore="UnusedAttribute">
<!--
......
......@@ -19,7 +19,7 @@ object FeatureFlags {
*
* Tracking issue: https://github.com/mozilla-mobile/fenix/issues/13892
*/
val syncedTabsInTabsTray = Config.channel.isNightlyOrDebug
const val syncedTabsInTabsTray = false
/**
* Enables the Nimbus experiments library, especially the settings toggle to opt-out of
......
......@@ -4,6 +4,7 @@
package org.mozilla.fenix
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import android.content.res.Configuration
......@@ -47,6 +48,8 @@ import mozilla.components.concept.engine.EngineSession
import mozilla.components.concept.engine.EngineView
import mozilla.components.concept.storage.BookmarkNode
import mozilla.components.concept.storage.BookmarkNodeType
import mozilla.components.feature.app.links.RedirectDialogFragment
import mozilla.components.feature.app.links.SimpleRedirectDialogFragment
import mozilla.components.feature.contextmenu.DefaultSelectionActionDelegate
import mozilla.components.feature.privatemode.notification.PrivateNotificationFeature
import mozilla.components.feature.search.BrowserStoreSearchAdapter
......@@ -61,6 +64,7 @@ import mozilla.components.support.ktx.kotlin.isUrl
import mozilla.components.support.ktx.kotlin.toNormalizedUrl
import mozilla.components.support.locale.LocaleAwareAppCompatActivity
import mozilla.components.support.utils.SafeIntent
import mozilla.components.support.utils.TorUtils
import mozilla.components.support.utils.toSafeIntent
import mozilla.components.support.webextensions.WebExtensionPopupFeature
import org.mozilla.fenix.GleanMetrics.Metrics
......@@ -163,6 +167,8 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity {
private lateinit var navigationToolbar: Toolbar
private var dialog: RedirectDialogFragment? = null
final override fun onCreate(savedInstanceState: Bundle?) {
components.strictMode.attachListenerToDisablePenaltyDeath(supportFragmentManager)
......@@ -448,6 +454,26 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity {
super.recreate()
}
// Copied from mozac AppLinksFeature.kt
internal fun getOrCreateDialog(): RedirectDialogFragment {
val existingDialog = dialog
if (existingDialog != null) {
return existingDialog
}
SimpleRedirectDialogFragment.newInstance().also {
dialog = it
return it
}
}
private fun isAlreadyADialogCreated(): Boolean {
return findPreviousDialogFragment() != null
}
private fun findPreviousDialogFragment(): RedirectDialogFragment? {
return supportFragmentManager.findFragmentByTag(RedirectDialogFragment.FRAGMENT_TAG) as? RedirectDialogFragment
}
/**
* Handles intents received when the activity is open.
*/
......@@ -459,6 +485,26 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity {
}
open fun handleNewIntent(intent: Intent) {
val startIntent = intent.getParcelableExtra<PendingIntent>(TorUtils.TORBROWSER_START_ACTIVITY_PROMPT)
if (startIntent != null) {
if (startIntent.creatorPackage == applicationContext.packageName) {
val dialog = getOrCreateDialog()
dialog.onConfirmRedirect = {
@Suppress("EmptyCatchBlock")
try {
startIntent.send()
} catch (error: PendingIntent.CanceledException) {
}
}
dialog.onCancelRedirect = {}
if (!isAlreadyADialogCreated()) {
dialog.showNow(supportFragmentManager, RedirectDialogFragment.FRAGMENT_TAG)
}
}
return
}
// Diagnostic breadcrumb for "Display already aquired" crash:
// https://github.com/mozilla-mobile/android-components/issues/7960
breadcrumb(
......@@ -639,11 +685,17 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity {
internal fun getModeFromIntentOrLastKnown(intent: Intent?): BrowsingMode {
intent?.toSafeIntent()?.let {
if (it.hasExtra(PRIVATE_BROWSING_MODE)) {
val startPrivateMode = it.getBooleanExtra(PRIVATE_BROWSING_MODE, false)
val startPrivateMode = settings().shouldDisableNormalMode ||
it.getBooleanExtra(PRIVATE_BROWSING_MODE, settings().openLinksInAPrivateTab)
return BrowsingMode.fromBoolean(isPrivate = startPrivateMode)
}
}
return settings().lastKnownMode
return when {
settings().shouldDisableNormalMode -> BrowsingMode.Private
settings().openLinksInAPrivateTab -> BrowsingMode.Private
else -> settings().lastKnownMode
}
}
/**
......
......@@ -439,7 +439,7 @@ abstract class BaseBrowserFragment : Fragment(), UserInteractionHandler,
shouldForwardToThirdParties = {
PreferenceManager.getDefaultSharedPreferences(context).getBoolean(
context.getPreferenceKey(R.string.pref_key_external_download_manager), false
)
) && false
},
promptsStyling = DownloadsFeature.PromptsStyling(
gravity = Gravity.BOTTOM,
......@@ -951,6 +951,14 @@ abstract class BaseBrowserFragment : Fragment(), UserInteractionHandler,
hideToolbar()
components.core.store.state.findTabOrCustomTabOrSelectedTab(customTabSessionId)?.let {
// If the most-recent session was a tab in Normal mode, and now Normal mode is disabled,
// then load the Private Mode home screen, instead.
if (!it.content.private && requireContext().settings().shouldDisableNormalMode) {
findNavController().nav(
R.id.browserFragment,
BrowserFragmentDirections.actionGlobalHomeFragment()
)
}
updateThemeForSession(it)
}
}
......
......@@ -111,7 +111,8 @@ class Core(
clearColor = ContextCompat.getColor(
context,
R.color.foundation_normal_theme
)
),
spoofEnglish = context.settings().spoofEnglish
)
GeckoEngine(
......@@ -123,7 +124,6 @@ class Core(
trackingProtectionPolicyFactory.createTrackingProtectionPolicy()
)
).also {
WebCompatFeature.install(it)
/**
* There are some issues around localization to be resolved, as well as questions around
......@@ -131,7 +131,8 @@ class Core(
* disabled in Fenix Release builds for now.
* This is consistent with both Fennec and Firefox Desktop.
*/
if (Config.channel.isNightlyOrDebug || Config.channel.isBeta) {
if (false && (Config.channel.isNightlyOrDebug || Config.channel.isBeta)) {
WebCompatFeature.install(it)
WebCompatReporterFeature.install(it, "fenix")
}
}
......
......@@ -183,24 +183,19 @@ class DefaultToolbarMenu(
// Predicates that are called once, during screen init
val shouldShowSaveToCollection = (context.asActivity() as? HomeActivity)
?.browsingModeManager?.mode == BrowsingMode.Normal
val shouldDeleteDataOnQuit = context.components.settings
.shouldDeleteBrowsingDataOnQuit
val syncedTabsInTabsTray = context.components.settings
.syncedTabsInTabsTray
val menuItems = listOfNotNull(
downloadsItem,
historyItem,
// historyItem,
bookmarksItem,
if (syncedTabsInTabsTray) null else syncedTabs,
settings,
if (shouldDeleteDataOnQuit) deleteDataOnQuit else null,
deleteDataOnQuit,
BrowserMenuDivider(),
reportSiteIssuePlaceholder,
findInPage,
addToTopSites,
addToHomescreen.apply { visible = ::canAddToHomescreen },
installToHomescreen.apply { visible = ::canInstall },
// addToHomescreen.apply { visible = ::canAddToHomescreen },
// installToHomescreen.apply { visible = ::canInstall },
if (shouldShowSaveToCollection) saveToCollection else null,
desktopMode,
openInApp.apply { visible = ::shouldShowOpenInApp },
......
......@@ -33,6 +33,7 @@ import androidx.coordinatorlayout.widget.CoordinatorLayout
import androidx.core.content.ContextCompat
import androidx.core.view.children
import androidx.core.view.doOnLayout
import androidx.core.view.isGone
import androidx.core.view.isVisible
import androidx.core.view.updateLayoutParams
import androidx.fragment.app.Fragment
......@@ -98,9 +99,7 @@ import org.mozilla.fenix.components.PrivateShortcutCreateManager
import org.mozilla.fenix.components.StoreProvider
import org.mozilla.fenix.components.TabCollectionStorage
import org.mozilla.fenix.components.metrics.Event
import org.mozilla.fenix.components.tips.FenixTipManager
import org.mozilla.fenix.components.tips.Tip
import org.mozilla.fenix.components.tips.providers.MasterPasswordTipProvider
import org.mozilla.fenix.components.toolbar.FenixTabCounterMenu
import org.mozilla.fenix.components.toolbar.ToolbarPosition
import org.mozilla.fenix.ext.components
......@@ -117,7 +116,6 @@ import org.mozilla.fenix.home.sessioncontrol.viewholders.CollectionViewHolder
import org.mozilla.fenix.home.sessioncontrol.viewholders.topsites.DefaultTopSitesView
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
......@@ -220,17 +218,6 @@ class HomeFragment : Fragment() {
expandedCollections = emptySet(),
mode = currentMode.getCurrentMode(),
topSites = components.core.topSitesStorage.cachedTopSites,
tip = components.strictMode.resetAfter(StrictMode.allowThreadDiskReads()) {
FenixTipManager(
listOf(
MasterPasswordTipProvider(
requireContext(),
::navToSavedLogins,
::dismissTip
)
)
).getTip()
},
showCollectionPlaceholder = components.settings.showCollectionsPlaceholderOnHome
)
)
......@@ -518,6 +505,8 @@ class HomeFragment : Fragment() {
}
}
privateBrowsingButton.isGone = view.context.settings().shouldDisableNormalMode
if (browsingModeManager.mode.isPrivate) {
requireActivity().window.addFlags(FLAG_SECURE)
} else {
......@@ -678,17 +667,6 @@ class HomeFragment : Fragment() {
collections = components.core.tabCollectionStorage.cachedTabCollections,
mode = currentMode.getCurrentMode(),
topSites = components.core.topSitesStorage.cachedTopSites,
tip = components.strictMode.resetAfter(StrictMode.allowThreadDiskReads()) {
FenixTipManager(
listOf(
MasterPasswordTipProvider(
requireContext(),
::navToSavedLogins,
::dismissTip
)
)
).getTip()
},
showCollectionPlaceholder = components.settings.showCollectionsPlaceholderOnHome
)
)
......@@ -942,7 +920,7 @@ class HomeFragment : Fragment() {
HomeMenu.Item.Help -> {
hideOnboardingIfNeeded()
(activity as HomeActivity).openToBrowserAndLoad(
searchTermOrURL = SupportUtils.getSumoURLForTopic(context, HELP),
searchTermOrURL = SupportUtils.getTorHelpPageUrl(),
newTab = true,
from = BrowserDirection.FromHome
)
......
......@@ -121,28 +121,29 @@ class HomeMenu(
// user isn't targeted, then we get still get the same treatment.
// The `let` block is degenerate here, but left here so as to document the form of how experiments
// are implemented here.
val historyIcon = experiments.withExperiment(Experiments.A_A_NIMBUS_VALIDATION) {
when (it) {
ExperimentBranch.A1 -> R.drawable.ic_history
ExperimentBranch.A2 -> R.drawable.ic_history
else -> R.drawable.ic_history
}
}
val historyItem = BrowserMenuImageText(
context.getString(R.string.library_history),
historyIcon,
primaryTextColor
) {
onItemTapped.invoke(Item.History)
}
// val historyIcon = experiments.withExperiment(Experiments.A_A_NIMBUS_VALIDATION) {
// when (it) {
// ExperimentBranch.A1 -> R.drawable.ic_history
// ExperimentBranch.A2 -> R.drawable.ic_history
// else -> R.drawable.ic_history
// }
// }
val addons = BrowserMenuImageText(
context.getString(R.string.browser_menu_add_ons),
R.drawable.ic_addons_extensions,
primaryTextColor
) {
onItemTapped.invoke(Item.AddonsManager)
}
// val historyItem = BrowserMenuImageText(
// context.getString(R.string.library_history),
// historyIcon,
// primaryTextColor
// ) {
// onItemTapped.invoke(Item.History)
// }
// val addons = BrowserMenuImageText(
// context.getString(R.string.browser_menu_add_ons),
// R.drawable.ic_addons_extensions,
// primaryTextColor
// ) {
// onItemTapped.invoke(Item.AddonsManager)
// }
val settingsItem = BrowserMenuImageText(
context.getString(R.string.browser_menu_settings),
......@@ -152,13 +153,13 @@ class HomeMenu(
onItemTapped.invoke(Item.Settings)
}
val syncedTabsItem = BrowserMenuImageText(
context.getString(R.string.library_synced_tabs),
R.drawable.ic_synced_tabs,
primaryTextColor
) {
onItemTapped.invoke(Item.SyncedTabs)
}
// val syncedTabsItem = BrowserMenuImageText(
// context.getString(R.string.library_synced_tabs),
// R.drawable.ic_synced_tabs,
// primaryTextColor
// ) {
// onItemTapped.invoke(Item.SyncedTabs)
// }
val helpItem = BrowserMenuImageText(
context.getString(R.string.browser_menu_help),
......@@ -184,18 +185,18 @@ class HomeMenu(
null
}
val settings = context.components.settings
// val settings = context.components.settings
val menuItems = listOfNotNull(
if (settings.shouldDeleteBrowsingDataOnQuit) quitItem else null,
quitItem,
settingsItem,
BrowserMenuDivider(),
if (settings.syncedTabsInTabsTray) null else syncedTabsItem,
// if (settings.syncedTabsInTabsTray) null else syncedTabsItem,
bookmarksItem,
historyItem,
// historyItem,
downloadsItem,
BrowserMenuDivider(),
addons,
// addons,
BrowserMenuDivider(),
whatsNewItem,
helpItem,
......
......@@ -48,7 +48,6 @@ import org.mozilla.fenix.ext.bookmarkStorage
import org.mozilla.fenix.ext.components
import org.mozilla.fenix.ext.minus
import org.mozilla.fenix.ext.nav
import org.mozilla.fenix.ext.requireComponents
import org.mozilla.fenix.ext.setTextColor
import org.mozilla.fenix.ext.toShortUrl
import org.mozilla.fenix.library.LibraryPageFragment
......@@ -128,16 +127,9 @@ class BookmarkFragment : LibraryPageFragment<BookmarkNode>(), UserInteractionHan
@ExperimentalCoroutinesApi
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val accountManager = requireComponents.backgroundServices.accountManager
consumeFrom(bookmarkStore) {
bookmarkView.update(it)
// Only display the sign-in prompt if we're inside of the virtual "Desktop Bookmarks" node.
// Don't want to pester user too much with it, and if there are lots of bookmarks present,
// it'll just get visually lost. Inside of the "Desktop Bookmarks" node, it'll nicely stand-out,
// since there are always only three other items in there. It's also the right place contextually.
bookmarkView.view.bookmark_folders_sign_in.isVisible =
it.tree?.guid == BookmarkRoot.Root.id && accountManager.authenticatedAccount() == null
bookmarkView.view.bookmark_folders_sign_in.isVisible = false
}
}
......
......@@ -305,6 +305,13 @@ class SearchDialogFragment : AppCompatDialogFragment(), UserInteractionHandler {
inflated.title.text =
getString(R.string.search_suggestions_onboarding_title)
// Hide Search Suggestions prompt and disable
inflated.visibility = View.GONE
requireContext().settings().also {
it.shouldShowSearchSuggestionsInPrivate = false
it.showSearchSuggestionsInPrivateOnboardingFinished = true
}
}
view.search_suggestions_hint.setOnInflateListener((stubListener))
......
......@@ -190,6 +190,10 @@ class SettingsFragment : PreferenceFragmentCompat() {
requirePreference<Preference>(R.string.pref_key_tabs)
tabSettingsPreference.summary = context?.settings()?.getTabTimeoutString()
// Hide "Delete browsing data on quit" when in Private Browsing-only mode
deleteBrowsingDataPreference.isVisible =
!deleteBrowsingDataPreference.context.settings().shouldDisableNormalMode
setupPreferences()
if (shouldUpdateAccountUIState) {
......@@ -246,10 +250,7 @@ class SettingsFragment : PreferenceFragmentCompat() {
}
resources.getString(R.string.pref_key_help) -> {
(activity as HomeActivity).openToBrowserAndLoad(
searchTermOrURL = SupportUtils.getSumoURLForTopic(
requireContext(),
SupportUtils.SumoTopic.HELP
),
searchTermOrURL = SupportUtils.getTorHelpPageUrl(),
newTab = true,
from = BrowserDirection.FromSettings
)
......@@ -275,6 +276,15 @@ class SettingsFragment : PreferenceFragmentCompat() {
resources.getString(R.string.pref_key_about) -> {
SettingsFragmentDirections.actionSettingsFragmentToAboutFragment()
}
resources.getString(R.string.pref_key_donate) -> {
(activity as HomeActivity).openToBrowserAndLoad(
searchTermOrURL = SupportUtils.DONATE_URL,
newTab = true,
from = BrowserDirection.FromSettings
)
null
}
resources.getString(R.string.pref_key_account) -> {
SettingsFragmentDirections.actionSettingsFragmentToAccountSettingsFragment()
}
......@@ -374,6 +384,10 @@ class SettingsFragment : PreferenceFragmentCompat() {
setTint(requireContext().getColorFromAttr(R.attr.primaryText))
}
requirePreference<Preference>(R.string.pref_key_allow_screenshots_in_private_mode).apply {
onPreferenceChangeListener = SharedPreferenceUpdater()
}
if (!Config.channel.isReleased) {
preferenceLeakCanary?.setOnPreferenceChangeListener { _, newValue ->
val isEnabled = newValue == true
......
......@@ -18,6 +18,7 @@ import java.io.UnsupportedEncodingException
import java.net.URLEncoder
import java.util.Locale
@Suppress("TooManyFunctions")
object SupportUtils {
const val RATE_APP_URL = "market://details?id=" + BuildConfig.APPLICATION_ID
const val POCKET_TRENDING_URL = "https://getpocket.com/fenix-top-articles"
......@@ -34,6 +35,8 @@ object SupportUtils {
const val GOOGLE_US_URL = "https://www.google.com/webhp?client=firefox-b-1-m&channel=ts"
const val GOOGLE_XX_URL = "https://www.google.com/webhp?client=firefox-b-m&channel=ts"
const val DONATE_URL = "https://donate.torproject.org/"
const val TB_MANUAL_URL = "https://tb-manual.torproject.org/mobile-tor"
const val TOR_RELEASES = "https://www.torproject.org/releases/"
enum class SumoTopic(internal val topicStr: String) {
FENIX_MOVING("sync-delist"),
......@@ -85,13 +88,23 @@ object SupportUtils {
return "https://support.mozilla.org/kb/access-mozilla-services-firefox-account"
}
fun getTorHelpPageUrl(): String {
return TB_MANUAL_URL
}
fun getTorReleasePageUrl(): String {
return TOR_RELEASES
}
fun getMozillaPageUrl(page: MozillaPage, locale: Locale = Locale.getDefault()): String {
val path = page.path
val langTag = getLanguageTag(locale)
return "https://www.mozilla.org/$langTag/$path"
}
fun getWhatsNewUrl(context: Context) = getSumoURLForTopic(context, SumoTopic.WHATS_NEW)
fun getWhatsNewUrl(@Suppress("UNUSED_PARAMETER") context: Context): String {
return getTorReleasePageUrl()
}
fun createCustomTabIntent(context: Context, url: String): Intent = CustomTabsIntent.Builder()
.setInstantAppsEnabled(false)
......
......@@ -136,25 +136,25 @@ class AboutFragment : Fragment(), AboutPageListener {
AboutPageItem(
AboutItem.ExternalLink(
SUPPORT,
SupportUtils.getSumoURLForTopic(context, SupportUtils.SumoTopic.HELP)
SupportUtils.getTorHelpPageUrl()
), getString(R.string.about_support)
),
AboutPageItem(
AboutItem.Crashes,
getString(R.string.about_crashes)
),
AboutPageItem(
AboutItem.ExternalLink(
PRIVACY_NOTICE,
SupportUtils.getMozillaPageUrl(SupportUtils.MozillaPage.PRIVATE_NOTICE)
), getString(R.string.about_privacy_notice)
),
AboutPageItem(
AboutItem.ExternalLink(
RIGHTS,
SupportUtils.getSumoURLForTopic(context, SupportUtils.SumoTopic.YOUR_RIGHTS)
), getString(R.string.about_know_your_rights)
),
// AboutPageItem(
// AboutItem.Crashes,
// getString(R.string.about_crashes)
// ),
// AboutPageItem(
// AboutItem.ExternalLink(
// PRIVACY_NOTICE,
// SupportUtils.getMozillaPageUrl(SupportUtils.MozillaPage.PRIVATE_NOTICE)
// ), getString(R.string.about_privacy_notice)
// ),
// AboutPageItem(
// AboutItem.ExternalLink(
// RIGHTS,
// SupportUtils.getSumoURLForTopic(context, SupportUtils.SumoTopic.YOUR_RIGHTS)
// ), getString(R.string.about_know_your_rights)
// ),
AboutPageItem(
AboutItem.ExternalLink(LICENSING_INFO, ABOUT_LICENSE_URL),
getString(R.string.about_licensing_information)
......
......@@ -86,7 +86,7 @@ class AccountUiView(
// Signed-out.
} else {
preferenceSignIn.isVisible = true
preferenceSignIn.isVisible = false
preferenceFirefoxAccount.isVisible = false
preferenceFirefoxAccountAuthError.isVisible = false
accountPreferenceCategory.isVisible = false
......
......@@ -7,6 +7,7 @@ package org.mozilla.fenix.settings.advanced
import android.app.Activity
import android.content.Context
import mozilla.components.support.locale.LocaleManager
import org.mozilla.fenix.ext.components
import java.util.Locale
interface LocaleSettingsController {
......@@ -55,5 +56,9 @@ class DefaultLocaleSettingsController(
config.setLocale(locale)
config.setLayoutDirection(locale)
resources.updateConfiguration(config, resources.displayMetrics)
// A slightly hacky way of triggering a `runtime.settings.locales` update,
// so that the locales are updated in GeckoView.
val spoofEnglish = context.components.core.engine.settings.spoofEnglish
context.components.core.engine.settings.spoofEnglish = spoofEnglish
}
}