1
0
Fork 0

Copione merged onto master
continuous-integration/drone/push Build is failing Details

master
blallo 2020-07-22 00:00:54 +02:00
commit d518f35c3e
106 changed files with 1592 additions and 966 deletions

View File

@ -67,7 +67,9 @@ Before you attempt to make a contribution please read the [Community Participati
## Build Instructions
Note: Both Android SDK and NDK are required.
Pre-requisites:
* Android SDK
* To run command line tools, you'll need to configure Java: see [our how-to guide](https://github.com/mozilla-mobile/shared-docs/blob/master/android/configure_java.md).
1. Clone or Download the repository:
@ -131,6 +133,9 @@ recommend you use our provided pre-push hook in `config/pre-push-recommended.sh`
Using this hook will guarantee your hook gets updated as the repository changes.
This hook tries to run as much as possible without taking too much time.
Before you can run the hook, you'll need to configure Java properly because it relies on command line tools: see
[our how-to guide](https://github.com/mozilla-mobile/shared-docs/blob/master/android/configure_java.md).
To add it on Mac/Linux, run this command from the project root:
```sh
ln -s ../../config/pre-push-recommended.sh .git/hooks/pre-push

View File

@ -72,20 +72,6 @@ android {
"sharedUserId": "org.mozilla.fenix.performancetest.sharedID"
]
}
fenixNightly releaseTemplate >> {
applicationIdSuffix ".fenix.nightly"
buildConfigField "boolean", "USE_RELEASE_VERSIONING", "true"
def deepLinkSchemeValue = "fenix-nightly"
buildConfigField "String", "DEEP_LINK_SCHEME", "\"$deepLinkSchemeValue\""
manifestPlaceholders = ["deepLinkScheme": deepLinkSchemeValue]
}
fenixBeta releaseTemplate >> {
applicationIdSuffix ".fenix.beta"
buildConfigField "boolean", "USE_RELEASE_VERSIONING", "true"
def deepLinkSchemeValue = "fenix-beta"
buildConfigField "String", "DEEP_LINK_SCHEME", "\"$deepLinkSchemeValue\""
manifestPlaceholders = ["deepLinkScheme": deepLinkSchemeValue]
}
fenixProduction releaseTemplate >> {
applicationIdSuffix ".fenix"
buildConfigField "boolean", "USE_RELEASE_VERSIONING", "true"
@ -127,23 +113,6 @@ android {
"deepLinkScheme": deepLinkSchemeValue
]
}
fennecNightly releaseTemplate >> {
buildConfigField "boolean", "USE_RELEASE_VERSIONING", "true"
applicationIdSuffix ".fennec_aurora"
def deepLinkSchemeValue = "fenix-nightly"
buildConfigField "String", "DEEP_LINK_SCHEME", "\"$deepLinkSchemeValue\""
manifestPlaceholders = [
// This release type is meant to replace Firefox (Release channel) and therefore needs to inherit
// its sharedUserId for all eternity. See:
// https://searchfox.org/mozilla-central/search?q=moz_android_shared_id&case=false&regexp=false&path=
// Shipping an app update without sharedUserId can have
// fatal consequences. For example see:
// - https://issuetracker.google.com/issues/36924841
// - https://issuetracker.google.com/issues/36905922
"sharedUserId": "org.mozilla.fennec.sharedID",
"deepLinkScheme": deepLinkSchemeValue
]
}
}
variantFilter { // There's a "release" build type that exists by default that we don't use (it's replaced by "nightly" and "beta")
@ -157,19 +126,12 @@ android {
// |--------------------|---------------|-----------|
// | debug | | | Both variants for testing and development.
// | forPerformanceTest | | | Both variants unless the perf team only cares about Nightly (TBD).
// | fenixNightly | | | Built with both, but only the "geckoNightly" one is published to Google Play
// | fenixBeta | | | Fenix Beta ships with GV Beta
// | fenixProduction | | | Fenix Production ships with GV Beta
// | fenixProduction | | | Fenix Production (to be renamed `Nightly`) ships with GV Nightly
// | fennecProduction | | | Fenix build to replace production Firefox builds
// | fennecBeta | | | Fenix build to replace beta Firefox builds
// | fennecNightly | | | Fenix build to replace Nightly Firefox builds
def flavors = flavors*.name.toString().toLowerCase()
if (buildType.name == 'fenixBeta' && flavors.contains("geckonightly")) {
setIgnore true
}
if (buildType.name == 'fenixProduction' && flavors.contains("geckobeta")) {
setIgnore true
}
@ -177,10 +139,6 @@ android {
if ((buildType.name == 'fennecProduction' || buildType.name == 'fennecBeta') && flavors.contains("geckonightly")) {
setIgnore true
}
if (buildType.name == 'fennecNightly' && flavors.contains("geckobeta")) {
setIgnore true
}
}
aaptOptions {
@ -207,10 +165,6 @@ android {
androidTest {
resources.srcDirs += ['src/androidTest/resources']
}
fennecNightly {
java.srcDirs = ['src/migration/java']
manifest.srcFile "src/migration/AndroidManifest.xml"
}
fennecBeta {
java.srcDirs = ['src/migration/java']
manifest.srcFile "src/migration/AndroidManifest.xml"

View File

@ -330,7 +330,10 @@ class BookmarksTest {
createBookmark(defaultWebPage.url)
}.openTabDrawer {
closeTab()
}.openHomeScreen { }.openThreeDotMenu {
}
homeScreen {
}.openThreeDotMenu {
}.openBookmarks {
bookmarksListIdlingResource =
RecyclerViewIdlingResource(activityTestRule.activity.findViewById(R.id.bookmark_list))

View File

@ -212,7 +212,9 @@ class HistoryTest {
mDevice.waitForIdle()
}.openTabDrawer {
closeTab()
}.openHomeScreen { }.openThreeDotMenu {
}
homeScreen { }.openThreeDotMenu {
}.openHistory {
longTapSelectItem(firstWebPage.url)
openActionBarOverflowOrOptionsMenu(activityTestRule.activity)

View File

@ -8,6 +8,7 @@ import androidx.test.uiautomator.UiSelector
import okhttp3.mockwebserver.MockWebServer
import org.junit.After
import org.junit.Before
import org.junit.Ignore
import org.junit.Rule
import org.junit.Test
import org.mozilla.fenix.helpers.AndroidAssetDispatcher
@ -135,6 +136,7 @@ class MediaNotificationTest {
}
}
@Ignore("Flaky test, temp disabled: https://github.com/mozilla-mobile/fenix/issues/12645")
@Test
fun mediaSystemNotificationInPrivateModeTest() {
val audioTestPage = TestAssetHelper.getAudioPageAsset(mockWebServer)
@ -158,7 +160,7 @@ class MediaNotificationTest {
verifyMediaIsPaused()
}.openTabDrawer {
closeTab()
}.openHomeScreen { }
}
mDevice.openNotification()

View File

@ -58,8 +58,10 @@ class NavigationToolbarTest {
mDevice.waitForIdle()
}.openNavigationToolbar {
}.enterURLAndEnterToBrowser(nextWebPage.url) {
mDevice.waitForIdle()
verifyUrl(nextWebPage.url.toString())
mDevice.pressBack()
mDevice.waitForIdle()
verifyUrl(defaultWebPage.url.toString())
}
}

View File

@ -215,4 +215,39 @@ class SmokeTest {
}
}
}
@Test
fun verifyETPToolbarShieldIconIsNotDisplayedIfETPIsOFFGloballyTest() {
val defaultWebPage = TestAssetHelper.getGenericAsset(mockWebServer, 1)
homeScreen {
}.openThreeDotMenu {
}.openSettings {
}.openEnhancedTrackingProtectionSubMenu {
clickEnhancedTrackingProtectionDefaults()
verifyEnhancedTrackingProtectionOptionsGrayedOut()
}.goBackToHomeScreen {
navigationToolbar {
}.enterURLAndEnterToBrowser(defaultWebPage.url) {
verifyEnhancedTrackingProtectionPanelNotVisible()
}.openThreeDotMenu {
}.clickAddOnsReportSiteIssue {
verifyUrl("webcompat.com/issues/new")
verifyTabCounter("2")
}.openTabDrawer {
}.openHomeScreen {
}.openThreeDotMenu {
}.openSettings {
}.openEnhancedTrackingProtectionSubMenu {
clickEnhancedTrackingProtectionDefaults()
}.goBackToHomeScreen {
}.openTabDrawer {
}.openTab(defaultWebPage.title) {
clickEnhancedTrackingProtectionPanel()
verifyEnhancedTrackingProtectionSwitch()
// Turning off TP Switch results in adding the WebPage to exception list
clickEnhancedTrackingProtectionSwitchOffOn()
}
}
}
}

View File

@ -46,7 +46,7 @@ class StrictEnhancedTrackingProtectionTest {
start()
}
InstrumentationRegistry.getInstrumentation().context.settings().setStrictETP()
activityTestRule.activity.settings().setStrictETP()
// Reset on-boarding notification for each test
TestHelper.setPreference(

View File

@ -9,6 +9,7 @@ import androidx.test.uiautomator.UiDevice
import okhttp3.mockwebserver.MockWebServer
import org.junit.After
import org.junit.Before
import org.junit.BeforeClass
import org.junit.Ignore
import org.junit.Rule
import org.junit.Test
@ -53,6 +54,16 @@ class TabbedBrowsingTest {
}
}
// changing the device preference for Touch and Hold delay, to avoid long-clicks instead of a single-click
companion object {
@BeforeClass
@JvmStatic
fun setDevicePreference() {
val mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
mDevice.executeShellCommand("settings put secure long_press_timeout 3000")
}
}
@After
fun tearDown() {
mockWebServer.shutdown()
@ -118,7 +129,6 @@ class TabbedBrowsingTest {
verifySaveCollection()
}.closeAllTabs {
verifyNoTabsOpened()
}.openHomeScreen {
}
// Repeat for Private Tabs
@ -134,8 +144,6 @@ class TabbedBrowsingTest {
verifyCloseAllTabsButton()
}.closeAllTabs {
verifyNoTabsOpened()
}.openHomeScreen {
verifyPrivateSessionMessage()
}
}

View File

@ -23,6 +23,7 @@ import androidx.test.espresso.matcher.ViewMatchers.isCompletelyDisplayed
import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
import androidx.test.espresso.matcher.ViewMatchers.withEffectiveVisibility
import androidx.test.espresso.matcher.ViewMatchers.withId
import androidx.test.espresso.matcher.ViewMatchers.withResourceName
import androidx.test.espresso.matcher.ViewMatchers.withText
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.By
@ -144,6 +145,8 @@ class BrowserRobot {
fun verifyEnhancedTrackingProtectionSwitch() = assertEnhancedTrackingProtectionSwitch()
fun clickEnhancedTrackingProtectionSwitchOffOn() = onView(withResourceName("switch_widget")).click()
fun verifyProtectionSettingsButton() = assertProtectionSettingsButton()
fun verifyEnhancedTrackingOptions() {
@ -188,6 +191,8 @@ class BrowserRobot {
fun clickEnhancedTrackingProtectionPanel() = enhancedTrackingProtectionPanel().click()
fun verifyEnhancedTrackingProtectionPanelNotVisible() = assertEnhancedTrackingProtectionPanelNotVisible()
fun clickContextOpenLinkInNewTab() {
val mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
mDevice.waitNotNull(
@ -420,6 +425,11 @@ private fun assertNavURLBar() = navURLBar()
fun enhancedTrackingProtectionPanel() = onView(withId(R.id.mozac_browser_toolbar_tracking_protection_indicator))
private fun assertEnhancedTrackingProtectionPanelNotVisible() {
enhancedTrackingProtectionPanel()
.check(matches(withEffectiveVisibility(Visibility.GONE)))
}
private fun assertEnhancedTrackingProtectionSwitch() {
withText(R.id.trackingProtectionSwitch)
.matches(withEffectiveVisibility(Visibility.VISIBLE))

View File

@ -63,6 +63,7 @@ class HomeScreenRobot {
fun verifyHomeToolbar() = assertHomeToolbar()
fun verifyHomeComponent() = assertHomeComponent()
fun verifyDefaultSearchEngine(searchEngine: String) = verifySearchEngineIcon(searchEngine)
fun verifyNoTabsOpened() = assertNoTabsOpened()
// First Run elements
fun verifyWelcomeHeader() = assertWelcomeHeader()
@ -529,6 +530,8 @@ private fun assertHomeComponent() =
onView(ViewMatchers.withResourceName("sessionControlRecyclerView"))
.check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
private fun assertNoTabsOpened() = onView(withId(R.id.counter_text)).check(matches(withText("0")))
private fun threeDotButton() = onView(allOf(withId(R.id.menuButton)))
private fun verifySearchEngineIcon(searchEngineIcon: Bitmap, searchEngineName: String) {

View File

@ -141,6 +141,7 @@ class SettingsRobot {
}
fun openEnhancedTrackingProtectionSubMenu(interact: SettingsSubMenuEnhancedTrackingProtectionRobot.() -> Unit): SettingsSubMenuEnhancedTrackingProtectionRobot.Transition {
scrollToElementByText("Enhanced Tracking Protection")
fun enhancedTrackingProtectionButton() =
onView(withText("Enhanced Tracking Protection"))
enhancedTrackingProtectionButton().click()

View File

@ -7,6 +7,7 @@ package org.mozilla.fenix.ui.robots
import androidx.preference.R
import androidx.recyclerview.widget.RecyclerView
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.Espresso.pressBack
import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.contrib.RecyclerViewActions
import androidx.test.espresso.matcher.ViewMatchers.hasDescendant
@ -23,9 +24,11 @@ import androidx.test.espresso.matcher.ViewMatchers.withText
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.UiDevice
import org.hamcrest.CoreMatchers.allOf
import org.hamcrest.CoreMatchers.not
import org.mozilla.fenix.helpers.assertIsChecked
import org.mozilla.fenix.helpers.click
import org.mozilla.fenix.helpers.isChecked
import org.mozilla.fenix.helpers.isEnabled
/**
* Implementation of Robot Pattern for the settings Enhanced Tracking Protection sub menu.
@ -44,8 +47,12 @@ class SettingsSubMenuEnhancedTrackingProtectionRobot {
fun verifyEnhancedTrackingProtectionOptions() = assertEnhancedTrackingProtectionOptions()
fun verifyEnhancedTrackingProtectionOptionsGrayedOut() = assertEnhancedTrackingProtectionOptionsGrayedOut()
fun verifyEnhancedTrackingProtectionDefaults() = assertEnhancedTrackingProtectionDefaults()
fun clickEnhancedTrackingProtectionDefaults() = onView(withResourceName("switch_widget")).click()
fun verifyRadioButtonDefaults() = assertRadioButtonDefaults()
fun verifyEnhancedTrackingProtectionProtectionSubMenuItems() {
@ -61,6 +68,16 @@ class SettingsSubMenuEnhancedTrackingProtectionRobot {
class Transition {
val mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())!!
fun goBackToHomeScreen(interact: HomeScreenRobot.() -> Unit): HomeScreenRobot.Transition {
// To settings
goBackButton().click()
// To HomeScreen
pressBack()
HomeScreenRobot().interact()
return HomeScreenRobot.Transition()
}
fun goBack(interact: SettingsRobot.() -> Unit): SettingsRobot.Transition {
goBackButton().click()
@ -141,6 +158,31 @@ private fun assertEnhancedTrackingProtectionOptions() {
.check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
}
private fun assertEnhancedTrackingProtectionOptionsGrayedOut() {
onView(withText("Standard (default)"))
.check(matches(not(isEnabled(true))))
val stdText = "Blocks fewer trackers. Pages will load normally."
onView(withText(stdText))
.check(matches(not(isEnabled(true))))
onView(withText("Strict"))
.check(matches(not(isEnabled(true))))
val strictText =
"Blocks more trackers, ads, and popups. Pages load faster, but some functionality might not work."
onView(withText(strictText))
.check(matches(not(isEnabled(true))))
onView(withText("Custom"))
.check(matches(not(isEnabled(true))))
val customText =
"Choose which trackers and scripts to block."
onView(withText(customText))
.check(matches(not(isEnabled(true))))
}
private fun assertEnhancedTrackingProtectionDefaults() {
onView(withResourceName("switch_widget")).check(
matches(

View File

@ -253,12 +253,11 @@ class ThreeDotMenuMainRobot {
return BrowserRobot.Transition()
}
fun closeAllTabs(interact: TabDrawerRobot.() -> Unit): TabDrawerRobot.Transition {
// mDevice.waitNotNull(Until.findObject(By.text("Close all tabs")), waitingTime)
fun closeAllTabs(interact: HomeScreenRobot.() -> Unit): HomeScreenRobot.Transition {
closeAllTabsButton().click()
TabDrawerRobot().interact()
return TabDrawerRobot.Transition()
HomeScreenRobot().interact()
return HomeScreenRobot.Transition()
}
fun openFindInPage(interact: FindInPageRobot.() -> Unit): FindInPageRobot.Transition {

View File

@ -10,7 +10,7 @@ import mozilla.components.concept.storage.LoginsStorage
import mozilla.components.lib.crash.handler.CrashHandlerService
import mozilla.components.service.sync.logins.GeckoLoginStorageDelegate
import org.mozilla.fenix.Config
import org.mozilla.fenix.utils.Settings
import org.mozilla.fenix.ext.components
import org.mozilla.geckoview.GeckoRuntime
import org.mozilla.geckoview.GeckoRuntimeSettings
@ -48,9 +48,10 @@ object GeckoProvider {
.debugLogging(Config.channel.isDebug)
.build()
if (!Settings.getInstance(context).shouldUseAutoSize) {
val settings = context.components.settings
if (!settings.shouldUseAutoSize) {
runtimeSettings.automaticFontSizeAdjustment = false
val fontSize = Settings.getInstance(context).fontSizeFactor
val fontSize = settings.fontSizeFactor
runtimeSettings.fontSizeFactor = fontSize
}

View File

@ -10,7 +10,7 @@ import mozilla.components.concept.storage.LoginsStorage
import mozilla.components.lib.crash.handler.CrashHandlerService
import mozilla.components.service.sync.logins.GeckoLoginStorageDelegate
import org.mozilla.fenix.Config
import org.mozilla.fenix.utils.Settings
import org.mozilla.fenix.ext.components
import org.mozilla.geckoview.GeckoRuntime
import org.mozilla.geckoview.GeckoRuntimeSettings
@ -48,9 +48,10 @@ object GeckoProvider {
.aboutConfigEnabled(true)
.build()
if (!Settings.getInstance(context).shouldUseAutoSize) {
val settings = context.components.settings
if (!settings.shouldUseAutoSize) {
runtimeSettings.automaticFontSizeAdjustment = false
val fontSize = Settings.getInstance(context).fontSizeFactor
val fontSize = settings.fontSizeFactor
runtimeSettings.fontSizeFactor = fontSize
}

View File

@ -7,13 +7,10 @@ package org.mozilla.fenix
enum class ReleaseChannel {
FenixDebug,
FenixNightly,
FenixBeta,
FenixProduction,
FennecProduction,
FennecBeta,
FennecNightly;
FennecBeta;
val isReleased: Boolean
get() = when (this) {
@ -33,8 +30,6 @@ enum class ReleaseChannel {
val isReleaseOrBeta: Boolean
get() = when (this) {
FenixProduction -> true
FenixBeta -> true
FennecProduction -> true
FennecBeta -> true
else -> false
@ -43,14 +38,11 @@ enum class ReleaseChannel {
val isBeta: Boolean
get() = when (this) {
FennecBeta -> true
FenixBeta -> true
else -> false
}
val isNightlyOrDebug: Boolean
get() = when (this) {
FenixNightly -> true
FennecNightly -> true
FenixDebug -> true
FenixProduction -> true
else -> false
@ -66,12 +58,9 @@ enum class ReleaseChannel {
object Config {
val channel = when (BuildConfig.BUILD_TYPE) {
"fenixProduction" -> ReleaseChannel.FenixProduction
"fenixBeta" -> ReleaseChannel.FenixBeta
"fenixNightly" -> ReleaseChannel.FenixNightly
"debug" -> ReleaseChannel.FenixDebug
"fennecProduction" -> ReleaseChannel.FennecProduction
"fennecBeta" -> ReleaseChannel.FennecBeta
"fennecNightly" -> ReleaseChannel.FennecNightly
// Builds for local performance analysis, recording benchmarks, automation, etc.
// This should be treated like a released channel because we want to test
@ -86,7 +75,6 @@ object Config {
}
private val fennecChannels: List<ReleaseChannel> = listOf(
ReleaseChannel.FennecNightly,
ReleaseChannel.FennecBeta,
ReleaseChannel.FennecProduction
)

View File

@ -47,7 +47,6 @@ 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.mozilla.fenix.utils.Settings
/**
*The main application class for Fenix. Records data to measure initialization performance.
@ -355,8 +354,7 @@ open class FenixApplication : LocaleAwareApplication() {
_, engineSession, url ->
val shouldCreatePrivateSession =
components.core.sessionManager.selectedSession?.private
?: Settings.instance?.openLinksInAPrivateTab
?: false
?: components.settings.openLinksInAPrivateTab
val session = Session(url, shouldCreatePrivateSession)
components.core.sessionManager.add(session, true, engineSession)

View File

@ -26,7 +26,6 @@ import androidx.navigation.fragment.NavHostFragment
import androidx.navigation.ui.AppBarConfiguration
import androidx.navigation.ui.NavigationUI
import androidx.recyclerview.widget.LinearLayoutManager
import com.google.android.gms.tasks.Tasks.call
import kotlinx.android.synthetic.main.activity_home.*
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
@ -65,7 +64,6 @@ import org.mozilla.fenix.browser.browsingmode.BrowsingModeManager
import org.mozilla.fenix.browser.browsingmode.DefaultBrowsingModeManager
import org.mozilla.fenix.components.metrics.BreadcrumbsRecorder
import org.mozilla.fenix.components.metrics.Event
import org.mozilla.fenix.trackingprotectionexceptions.TrackingProtectionExceptionsFragmentDirections
import org.mozilla.fenix.ext.alreadyOnDestination
import org.mozilla.fenix.ext.components
import org.mozilla.fenix.ext.nav
@ -87,8 +85,8 @@ import org.mozilla.fenix.session.NotificationSessionObserver
import org.mozilla.fenix.settings.SettingsFragmentDirections
import org.mozilla.fenix.settings.TrackingProtectionFragmentDirections
import org.mozilla.fenix.settings.about.AboutFragmentDirections
import org.mozilla.fenix.settings.logins.fragment.SavedLoginsAuthFragmentDirections
import org.mozilla.fenix.settings.logins.fragment.LoginDetailFragmentDirections
import org.mozilla.fenix.settings.logins.fragment.SavedLoginsAuthFragmentDirections
import org.mozilla.fenix.settings.search.AddSearchEngineFragmentDirections
import org.mozilla.fenix.settings.search.EditCustomSearchEngineFragmentDirections
import org.mozilla.fenix.share.AddNewDeviceFragmentDirections
@ -97,6 +95,7 @@ import org.mozilla.fenix.tabtray.FenixTabsAdapter
import org.mozilla.fenix.tabtray.TabTrayDialogFragment
import org.mozilla.fenix.theme.DefaultThemeManager
import org.mozilla.fenix.theme.ThemeManager
import org.mozilla.fenix.trackingprotectionexceptions.TrackingProtectionExceptionsFragmentDirections
import org.mozilla.fenix.utils.BrowsersCache
import org.mozilla.fenix.utils.RunWhenReadyQueue
@ -575,7 +574,7 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity {
}
protected open fun createBrowsingModeManager(initialMode: BrowsingMode): BrowsingModeManager {
return DefaultBrowsingModeManager(initialMode) { newMode ->
return DefaultBrowsingModeManager(initialMode, components.settings) { newMode ->
themeManager.currentTheme = newMode
}
}

View File

@ -25,7 +25,6 @@ import mozilla.components.feature.addons.ui.translatedName
import org.mozilla.fenix.R
import org.mozilla.fenix.ext.components
import org.mozilla.fenix.ext.showToolbar
import org.mozilla.fenix.utils.Settings
/**
* An activity to show the details of a installed add-on.
@ -191,8 +190,7 @@ class InstalledAddonDetailsFragment : Fragment() {
val components = it.context.components
val shouldCreatePrivateSession =
components.core.store.state.selectedTab?.content?.private
?: Settings.instance?.openLinksInAPrivateTab
?: false
?: components.settings.openLinksInAPrivateTab
if (shouldCreatePrivateSession) {
components.useCases.tabsUseCases.addPrivateTab(settingUrl)

View File

@ -150,7 +150,8 @@ class BrowserFragment : BaseBrowserFragment(), UserInteractionHandler {
val toolbarSessionObserver = TrackingProtectionOverlay(
context = context,
settings = settings
settings = settings,
metrics = context.components.analytics.metrics
) {
browserToolbarView.view
}

View File

@ -36,6 +36,7 @@ interface BrowsingModeManager {
*/
class DefaultBrowsingModeManager(
private var _mode: BrowsingMode,
private val settings: Settings,
private val modeDidChange: (BrowsingMode) -> Unit
) : BrowsingModeManager {
@ -44,6 +45,6 @@ class DefaultBrowsingModeManager(
set(value) {
_mode = value
modeDidChange(value)
Settings.instance?.lastKnownMode = value
settings.lastKnownMode = value
}
}

View File

@ -20,8 +20,7 @@ import kotlinx.android.synthetic.main.tracking_protection_onboarding_popup.view.
import org.mozilla.fenix.R
import org.mozilla.fenix.components.SearchWidgetCreator
import org.mozilla.fenix.components.metrics.Event
import org.mozilla.fenix.ext.components
import org.mozilla.fenix.ext.settings
import org.mozilla.fenix.components.metrics.MetricController
import org.mozilla.fenix.utils.Settings
/**
@ -29,27 +28,29 @@ import org.mozilla.fenix.utils.Settings
*/
class SearchWidgetCFR(
private val context: Context,
private val settings: Settings,
private val metrics: MetricController,
private val getToolbar: () -> View
) {
fun displayIfNecessary() {
if (!context.settings().isInSearchWidgetExperiment ||
!context.settings().shouldDisplaySearchWidgetCFR() ||
isShown
) { return }
isShown = true
showSearchWidgetCFR()
if (settings.isInSearchWidgetExperiment &&
settings.shouldDisplaySearchWidgetCFR() &&
!isShown
) {
isShown = true
showSearchWidgetCFR()
}
}
@Suppress("MagicNumber", "InflateParams")
@Suppress("InflateParams")
private fun showSearchWidgetCFR() {
context.settings().incrementSearchWidgetCFRDisplayed()
settings.incrementSearchWidgetCFRDisplayed()
val searchWidgetCFRDialog = Dialog(context)
val layout = LayoutInflater.from(context)
.inflate(R.layout.search_widget_cfr, null)
val isBottomToolbar = Settings.getInstance(context).shouldUseBottomToolbar
val isBottomToolbar = settings.shouldUseBottomToolbar
layout.drop_down_triangle.isGone = isBottomToolbar
layout.pop_up_triangle.isVisible = isBottomToolbar
@ -63,16 +64,16 @@ class SearchWidgetCFR(
}
layout.cfr_neg_button.setOnClickListener {
context.components.analytics.metrics.track(Event.SearchWidgetCFRNotNowPressed)
metrics.track(Event.SearchWidgetCFRNotNowPressed)
searchWidgetCFRDialog.dismiss()
context.settings().manuallyDismissSearchWidgetCFR()
settings.manuallyDismissSearchWidgetCFR()
}
layout.cfr_pos_button.setOnClickListener {
context.components.analytics.metrics.track(Event.SearchWidgetCFRAddWidgetPressed)
metrics.track(Event.SearchWidgetCFRAddWidgetPressed)
SearchWidgetCreator.createSearchWidget(context)
searchWidgetCFRDialog.dismiss()
context.settings().manuallyDismissSearchWidgetCFR()
settings.manuallyDismissSearchWidgetCFR()
}
searchWidgetCFRDialog.apply {
@ -90,16 +91,16 @@ class SearchWidgetCFR(
searchWidgetCFRDialog.setOnCancelListener {
isShown = false
context.components.analytics.metrics.track(Event.SearchWidgetCFRCanceled)
metrics.track(Event.SearchWidgetCFRCanceled)
}
searchWidgetCFRDialog.setOnDismissListener {
isShown = false
context.settings().incrementSearchWidgetCFRDismissed()
settings.incrementSearchWidgetCFRDismissed()
}
searchWidgetCFRDialog.show()
context.components.analytics.metrics.track(Event.SearchWidgetCFRDisplayed)
metrics.track(Event.SearchWidgetCFRDisplayed)
}
companion object {

View File

@ -10,6 +10,7 @@ import org.mozilla.fenix.home.Tab
/**
* Diff callback for comparing tab lists with selected state.
*/
@Suppress("LongParameterList")
internal class TabDiffUtil(
private val old: List<Tab>,
private val new: List<Tab>,

View File

@ -103,11 +103,8 @@ private fun getSentryProjectUrl(): String? {
val baseUrl = "https://sentry.prod.mozaws.net/operations"
return when (Config.channel) {
ReleaseChannel.FenixProduction -> "$baseUrl/fenix"
ReleaseChannel.FenixBeta -> "$baseUrl/fenix-beta"
ReleaseChannel.FenixNightly -> "$baseUrl/fenix-nightly"
ReleaseChannel.FennecProduction -> "$baseUrl/fenix-fennec"
ReleaseChannel.FennecBeta -> "$baseUrl/fenix-fennec-beta"
ReleaseChannel.FennecNightly -> "$baseUrl/fenix-fennec-nightly"
else -> null
}
}

View File

@ -40,6 +40,7 @@ import org.mozilla.fenix.ext.settings
import org.mozilla.fenix.sync.SyncedTabsIntegration
import org.mozilla.fenix.utils.Mockable
import org.mozilla.fenix.utils.RunWhenReadyQueue
import org.mozilla.fenix.utils.Settings
/**
* Component group for background services. These are the components that need to be accessed from within a
@ -103,7 +104,7 @@ class BackgroundServices(
}
private val telemetryAccountObserver = TelemetryAccountObserver(
context,
context.settings(),
context.components.analytics.metrics
)
@ -178,8 +179,8 @@ class BackgroundServices(
}
@VisibleForTesting(otherwise = PRIVATE)
class TelemetryAccountObserver(
private val context: Context,
internal class TelemetryAccountObserver(
private val settings: Settings,
private val metricController: MetricController
) : AccountObserver {
override fun onAuthenticated(account: OAuthAccount, authType: AuthType) {
@ -211,12 +212,12 @@ class TelemetryAccountObserver(
metricController.track(Event.SyncAuthOtherExternal)
}
// Used by Leanplum as a context variable.
context.settings().fxaSignedIn = true
settings.fxaSignedIn = true
}
override fun onLoggedOut() {
metricController.track(Event.SyncAuthSignOut)
// Used by Leanplum as a context variable.
context.settings().fxaSignedIn = false
settings.fxaSignedIn = false
}
}

View File

@ -20,6 +20,7 @@ import org.mozilla.fenix.BuildConfig
import org.mozilla.fenix.HomeActivity
import org.mozilla.fenix.utils.ClipboardHandler
import org.mozilla.fenix.utils.Mockable
import org.mozilla.fenix.utils.Settings
import org.mozilla.fenix.wifi.WifiConnectionMonitor
import java.util.concurrent.TimeUnit
@ -107,4 +108,6 @@ class Components(private val context: Context) {
val performance by lazy { PerformanceComponent() }
val push by lazy { Push(context, analytics.crashReporter) }
val wifiConnectionMonitor by lazy { WifiConnectionMonitor(context.getSystemService()!!) }
val settings by lazy { Settings(context) }
}

View File

@ -13,7 +13,6 @@ import com.adjust.sdk.AdjustConfig
import com.adjust.sdk.LogLevel
import org.mozilla.fenix.BuildConfig
import org.mozilla.fenix.Config
import org.mozilla.fenix.ReleaseChannel
import org.mozilla.fenix.ext.settings
class AdjustMetricsService(private val application: Application) : MetricsService {
@ -23,7 +22,7 @@ class AdjustMetricsService(private val application: Application) : MetricsServic
if ((BuildConfig.ADJUST_TOKEN.isNullOrBlank())) {
Log.i(LOGTAG, "No adjust token defined")
if (Config.channel.isReleased && Config.channel != ReleaseChannel.FennecNightly) {
if (Config.channel.isReleased) {
throw IllegalStateException("No adjust token defined for release build")
}

View File

@ -24,15 +24,14 @@ import mozilla.components.browser.state.selector.findTab
import mozilla.components.browser.state.store.BrowserStore
import mozilla.components.concept.storage.BookmarksStorage
import mozilla.components.support.ktx.android.content.getColorFromAttr
import org.mozilla.fenix.R
import org.mozilla.fenix.FeatureFlags
import org.mozilla.fenix.HomeActivity
import org.mozilla.fenix.R
import org.mozilla.fenix.browser.browsingmode.BrowsingMode
import org.mozilla.fenix.ext.asActivity
import org.mozilla.fenix.ext.components
import org.mozilla.fenix.ext.settings
import org.mozilla.fenix.theme.ThemeManager
import org.mozilla.fenix.utils.Settings
/**
* Builds the toolbar object used with the 3-dot menu in the browser fragment.
@ -160,7 +159,7 @@ class DefaultToolbarMenu(
// Predicates that are called once, during screen init
val shouldShowSaveToCollection = (context.asActivity() as? HomeActivity)
?.browsingModeManager?.mode == BrowsingMode.Normal
val shouldDeleteDataOnQuit = Settings.getInstance(context)
val shouldDeleteDataOnQuit = context.components.settings
.shouldDeleteBrowsingDataOnQuit
val menuItems = listOfNotNull(

View File

@ -12,13 +12,10 @@ import android.view.ViewGroup
import androidx.annotation.StringRes
import mozilla.components.browser.search.SearchEngineManager
import mozilla.components.support.locale.LocaleManager
import org.mozilla.fenix.BuildConfig
import org.mozilla.fenix.Config
import org.mozilla.fenix.FenixApplication
import org.mozilla.fenix.components.Components
import org.mozilla.fenix.components.metrics.MetricController
import org.mozilla.fenix.settings.advanced.getSelectedLocale
import org.mozilla.fenix.utils.Settings
import java.lang.String.format
import java.util.Locale
@ -60,8 +57,7 @@ fun Context.getPreferenceKey(@StringRes resourceId: Int): String =
fun Context.getRootView(): View? =
asActivity()?.window?.decorView?.findViewById<View>(android.R.id.content) as? ViewGroup
fun Context.settings(isCrashReportEnabledInBuild: Boolean = BuildConfig.CRASH_REPORTING && Config.channel.isReleased) =
Settings.getInstance(this, isCrashReportEnabledInBuild)
fun Context.settings() = components.settings
/**
* Used to catch IllegalArgumentException that is thrown when

View File

@ -0,0 +1,34 @@
/* 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.ext
import android.content.Context
import android.text.Spannable
import android.text.SpannableString
import android.text.style.AbsoluteSizeSpan
import android.text.style.ForegroundColorSpan
import androidx.core.content.ContextCompat
import mozilla.components.support.ktx.android.util.dpToPx
fun SpannableString.setTextSize(context: Context, textSize: Int) =
this.setSpan(
AbsoluteSizeSpan(textSize.dpToPx(context.resources.displayMetrics)),
0,
this.length,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
)
fun SpannableString.setTextColor(context: Context, colorResId: Int) =
this.setSpan(
ForegroundColorSpan(
ContextCompat.getColor(
context,
colorResId
)
),
0,
this.length,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
)

View File

@ -375,7 +375,13 @@ class HomeFragment : Fragment() {
// the CFR in.
view.toolbar_wrapper.doOnLayout {
if (!browsingModeManager.mode.isPrivate) {
SearchWidgetCFR(view.context) { view.toolbar_wrapper }.displayIfNecessary()
SearchWidgetCFR(
context = view.context,
settings = view.context.settings(),
metrics = view.context.components.analytics.metrics
) {
view.toolbar_wrapper
}.displayIfNecessary()
}
}

View File

@ -26,7 +26,6 @@ import org.mozilla.fenix.R
import org.mozilla.fenix.ext.components
import org.mozilla.fenix.ext.settings
import org.mozilla.fenix.theme.ThemeManager
import org.mozilla.fenix.utils.Settings
import org.mozilla.fenix.whatsnew.WhatsNew
class HomeMenu(
@ -153,40 +152,29 @@ class HomeMenu(
null
}
val settings = context.components.settings
val menuItems = listOfNotNull(
if (settings.shouldDeleteBrowsingDataOnQuit) quitItem else null,
settingsItem,
BrowserMenuDivider(),
if (FeatureFlags.syncedTabs) syncedTabsItem else null,
bookmarksItem,
historyItem,
BrowserMenuDivider(),
addons,
BrowserMenuDivider(),
whatsNewItem,
helpItem,
accountAuthItem
).also { items ->
items.getHighlight()?.let { onHighlightPresent(it) }
}
if (shouldUseBottomToolbar) {
listOfNotNull(
accountAuthItem,
helpItem,
whatsNewItem,
BrowserMenuDivider(),
addons,
BrowserMenuDivider(),
historyItem,
bookmarksItem,
if (FeatureFlags.syncedTabs) syncedTabsItem else null,
BrowserMenuDivider(),
settingsItem,
if (Settings.getInstance(context).shouldDeleteBrowsingDataOnQuit) quitItem else null
).also { items ->
items.getHighlight()?.let { onHighlightPresent(it) }
}
menuItems.reversed()
} else {
listOfNotNull(
if (Settings.getInstance(context).shouldDeleteBrowsingDataOnQuit) quitItem else null,
settingsItem,
BrowserMenuDivider(),
if (FeatureFlags.syncedTabs) syncedTabsItem else null,
bookmarksItem,
historyItem,
BrowserMenuDivider(),
addons,
BrowserMenuDivider(),
whatsNewItem,
helpItem,
accountAuthItem
).also { items ->
items.getHighlight()?.let { onHighlightPresent(it) }
}
menuItems
}
}

View File

@ -128,7 +128,7 @@ interface SessionControlController {
fun handleCreateCollection()
}
@SuppressWarnings("TooManyFunctions", "LargeClass")
@SuppressWarnings("TooManyFunctions", "LargeClass", "LongParameterList")
class DefaultSessionControlController(
private val activity: HomeActivity,
private val engine: Engine,

View File

@ -12,12 +12,13 @@ import org.mozilla.fenix.components.metrics.Event
import org.mozilla.fenix.components.metrics.Event.OnboardingToolbarPosition.Position
import org.mozilla.fenix.ext.asActivity
import org.mozilla.fenix.ext.components
import org.mozilla.fenix.ext.settings
import org.mozilla.fenix.onboarding.OnboardingRadioButton
import org.mozilla.fenix.utils.view.addToRadioGroup
class OnboardingToolbarPositionPickerViewHolder(view: View) : RecyclerView.ViewHolder(view) {
private val metrics = view.context.components.analytics.metrics
init {
val radioTopToolbar = view.toolbar_top_radio_button
val radioBottomToolbar = view.toolbar_bottom_radio_button
@ -27,7 +28,8 @@ class OnboardingToolbarPositionPickerViewHolder(view: View) : RecyclerView.ViewH
radioTopToolbar.addIllustration(view.toolbar_top_image)
radioBottomToolbar.addIllustration(view.toolbar_bottom_image)
radio = if (view.context.settings().shouldUseBottomToolbar) {
val settings = view.context.components.settings
radio = if (settings.shouldUseBottomToolbar) {
radioBottomToolbar
} else {
radioTopToolbar
@ -35,28 +37,24 @@ class OnboardingToolbarPositionPickerViewHolder(view: View) : RecyclerView.ViewH
radio.updateRadioValue(true)
radioBottomToolbar.onClickListener {
itemView.context.components.analytics.metrics
.track(Event.OnboardingToolbarPosition(Position.BOTTOM))
metrics.track(Event.OnboardingToolbarPosition(Position.BOTTOM))
itemView.context.asActivity()?.recreate()
}
view.toolbar_bottom_image.setOnClickListener {
itemView.context.components.analytics.metrics
.track(Event.OnboardingToolbarPosition(Position.BOTTOM))
metrics.track(Event.OnboardingToolbarPosition(Position.BOTTOM))
radioBottomToolbar.performClick()
}
radioTopToolbar.onClickListener {
itemView.context.components.analytics.metrics
.track(Event.OnboardingToolbarPosition(Position.TOP))
metrics.track(Event.OnboardingToolbarPosition(Position.TOP))
itemView.context.asActivity()?.recreate()
}
view.toolbar_top_image.setOnClickListener {
itemView.context.components.analytics.metrics
.track(Event.OnboardingToolbarPosition(Position.TOP))
metrics.track(Event.OnboardingToolbarPosition(Position.TOP))
radioTopToolbar.performClick()
}
}

View File

@ -37,17 +37,17 @@ class OnboardingTrackingProtectionViewHolder(view: View) : RecyclerView.ViewHold
isChecked = view.context.settings().shouldUseTrackingProtection
setOnCheckedChangeListener { _, isChecked ->
updateTrackingProtectionSetting(isChecked)
updateRadioGroupState(view, isChecked)
updateRadioGroupState(isChecked)
}
}
setupRadioGroup(view, trackingProtectionToggle.isChecked)
updateRadioGroupState(view, trackingProtectionToggle.isChecked)
setupRadioGroup(trackingProtectionToggle.isChecked)
updateRadioGroupState(trackingProtectionToggle.isChecked)
}
private fun setupRadioGroup(view: View, isChecked: Boolean) {
private fun setupRadioGroup(isChecked: Boolean) {
updateRadioGroupState(view, isChecked)
updateRadioGroupState(isChecked)
addToRadioGroup(standardTrackingProtection, strictTrackingProtection)
@ -58,56 +58,20 @@ class OnboardingTrackingProtectionViewHolder(view: View) : RecyclerView.ViewHold
standardTrackingProtection.onClickListener {
updateTrackingProtectionPolicy()
view.context.components.analytics.metrics
itemView.context.components.analytics.metrics
.track(Event.OnboardingTrackingProtection(Setting.STANDARD))
}
view.clickable_region_standard.apply {
setOnClickListener {
standardTrackingProtection.performClick()
view.context.components.analytics.metrics
.track(Event.OnboardingTrackingProtection(Setting.STANDARD))
}
val standardTitle = view.context.getString(
R.string.onboarding_tracking_protection_standard_button_2
)
val standardSummary = view.context.getString(
R.string.onboarding_tracking_protection_standard_button_description_2
)
contentDescription = "$standardTitle. $standardSummary"
}
strictTrackingProtection.onClickListener {
updateTrackingProtectionPolicy()
view.context.components.analytics.metrics
itemView.context.components.analytics.metrics
.track(Event.OnboardingTrackingProtection(Setting.STRICT))
}
view.clickable_region_strict.apply {
setOnClickListener {
strictTrackingProtection.performClick()
view.context.components.analytics.metrics
.track(Event.OnboardingTrackingProtection(Setting.STRICT))
}
val strictTitle =
view.context.getString(R.string.onboarding_tracking_protection_strict_option)
val strictSummary =
view.context.getString(R.string.onboarding_tracking_protection_strict_button_description_2)
contentDescription = "$strictTitle. $strictSummary"
}
}
private fun updateRadioGroupState(view: View, isChecked: Boolean) {
private fun updateRadioGroupState(isChecked: Boolean) {
standardTrackingProtection.isEnabled = isChecked
strictTrackingProtection.isEnabled = isChecked
view.protection_standard_description.isEnabled = isChecked
view.protection_strict_description.isEnabled = isChecked
view.clickable_region_standard.isClickable = isChecked
view.protection_standard_title.isEnabled = isChecked
view.protection_strict_title.isEnabled = isChecked
view.clickable_region_strict.isClickable = isChecked
}
private fun updateTrackingProtectionSetting(enabled: Boolean) {

View File

@ -47,7 +47,7 @@ interface BookmarkController {
fun handleBackPressed()
}
@SuppressWarnings("TooManyFunctions")
@SuppressWarnings("TooManyFunctions", "LongParameterList")
class DefaultBookmarkController(
private val activity: HomeActivity,
private val navController: NavController,

View File

@ -51,6 +51,7 @@ import org.mozilla.fenix.ext.nav
import org.mozilla.fenix.ext.requireComponents
import org.mozilla.fenix.ext.toShortUrl
import org.mozilla.fenix.library.LibraryPageFragment
import org.mozilla.fenix.tabtray.TabTrayDialogFragment
import org.mozilla.fenix.utils.allowUndo
/**
@ -207,14 +208,14 @@ class BookmarkFragment : LibraryPageFragment<BookmarkNode>(), UserInteractionHan
R.id.open_bookmarks_in_new_tabs_multi_select -> {
openItemsInNewTab { node -> node.url }
navigate(BookmarkFragmentDirections.actionGlobalTabTrayDialogFragment())
showTabTray()
metrics?.track(Event.OpenedBookmarksInNewTabs)
true
}
R.id.open_bookmarks_in_private_tabs_multi_select -> {
openItemsInNewTab(private = true) { node -> node.url }
navigate(BookmarkFragmentDirections.actionGlobalTabTrayDialogFragment())
showTabTray()
metrics?.track(Event.OpenedBookmarksInPrivateTabs)
true
}
@ -237,6 +238,11 @@ class BookmarkFragment : LibraryPageFragment<BookmarkNode>(), UserInteractionHan
}
}
private fun showTabTray() {
invokePendingDeletion()
TabTrayDialogFragment.show(parentFragmentManager)
}
private fun navigate(directions: NavDirections) {
invokePendingDeletion()
findNavController().nav(

View File

@ -44,6 +44,7 @@ import org.mozilla.fenix.ext.requireComponents
import org.mozilla.fenix.ext.showToolbar
import org.mozilla.fenix.ext.toShortUrl
import org.mozilla.fenix.library.LibraryPageFragment
import org.mozilla.fenix.tabtray.TabTrayDialogFragment
import org.mozilla.fenix.utils.allowUndo
@SuppressWarnings("TooManyFunctions", "LargeClass")
@ -184,9 +185,7 @@ class HistoryFragment : LibraryPageFragment<HistoryItem>(), UserInteractionHandl
selectedItem.url
}
navigate(
HistoryFragmentDirections.actionGlobalTabTrayDialogFragment()
)
showTabTray()
true
}
R.id.open_history_in_private_tabs_multi_select -> {
@ -199,14 +198,18 @@ class HistoryFragment : LibraryPageFragment<HistoryItem>(), UserInteractionHandl
browsingModeManager.mode = BrowsingMode.Private
supportActionBar?.hide()
}
navigate(
HistoryFragmentDirections.actionGlobalTabTrayDialogFragment()
)
showTabTray()
true
}
else -> super.onOptionsItemSelected(item)
}
private fun showTabTray() {
invokePendingDeletion()
TabTrayDialogFragment.show(parentFragmentManager)
}
private fun getMultiSelectSnackBarMessage(historyItems: Set<HistoryItem>): String {
return if (historyItems.size > 1) {
getString(R.string.history_delete_multiple_items_snackbar)

View File

@ -5,12 +5,16 @@
package org.mozilla.fenix.onboarding
import android.content.Context
import android.text.SpannableString
import android.text.SpannableStringBuilder
import android.util.AttributeSet
import android.widget.ImageView
import androidx.appcompat.widget.AppCompatRadioButton
import androidx.core.content.edit
import androidx.core.content.withStyledAttributes
import org.mozilla.fenix.R
import org.mozilla.fenix.ext.setTextColor
import org.mozilla.fenix.ext.setTextSize
import org.mozilla.fenix.ext.settings
import org.mozilla.fenix.utils.view.GroupableRadioButton
import org.mozilla.fenix.utils.view.uncheckAll
@ -23,6 +27,8 @@ class OnboardingRadioButton(
private var illustration: ImageView? = null
private var clickListener: (() -> Unit)? = null
var key: Int = 0
var title: Int = 0
var description: Int = 0
init {
context.withStyledAttributes(
@ -31,6 +37,9 @@ class OnboardingRadioButton(
0, 0
) {
key = getResourceId(R.styleable.OnboardingRadioButton_onboardingKey, 0)
title = getResourceId(R.styleable.OnboardingRadioButton_onboardingKeyTitle, 0)
description =
getResourceId(R.styleable.OnboardingRadioButton_onboardingKeyDescription, 0)
}
}
@ -52,6 +61,28 @@ class OnboardingRadioButton(
toggleRadioGroups()
clickListener?.invoke()
}
if (title != 0) {
setRadioButtonText(context)
}
}
private fun setRadioButtonText(context: Context) {
val builder = SpannableStringBuilder()
val spannableTitle = SpannableString(resources.getString(title))
spannableTitle.setTextSize(context, TITLE_TEXT_SIZE)
spannableTitle.setTextColor(context, R.color.primary_state_list_text_color)
builder.append(spannableTitle)
if (description != 0) {
val spannableDescription = SpannableString(resources.getString(description))
spannableDescription.setTextSize(context, DESCRIPTION_TEXT_SIZE)
spannableDescription.setTextColor(context, R.color.secondary_state_list_text_color)
builder.append("\n")
builder.append(spannableDescription)
}
this.text = builder
}
override fun updateRadioValue(isChecked: Boolean) {
@ -69,4 +100,9 @@ class OnboardingRadioButton(
radioGroups.uncheckAll()
}
}
companion object {
private const val TITLE_TEXT_SIZE = 16
private const val DESCRIPTION_TEXT_SIZE = 14
}
}

View File

@ -8,8 +8,8 @@ import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.os.BatteryManager
import org.mozilla.fenix.ext.components
import org.mozilla.fenix.onboarding.FenixOnboarding
import org.mozilla.fenix.utils.Settings
import android.provider.Settings as AndroidSettings
/**
@ -67,13 +67,13 @@ object Performance {
* Disables the tracking protection popup. However, TP is still on.
*/
private fun disableTrackingProtectionPopups(context: Context) {
Settings.getInstance(context).isOverrideTPPopupsForPerformanceTest = true
context.components.settings.isOverrideTPPopupsForPerformanceTest = true
}
/**
* Disables the first time PWA popup.
*/
private fun disableFirstTimePWAPopup(context: Context) {
Settings.getInstance(context).userKnowsAboutPwas = true
context.components.settings.userKnowsAboutPwas = true
}
}

View File

@ -6,7 +6,7 @@ package org.mozilla.fenix.push
import android.util.Base64
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.MainScope
import kotlinx.coroutines.launch
import mozilla.components.concept.engine.Engine
import mozilla.components.concept.engine.webpush.WebPushDelegate
@ -22,7 +22,8 @@ import mozilla.components.support.base.log.logger.Logger
*/
class WebPushEngineIntegration(
private val engine: Engine,
private val pushFeature: AutoPushFeature
private val pushFeature: AutoPushFeature,
private val coroutineScope: CoroutineScope = MainScope()
) : AutoPushFeature.Observer {
private var handler: WebPushHandler? = null
@ -39,13 +40,13 @@ class WebPushEngineIntegration(
}
override fun onMessageReceived(scope: PushScope, message: ByteArray?) {
CoroutineScope(Dispatchers.Main).launch {
coroutineScope.launch {
handler?.onPushMessage(scope, message)
}
}
override fun onSubscriptionChanged(scope: PushScope) {
CoroutineScope(Dispatchers.Main).launch {
coroutineScope.launch {
handler?.onSubscriptionChanged(scope)
}
}

View File

@ -0,0 +1,53 @@
/* 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.search.awesomebar
import mozilla.components.browser.search.SearchEngine
import mozilla.components.browser.session.Session
/**
* Interface for the AwesomeBarView Interactor. This interface is implemented by objects that want
* to respond to user interaction on the AwesomebarView
*/
interface AwesomeBarInteractor {
/**
* Called whenever a suggestion containing a URL is tapped
* @param url the url the suggestion was providing
*/
fun onUrlTapped(url: String)
/**
* Called whenever a search engine suggestion is tapped
* @param searchTerms the query contained by the search suggestion
*/
fun onSearchTermsTapped(searchTerms: String)
/**
* Called whenever a search engine shortcut is tapped
* @param searchEngine the searchEngine that was selected
*/
fun onSearchShortcutEngineSelected(searchEngine: SearchEngine)
/**
* Called whenever the "Search Engine Settings" item is tapped
*/
fun onClickSearchEngineSettings()
/**
* Called whenever an existing session is selected from the sessionSuggestionProvider
*/
fun onExistingSessionSelected(session: Session)
/**
* Called whenever an existing session is selected from the sessionSuggestionProvider
*/
fun onExistingSessionSelected(tabId: String)
/**
* Called whenever the Shortcuts button is clicked
*/
fun onSearchShortcutsButtonClicked()
}

View File

@ -16,6 +16,7 @@ import mozilla.components.concept.awesomebar.AwesomeBar
import mozilla.components.concept.engine.EngineSession
import mozilla.components.feature.awesomebar.provider.BookmarksStorageSuggestionProvider
import mozilla.components.feature.awesomebar.provider.HistoryStorageSuggestionProvider
import mozilla.components.feature.awesomebar.provider.SearchActionProvider
import mozilla.components.feature.awesomebar.provider.SearchSuggestionProvider
import mozilla.components.feature.awesomebar.provider.SessionSuggestionProvider
import mozilla.components.feature.search.SearchUseCases
@ -29,51 +30,6 @@ import org.mozilla.fenix.ext.components
import org.mozilla.fenix.search.SearchEngineSource
import org.mozilla.fenix.search.SearchFragmentState
/**
* Interface for the AwesomeBarView Interactor. This interface is implemented by objects that want
* to respond to user interaction on the AwesomebarView
*/
interface AwesomeBarInteractor {
/**
* Called whenever a suggestion containing a URL is tapped
* @param url the url the suggestion was providing
*/
fun onUrlTapped(url: String)
/**
* Called whenever a search engine suggestion is tapped
* @param searchTerms the query contained by the search suggestion
*/
fun onSearchTermsTapped(searchTerms: String)
/**
* Called whenever a search engine shortcut is tapped
* @param searchEngine the searchEngine that was selected
*/
fun onSearchShortcutEngineSelected(searchEngine: SearchEngine)
/**
* Called whenever the "Search Engine Settings" item is tapped
*/
fun onClickSearchEngineSettings()
/**
* Called whenever an existing session is selected from the sessionSuggestionProvider
*/
fun onExistingSessionSelected(session: Session)
/**
* Called whenever an existing session is selected from the sessionSuggestionProvider
*/
fun onExistingSessionSelected(tabId: String)
/**
* Called whenever the Shortcuts button is clicked
*/
fun onSearchShortcutsButtonClicked()
}
/**
* View that contains and configures the BrowserAwesomeBar
*/
@ -88,7 +44,8 @@ class AwesomeBarView(
private val shortcutsEnginePickerProvider: ShortcutsSuggestionProvider
private val bookmarksStorageSuggestionProvider: BookmarksStorageSuggestionProvider
private val defaultSearchSuggestionProvider: SearchSuggestionProvider
private val searchSuggestionProviderMap: MutableMap<SearchEngine, SearchSuggestionProvider>
private val defaultSearchActionProvider: SearchActionProvider
private val searchSuggestionProviderMap: MutableMap<SearchEngine, List<AwesomeBar.SuggestionProvider>>
private var providersInUse = mutableSetOf<AwesomeBar.SuggestionProvider>()
private val loadUrlUseCase = object : SessionUseCases.LoadUrlUseCase {
@ -141,7 +98,8 @@ class AwesomeBarView(
val draw = getDrawable(context, R.drawable.ic_link)!!
draw.colorFilter = createBlendModeColorFilterCompat(primaryTextColor, SRC_IN)
val engineForSpeculativeConnects = if (!isBrowsingModePrivate()) components.core.engine else null
val engineForSpeculativeConnects =
if (!isBrowsingModePrivate()) components.core.engine else null
sessionProvider =
SessionSuggestionProvider(
context.resources,
@ -167,8 +125,9 @@ class AwesomeBarView(
engineForSpeculativeConnects
)
val searchDrawable = getDrawable(context, R.drawable.ic_search)!!
searchDrawable.colorFilter = createBlendModeColorFilterCompat(primaryTextColor, SRC_IN)
val searchBitmap = getDrawable(context, R.drawable.ic_search)!!.apply {
colorFilter = createBlendModeColorFilterCompat(primaryTextColor, SRC_IN)
}.toBitmap()
defaultSearchSuggestionProvider =
SearchSuggestionProvider(
@ -178,9 +137,20 @@ class AwesomeBarView(
fetchClient = components.core.client,
mode = SearchSuggestionProvider.Mode.MULTIPLE_SUGGESTIONS,
limit = 3,
icon = searchDrawable.toBitmap(),
icon = searchBitmap,
showDescription = false,
engine = engineForSpeculativeConnects
engine = engineForSpeculativeConnects,
filterExactMatch = true
)
defaultSearchActionProvider =
SearchActionProvider(
searchEngineGetter = suspend {
components.search.searchEngineManager.getDefaultSearchEngineAsync(context)
},
searchUseCase = searchUseCase,
icon = searchBitmap,
showDescription = false
)
shortcutsEnginePickerProvider =
@ -248,9 +218,7 @@ class AwesomeBarView(
}
if (state.showSearchSuggestions) {
getSelectedSearchSuggestionProvider(state)?.let {
providersToAdd.add(it)
}
providersToAdd.addAll(getSelectedSearchSuggestionProvider(state))
}
if (!isBrowsingModePrivate()) {
@ -274,9 +242,7 @@ class AwesomeBarView(
}
if (!state.showSearchSuggestions) {
getSelectedSearchSuggestionProvider(state)?.let {
providersToRemove.add(it)
}
providersToRemove.addAll(getSelectedSearchSuggestionProvider(state))
}
if (isBrowsingModePrivate()) {
@ -291,9 +257,12 @@ class AwesomeBarView(
?: false
}
private fun getSelectedSearchSuggestionProvider(state: SearchFragmentState): SearchSuggestionProvider? {
private fun getSelectedSearchSuggestionProvider(state: SearchFragmentState): List<AwesomeBar.SuggestionProvider> {
return when (state.searchEngineSource) {
is SearchEngineSource.Default -> defaultSearchSuggestionProvider
is SearchEngineSource.Default -> listOf(
defaultSearchActionProvider,
defaultSearchSuggestionProvider
)
is SearchEngineSource.Shortcut -> getSuggestionProviderForEngine(
state.searchEngineSource.searchEngine
)
@ -307,26 +276,38 @@ class AwesomeBarView(
view.addProviders(shortcutsEnginePickerProvider)
}
private fun getSuggestionProviderForEngine(engine: SearchEngine): SearchSuggestionProvider? {
private fun getSuggestionProviderForEngine(engine: SearchEngine): List<AwesomeBar.SuggestionProvider> {
return searchSuggestionProviderMap.getOrPut(engine) {
val context = container.context
val components = context.components
val primaryTextColor = context.getColorFromAttr(R.attr.primaryText)
val draw = getDrawable(context, R.drawable.ic_search)
draw?.colorFilter = createBlendModeColorFilterCompat(primaryTextColor, SRC_IN)
val searchBitmap = getDrawable(context, R.drawable.ic_search)?.apply {
colorFilter = createBlendModeColorFilterCompat(primaryTextColor, SRC_IN)
}?.toBitmap()
val engineForSpeculativeConnects = if (!isBrowsingModePrivate()) components.core.engine else null
SearchSuggestionProvider(
val engineForSpeculativeConnects =
if (!isBrowsingModePrivate()) components.core.engine else null
val searchEngine =
components.search.provider.installedSearchEngines(context).list.find { it.name == engine.name }
?: components.search.provider.getDefaultEngine(context),
shortcutSearchUseCase,
components.core.client,
limit = 3,
mode = SearchSuggestionProvider.Mode.MULTIPLE_SUGGESTIONS,
icon = draw?.toBitmap(),
engine = engineForSpeculativeConnects
?: components.search.provider.getDefaultEngine(context)
listOf(
SearchActionProvider(
searchEngineGetter = suspend { searchEngine },
searchUseCase = shortcutSearchUseCase,
icon = searchBitmap
),
SearchSuggestionProvider(
searchEngine,
shortcutSearchUseCase,
components.core.client,
limit = 3,
mode = SearchSuggestionProvider.Mode.MULTIPLE_SUGGESTIONS,
icon = searchBitmap,
engine = engineForSpeculativeConnects,
filterExactMatch = true
)
)
}
}

View File

@ -7,6 +7,7 @@ package org.mozilla.fenix.search.toolbar
import android.content.Context
import android.graphics.Bitmap
import android.graphics.drawable.BitmapDrawable
import androidx.annotation.VisibleForTesting
import androidx.appcompat.content.res.AppCompatResources
import androidx.core.content.ContextCompat
import mozilla.components.browser.domains.autocomplete.ShippedDomainsProvider
@ -56,7 +57,8 @@ class ToolbarView(
engine: Engine
) {
private var isInitialized = false
@VisibleForTesting
internal var isInitialized = false
init {
view.apply {

View File

@ -37,7 +37,6 @@ import org.mozilla.fenix.IntentReceiverActivity
import org.mozilla.fenix.R
import org.mozilla.fenix.ext.components
import org.mozilla.fenix.settings.PhoneFeature
import org.mozilla.fenix.utils.Settings
import com.google.android.material.R as MaterialR
/**
@ -62,6 +61,7 @@ class QuickSettingsSheetDialogFragment : AppCompatDialogFragment() {
savedInstanceState: Bundle?
): View {
val context = requireContext()
val components = context.components
val rootView = inflateRootView(container)
quickSettingsStore = QuickSettingsFragmentStore.createStore(
@ -70,7 +70,7 @@ class QuickSettingsSheetDialogFragment : AppCompatDialogFragment() {
websiteTitle = args.title,
isSecured = args.isSecured,
permissions = args.sitePermissions,
settings = Settings.getInstance(context),
settings = components.settings,
certificateName = args.certificateName
)
@ -79,12 +79,12 @@ class QuickSettingsSheetDialogFragment : AppCompatDialogFragment() {
quickSettingsStore = quickSettingsStore,
ioScope = viewLifecycleOwner.lifecycleScope + Dispatchers.IO,
navController = findNavController(),
session = context.components.core.sessionManager.findSessionById(args.sessionId),
session = components.core.sessionManager.findSessionById(args.sessionId),
sitePermissions = args.sitePermissions,
settings = Settings.getInstance(context),
permissionStorage = context.components.core.permissionStorage,
reload = context.components.useCases.sessionUseCases.reload,
addNewTab = context.components.useCases.tabsUseCases.addTab,
settings = components.settings,
permissionStorage = components.core.permissionStorage,
reload = components.useCases.sessionUseCases.reload,
addNewTab = components.useCases.tabsUseCases.addTab,
requestRuntimePermissions = { permissions ->
requestPermissions(permissions, REQUEST_CODE_QUICK_SETTINGS_PERMISSIONS)
tryToRequestPermissions = true

View File

@ -16,8 +16,10 @@ import androidx.lifecycle.LifecycleCoroutineScope
import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.google.android.material.tabs.TabLayout
import kotlinx.android.extensions.LayoutContainer
import kotlinx.android.synthetic.main.component_tabstray.*
import kotlinx.android.synthetic.main.component_tabstray.view.*
import kotlinx.android.synthetic.main.component_tabstray_fab.view.*
import kotlinx.android.synthetic.main.tabs_tray_tab_counter.*
import kotlinx.coroutines.Dispatchers.Main
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
@ -36,7 +38,7 @@ import org.mozilla.fenix.ext.settings
/**
* View that contains and configures the BrowserAwesomeBar
*/
@Suppress("LongParameterList")
@Suppress("LongParameterList", "TooManyFunctions", "LargeClass")
class TabTrayView(
private val container: ViewGroup,
private val interactor: TabTrayInteractor,
@ -245,6 +247,17 @@ class TabTrayView(
View.VISIBLE
}
view.tab_tray_overflow.isVisible = !hasNoTabs
counter_text.text = "${state.normalTabs.size}"
updateTabCounterContentDescription(state.normalTabs.size)
}
}
private fun updateTabCounterContentDescription(count: Int) {
view.tab_layout.getTabAt(0)?.contentDescription = if (count == 1) {
view.context?.getString(R.string.open_tab_tray_single)
} else {
view.context?.getString(R.string.open_tab_tray_plural, count.toString())
}
}

View File

@ -7,11 +7,11 @@ package org.mozilla.fenix.tabtray
import android.view.View
import android.view.accessibility.AccessibilityNodeInfo
import android.widget.ImageButton
import android.widget.ImageView
import android.widget.TextView
import androidx.annotation.VisibleForTesting
import androidx.appcompat.widget.AppCompatImageButton
import androidx.core.content.ContextCompat
import androidx.core.view.doOnNextLayout
import mozilla.components.browser.state.state.MediaState
import mozilla.components.browser.tabstray.TabViewHolder
import mozilla.components.browser.tabstray.thumbnail.TabThumbnailView
@ -21,7 +21,7 @@ import mozilla.components.concept.tabstray.TabsTray
import mozilla.components.feature.media.ext.pauseIfPlaying
import mozilla.components.feature.media.ext.playIfPaused
import mozilla.components.support.base.observer.Observable
import mozilla.components.support.images.ext.loadIntoView
import mozilla.components.support.images.ImageLoadRequest
import mozilla.components.support.images.loader.ImageLoader
import mozilla.components.support.ktx.kotlin.tryGetHostFromUrl
import org.mozilla.fenix.R
@ -32,6 +32,7 @@ import org.mozilla.fenix.ext.removeAndDisable
import org.mozilla.fenix.ext.removeTouchDelegate
import org.mozilla.fenix.ext.showAndEnable
import org.mozilla.fenix.ext.toTab
import kotlin.math.max
/**
* A RecyclerView ViewHolder implementation for "tab" items.
@ -73,10 +74,7 @@ class TabTrayViewHolder(
if (tab.thumbnail != null) {
thumbnailView.setImageBitmap(tab.thumbnail)
} else {
// Make sure we have the view's dimensions so we can load the image at the correct size
thumbnailView.doOnNextLayout {
imageLoader.loadIntoView(thumbnailView, tab.id)
}
loadIntoThumbnailView(thumbnailView, tab.id)
}
// Media state
@ -182,6 +180,14 @@ class TabTrayViewHolder(
closeView.context.getString(R.string.close_tab_title, title)
}
private fun loadIntoThumbnailView(thumbnailView: ImageView, id: String) {
val thumbnailSize = max(
itemView.resources.getDimensionPixelSize(R.dimen.tab_tray_thumbnail_height),
itemView.resources.getDimensionPixelSize(R.dimen.tab_tray_thumbnail_width)
)
imageLoader.loadIntoView(thumbnailView, ImageLoadRequest(id, thumbnailSize))
}
internal fun updateAccessibilityRowIndex(item: View, newIndex: Int) {
item.setAccessibilityDelegate(object : View.AccessibilityDelegate() {
override fun onInitializeAccessibilityNodeInfo(

View File

@ -21,7 +21,7 @@ import kotlinx.android.synthetic.main.tracking_protection_onboarding_popup.view.
import mozilla.components.browser.session.Session
import org.mozilla.fenix.R
import org.mozilla.fenix.components.metrics.Event
import org.mozilla.fenix.ext.components
import org.mozilla.fenix.components.metrics.MetricController
import org.mozilla.fenix.ext.increaseTapArea
import org.mozilla.fenix.utils.Settings
@ -32,6 +32,7 @@ import org.mozilla.fenix.utils.Settings
class TrackingProtectionOverlay(
private val context: Context,
private val settings: Settings,
private val metrics: MetricController,
private val getToolbar: () -> View
) : Session.Observer {
@ -54,7 +55,7 @@ class TrackingProtectionOverlay(
override fun onTouchEvent(event: MotionEvent): Boolean {
if (event.action == MotionEvent.ACTION_DOWN) {
context.components.analytics.metrics.track(Event.ContextualHintETPOutsideTap)
metrics.track(Event.ContextualHintETPOutsideTap)
}
return super.onTouchEvent(event)
}
@ -62,7 +63,7 @@ class TrackingProtectionOverlay(
val layout = LayoutInflater.from(context)
.inflate(R.layout.tracking_protection_onboarding_popup, null)
val isBottomToolbar = Settings.getInstance(context).shouldUseBottomToolbar
val isBottomToolbar = settings.shouldUseBottomToolbar
layout.drop_down_triangle.isGone = isBottomToolbar
layout.pop_up_triangle.isVisible = isBottomToolbar
@ -76,13 +77,13 @@ class TrackingProtectionOverlay(
val closeButton = layout.findViewById<ImageView>(R.id.close_onboarding)
closeButton.increaseTapArea(BUTTON_INCREASE_DPS)
closeButton.setOnClickListener {
context.components.analytics.metrics.track(Event.ContextualHintETPDismissed)
metrics.track(Event.ContextualHintETPDismissed)
trackingOnboardingDialog.dismiss()
}
val res = context.resources
val triangleWidthPx = res.getDimension(R.dimen.tp_onboarding_triangle_height)
val triangleMarginStartPx = res.getDimension(R.dimen.tp_onboarding_triangle_margin_start)
val triangleWidthPx = res.getDimension(R.dimen.cfr_triangle_height)
val triangleMarginStartPx = res.getDimension(R.dimen.cfr_triangle_margin_edge)
val toolbar = getToolbar()
val trackingProtectionIcon: View =
@ -115,12 +116,12 @@ class TrackingProtectionOverlay(
val etpShield =
getToolbar().findViewById<View>(R.id.mozac_browser_toolbar_tracking_protection_indicator)
trackingOnboardingDialog.message.setOnClickListener {
context.components.analytics.metrics.track(Event.ContextualHintETPInsideTap)
metrics.track(Event.ContextualHintETPInsideTap)
trackingOnboardingDialog.dismiss()
etpShield.performClick()
}
context.components.analytics.metrics.track(Event.ContextualHintETPDisplayed)
metrics.track(Event.ContextualHintETPDisplayed)
trackingOnboardingDialog.show()
settings.incrementTrackingProtectionOnboardingCount()
}

View File

@ -33,9 +33,9 @@ import org.mozilla.fenix.ext.components
import org.mozilla.fenix.ext.getPreferenceKey
import org.mozilla.fenix.settings.PhoneFeature
import org.mozilla.fenix.settings.deletebrowsingdata.DeleteBrowsingDataOnQuitType
import org.mozilla.fenix.settings.logins.fragment.SavedLoginsFragment
import org.mozilla.fenix.settings.logins.SavedLoginsSortingStrategyMenu
import org.mozilla.fenix.settings.logins.SortingStrategy
import org.mozilla.fenix.settings.logins.fragment.SavedLoginsFragment
import org.mozilla.fenix.settings.registerOnSharedPreferenceChangeListener
import java.security.InvalidParameterException
@ -43,12 +43,11 @@ private const val AUTOPLAY_USER_SETTING = "AUTOPLAY_USER_SETTING"
/**
* A simple wrapper for SharedPreferences that makes reading preference a little bit easier.
* @param appContext Reference to application context.
*/
@Suppress("LargeClass", "TooManyFunctions")
class Settings private constructor(
context: Context,
private val isCrashReportEnabledInBuild: Boolean
) : PreferencesHolder {
class Settings(private val appContext: Context) : PreferencesHolder {
companion object {
const val showLoginsSecureWarningSyncMaxCount = 1
const val showLoginsSecureWarningMaxCount = 1
@ -88,24 +87,11 @@ class Settings private constructor(
ASK_TO_ALLOW_INT -> AutoplayAction.BLOCKED
else -> throw InvalidParameterException("$this is not a valid SitePermissionsRules.AutoplayAction")
}
@VisibleForTesting
internal var instance: Settings? = null
@JvmStatic
@Synchronized
fun getInstance(
context: Context,
isCrashReportEnabledInBuild: Boolean = BuildConfig.CRASH_REPORTING && Config.channel.isReleased
): Settings {
if (instance == null) {
instance = Settings(context.applicationContext, isCrashReportEnabledInBuild)
}
return instance ?: throw AssertionError("Instance cleared")
}
}
private val appContext = context.applicationContext
@VisibleForTesting
internal val isCrashReportEnabledInBuild: Boolean =
BuildConfig.CRASH_REPORTING && Config.channel.isReleased
override val preferences: SharedPreferences =
appContext.getSharedPreferences(FENIX_PREFERENCES, MODE_PRIVATE)
@ -372,7 +358,7 @@ class Settings private constructor(
appContext.getPreferenceKey(R.string.pref_key_tracking_protection_standard_option),
false
).apply()
appContext?.components?.let {
appContext.components.let {
val policy = it.core.trackingProtectionPolicyFactory
.createTrackingProtectionPolicy()
it.useCases.settingsUseCases.updateTrackingProtection.invoke(policy)

View File

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 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/. -->
<rotate xmlns:android="http://schemas.android.com/apk/res/android"
android:fromDegrees="45"
android:pivotX="-40%"
android:pivotY="87%"
android:toDegrees="45">
<shape android:shape="rectangle">
<stroke
android:width="10dp"
android:color="#FFFFFF" />
<solid android:color="#FFFFFF" />
</shape>
</rotate>

View File

@ -1,20 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 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/. -->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<rotate
android:fromDegrees="45"
android:pivotX="-40%"
android:pivotY="87%"
android:toDegrees="45">
<shape android:shape="rectangle">
<stroke
android:width="10dp"
android:color="#7542E5" />
<solid android:color="#7542E5" />
</shape>
</rotate>
</item>
</layer-list>

View File

@ -1,20 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 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/. -->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<rotate
android:fromDegrees="45"
android:pivotX="-40%"
android:pivotY="87%"
android:toDegrees="45">
<shape android:shape="rectangle">
<stroke
android:width="10dp"
android:color="#0250BB" />
<solid android:color="#0250BB" />
</shape>
</rotate>
</item>
</layer-list>

View File

@ -46,27 +46,28 @@
android:layout_width="0dp"
android:layout_height="80dp"
android:background="@color/foundation_normal_theme"
app:tabIndicatorColor="@color/accent_normal_theme"
app:tabIconTint="@color/tab_icon"
app:tabRippleColor="@android:color/transparent"
app:tabGravity="fill"
app:layout_constraintWidth_percent="0.5"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/handle"
app:layout_constraintStart_toStartOf="parent">
app:layout_constraintWidth_percent="0.5"
app:tabGravity="fill"
app:tabIconTint="@color/tab_icon"
app:tabIndicatorColor="@color/accent_normal_theme"
app:tabRippleColor="@android:color/transparent">
<com.google.android.material.tabs.TabItem
android:id="@+id/default_tab_item"
android:layout_height="match_parent"
android:layout_width="0dp"
android:icon="@drawable/ic_tabs"
android:contentDescription="@string/tab_header_label" />
android:layout_height="match_parent"
android:contentDescription="@string/tab_header_label"
android:layout="@layout/tabs_tray_tab_counter"
app:tabIconTint="@color/tab_icon" />
<com.google.android.material.tabs.TabItem
android:id="@+id/private_tab_item"
android:layout_height="match_parent"
android:layout_width="0dp"
android:icon="@drawable/ic_private_browsing"
android:contentDescription="@string/tabs_header_private_tabs_title" />
android:layout_height="match_parent"
android:contentDescription="@string/tabs_header_private_tabs_title"
android:icon="@drawable/ic_private_browsing" />
</com.google.android.material.tabs.TabLayout>

View File

@ -25,7 +25,7 @@
android:background="?android:attr/selectableItemBackground"
android:button="@null"
app:drawableStartCompat="?android:attr/listChoiceIndicatorSingle"
android:drawablePadding="@dimen/preference_seek_bar_padding"
android:drawablePadding="@dimen/radio_button_preference_drawable_padding"
android:paddingTop="@dimen/radio_button_preference_vertical"
android:paddingStart="@dimen/radio_button_preference_horizontal"
android:paddingEnd="@dimen/radio_button_preference_horizontal"
@ -40,7 +40,7 @@
android:background="?android:attr/selectableItemBackground"
android:button="@null"
app:drawableStartCompat="?android:attr/listChoiceIndicatorSingle"
android:drawablePadding="@dimen/preference_seek_bar_padding"
android:drawablePadding="@dimen/radio_button_preference_drawable_padding"
android:paddingTop="@dimen/radio_button_preference_vertical"
android:paddingStart="@dimen/radio_button_preference_horizontal"
android:paddingEnd="@dimen/radio_button_preference_horizontal"

View File

@ -24,7 +24,7 @@
android:layout_height="wrap_content"
android:background="?android:attr/selectableItemBackground"
android:button="@null"
android:drawablePadding="@dimen/preference_seek_bar_padding"
android:drawablePadding="@dimen/radio_button_preference_drawable_padding"
android:paddingStart="@dimen/radio_button_preference_horizontal"
android:paddingTop="@dimen/radio_button_preference_vertical"
android:paddingEnd="@dimen/radio_button_preference_horizontal"
@ -39,7 +39,7 @@
android:layout_height="wrap_content"
android:background="?android:attr/selectableItemBackground"
android:button="@null"
android:drawablePadding="@dimen/preference_seek_bar_padding"
android:drawablePadding="@dimen/radio_button_preference_drawable_padding"
android:paddingStart="@dimen/radio_button_preference_horizontal"
android:paddingTop="@dimen/radio_button_preference_vertical"
android:paddingEnd="@dimen/radio_button_preference_horizontal"
@ -54,7 +54,7 @@
android:layout_height="wrap_content"
android:background="?android:attr/selectableItemBackground"
android:button="@null"
android:drawablePadding="@dimen/preference_seek_bar_padding"
android:drawablePadding="@dimen/radio_button_preference_drawable_padding"
android:paddingStart="@dimen/radio_button_preference_horizontal"
android:paddingTop="@dimen/radio_button_preference_vertical"
android:paddingEnd="@dimen/radio_button_preference_horizontal"
@ -68,7 +68,7 @@
android:layout_height="wrap_content"
android:background="?android:attr/selectableItemBackground"
android:button="@null"
android:drawablePadding="@dimen/preference_seek_bar_padding"
android:drawablePadding="@dimen/radio_button_preference_drawable_padding"
android:paddingStart="@dimen/radio_button_preference_horizontal"
android:paddingTop="@dimen/radio_button_preference_vertical"
android:paddingEnd="@dimen/radio_button_preference_horizontal"

View File

@ -4,6 +4,7 @@
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/onboarding_card"
android:layout_width="match_parent"
android:layout_height="wrap_content"
@ -140,34 +141,18 @@
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:layout_marginBottom="16dp"
android:background="@android:color/transparent"
android:contentDescription="@string/onboarding_theme_automatic_title"
android:foreground="@drawable/rounded_ripple"
android:paddingStart="0dp"
android:paddingEnd="8dp"
android:theme="@style/Checkable.Colored"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/divider"
app:onboardingKey="@string/pref_key_follow_device_theme" />
app:onboardingKey="@string/pref_key_follow_device_theme"
app:onboardingKeyDescription="@string/onboarding_theme_automatic_summary"
app:onboardingKeyTitle="@string/onboarding_theme_automatic_title"
tools:text="Automatic" />
<TextView
android:id="@+id/automatic_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="9dp"
android:text="@string/onboarding_theme_automatic_title"
android:textColor="?primaryText"
android:textSize="16sp"
app:layout_constraintStart_toEndOf="@+id/theme_automatic_radio_button"
app:layout_constraintTop_toBottomOf="@+id/divider" />
<TextView
android:id="@+id/automatic_theme_summary"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="@string/onboarding_theme_automatic_summary"
android:textColor="?secondaryText"
android:textSize="14sp"
app:layout_constraintStart_toEndOf="@+id/theme_automatic_radio_button"
app:layout_constraintEnd_toEndOf="@id/clickable_region_automatic"
app:layout_constraintTop_toBottomOf="@id/automatic_title" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -7,7 +7,9 @@
android:id="@+id/onboarding_card"
style="@style/OnboardingCardLightWithPadding"
android:layout_width="match_parent"
android:layout_height="wrap_content">
android:layout_height="wrap_content"
android:clipChildren="false"
android:clipToPadding="false">
<TextView
android:id="@+id/header_text"
@ -44,97 +46,48 @@
app:layout_constraintTop_toBottomOf="@id/header_text"
tools:text="@string/onboarding_tracking_protection_description_2" />
<View
android:id="@+id/clickable_region_standard"
android:layout_width="match_parent"
android:layout_height="0dp"
android:foreground="@drawable/rounded_ripple"
app:layout_constraintBottom_toBottomOf="@id/protection_standard_description"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@id/tracking_protection_standard_option" />
<org.mozilla.fenix.onboarding.OnboardingRadioButton
android:id="@+id/tracking_protection_standard_option"
android:layout_width="wrap_content"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:layout_marginBottom="16dp"
android:paddingStart="0dp"
android:paddingEnd="8dp"
android:background="@android:color/transparent"
android:checked="true"
android:foreground="@drawable/rounded_ripple"
android:gravity="top"
android:paddingStart="8dp"
android:paddingEnd="8dp"
android:theme="@style/Checkable.Colored"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/description_text"
app:onboardingKey="@string/pref_key_tracking_protection_standard_option" />
<TextView
android:id="@+id/protection_standard_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/onboarding_tracking_protection_standard_button_2"
android:textColor="@color/primary_state_list_text_color"
android:textSize="16sp"
app:layout_constraintStart_toEndOf="@+id/tracking_protection_standard_option"
app:layout_constraintTop_toTopOf="@+id/tracking_protection_standard_option" />
<TextView
android:id="@+id/protection_standard_description"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="@string/onboarding_tracking_protection_standard_button_description_2"
android:textColor="@color/secondary_state_list_text_color"
android:textSize="14sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="@+id/protection_standard_title"
app:layout_constraintTop_toBottomOf="@id/protection_standard_title" />
<View
android:id="@+id/clickable_region_strict"
android:layout_width="match_parent"
android:layout_height="0dp"
android:foreground="@drawable/rounded_ripple"
app:layout_constraintBottom_toBottomOf="@id/protection_strict_description"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@id/tracking_protection_strict_default" />
app:onboardingKey="@string/pref_key_tracking_protection_standard_option"
app:onboardingKeyDescription="@string/onboarding_tracking_protection_standard_button_description_2"
app:onboardingKeyTitle="@string/onboarding_tracking_protection_standard_button_2"
tools:text="Standard" />
<org.mozilla.fenix.onboarding.OnboardingRadioButton
android:id="@+id/tracking_protection_strict_default"
android:layout_width="wrap_content"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:layout_marginBottom="16dp"
android:background="@android:color/transparent"
android:checked="false"
android:paddingStart="0dp"
android:foreground="@drawable/rounded_ripple"
android:gravity="top"
android:paddingStart="8dp"
android:paddingEnd="8dp"
android:textColor="@color/primary_state_list_text_color"
android:theme="@style/Checkable.Colored"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/tracking_protection_standard_option"
app:onboardingKey="@string/pref_key_tracking_protection_strict_default" />
<TextView
android:id="@+id/protection_strict_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/onboarding_tracking_protection_strict_option"
android:textColor="@color/primary_state_list_text_color"
android:textSize="16sp"
app:layout_constraintStart_toEndOf="@+id/tracking_protection_strict_default"
app:layout_constraintTop_toTopOf="@+id/tracking_protection_strict_default" />
<TextView
android:id="@+id/protection_strict_description"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="@string/onboarding_tracking_protection_strict_button_description_2"
android:textColor="@color/secondary_state_list_text_color"
android:textSize="14sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="@+id/protection_strict_title"
app:layout_constraintTop_toBottomOf="@id/protection_strict_title" />
app:onboardingKey="@string/pref_key_tracking_protection_strict_default"
app:onboardingKeyDescription="@string/onboarding_tracking_protection_strict_button_description_2"
app:onboardingKeyTitle="@string/onboarding_tracking_protection_strict_option"
tools:text="Strict" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -9,12 +9,13 @@
android:orientation="vertical">
<ImageView
android:layout_width="16dp"
android:layout_height="16dp"
android:layout_width="@dimen/cfr_triangle_width"
android:layout_height="@dimen/cfr_triangle_height"
android:layout_gravity="end"
android:layout_marginEnd="16dp"
android:layout_marginEnd="@dimen/cfr_triangle_margin_edge"
android:importantForAccessibility="no"
app:srcCompat="@drawable/ic_pbm_triangle" />
app:srcCompat="@drawable/ic_cfr_triangle"
app:tint="#7542E5" />
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"

View File

@ -13,11 +13,12 @@
<androidx.appcompat.widget.AppCompatImageView
android:visibility="gone"
android:id="@+id/drop_down_triangle"
android:layout_width="@dimen/tp_onboarding_triangle_width"
android:layout_height="@dimen/tp_onboarding_triangle_height"
android:layout_width="@dimen/cfr_triangle_width"
android:layout_height="@dimen/cfr_triangle_height"
android:importantForAccessibility="no"
android:rotation="0"
app:srcCompat="@drawable/ic_pbm_triangle"
app:srcCompat="@drawable/ic_cfr_triangle"
app:tint="#7542E5"
android:layout_gravity="center" />
<androidx.constraintlayout.widget.ConstraintLayout
@ -92,9 +93,10 @@
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/pop_up_triangle"
android:layout_width="16dp"
android:layout_height="@dimen/tp_onboarding_triangle_height"
android:layout_height="@dimen/cfr_triangle_height"
android:importantForAccessibility="no"
android:rotation="180"
app:srcCompat="@drawable/ic_pbm_triangle"
app:srcCompat="@drawable/ic_cfr_triangle"
app:tint="#7542E5"
android:layout_gravity="center" />
</LinearLayout>

View File

@ -25,8 +25,8 @@
<androidx.cardview.widget.CardView
android:id="@+id/mozac_browser_tabstray_card"
android:layout_width="92dp"
android:layout_height="69dp"
android:layout_width="@dimen/tab_tray_thumbnail_width"
android:layout_height="@dimen/tab_tray_thumbnail_height"
android:layout_marginStart="16dp"
android:layout_marginTop="8dp"
android:backgroundTint="?tabTrayThumbnailItemBackground"

View File

@ -0,0 +1,34 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 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/. -->
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/counter_root"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_centerVertical="true">
<ImageView
android:id="@+id/counter_box"
android:layout_width="@dimen/tab_counter_box_width_height"
android:layout_height="@dimen/tab_counter_box_width_height"
android:importantForAccessibility="no"
app:tint="@color/tab_icon"
app:srcCompat="@drawable/ic_tabs"/>
<!-- This text size auto adjusts based on num digits in `TabCounter` -->
<TextView
android:id="@+id/counter_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:textAlignment="center"
android:textColor="@color/tab_icon"
android:textSize="12sp"
android:textStyle="bold"
tools:text="16" />
</FrameLayout>

View File

@ -11,12 +11,13 @@
<ImageView
android:visibility="gone"
android:id="@+id/drop_down_triangle"
android:layout_width="@dimen/tp_onboarding_triangle_width"
android:layout_height="@dimen/tp_onboarding_triangle_height"
android:layout_marginStart="@dimen/tp_onboarding_triangle_margin_start"
android:layout_width="@dimen/cfr_triangle_width"
android:layout_height="@dimen/cfr_triangle_height"
android:layout_marginStart="@dimen/cfr_triangle_margin_edge"
android:importantForAccessibility="no"
android:rotation="0"
app:srcCompat="@drawable/ic_triangle"
app:srcCompat="@drawable/ic_cfr_triangle"
app:tint="#0250BB"
android:layout_gravity="start" />
<androidx.constraintlayout.widget.ConstraintLayout
@ -36,7 +37,7 @@
android:focusable="true"
android:contentDescription="@string/onboarding_close"
app:srcCompat="@drawable/ic_close"
android:tint="@color/primary_text_dark_theme"
app:tint="@color/primary_text_dark_theme"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
@ -60,11 +61,12 @@
<ImageView
android:id="@+id/pop_up_triangle"
android:layout_width="16dp"
android:layout_height="@dimen/tp_onboarding_triangle_height"
android:layout_marginStart="16dp"
android:layout_width="@dimen/cfr_triangle_width"
android:layout_height="@dimen/cfr_triangle_height"
android:layout_marginStart="@dimen/cfr_triangle_margin_edge"
android:importantForAccessibility="no"
android:rotation="180"
app:srcCompat="@drawable/ic_triangle"
app:srcCompat="@drawable/ic_cfr_triangle"
app:tint="#0250BB"
android:layout_gravity="start" />
</LinearLayout>

View File

@ -78,7 +78,7 @@
<!-- Browser menu button that opens the addon manager -->
<string name="browser_menu_add_ons">Askouezhioù</string>
<!-- Text displayed when there are no add-ons to be shown -->
<string name="no_add_ons">Nʼeus tamm enlugellad ebet amañ</string>
<string name="no_add_ons">Nʼeus tamm askouezh ebet amañ</string>
<!-- Browser menu button that sends a user to help articles -->
<string name="browser_menu_help">Skoazell</string>
<!-- Browser menu button that sends a to a the what's new article -->
@ -296,7 +296,7 @@
<!-- Text shown when user enters empty device name -->
<string name="empty_device_name_error">Anv an trevnad nʼhall ket bezañ goullo.</string>
<!-- Label indicating that sync is in progress -->
<string name="sync_syncing_in_progress">O goubredañ...</string>
<string name="sync_syncing_in_progress">O choubredañ...</string>
<!-- Label summary indicating that sync failed. The first parameter is the date stamp showing last time it succeeded -->
<string name="sync_failed_summary">Goubredañ cʼhwitet. Berzh diwezhañ: %s</string>
@ -390,7 +390,7 @@
<!-- Preference for using top toolbar -->
<string name="preference_top_toolbar">Krecʼh</string>
<!-- Preference for using bottom toolbar -->
<string name="preference_bottom_toolbar">Diaz</string>
<string name="preference_bottom_toolbar">Traoñ</string>
<!-- Theme Preferences -->
<!-- Preference for using light theme -->
@ -398,7 +398,7 @@
<!-- Preference for using dark theme -->
<string name="preference_dark_theme">Teñval</string>
<!-- Preference for using using dark or light theme automatically set by battery -->
<string name="preference_auto_battery_theme">Lakaet gant an esperner tredan</string>
<string name="preference_auto_battery_theme">Dibabet gant an esperner tredan</string>
<!-- Preference for using following device theme -->
<string name="preference_follow_device_theme">Mont gant neuz ar benveg</string>
@ -474,7 +474,7 @@
<!-- Open tabs menu item to share all tabs -->
<string name="tabs_menu_share_tabs">Rannañ an ivinelloù</string>
<!-- Open tabs menu item to save tabs to collection -->
<string name="tabs_menu_save_to_collection1">Enrollañ an ivinelloù en dastumad</string>
<string name="tabs_menu_save_to_collection1">Enrollañ an ivinelloù en un dastumad</string>
<!-- Content description (not visible, for screen readers etc.): Opens the tab menu when pressed -->
<string name="tab_menu">Lañser an ivinell</string>
@ -690,7 +690,7 @@
<!-- Content description (not visible, for screen readers etc.): Opens the collection menu when pressed -->
<string name="collection_menu_button_content_description">Lañser an dastumadoù</string>
<!-- No Open Tabs Message Header -->
<string name="no_collections_header1">Dastumit ar pezh a gont evidocʼh</string>
<string name="no_collections_header1">Dastumit ar pezh a zo pouezus evidoch</string>
<!-- Label to describe what collections are to a new user without any collections -->
<string name="no_collections_description1">Strollit ar cʼhlaskoù, al lecʼhiennoù hag an ivinelloù heñvel evit mont daveto buanocʼh.</string>
<!-- Title for the "select tabs" step of the collection creator -->
@ -863,7 +863,7 @@
<!-- Title for the cached images and files item in Delete browsing data -->
<string name="preferences_delete_browsing_data_cached_files">Skeudennoù ha restroù er chrubuilh</string>
<!-- Subtitle for the cached images and files item in Delete browsing data -->
<string name="preferences_delete_browsing_data_cached_files_subtitle">Dieubiñ a ra egor kadaviñ</string>
<string name="preferences_delete_browsing_data_cached_files_subtitle">Dieubiñ a raio egor kadaviñ</string>
<!-- Title for the site permissions item in Delete browsing data -->
<string name="preferences_delete_browsing_data_site_permissions">Aotreoù al lechienn</string>
@ -1170,7 +1170,7 @@
<string name="browser_toolbar_url_copied_to_clipboard_snackbar">URL eilet er golver</string>
<!-- Title text for the Add To Homescreen dialog -->
<string name="add_to_homescreen_title">Ouzhpennañ dʼar skramm degemer</string>
<string name="add_to_homescreen_title">Lakaat er skramm degemer</string>
<!-- Cancel button text for the Add to Homescreen dialog -->
<string name="add_to_homescreen_cancel">Nullañ</string>
<!-- Add button text for the Add to Homescreen dialog -->
@ -1365,7 +1365,7 @@
<!-- Bookmark deletion confirmation -->
<string name="bookmark_deletion_confirmation">Ha fellout a ra deocʼh dilemel ar sined-mañ ?</string>
<!-- Browser menu button that adds a top site to the home fragment -->
<string name="browser_menu_add_to_top_sites">Ouzhpennañ dʼal lecʼhiennoù gwellañ</string>
<string name="browser_menu_add_to_top_sites">Lakaat el lecʼhiennoù gwellañ</string>
<!-- text shown before the issuer name to indicate who its verified by, parameter is the name of
the certificate authority that verified the ticket-->
<string name="certificate_info_verified_by">Gwiriet gant:%1$s</string>

View File

@ -919,6 +919,20 @@
<!-- text for firefox preview moving tip header. "Firefox Nightly" is intentionally hardcoded -->
<string name="tip_firefox_preview_moved_header_preview_installed">Firefox Nightly हलले आहे</string>
<!-- text for firefox preview moving tip description -->
<string name="tip_firefox_preview_moved_description_preview_installed">या अॅपला यापुढे सुरक्षा अद्यतने प्राप्त होणार नाहीत. हे अॅप वापरणे थांबवा आणि नवीन Nightly वर स्विच करा.
\n\nआपल्या वाचनखुणा, लॉगिन, आणि इतिहास इतर अॅप वर स्थानांतरित करण्यासाठी, एक Firefox खाते त.ार करा.</string>
<!-- text for firefox preview moving tip button -->
<string name="tip_firefox_preview_moved_button_preview_installed">नवीन Nightly वर स्विच करा</string>
<!-- text for firefox preview moving tip header. "Firefox Nightly" is intentionally hardcoded -->
<string name="tip_firefox_preview_moved_header_preview_not_installed">Firefox Nightly हलले आहे</string>
<!-- text for firefox preview moving tip description -->
<string name="tip_firefox_preview_moved_description_preview_not_installed">या अॅपला यापुढे सुरक्षा अद्यतने प्राप्त होणार नाहीत. नवीन Nightly मिळवा आणि हे अॅप वापरणे थांबवा.
\n\nआपल्या वाचनखुणा, लॉगिन, आणि इतिहास इतर अॅप वर स्थानांतरित करण्यासाठी, एक Firefox खाते त.ार करा.</string>
<!-- text for firefox preview moving tip button -->
<string name="tip_firefox_preview_moved_button_preview_not_installed">नवीन Nightly मिळवा</string>
<!-- Onboarding -->
<!-- Text for onboarding welcome message
The first parameter is the name of the app (e.g. Firefox Preview) -->
@ -954,6 +968,8 @@
<string name="onboarding_firefox_account_sync_is_on">सिंक चालू आहे</string>
<!-- text to display in the snackbar if automatic sign-in fails. user may try again -->
<string name="onboarding_firefox_account_automatic_signin_failed">साइन इन करण्यात अयशस्वी</string>
<!-- text for the tracking protection onboarding card header -->
<string name="onboarding_tracking_protection_header_2">स्वयंचलित गोपनीयता</string>
<!-- text for tracking protection radio button option for strict level of blocking -->
<string name="onboarding_tracking_protection_strict_button">कठोर (सुचवलेले)</string>
<!-- text for the toolbar position card header

View File

@ -16,6 +16,13 @@
<!-- No Open Tabs Message Description -->
<string name="no_open_tabs_description">Filele deschise vor fi afișate aici.</string>
<!-- No Private Tabs Message Description -->
<string name="no_private_tabs_description">Aici vor fi afișate filele tale private.</string>
<!-- Message announced to the user when tab tray is selected with 1 tab -->
<string name="open_tab_tray_single">1 filă deschisă. Atinge pentru a comuta filele.</string>
<!-- Message announced to the user when tab tray is selected with 0 or 2+ tabs -->
<string name="open_tab_tray_plural">%1$s file deschise. Atinge pentru a comuta filele.</string>
<!-- About content. The first parameter is the name of the application. (For example: Fenix) -->
<string name="about_content">%1$s este realizat de Mozilla.</string>
@ -38,6 +45,9 @@
<!-- Text for the negative button -->
<string name="cfr_neg_button_text">Nu, mulțumesc</string>
<!-- Search widget "contextual feature recommendation" (CFR) -->
<!-- Text for the main message. 'Firefox' intentionally hardcoded here.-->
<string name="search_widget_cfr_message">Accesează Firefox mai repede. Adaugă un widget pe ecranul de start.</string>
<!-- Text for the positive button -->
<string name="search_widget_cfr_pos_button_text">Adaugă widget</string>
<!-- Text for the negative button -->
@ -110,6 +120,8 @@
<string name="browser_menu_powered_by2">Cu tehnologie %1$s</string>
<!-- Browser menu button to put the current page in reader mode -->
<string name="browser_menu_read">Mod de lectură</string>
<!-- Browser menu button content description to close reader mode and return the user to the regular browser -->
<string name="browser_menu_read_close">Închide modul de lectură</string>
<!-- Browser menu button to open the current page in an external app -->
<string name="browser_menu_open_app_link">Deschide în aplicație</string>
<!-- Browser menu button to configure reader mode appearance e.g. the used font type and size -->
@ -251,6 +263,8 @@
<string name="preferences_show_search_shortcuts">Afișează comenzile rapide pentru căutări</string>
<!-- Preference title for switch preference to show search suggestions -->
<string name="preferences_show_search_suggestions">Afișează sugestii de căutare</string>
<!-- Preference title for switch preference to show voice search button -->
<string name="preferences_show_voice_search">Afișează căutarea vocală</string>
<!-- Preference title for switch preference to show search suggestions also in private mode -->
<string name="preferences_show_search_suggestions_in_private">Afișează în sesiuni private</string>
<!-- Preference title for switch preference to show a clipboard suggestion when searching -->
@ -367,6 +381,9 @@
<!-- Preference for removing FxA account -->
<string name="preferences_sync_remove_account">Șterge contul</string>
<!-- Pairing Feature strings -->
<!-- Instructions on how to access pairing -->
<string name="pair_instructions_2"><![CDATA[Scanează codul QR afișat pe <b>firefox.com/pair</b>]]></string>
<!-- Button to open camera for pairing -->
<string name="pair_open_camera">Cameră deschisă</string>
<!-- Button to cancel pairing -->
@ -408,6 +425,8 @@
<!-- Option in Library to open History page -->
<string name="library_history">Istoric</string>
<!-- Option in Library to open Synced Tabs page -->
<string name="library_synced_tabs">File sincronizate</string>
<!-- Option in Library to open Reading List -->
<string name="library_reading_list">Listă de lectură</string>
<!-- Menu Item Label for Search in Library -->
@ -430,6 +449,24 @@
<string name="add_tab">Adaugă fila</string>
<!-- Content description (not visible, for screen readers etc.): Add tab button. Adds a news tab when pressed -->
<string name="add_private_tab">Adaugă filă privată</string>
<!-- Text for the new tab button to indicate adding a new private tab in the tab -->
<string name="tab_drawer_fab_content">Privată</string>
<!-- Text shown as the title of the open tab tray -->
<string name="tab_tray_title">File deschise</string>
<!-- Text shown in the menu for saving tabs to a collection -->
<string name="tab_tray_menu_item_save">Salvează în colecție</string>
<!-- Text shown in the menu for sharing all tabs -->
<string name="tab_tray_menu_item_share">Partajează toate filele</string>
<!-- Text shown in the menu for closing all tabs -->
<string name="tab_tray_menu_item_close">Închide toate filele</string>
<!-- Shortcut action to open new tab -->
<string name="tab_tray_menu_open_new_tab">Filă nouă</string>
<!-- Shortcut action to open the home screen -->
<string name="tab_tray_menu_home">Acasă</string>
<!-- Shortcut action to toggle private mode -->
<string name="tab_tray_menu_toggle">Mod de comutare a filelor</string>
<!-- Content description (not visible, for screen readers etc.): Removes tab from collection button. Removes the selected tab from collection when pressed -->
<string name="remove_tab_from_collection">Elimină fila din colecție</string>
<!-- Content description (not visible, for screen readers etc.): Close tab button. Closes the current session when pressed -->
<string name="close_tab">Închide fila</string>
<!-- Content description (not visible, for screen readers etc.): Close tab <title> button. First parameter is tab title -->
@ -440,6 +477,8 @@
<string name="tabs_menu_close_all_tabs">Închide toate filele</string>
<!-- Open tabs menu item to share all tabs -->
<string name="tabs_menu_share_tabs">Partajează filele</string>
<!-- Open tabs menu item to save tabs to collection -->
<string name="tabs_menu_save_to_collection1">Salvează filele în colecție</string>
<!-- Content description (not visible, for screen readers etc.): Opens the tab menu when pressed -->
<string name="tab_menu">Meniu de file</string>
<!-- Tab menu item to share the tab -->
@ -463,6 +502,9 @@
<!-- Text for the menu button to remove a top site -->
<string name="remove_top_site">Șterge</string>
<!-- Postfix for private WebApp titles, placeholder is replaced with app name -->
<string name="pwa_site_controls_title_private">%1$s (mod privat)</string>
<!-- History -->
<!-- Text for the button to clear all history -->
<string name="history_delete_all">Șterge istoricul</string>
@ -652,6 +694,10 @@
<!-- Content description (not visible, for screen readers etc.): Opens the collection menu when pressed -->
<string name="collection_menu_button_content_description">Meniu de colecții</string>
<!-- No Open Tabs Message Header -->
<string name="no_collections_header1">Colectează ce te interesează</string>
<!-- Label to describe what collections are to a new user without any collections -->
<string name="no_collections_description1">Grupează căutările similare, site-urile și filele pentru acces mai rapid ulterior.</string>
<!-- Title for the "select tabs" step of the collection creator -->
<string name="create_collection_select_tabs">Selectează filele</string>
<!-- Title for the "select collection" step of the collection creator -->
@ -681,6 +727,9 @@
<!-- Button to save currently selected tabs in the "select tabs" step of the collection creator-->
<string name="create_collection_save">Salvează</string>
<!-- Snackbar action to view the collection the user just created or updated -->
<string name="create_collection_view">Afișează</string>
<!-- Default name for a new collection in "name new collection" step of the collection creator. %d is a placeholder for the number of collections-->
<string name="create_collection_default_name">Colecția %d</string>
@ -791,11 +840,15 @@
<!-- Title for Accessibility Text Size Scaling Preference -->
<string name="preference_accessibility_font_size_title">Dimensiunea fontului</string>
<!-- Title for Accessibility Text Automatic Size Scaling Preference -->
<string name="preference_accessibility_auto_size_2">Mărime automată a fontului</string>
<!-- Summary for Accessibility Text Automatic Size Scaling Preference -->
<string name="preference_accessibility_auto_size_summary">Dimensiunea fontului va coincide cu cea din setările Android. Dezactivează pentru a gestiona aici dimensiunea fontului.</string>
<!-- Title for the Delete browsing data preference -->
<string name="preferences_delete_browsing_data">Șterge datele de navigare</string>
<!-- Title for the tabs item in Delete browsing data -->
<string name="preferences_delete_browsing_data_tabs_title_2">File deschise</string>
<!-- Subtitle for the tabs item in Delete browsing data, parameter will be replaced with the number of open tabs -->
<string name="preferences_delete_browsing_data_tabs_subtitle">%d (de) file</string>
@ -827,8 +880,6 @@
<string name="preference_summary_delete_browsing_data_on_quit">Șterge automat datele de navigare când selectezi „Închide” în meniul principal</string>
<!-- Summary for the Delete browsing data on quit preference. "Quit" translation should match delete_browsing_data_on_quit_action translation. -->
<string name="preference_summary_delete_browsing_data_on_quit_2">Șterge automat datele de navigare când selectezi \„Închide\” în meniul principal</string>
<!-- Category for history items to delete on quit in delete browsing data on quit -->
<string name="preferences_delete_browsing_data_on_quit_browsing_history">Istoric de navigare</string>
<!-- Action item in menu for the Delete browsing data on quit feature -->
<string name="delete_browsing_data_on_quit_action">Închide</string>
@ -852,6 +903,9 @@
<string name="tip_firefox_preview_moved_description">Firefox Nightly se actualizează în fiecare seară și are funcționalități experimentale noi
Dar poate fi mai puțin stabil. Descarcă browserul nostru beta pentru o experiență mai stabilă.</string>
<!-- text for firefox preview moving tip button. "Firefox for Android Beta" is intentionally hardcoded -->
<string name="tip_firefox_preview_moved_button_2">Descarcă Firefox Beta pentru Android</string>
<!-- text for firefox preview moving tip header. "Firefox Nightly" is intentionally hardcoded -->
<string name="tip_firefox_preview_moved_header_preview_installed">Firefox Nightly s-a mutat</string>
<!-- text for firefox preview moving tip description -->
@ -903,8 +957,21 @@
<string name="onboarding_firefox_account_sync_is_on">Sync este activat</string>
<!-- text to display in the snackbar if automatic sign-in fails. user may try again -->
<string name="onboarding_firefox_account_automatic_signin_failed">Eroare de autentificare</string>
<!-- text for the tracking protection onboarding card header -->
<string name="onboarding_tracking_protection_header_2">Confidențialitate automată</string>
<!-- text for the tracking protection card description
The first parameter is the name of the app (e.g. Firefox Preview) -->
<string name="onboarding_tracking_protection_description_2">Setările de confidențialitate și securitate blochează elementele de urmărire, softurile rău-intenționate și firmele care te urmăresc.</string>
<!-- text for tracking protection radio button option for standard level of blocking -->
<string name="onboarding_tracking_protection_standard_button_2">Standard (implicit)</string>
<!-- text for standard blocking option button description -->
<string name="onboarding_tracking_protection_standard_button_description_2">Blochează mai puține elemente de urmărire. Paginile se vor încărca normal.</string>
<!-- text for tracking protection radio button option for strict level of blocking -->
<string name="onboarding_tracking_protection_strict_button">Strictă (recomandat)</string>
<!-- text for tracking protection radio button option for strict level of blocking -->
<string name="onboarding_tracking_protection_strict_option">Strictă</string>
<!-- text for strict blocking option button description -->
<string name="onboarding_tracking_protection_strict_button_description_2">Blochează mai multe elemente de urmărire, reclame și ferestre pop-up. Paginile se încarcă mai repede, dar se poate ca unele funcționalități să nu meargă.</string>
<!-- text for the toolbar position card header
In English this is an idiom for "choose a side as in an argument or fight"
but it is ok to make this more literally about "choosing a position in a physical space -->
@ -991,14 +1058,22 @@
<string name="preference_enhanced_tracking_protection_explanation">Păstrează-ți datele pentru tine. %s te protejează de multe dintre cele mai frecvente elemente de urmărire care monitorizează ce faci online.</string>
<!-- Text displayed that links to website about enhanced tracking protection -->
<string name="preference_enhanced_tracking_protection_explanation_learn_more">Află mai multe</string>
<!-- Preference for enhanced tracking protection for the standard protection settings -->
<string name="preference_enhanced_tracking_protection_standard_default_1">Standard (implicit)</string>
<!-- Preference description for enhanced tracking protection for the standard protection settings -->
<string name="preference_enhanced_tracking_protection_standard_description_3">Blochează mai puține elemente de urmărire. Paginile se vor încărca normal.</string>
<!-- Accessibility text for the Standard protection information icon -->
<string name="preference_enhanced_tracking_protection_standard_info_button">Ce blochează protecția standard împotriva urmăririi</string>
<!-- Preference for enhanced tracking protection for the strict protection settings -->
<string name="preference_enhanced_tracking_protection_strict">Strictă</string>
<!-- Preference description for enhanced tracking protection for the strict protection settings -->
<string name="preference_enhanced_tracking_protection_strict_description_2">Blochează mai multe elemente de urmărire, reclame și ferestre pop-up. Paginile se încarcă mai repede, dar se poate ca unele funcționalități să nu meargă.</string>
<!-- Accessibility text for the Strict protection information icon -->
<string name="preference_enhanced_tracking_protection_strict_info_button">Ce blochează protecția strictă împotriva urmăririi</string>
<!-- Preference for enhanced tracking protection for the custom protection settings -->
<string name="preference_enhanced_tracking_protection_custom">Personalizat</string>
<!-- Preference description for enhanced tracking protection for the strict protection settings -->
<string name="preference_enhanced_tracking_protection_custom_description_2">Alege ce elemente de urmărire și scripturi să blochezi.</string>
<!-- Accessibility text for the Strict protection information icon -->
<string name="preference_enhanced_tracking_protection_custom_info_button">Ce blochează protecția personalizată împotriva urmăririi</string>
<!-- Header for categories that are being blocked by current Enhanced Tracking Protection settings -->
@ -1048,6 +1123,8 @@
<!-- Description of tracking content that can be blocked by Enhanced Tracking Protection -->
<string name="etp_tracking_content_description">Oprește încărcarea reclamelor, videoclipurilor și a altor conținuturi externe care conțin coduri de urmărire. Poate afecta anumite funcționalități ale site-ului.</string>
<!-- Enhanced Tracking Protection Onboarding Message shown in a dialog above the toolbar. The first parameter is the name of the application (For example: Fenix) -->
<string name="etp_onboarding_cfr_message">Când scutul este violet, %s a blocat elemente de urmărire de pe un site. Atinge pentru mai multe informații.</string>
<!-- Enhanced Tracking Protection message that protection is currently on for this site -->
<string name="etp_panel_on">Protecțiile sunt ACTIVE pentru acest site</string>
<!-- Enhanced Tracking Protection message that protection is currently off for this site -->
@ -1069,6 +1146,8 @@
<!-- About page link text to open support link -->
<string name="about_support">Asistență</string>
<!-- About page link text to list of past crashes (like about:crashes on desktop) -->
<string name="about_crashes">Defecțiuni</string>
<!-- About page link text to open privacy notice link -->
<string name="about_privacy_notice">Politică de confidențialitate</string>
<!-- About page link text to open know your rights link -->
@ -1204,6 +1283,13 @@
<!-- Summary for Accessibility Force Enable Zoom Preference -->
<string name="preference_accessibility_force_enable_zoom_summary">Activează permiterea apropierii/depărtării cu degetele pentru zoom, chiar și pe site-uri web care împiedică acest gest.</string>
<!-- Saved logins sorting strategy menu item -by name- (if selected, it will sort saved logins alphabetically) -->
<string name="saved_logins_sort_strategy_alphabetically">Denumire (A-Z)</string>
<!-- Saved logins sorting strategy menu item -by last used- (if selected, it will sort saved logins by last used) -->
<string name="saved_logins_sort_strategy_last_used">Folosite ultima dată</string>
<!-- Content description (not visible, for screen readers etc.): Sort saved logins dropdown menu chevron icon -->
<string name="saved_logins_menu_dropdown_chevron_icon_content_description">Meniu de sortare a datelor de autentificare</string>
<!-- Title of the Add search engine screen -->
<string name="search_engine_add_custom_search_engine_title">Adaugă un motor de căutare</string>
<!-- Title of the Edit search engine screen -->
@ -1296,11 +1382,57 @@
<string name="certificate_info_verified_by">Verificat de: %1$s</string>
<!-- Login overflow menu delete button -->
<string name="login_menu_delete_button">Șterge</string>
<!-- Login overflow menu edit button -->
<string name="login_menu_edit_button">Editează</string>
<!-- Message in delete confirmation dialog for logins -->
<string name="login_deletion_confirmation">Sigur vrei să ștergi aceste date de autentificare?</string>
<!-- Positive action of a dialog asking to delete -->
<string name="dialog_delete_positive">Șterge</string>
<!-- The saved login options menu description. -->
<string name="login_options_menu">Opțiuni de autentificare</string>
<!-- The editable text field for a login's web address. -->
<string name="saved_login_hostname_description">Câmpul de text editabil pentru adresa web a datelor de autentificare.</string>
<!-- The editable text field for a login's username. -->
<string name="saved_login_username_description">Câmpul de text editabil pentru numele de utilizator al datelor de autentificare.</string>
<!-- The editable text field for a login's password. -->
<string name="saved_login_password_description">Câmp de text editabil pentru parola datelor de autentificare.</string>
<!-- The button description to save changes to an edited login. -->
<string name="save_changes_to_login">Salvează modificările datelor de autentificare.</string>
<!-- The button description to discard changes to an edited login. -->
<string name="discard_changes">Renunță la modificări</string>
<!-- The page title for editing a saved login. -->
<string name="edit">Editează</string>
<!-- The error message in edit login view when password field is blank. -->
<string name="saved_login_password_required">Necesită o parolă</string>
<!-- Voice search button content description -->
<string name="voice_search_content_description">Căutare vocală</string>
<!-- Voice search prompt description displayed after the user presses the voice search button -->
<string name="voice_search_explainer">Vorbește acum</string>
<!-- The error message in edit login view when a duplicate username exists. -->
<string name="saved_login_duplicate">Există un set de date de autentificare cu acest nume de utilizator</string>
<!-- Synced Tabs -->
<!-- Text displayed when user is not logged into a Firefox Account -->
<string name="synced_tabs_connect_to_sync_account">Conectare cu un cont Firefox.</string>
<!-- Text displayed to ask user to connect another device as no devices found with account -->
<string name="synced_tabs_connect_another_device">Conectează alt dispozitiv.</string>
<!-- Text displayed asking user to re-authenticate -->
<string name="synced_tabs_reauth">Te rugăm să te autentifici din nou.</string>
<!-- Text displayed when user has disabled tab syncing in Firefox Sync Account -->
<string name="synced_tabs_enable_tab_syncing">Te rugăm să activezi sincronizarea filelor.</string>
<!-- Text displayed when user has no tabs that have been synced -->
<string name="synced_tabs_no_tabs">Nu ai nicio filă deschisă în Firefox pe celelalte dispozitive.</string>
<!-- Text displayed in the synced tabs screen when a user is not signed in to Firefox Sync describing Synced Tabs -->
<string name="synced_tabs_sign_in_message">Afișează o listă de file de pe celelalte dispozitive.</string>
<!-- Text displayed on a button in the synced tabs screen to link users to sign in when a user is not signed in to Firefox Sync -->
<string name="synced_tabs_sign_in_button">Autentifică-te în Sync</string>
<!-- Top Sites -->
<!-- Title text displayed in the dialog when top sites limit is reached. -->
<string name="top_sites_max_limit_title">A fost atinsă limita de site-uri de top</string>
<!-- Content description text displayed in the dialog when top sites limit is reached. -->
<string name="top_sites_max_limit_content">Pentru a adăuga un site de top nou, trebuie să elimini unul existent. Ține degetul pe denumirea site-ului pentru selectare și apoi atinge „Elimină”.</string>
<!-- Confirmation dialog button text when top sites limit is reached. -->
<string name="top_sites_max_limit_confirmation_button">Ok, am înțeles</string>

View File

@ -84,6 +84,8 @@
<declare-styleable name="OnboardingRadioButton">
<attr name="onboardingKey" format="reference" />
<attr name="onboardingKeyTitle" format="reference" />
<attr name="onboardingKeyDescription" format="reference" />
</declare-styleable>
</resources>

View File

@ -28,7 +28,7 @@
<dimen name="radio_button_padding_vertical">12dp</dimen>
<dimen name="radio_button_preference_height">48dp</dimen>
<dimen name="radio_button_preference_horizontal">16dp</dimen>
<dimen name="radio_button_preference_drawable_padding">16dp</dimen>
<dimen name="radio_button_preference_drawable_padding">24dp</dimen>
<dimen name="radio_button_preference_vertical">12dp</dimen>
<dimen name="phone_feature_label_recommended_text_size">14sp</dimen>
<dimen name="site_permissions_exceptions_item_text_size">18sp</dimen>
@ -42,9 +42,10 @@
<dimen name="context_menu_height">48dp</dimen>
<dimen name="context_menu_x_offset">8dp</dimen>
<dimen name="tp_onboarding_width">256dp</dimen>
<dimen name="tp_onboarding_triangle_height">16dp</dimen>
<dimen name="tp_onboarding_triangle_width">16dp</dimen>
<dimen name="tp_onboarding_triangle_margin_start">16dp</dimen>
<!-- Dimensions for the CFR (Contextual Feature Recommendation) tooltip triangle. -->
<dimen name="cfr_triangle_height">16dp</dimen>
<dimen name="cfr_triangle_width">16dp</dimen>
<dimen name="cfr_triangle_margin_edge">16dp</dimen>
<dimen name="tab_counter_box_width_height">24dp</dimen>
@ -155,6 +156,8 @@
<!-- Tabs Tray -->
<dimen name="tab_tray_top_offset">40dp</dimen>
<dimen name="tab_tray_thumbnail_width">92dp</dimen>
<dimen name="tab_tray_thumbnail_height">69dp</dimen>
<!-- Saved Logins Fragment -->
<dimen name="saved_logins_sort_menu_dropdown_chevron_icon_margin_start">10dp</dimen>

View File

@ -33,4 +33,7 @@
<!-- Label for the secret settings preference -->
<string name="preferences_debug_settings">Secret Settings</string>
<string name="preferences_debug_settings_enable_tab_tray">Use New Tab Tray</string>
<!-- Content description (not visible, for screen readers etc.) used to announce [LinkTextView]. -->
<string name="link_text_view_type_announcement" translatable="false">link</string>
</resources>

View File

@ -1432,6 +1432,4 @@
<string name="top_sites_max_limit_content">To add a new top site, remove one. Long press the site and select remove.</string>
<!-- Confirmation dialog button text when top sites limit is reached. -->
<string name="top_sites_max_limit_confirmation_button">OK, Got It</string>
<!-- Content description (not visible, for screen readers etc.) used to announce [LinkTextView]. -->
<string name="link_text_view_type_announcement">link</string>
</resources>

View File

@ -6,6 +6,8 @@ package org.mozilla.fenix
import android.content.Intent
import android.os.Bundle
import io.mockk.every
import io.mockk.spyk
import mozilla.components.support.test.robolectric.testContext
import mozilla.components.support.utils.toSafeIntent
import org.junit.Assert.assertEquals
@ -13,6 +15,7 @@ import org.junit.Assert.assertFalse
import org.junit.Assert.assertNotEquals
import org.junit.Assert.assertNull
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mozilla.fenix.HomeActivity.Companion.PRIVATE_BROWSING_MODE
@ -24,10 +27,17 @@ import org.mozilla.fenix.helpers.FenixRobolectricTestRunner
@RunWith(FenixRobolectricTestRunner::class)
class HomeActivityTest {
private lateinit var activity: HomeActivity
@Before
fun setup() {
activity = spyk(HomeActivity())
every { activity.applicationContext } returns testContext
}
@Test
fun getIntentSource() {
val activity = HomeActivity()
val launcherIntent = Intent(Intent.ACTION_MAIN).apply {
addCategory(Intent.CATEGORY_LAUNCHER)
}.toSafeIntent()
@ -42,8 +52,6 @@ class HomeActivityTest {
@Test
fun `getModeFromIntentOrLastKnown returns mode from settings when intent does not set`() {
val activity = HomeActivity()
testContext.settings().lastKnownMode = BrowsingMode.Private
assertEquals(testContext.settings().lastKnownMode, activity.getModeFromIntentOrLastKnown(null))
@ -51,8 +59,6 @@ class HomeActivityTest {
@Test
fun `getModeFromIntentOrLastKnown returns mode from intent when set`() {
val activity = HomeActivity()
testContext.settings().lastKnownMode = BrowsingMode.Normal
val intent = Intent()
@ -64,21 +70,16 @@ class HomeActivityTest {
@Test
fun `isActivityColdStarted returns true for null savedInstanceState and not launched from history`() {
val activity = HomeActivity()
assertTrue(activity.isActivityColdStarted(Intent(), null))
}
@Test
fun `isActivityColdStarted returns false for valid savedInstanceState and not launched from history`() {
val activity = HomeActivity()
assertFalse(activity.isActivityColdStarted(Intent(), Bundle()))
}
@Test
fun `isActivityColdStarted returns false for null savedInstanceState and launched from history`() {
val activity = HomeActivity()
val startingIntent = Intent().apply {
flags = flags or Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY
}
@ -88,7 +89,6 @@ class HomeActivityTest {
@Test
fun `isActivityColdStarted returns false for null savedInstanceState and not launched from history`() {
val activity = HomeActivity()
val startingIntent = Intent().apply {
flags = flags or Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY
}

View File

@ -5,14 +5,19 @@
package org.mozilla.fenix.browser.browsingmode
import io.mockk.MockKAnnotations
import io.mockk.Runs
import io.mockk.every
import io.mockk.impl.annotations.MockK
import io.mockk.just
import io.mockk.verify
import org.junit.Assert.assertEquals
import org.junit.Before
import org.junit.Test
import org.mozilla.fenix.utils.Settings
class DefaultBrowsingModeManagerTest {
@MockK lateinit var settings: Settings
@MockK(relaxed = true) lateinit var callback: (BrowsingMode) -> Unit
lateinit var manager: BrowsingModeManager
@ -21,7 +26,9 @@ class DefaultBrowsingModeManagerTest {
@Before
fun before() {
MockKAnnotations.init(this)
manager = DefaultBrowsingModeManager(initMode, callback)
manager = DefaultBrowsingModeManager(initMode, settings, callback)
every { settings.lastKnownMode = any() } just Runs
}
@Test
@ -46,8 +53,10 @@ class DefaultBrowsingModeManagerTest {
manager.mode = BrowsingMode.Private
assertEquals(BrowsingMode.Private, manager.mode)
verify { settings.lastKnownMode = BrowsingMode.Private }
manager.mode = BrowsingMode.Normal
assertEquals(BrowsingMode.Normal, manager.mode)
verify { settings.lastKnownMode = BrowsingMode.Normal }
}
}

View File

@ -4,43 +4,43 @@
package org.mozilla.fenix.components
import android.content.Context
import io.mockk.MockKAnnotations
import io.mockk.Runs
import io.mockk.every
import io.mockk.impl.annotations.MockK
import io.mockk.just
import io.mockk.mockk
import io.mockk.verify
import mozilla.components.concept.sync.AccountObserver
import mozilla.components.concept.sync.AuthType
import mozilla.components.concept.sync.OAuthAccount
import mozilla.components.service.fxa.DeviceConfig
import mozilla.components.service.fxa.ServerConfig
import mozilla.components.service.fxa.SyncConfig
import mozilla.components.service.fxa.manager.FxaAccountManager
import mozilla.components.support.base.observer.ObserverRegistry
import org.junit.Before
import org.junit.Test
import org.mozilla.fenix.components.metrics.Event
import org.mozilla.fenix.components.metrics.MetricController
import org.mozilla.fenix.utils.Settings
class BackgroundServicesTest {
class TestableBackgroundServices(
val context: Context
) : BackgroundServices(context, mockk(), mockk(), mockk(), mockk(), mockk(), mockk()) {
override fun makeAccountManager(
context: Context,
serverConfig: ServerConfig,
deviceConfig: DeviceConfig,
syncConfig: SyncConfig?
) = mockk<FxaAccountManager>(relaxed = true)
@MockK private lateinit var metrics: MetricController
@MockK private lateinit var settings: Settings
private lateinit var observer: TelemetryAccountObserver
private lateinit var registry: ObserverRegistry<AccountObserver>
@Before
fun setup() {
MockKAnnotations.init(this)
every { metrics.track(any()) } just Runs
every { settings.fxaSignedIn = any() } just Runs
observer = TelemetryAccountObserver(mockk(relaxed = true), metrics)
registry = ObserverRegistry<AccountObserver>().apply { register(observer) }
}
@Test
fun `telemetry account observer`() {
val metrics = mockk<MetricController>()
every { metrics.track(any()) } just Runs
val observer = TelemetryAccountObserver(mockk(relaxed = true), metrics)
val registry = ObserverRegistry<AccountObserver>()
registry.register(observer)
val account = mockk<OAuthAccount>()
// Sign-in

View File

@ -7,6 +7,7 @@ package org.mozilla.fenix.components
import android.content.Context
import io.mockk.mockk
import org.mozilla.fenix.utils.ClipboardHandler
import org.mozilla.fenix.utils.Settings
class TestComponents(private val context: Context) : Components(context) {
override val backgroundServices by lazy {
@ -29,4 +30,6 @@ class TestComponents(private val context: Context) : Components(context) {
override val analytics by lazy { Analytics(context) }
override val clipboardHandler by lazy { ClipboardHandler(context) }
override val settings by lazy { mockk<Settings>(relaxed = true) }
}

View File

@ -543,7 +543,10 @@ class DefaultBrowserToolbarControllerTest {
@Test
fun handleToolbarNewTabPress() = runBlockingTest {
val browsingModeManager: BrowsingModeManager = DefaultBrowsingModeManager(BrowsingMode.Private) {}
val browsingModeManager: BrowsingModeManager = DefaultBrowsingModeManager(
BrowsingMode.Private,
mockk(relaxed = true)
) {}
val item = TabCounterMenuItem.NewTab(false)
every { activity.browsingModeManager } returns browsingModeManager
@ -556,7 +559,10 @@ class DefaultBrowserToolbarControllerTest {
@Test
fun handleToolbarNewPrivateTabPress() = runBlockingTest {
val browsingModeManager: BrowsingModeManager = DefaultBrowsingModeManager(BrowsingMode.Normal) {}
val browsingModeManager: BrowsingModeManager = DefaultBrowsingModeManager(
BrowsingMode.Normal,
mockk(relaxed = true)
) {}
val item = TabCounterMenuItem.NewTab(true)
every { activity.browsingModeManager } returns browsingModeManager

View File

@ -7,15 +7,14 @@ package org.mozilla.fenix.home.sessioncontrol.viewholders.onboarding
import android.view.LayoutInflater
import android.view.View
import io.mockk.every
import io.mockk.mockk
import kotlinx.android.synthetic.main.onboarding_toolbar_position_picker.view.*
import mozilla.components.support.test.robolectric.testContext
import org.junit.After
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mozilla.fenix.ext.components
import org.mozilla.fenix.helpers.FenixRobolectricTestRunner
import org.mozilla.fenix.utils.Settings
@ -27,16 +26,10 @@ class OnboardingToolbarPositionPickerViewHolderTest {
@Before
fun setup() {
val components = testContext.components
view = LayoutInflater.from(testContext)
.inflate(OnboardingToolbarPositionPickerViewHolder.LAYOUT_ID, null)
settings = mockk(relaxed = true)
Settings.instance = settings
}
@After
fun teardown() {
Settings.instance = null
settings = components.settings
}
@Test

View File

@ -0,0 +1,183 @@
/* 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.push
import android.util.Base64
import io.mockk.Called
import io.mockk.CapturingSlot
import io.mockk.MockKAnnotations
import io.mockk.Runs
import io.mockk.every
import io.mockk.impl.annotations.MockK
import io.mockk.just
import io.mockk.mockk
import io.mockk.mockkStatic
import io.mockk.slot
import io.mockk.unmockkStatic
import io.mockk.verify
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.TestCoroutineScope
import kotlinx.coroutines.test.runBlockingTest
import mozilla.components.concept.engine.Engine
import mozilla.components.concept.engine.webpush.WebPushDelegate
import mozilla.components.concept.engine.webpush.WebPushHandler
import mozilla.components.concept.engine.webpush.WebPushSubscription
import mozilla.components.feature.push.AutoPushFeature
import mozilla.components.feature.push.AutoPushSubscription
import org.junit.After
import org.junit.Before
import org.junit.Test
@ExperimentalCoroutinesApi
class WebPushEngineIntegrationTest {
private val scope = TestCoroutineScope()
@MockK private lateinit var engine: Engine
@MockK private lateinit var pushFeature: AutoPushFeature
@MockK(relaxed = true) private lateinit var handler: WebPushHandler
private lateinit var delegate: CapturingSlot<WebPushDelegate>
private lateinit var integration: WebPushEngineIntegration
@Before
fun setup() {
MockKAnnotations.init(this)
mockkStatic(Base64::class)
delegate = slot()
every { engine.registerWebPushDelegate(capture(delegate)) } returns handler
every { pushFeature.register(any()) } just Runs
every { pushFeature.unregister(any()) } just Runs
every { Base64.decode(any<ByteArray>(), any()) } answers { firstArg() }
integration = WebPushEngineIntegration(engine, pushFeature, scope)
}
@After
fun teardown() {
unmockkStatic(Base64::class)
}
@Test
fun `methods are no-op before calling start`() = scope.runBlockingTest {
integration.onMessageReceived("push", null)
integration.onSubscriptionChanged("push")
verify { handler wasNot Called }
integration.start()
integration.onMessageReceived("push", null)
verify { handler.onPushMessage("push", null) }
integration.onSubscriptionChanged("push")
verify { handler.onSubscriptionChanged("push") }
}
@Test
fun `start and stop register and unregister pushFeature`() {
integration.start()
verify { pushFeature.register(integration) }
integration.stop()
verify { pushFeature.unregister(integration) }
}
@Test
fun `delegate calls getSubscription`() {
integration.start()
val slot = slot<(AutoPushSubscription?) -> Unit>()
every { pushFeature.getSubscription("scope", block = capture(slot)) } just Runs
val onSubscription = mockk<(WebPushSubscription?) -> Unit>(relaxed = true)
delegate.captured.onGetSubscription("scope", onSubscription)
verify { onSubscription wasNot Called }
slot.captured(AutoPushSubscription(
scope = "scope",
publicKey = "abc",
endpoint = "def",
authKey = "xyz",
appServerKey = null
))
verify {
onSubscription(
WebPushSubscription(
scope = "scope",
publicKey = "abc".toByteArray(),
endpoint = "def",
authSecret = "xyz".toByteArray(),
appServerKey = null
)
)
}
}
@Test
fun `delegate calls subscribe`() {
integration.start()
val onSubscribeError = slot<() -> Unit>()
val onSubscribe = slot<(AutoPushSubscription?) -> Unit>()
every {
pushFeature.subscribe(
scope = "scope",
appServerKey = null,
onSubscribeError = capture(onSubscribeError),
onSubscribe = capture(onSubscribe)
)
} just Runs
val onSubscription = mockk<(WebPushSubscription?) -> Unit>(relaxed = true)
delegate.captured.onSubscribe("scope", null, onSubscription)
verify { onSubscription wasNot Called }
onSubscribeError.captured()
verify { onSubscription(null) }
onSubscribe.captured(AutoPushSubscription(
scope = "scope",
publicKey = "abc",
endpoint = "def",
authKey = "xyz",
appServerKey = null
))
verify {
onSubscription(
WebPushSubscription(
scope = "scope",
publicKey = "abc".toByteArray(),
endpoint = "def",
authSecret = "xyz".toByteArray(),
appServerKey = null
)
)
}
}
@Test
fun `delegate calls unsubscribe`() {
integration.start()
val onUnsubscribeError = slot<() -> Unit>()
val onUnsubscribe = slot<(Boolean) -> Unit>()
every {
pushFeature.unsubscribe(
scope = "scope",
onUnsubscribeError = capture(onUnsubscribeError),
onUnsubscribe = capture(onUnsubscribe)
)
} just Runs
val onUnsubscription = mockk<(Boolean) -> Unit>(relaxed = true)
delegate.captured.onUnsubscribe("scope", onUnsubscription)
verify { onUnsubscription wasNot Called }
onUnsubscribeError.captured()
verify { onUnsubscription(false) }
onUnsubscribe.captured(true)
verify { onUnsubscription(true) }
}
}

View File

@ -35,7 +35,6 @@ import org.mozilla.fenix.ext.settings
import org.mozilla.fenix.helpers.FenixRobolectricTestRunner
import org.mozilla.fenix.settings.SupportUtils
import org.mozilla.fenix.utils.Settings
import org.mozilla.fenix.whatsnew.clear
@ExperimentalCoroutinesApi
@RunWith(FenixRobolectricTestRunner::class)
@ -48,10 +47,10 @@ class DefaultSearchControllerTest {
private val searchEngine: SearchEngine = mockk(relaxed = true)
private val metrics: MetricController = mockk(relaxed = true)
private val sessionManager: SessionManager = mockk(relaxed = true)
private val settings: Settings = mockk(relaxed = true)
private val clearToolbarFocus: (() -> Unit) = mockk(relaxed = true)
private lateinit var controller: DefaultSearchController
private lateinit var settings: Settings
@Before
fun setUp() {
@ -60,6 +59,7 @@ class DefaultSearchControllerTest {
every { store.state.searchEngineSource.searchEngine } returns searchEngine
every { activity.metrics } returns metrics
every { activity.components.core.sessionManager } returns sessionManager
every { activity.components.settings } returns settings
controller = DefaultSearchController(
activity = activity,
@ -67,8 +67,6 @@ class DefaultSearchControllerTest {
navController = navController,
clearToolbarFocus = clearToolbarFocus
)
settings = testContext.settings().apply { testContext.settings().clear() }
}
@Test
@ -154,10 +152,7 @@ class DefaultSearchControllerTest {
@Test
fun `show search shortcuts when setting enabled AND query empty`() {
val text = ""
testContext.settings().preferences
.edit()
.putBoolean(testContext.getString(R.string.pref_key_show_search_shortcuts), true)
.apply()
every { settings.shouldShowSearchShortcuts } returns true
controller.handleTextChanged(text)
@ -168,10 +163,7 @@ class DefaultSearchControllerTest {
fun `show search shortcuts when setting enabled AND query equals url`() {
val text = "mozilla.org"
every { store.state.url } returns "mozilla.org"
testContext.settings().preferences
.edit()
.putBoolean(testContext.getString(R.string.pref_key_show_search_shortcuts), true)
.apply()
every { settings.shouldShowSearchShortcuts } returns true
controller.handleTextChanged(text)
@ -189,10 +181,7 @@ class DefaultSearchControllerTest {
@Test
fun `do not show search shortcuts when setting disabled AND query empty AND url not matching query`() {
testContext.settings().preferences
.edit()
.putBoolean(testContext.getString(R.string.pref_key_show_search_shortcuts), false)
.apply()
every { settings.shouldShowSearchShortcuts } returns false
assertFalse(testContext.settings().shouldShowSearchShortcuts)
@ -205,10 +194,7 @@ class DefaultSearchControllerTest {
@Test
fun `do not show search shortcuts when setting disabled AND query non-empty`() {
testContext.settings().preferences
.edit()
.putBoolean(testContext.getString(R.string.pref_key_show_search_shortcuts), false)
.apply()
every { settings.shouldShowSearchShortcuts } returns false
assertFalse(testContext.settings().shouldShowSearchShortcuts)

View File

@ -6,33 +6,58 @@ package org.mozilla.fenix.search.toolbar
import android.content.Context
import androidx.appcompat.view.ContextThemeWrapper
import androidx.core.graphics.drawable.toBitmap
import io.mockk.MockKAnnotations
import io.mockk.Runs
import io.mockk.every
import io.mockk.impl.annotations.MockK
import io.mockk.just
import io.mockk.mockk
import io.mockk.slot
import io.mockk.spyk
import io.mockk.verify
import mozilla.components.browser.toolbar.BrowserToolbar
import mozilla.components.browser.toolbar.edit.EditToolbar
import mozilla.components.concept.engine.Engine
import mozilla.components.concept.toolbar.Toolbar
import mozilla.components.support.test.robolectric.testContext
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mozilla.fenix.R
import org.mozilla.fenix.components.metrics.Event
import org.mozilla.fenix.helpers.FenixRobolectricTestRunner
import org.mozilla.fenix.search.SearchEngineSource
import org.mozilla.fenix.search.SearchFragmentState
@RunWith(FenixRobolectricTestRunner::class)
class ToolbarViewTest {
@MockK(relaxed = true) private lateinit var interactor: ToolbarInteractor
@MockK private lateinit var engine: Engine
private lateinit var context: Context
private lateinit var toolbar: BrowserToolbar
private val defaultState: SearchFragmentState = SearchFragmentState(
tabId = null,
url = "",
searchTerms = "",
query = "",
searchEngineSource = SearchEngineSource.Default(mockk {
every { name } returns "Search Engine"
every { icon } returns testContext.getDrawable(R.drawable.ic_search)!!.toBitmap()
}),
defaultEngineSource = mockk(relaxed = true),
showSearchSuggestionsHint = false,
showSearchSuggestions = false,
showSearchShortcuts = false,
areShortcutsAvailable = true,
showClipboardSuggestions = false,
showHistorySuggestions = false,
showBookmarkSuggestions = false,
searchAccessPoint = Event.PerformedSearch.SearchAccessPoint.NONE
)
@Before
fun setup() {
@ -72,6 +97,69 @@ class ToolbarViewTest {
assertTrue(toolbar.private)
}
@Test
fun `View gets initialized only once`() {
val view = buildToolbarView(false)
assertFalse(view.isInitialized)
view.update(defaultState)
view.update(defaultState)
view.update(defaultState)
verify(exactly = 1) { toolbar.url = any() }
verify(exactly = 1) { toolbar.setSearchTerms(any()) }
verify(exactly = 1) { interactor.onTextChanged(any()) }
// editMode gets called when the view is initialized. So it is called twice in this test
verify(exactly = 2) { toolbar.editMode() }
assertTrue(view.isInitialized)
}
@Test
fun `URL gets set to the states query`() {
val toolbarView = buildToolbarView(false)
toolbarView.update(defaultState.copy(query = "Query"))
assertEquals("Query", toolbarView.view.url)
}
@Test
fun `URL gets set to the states pastedText if exists`() {
val toolbarView = buildToolbarView(false)
toolbarView.update(defaultState.copy(query = "Query", pastedText = "Pasted"))
assertEquals("Pasted", toolbarView.view.url)
}
@Test
fun `searchTerms get set if pastedText is null or empty`() {
val toolbarView = buildToolbarView(false)
toolbarView.update(defaultState.copy(query = "Query", pastedText = "", searchTerms = "Search Terms"))
verify { toolbar.setSearchTerms("Search Terms") }
}
@Test
fun `searchTerms don't get set if pastedText has a value`() {
val toolbarView = buildToolbarView(false)
toolbarView.update(
defaultState.copy(query = "Query", pastedText = "PastedText", searchTerms = "Search Terms")
)
verify(exactly = 0) { toolbar.setSearchTerms("Search Terms") }
}
@Test
fun `searchEngine name and icon get set on update`() {
val editToolbar: EditToolbar = mockk(relaxed = true)
every { toolbar.edit } returns editToolbar
val toolbarView = buildToolbarView(false)
toolbarView.update(defaultState)
verify { editToolbar.setIcon(any(), "Search Engine") }
}
private fun buildToolbarView(isPrivate: Boolean) = ToolbarView(
context,
interactor,

View File

@ -16,7 +16,6 @@ import kotlinx.coroutines.test.runBlockingTest
import mozilla.components.browser.storage.sync.PlacesHistoryStorage
import mozilla.components.concept.engine.Engine
import mozilla.components.feature.tabs.TabsUseCases
import mozilla.components.support.test.robolectric.testContext
import mozilla.components.support.test.rule.MainCoroutineRule
import org.junit.Before
import org.junit.Ignore
@ -26,7 +25,6 @@ import org.junit.runner.RunWith
import org.mozilla.fenix.HomeActivity
import org.mozilla.fenix.components.FenixSnackbar
import org.mozilla.fenix.components.PermissionStorage
import org.mozilla.fenix.ext.clearAndCommit
import org.mozilla.fenix.ext.components
import org.mozilla.fenix.helpers.FenixRobolectricTestRunner
import org.mozilla.fenix.utils.Settings
@ -38,8 +36,8 @@ class DeleteAndQuitTest {
@get:Rule
val coroutinesTestRule = MainCoroutineRule(TestCoroutineDispatcher())
private var activity: HomeActivity = mockk(relaxed = true)
lateinit var settings: Settings
private val activity: HomeActivity = mockk(relaxed = true)
private val settings: Settings = mockk(relaxed = true)
private val tabUseCases: TabsUseCases = mockk(relaxed = true)
private val historyStorage: PlacesHistoryStorage = mockk(relaxed = true)
private val permissionStorage: PermissionStorage = mockk(relaxed = true)
@ -49,25 +47,18 @@ class DeleteAndQuitTest {
@Before
fun setUp() {
settings = Settings.getInstance(testContext).apply {
clear()
}
every { activity.components.core.historyStorage } returns historyStorage
every { activity.components.core.permissionStorage } returns permissionStorage
every { activity.components.useCases.tabsUseCases } returns tabUseCases
every { tabUseCases.removeAllTabs } returns removeAllTabsUseCases
every { activity.components.core.engine } returns engine
}
private fun Settings.clear() {
preferences.clearAndCommit()
every { activity.components.settings } returns settings
}
@Test
fun `delete only tabs and quit`() = runBlockingTest {
// When
settings.setDeleteDataOnQuit(DeleteBrowsingDataOnQuitType.TABS, true)
every { settings.getDeleteDataOnQuit(DeleteBrowsingDataOnQuitType.TABS) } returns true
deleteAndQuit(activity, this, snackbar)
@ -97,7 +88,7 @@ class DeleteAndQuitTest {
fun `delete everything and quit`() = runBlockingTest {
// When
DeleteBrowsingDataOnQuitType.values().forEach {
settings.setDeleteDataOnQuit(it, true)
every { settings.getDeleteDataOnQuit(it) } returns true
}
deleteAndQuit(activity, this, snackbar)

View File

@ -6,7 +6,9 @@ package org.mozilla.fenix.trackingprotection
import android.content.Context
import android.view.View
import io.mockk.MockKAnnotations
import io.mockk.every
import io.mockk.impl.annotations.MockK
import io.mockk.mockk
import io.mockk.spyk
import io.mockk.verify
@ -16,28 +18,27 @@ import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mozilla.fenix.R
import org.mozilla.fenix.utils.Settings
import org.mozilla.fenix.components.metrics.MetricController
import org.mozilla.fenix.helpers.FenixRobolectricTestRunner
import org.mozilla.fenix.utils.Settings
@RunWith(FenixRobolectricTestRunner::class)
class TrackingProtectionOverlayTest {
private lateinit var context: Context
private lateinit var settings: Settings
private lateinit var toolbar: View
private lateinit var icon: View
private lateinit var session: Session
private lateinit var overlay: TrackingProtectionOverlay
@MockK(relaxed = true) private lateinit var settings: Settings
@MockK(relaxed = true) private lateinit var metrics: MetricController
@MockK(relaxed = true) private lateinit var toolbar: View
@MockK(relaxed = true) private lateinit var icon: View
@MockK(relaxed = true) private lateinit var session: Session
@MockK(relaxed = true) private lateinit var overlay: TrackingProtectionOverlay
@Before
fun setup() {
MockKAnnotations.init(this)
context = spyk(testContext)
settings = mockk(relaxed = true)
toolbar = mockk(relaxed = true)
icon = mockk(relaxed = true)
session = mockk(relaxed = true)
overlay = TrackingProtectionOverlay(context, settings) { toolbar }
overlay = TrackingProtectionOverlay(context, settings, metrics) { toolbar }
every { toolbar.findViewById<View>(R.id.mozac_browser_toolbar_tracking_protection_indicator) } returns icon
}

View File

@ -4,7 +4,6 @@
package org.mozilla.fenix.utils
import org.mozilla.fenix.helpers.FenixRobolectricTestRunner
import mozilla.components.feature.sitepermissions.SitePermissionsRules
import mozilla.components.feature.sitepermissions.SitePermissionsRules.Action.ALLOWED
import mozilla.components.feature.sitepermissions.SitePermissionsRules.Action.ASK_TO_ALLOW
@ -17,8 +16,7 @@ import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mozilla.fenix.ext.clearAndCommit
import org.mozilla.fenix.ext.settings
import org.mozilla.fenix.helpers.FenixRobolectricTestRunner
import org.mozilla.fenix.settings.PhoneFeature
import org.mozilla.fenix.settings.deletebrowsingdata.DeleteBrowsingDataOnQuitType
@ -27,9 +25,18 @@ class SettingsTest {
lateinit var settings: Settings
private val defaultPermissions = SitePermissionsRules(
camera = ASK_TO_ALLOW,
location = ASK_TO_ALLOW,
microphone = ASK_TO_ALLOW,
notification = ASK_TO_ALLOW,
autoplayAudible = AutoplayAction.BLOCKED,
autoplayInaudible = AutoplayAction.BLOCKED
)
@Before
fun setUp() {
settings = testContext.settings().apply(Settings::clear)
settings = Settings(testContext)
}
@Test
@ -103,28 +110,6 @@ class SettingsTest {
assertEquals("Mozilla", settings.defaultSearchEngineName)
}
@Test
fun isCrashReportingEnabled_enabledInBuild() {
// When
clearExistingInstance()
val settings = testContext.settings(true)
.apply(Settings::clear)
// Then
assertTrue(settings.isCrashReportingEnabled)
}
@Test
fun isCrashReportingEnabled_disabledInBuild() {
// When
clearExistingInstance()
val settings = testContext.settings(false)
.apply(Settings::clear)
// Then
assertFalse(settings.isCrashReportingEnabled)
}
@Test
fun isRemoteDebuggingEnabled() {
// When just created
@ -451,7 +436,7 @@ class SettingsTest {
// When just created
// Then
assertEquals(
defaultPermissions(),
defaultPermissions,
settings.getSitePermissionsCustomSettingsRules()
)
}
@ -463,7 +448,7 @@ class SettingsTest {
// Then
assertEquals(
defaultPermissions().copy(camera = BLOCKED),
defaultPermissions.copy(camera = BLOCKED),
settings.getSitePermissionsCustomSettingsRules()
)
}
@ -475,7 +460,7 @@ class SettingsTest {
// Then
assertEquals(
defaultPermissions().copy(notification = BLOCKED),
defaultPermissions.copy(notification = BLOCKED),
settings.getSitePermissionsCustomSettingsRules()
)
}
@ -487,7 +472,7 @@ class SettingsTest {
// Then
assertEquals(
defaultPermissions().copy(location = BLOCKED),
defaultPermissions.copy(location = BLOCKED),
settings.getSitePermissionsCustomSettingsRules()
)
}
@ -499,7 +484,7 @@ class SettingsTest {
// Then
assertEquals(
defaultPermissions().copy(microphone = BLOCKED),
defaultPermissions.copy(microphone = BLOCKED),
settings.getSitePermissionsCustomSettingsRules()
)
}
@ -509,7 +494,7 @@ class SettingsTest {
settings.setSitePermissionsPhoneFeatureAction(PhoneFeature.AUTOPLAY_AUDIBLE, ALLOWED)
assertEquals(
defaultPermissions().copy(autoplayAudible = ALLOWED),
defaultPermissions.copy(autoplayAudible = ALLOWED),
settings.getSitePermissionsCustomSettingsRules()
)
}
@ -519,25 +504,8 @@ class SettingsTest {
settings.setSitePermissionsPhoneFeatureAction(PhoneFeature.AUTOPLAY_INAUDIBLE, ALLOWED)
assertEquals(
defaultPermissions().copy(autoplayInaudible = ALLOWED),
defaultPermissions.copy(autoplayInaudible = ALLOWED),
settings.getSitePermissionsCustomSettingsRules()
)
}
}
private fun clearExistingInstance() {
Settings.instance = null
}
private fun Settings.clear() {
preferences.clearAndCommit()
}
private fun defaultPermissions() = SitePermissionsRules(
camera = ASK_TO_ALLOW,
location = ASK_TO_ALLOW,
microphone = ASK_TO_ALLOW,
notification = ASK_TO_ALLOW,
autoplayAudible = AutoplayAction.BLOCKED,
autoplayInaudible = AutoplayAction.BLOCKED
)

View File

@ -4,25 +4,24 @@
package org.mozilla.fenix.whatsnew
import org.mozilla.fenix.helpers.FenixRobolectricTestRunner
import androidx.preference.PreferenceManager
import mozilla.components.support.test.robolectric.testContext
import org.junit.Assert
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mozilla.fenix.ext.clearAndCommit
import org.mozilla.fenix.utils.Settings
import org.mozilla.fenix.helpers.FenixRobolectricTestRunner
@RunWith(FenixRobolectricTestRunner::class)
class WhatsNewStorageTest {
private lateinit var storage: SharedPreferenceWhatsNewStorage
private lateinit var settings: Settings
@Before
fun setUp() {
storage = SharedPreferenceWhatsNewStorage(testContext)
settings = Settings.getInstance(testContext)
.apply(Settings::clear)
PreferenceManager.getDefaultSharedPreferences(testContext).clearAndCommit()
}
@Test
@ -57,7 +56,3 @@ class WhatsNewStorageTest {
const val DAY_IN_MILLIS = 3600 * 1000 * 24
}
}
fun Settings.clear() {
preferences.clearAndCommit()
}

View File

@ -4,24 +4,24 @@ package org.mozilla.fenix.whatsnew
* 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/. */
import org.mozilla.fenix.helpers.FenixRobolectricTestRunner
import androidx.preference.PreferenceManager
import mozilla.components.support.test.robolectric.testContext
import org.junit.Assert.assertEquals
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mozilla.fenix.ext.settings
import org.mozilla.fenix.utils.Settings
import org.mozilla.fenix.ext.clearAndCommit
import org.mozilla.fenix.helpers.FenixRobolectricTestRunner
@RunWith(FenixRobolectricTestRunner::class)
class WhatsNewTest {
private lateinit var storage: SharedPreferenceWhatsNewStorage
private lateinit var settings: Settings
@Before
fun setup() {
storage = SharedPreferenceWhatsNewStorage(testContext)
settings = testContext.settings().apply(Settings::clear)
PreferenceManager.getDefaultSharedPreferences(testContext).clearAndCommit()
WhatsNew.wasUpdatedRecently = null
}

View File

@ -18,11 +18,11 @@ def trim_to_locale(str):
return match.group(1)
# This file is a dumb parser that converts values from '/l10n.toml' to be easily consumed from
# Python.
# This file is a dumb parser that converts values from '/l10n-release.toml' to be easily
# consumed from Python.
#
# 'l10n.toml' has a very simple structure, and it is reasonable to believe that this (very basic)
# algorithm will continue to work as it is changed.
# 'l10n-release.toml' has a very simple structure, and it is reasonable to believe that this
# (very basic) algorithm will continue to work as it is changed.
#
# Alternatives to custom parsing that were considered:
# - Using standard library module --- none exists to parse TOML
@ -31,7 +31,7 @@ def trim_to_locale(str):
# - Vendoring a TOML module --- large amount of code given the use case. Introduces a security
# risk
def get_release_locales():
with open(r"l10n.toml") as f:
with open(r"l10n-release.toml") as f:
file = f.read().splitlines()
locales_opened = False

View File

@ -72,7 +72,7 @@ buildscript {
}
plugins {
id("io.gitlab.arturbosch.detekt").version("1.6.0")
id("io.gitlab.arturbosch.detekt").version("1.9.1")
}
allprojects {
@ -133,8 +133,7 @@ allprojects {
kotlinOptions.jvmTarget = "1.8"
kotlinOptions.allWarningsAsErrors = true
kotlinOptions.freeCompilerArgs += [
"-Xuse-experimental=kotlin.Experimental",
"-Xskip-runtime-version-check"
"-Xuse-experimental=kotlin.Experimental"
]
}
}
@ -145,9 +144,10 @@ task clean(type: Delete) {
detekt {
// The version number is duplicated, please refer to plugins block for more details
version = "1.6.0"
version = "1.9.1"
input = files("$projectDir/app/src")
config = files("$projectDir/config/detekt.yml")
baseline = file("$projectDir/config/detekt-baseline.xml")
reports {
html {

View File

@ -3,5 +3,5 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
object AndroidComponents {
const val VERSION = "51.0.20200720130437"
const val VERSION = "51.0.20200721130108"
}

View File

@ -11,7 +11,7 @@ object Versions {
const val leanplum = "5.4.0"
const val osslicenses_plugin = "0.9.5"
const val osslicenses_library = "17.0.0"
const val detekt = "1.6.0"
const val detekt = "1.9.1"
const val androidx_appcompat = "1.2.0-rc01"
const val androidx_biometric = "1.1.0-alpha01"

File diff suppressed because one or more lines are too long

View File

@ -23,10 +23,6 @@ console-reports:
# - 'NotificationReport'
# - 'FindingsReport'
# - 'BuildFailureReport'
console-reports:
active: true
exclude:
# - 'HtmlOutputReport'
- 'PlainOutputReport'
- 'XmlOutputReport'
@ -34,6 +30,8 @@ console-reports:
comments:
active: true
excludes: "**/*Test.kt, **/*Spec.kt, **/test/**, **/androidTest/**"
AbsentOrWrongFileLicense:
active: true
CommentOverPrivateFunction:
active: false
CommentOverPrivateProperty:

View File

@ -0,0 +1,3 @@
/* 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/. */

View File

@ -1,36 +0,0 @@
# The `activation` ping
## Description
This ping provides a measure of the activation of mobile products.
## Scheduling
The `activation` ping is automatically sent at the very first startup, after Glean is initialized.
It is only sent once and only re-attempted a subsequent startups if it hasn't been sent yet.
## Contents
This ping contains the following fields:
| Field name | Type | Description |
|---|---|---|
| `identifier` | String | An hashed and salted version of the Google Advertising ID from the device. |
| `activation_id` | UUID | An alternate identifier, not correlated with the client_id, generated once and only sent with the activation ping. |
The `activation` ping also includes the common [ping sections](https://github.com/mozilla-mobile/android-components/blob/master/components/service/glean/docs/pings/pings.md#ping-sections)
found in all pings, with the exclusion of the `client_id` (as defined by the [`pings.yaml`](../app/pings.yaml) file).
## Example `activation` ping
```json
{
"ping_info": { },
"client_info": { },
"metrics": {
"string": {
"activation.identifier": "d+lnddDYN2ILBDGvhBIBHORRMrmVwTCp6rGLLFi8SMo="
},
"uuid": {
"activation.activation_id": "c0c40a5f-bd75-41ca-8097-9a38103de7fe"
}
}
}
```

View File

@ -2,21 +2,10 @@
Fenix uses Mozilla's telemetry service (Glean) and LeanPlum to measure feature performance and engagement.
## Baseline ping
## Glean pings and metrics
By using the Glean SDK, Fenix can send the pings the SDK owns and defines, as documented [in the Glean SDK docs](https://mozilla.github.io/glean/book/user/pings/index.html).
Fenix creates and tries to send a "baseline" ping when the app goes to the background. This baseline ping is defined by the [Glean](https://github.com/mozilla/glean/tree/master/docs/user/pings) component and [documented in the Android Components repository](https://github.com/mozilla/glean/blob/master/docs/user/pings/baseline.md).
## Metrics ping
Fenix creates and tries to send a "baseline" ping. It is defined inside the [`metrics.yaml`](https://github.com/mozilla-mobile/fenix/blob/master/app/metrics.yaml) file. This ping includes things like whether or not Fenix is currently the default browser.
## Events
Fenix sends event pings that allows us to measure feature performance. These are defined inside the [`metrics.yaml`](https://github.com/mozilla-mobile/fenix/blob/master/app/metrics.yaml) file.
## Activation
Fenix sends an activation ping once, at startup. Further documentation can be found in the [`activation` ping](activation.md) docs.
Additional metrics or pings defined by Fenix are documented in the [Glean SDK autogenerated docs](metrics.md).
## Leanplum
See [here](https://github.com/mozilla-mobile/fenix/blob/master/docs/mma.md) for details on Leanplum usage in Firefox Preview.

91
l10n-release.toml 100644
View File

@ -0,0 +1,91 @@
# Locales that should be present in release builds
# Locales that are at 70% or higher completion on https://pontoon.mozilla.org/projects/android-l10n/
# should be in this list
locales = [
"an",
"ar",
"ast",
"az",
"be",
"bn",
"br",
"bs",
"ca",
"cak",
"co",
"cs",
"cy",
"da",
"de",
"dsb",
"el",
"en-CA",
"en-GB",
"eo",
"es",
"es-AR",
"es-CL",
"es-ES",
"es-MX",
"et",
"eu",
"fa",
"ff",
"fi",
"fr",
"fy-NL",
"ga-IE",
"gd",
"gn",
"gu-IN",
"he",
"hi-IN",
"hr",
"hsb",
"hu",
"hy-AM",
"id",
"is",
"it",
"ja",
"ka",
"kab",
"kk",
"kn",
"ko",
"lij",
"lo",
"lt",
"ml",
"mr",
"my",
"nb-NO",
"nl",
"nn-NO",
"oc",
"pa-IN",
"pl",
"pt-BR",
"pt-PT",
"rm",
"ro",
"ru",
"sk",
"sl",
"sq",
"sr",
"su",
"sv-SE",
"ta",
"te",
"th",
"tr",
"trs",
"uk",
"ur",
"vec",
"vi",
"zh-CN",
"zh-TW",
]

View File

@ -7,6 +7,7 @@ locales = [
"ast",
"az",
"be",
"bg",
"bn",
"br",
"bs",
@ -58,10 +59,13 @@ locales = [
"lt",
"ml",
"mr",
"ms",
"my",
"nb-NO",
"ne-NP",
"nl",
"nn-NO",
"nv",
"oc",
"pa-IN",
"pl",
@ -83,6 +87,7 @@ locales = [
"trs",
"uk",
"ur",
"uz",
"vec",
"vi",
"zh-CN",

View File

@ -45,22 +45,14 @@ if (localProperties != null) {
if (appServicesLocalPath != null) {
log("Enabling automatic publication of application-services from: $appServicesLocalPath")
def publishAppServicesCmd = ["./automation/publish_to_maven_local_if_modified.py"]
// Application-services doesn't build on native Windows. However, it still makes sense to
// enable these workflows on Windows, even if it isn't quote as automatic as elsewhere -
// specifically, you must run the build command on WSL, but after that you can happily build
// and debug directly from within Android Studio on native Windows - but only after following
// https://github.com/mozilla/application-services/blob/master/docs/howtos/setup-android-build-environment.md#using-windows
// So rather than fail we make noise...
// Windows can't execute .py files directly, so we assume a "manually installed" python,
// which comes with a "py" launcher and respects the shebang line to specify the version.
def publishAppServicesCmd = [];
if (System.properties['os.name'].toLowerCase().contains('windows')) {
log('NOTE: The autoPublish workflows do not work on native windows.');
log('You must manually ensure that the following command has completed successfully in WSL:');
log("> $publishAppServicesCmd");
log("(from the '$appServicesLocalPath' directory)");
log('Then restart the build');
} else {
runCmd(publishAppServicesCmd, appServicesLocalPath, "Published application-services for local development.", false)
publishAppServicesCmd << "py";
}
publishAppServicesCmd << "./automation/publish_to_maven_local_if_modified.py";
runCmd(publishAppServicesCmd, appServicesLocalPath, "Published application-services for local development.", false)
} else {
log("Disabled auto-publication of application-services. Enable it by settings '$settingAppServicesPath' in local.properties")
}
@ -68,10 +60,8 @@ if (localProperties != null) {
String androidComponentsLocalPath = localProperties.getProperty(settingAndroidComponentsPath)
if (androidComponentsLocalPath != null) {
// android-components does build on native windows, so it doesn't get the special Windows treatment above.
// But it doesn't like executing .py files directly. We assume a "manually installed" python,
// which comes with a "py" launcher and respects the shebang line to specify the version.
log("Enabling automatic publication of android-components from: $androidComponentsLocalPath")
// As above, hacks to execute .py files on Windows.
def publishAcCmd = [];
if (System.properties['os.name'].toLowerCase().contains('windows')) {
publishAcCmd << "py";

View File

@ -82,7 +82,7 @@ job-defaults:
- '--app=fenix'
- '--browsertime'
- '--cold'
- '--binary=org.mozilla.fenix.nightly'
- '--binary=org.mozilla.fenix'
- '--activity=org.mozilla.fenix.IntentReceiverActivity'
- '--download-symbols=ondemand'
- '--browsertime-node=$MOZ_FETCHES_DIR/node/bin/node'
@ -232,3 +232,15 @@ jobs:
test-name: google-search-restaurants
treeherder:
symbol: 'Btime(tp6m-28-c)'
youtube-playback:
test-name: youtube-playback
run-visual-metrics: False
treeherder:
symbol: 'Btime(ytp)'
args:
by-abi:
# Bug 1558456 - Stop tracking youtube-playback-test on motoG5 for >1080p cases
armeabi-v7a:
- '--test-url-params=exclude=1,2,9,10,17,18,21,22,26,28,30,32,39,40,47,48,55,56,63,64,71,72,79,80,83,84,89,90,95,96'
default: []

View File

@ -93,17 +93,6 @@ jobs:
treeherder:
symbol: forPerformanceTest(B)
nightly:
attributes:
nightly: true
include-nightly-version: true
include-shippable-secrets: true
run:
geckoview-engine: geckoNightly
gradle-build-type: fenixNightly
treeherder:
symbol: nightly(B)
nightly-simulation:
attributes:
nightly: false
@ -112,47 +101,22 @@ jobs:
include-shippable-secrets: true
run:
geckoview-engine: geckoNightly
gradle-build-type: fennecNightly
gradle-build-type: fenixProduction
treeherder:
symbol: nightlySim(B)
beta:
attributes:
release-type: beta
include-release-version: true
include-shippable-secrets: true
filter-incomplete-translations: true
run:
geckoview-engine: geckoBeta
gradle-build-type: fenixBeta
run-on-tasks-for: [github-release]
treeherder:
symbol: beta(B)
# XXX `production` is now the new nightly. We keep this name around while we officially remove
# `nightly` and `fennec-nightly`
production:
nightly:
attributes:
nightly: true
include-nightly-version: true
include-shippable-secrets: true
run:
geckoview-engine: geckoNightly
# XXX `fenixProduction` is now the new nightly.
gradle-build-type: fenixProduction
run-on-tasks-for: [github-release]
run-on-tasks-for: []
treeherder:
symbol: production(B)
fennec-nightly:
attributes:
nightly: true
include-nightly-version: true
include-shippable-secrets: true
run:
geckoview-engine: geckoNightly
gradle-build-type: fennecNightly
treeherder:
symbol: nightlyFennec(B)
symbol: nightly(B)
fennec-beta:
attributes:

View File

@ -2,7 +2,6 @@
trust-domain: mobile
treeherder:
group-names:
'beta': 'Beta-related tasks'
'betaFennec': 'Beta-related tasks with same APK configuration as Fennec'
'Btime': 'Raptor-Browsertime tests'
'bump': 'Bump dependencies'
@ -12,7 +11,6 @@ treeherder:
'I': 'Docker Image Builds'
'nightly': 'Nightly-related tasks'
'nightlySim': 'Nightly-related tasks that run on each github push'
'nightlyFennec': 'Nightly-related tasks with same APK configuration as Fennec'
'production': 'Release-related tasks'
'productionFennec': 'Production-related tasks with same APK configuration as Fennec'
'Rap': 'Raptor tests'

View File

@ -17,8 +17,8 @@ primary-dependency: push-apk
group-by: build-type
only-for-build-types:
- fenix-beta
- fenix-production
- fennec-beta
- fennec-production
job-template:
description: Mark Fenix as shipped in ship-it

Some files were not shown because too many files have changed in this diff Show More