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 ## 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: 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. 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. 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: To add it on Mac/Linux, run this command from the project root:
```sh ```sh
ln -s ../../config/pre-push-recommended.sh .git/hooks/pre-push ln -s ../../config/pre-push-recommended.sh .git/hooks/pre-push

View File

@ -72,20 +72,6 @@ android {
"sharedUserId": "org.mozilla.fenix.performancetest.sharedID" "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 >> { fenixProduction releaseTemplate >> {
applicationIdSuffix ".fenix" applicationIdSuffix ".fenix"
buildConfigField "boolean", "USE_RELEASE_VERSIONING", "true" buildConfigField "boolean", "USE_RELEASE_VERSIONING", "true"
@ -127,23 +113,6 @@ android {
"deepLinkScheme": deepLinkSchemeValue "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") 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. // | debug | | | Both variants for testing and development.
// | forPerformanceTest | | | Both variants unless the perf team only cares about Nightly (TBD). // | 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 // | fenixProduction | | | Fenix Production (to be renamed `Nightly`) ships with GV Nightly
// | fenixBeta | | | Fenix Beta ships with GV Beta
// | fenixProduction | | | Fenix Production ships with GV Beta
// | fennecProduction | | | Fenix build to replace production Firefox builds // | fennecProduction | | | Fenix build to replace production Firefox builds
// | fennecBeta | | | Fenix build to replace beta Firefox builds // | fennecBeta | | | Fenix build to replace beta Firefox builds
// | fennecNightly | | | Fenix build to replace Nightly Firefox builds
def flavors = flavors*.name.toString().toLowerCase() def flavors = flavors*.name.toString().toLowerCase()
if (buildType.name == 'fenixBeta' && flavors.contains("geckonightly")) {
setIgnore true
}
if (buildType.name == 'fenixProduction' && flavors.contains("geckobeta")) { if (buildType.name == 'fenixProduction' && flavors.contains("geckobeta")) {
setIgnore true setIgnore true
} }
@ -177,10 +139,6 @@ android {
if ((buildType.name == 'fennecProduction' || buildType.name == 'fennecBeta') && flavors.contains("geckonightly")) { if ((buildType.name == 'fennecProduction' || buildType.name == 'fennecBeta') && flavors.contains("geckonightly")) {
setIgnore true setIgnore true
} }
if (buildType.name == 'fennecNightly' && flavors.contains("geckobeta")) {
setIgnore true
}
} }
aaptOptions { aaptOptions {
@ -207,10 +165,6 @@ android {
androidTest { androidTest {
resources.srcDirs += ['src/androidTest/resources'] resources.srcDirs += ['src/androidTest/resources']
} }
fennecNightly {
java.srcDirs = ['src/migration/java']
manifest.srcFile "src/migration/AndroidManifest.xml"
}
fennecBeta { fennecBeta {
java.srcDirs = ['src/migration/java'] java.srcDirs = ['src/migration/java']
manifest.srcFile "src/migration/AndroidManifest.xml" manifest.srcFile "src/migration/AndroidManifest.xml"

View File

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

View File

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

View File

@ -8,6 +8,7 @@ import androidx.test.uiautomator.UiSelector
import okhttp3.mockwebserver.MockWebServer import okhttp3.mockwebserver.MockWebServer
import org.junit.After import org.junit.After
import org.junit.Before import org.junit.Before
import org.junit.Ignore
import org.junit.Rule import org.junit.Rule
import org.junit.Test import org.junit.Test
import org.mozilla.fenix.helpers.AndroidAssetDispatcher 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 @Test
fun mediaSystemNotificationInPrivateModeTest() { fun mediaSystemNotificationInPrivateModeTest() {
val audioTestPage = TestAssetHelper.getAudioPageAsset(mockWebServer) val audioTestPage = TestAssetHelper.getAudioPageAsset(mockWebServer)
@ -158,7 +160,7 @@ class MediaNotificationTest {
verifyMediaIsPaused() verifyMediaIsPaused()
}.openTabDrawer { }.openTabDrawer {
closeTab() closeTab()
}.openHomeScreen { } }
mDevice.openNotification() mDevice.openNotification()

View File

@ -58,8 +58,10 @@ class NavigationToolbarTest {
mDevice.waitForIdle() mDevice.waitForIdle()
}.openNavigationToolbar { }.openNavigationToolbar {
}.enterURLAndEnterToBrowser(nextWebPage.url) { }.enterURLAndEnterToBrowser(nextWebPage.url) {
mDevice.waitForIdle()
verifyUrl(nextWebPage.url.toString()) verifyUrl(nextWebPage.url.toString())
mDevice.pressBack() mDevice.pressBack()
mDevice.waitForIdle()
verifyUrl(defaultWebPage.url.toString()) 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() start()
} }
InstrumentationRegistry.getInstrumentation().context.settings().setStrictETP() activityTestRule.activity.settings().setStrictETP()
// Reset on-boarding notification for each test // Reset on-boarding notification for each test
TestHelper.setPreference( TestHelper.setPreference(

View File

@ -9,6 +9,7 @@ import androidx.test.uiautomator.UiDevice
import okhttp3.mockwebserver.MockWebServer import okhttp3.mockwebserver.MockWebServer
import org.junit.After import org.junit.After
import org.junit.Before import org.junit.Before
import org.junit.BeforeClass
import org.junit.Ignore import org.junit.Ignore
import org.junit.Rule import org.junit.Rule
import org.junit.Test 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 @After
fun tearDown() { fun tearDown() {
mockWebServer.shutdown() mockWebServer.shutdown()
@ -118,7 +129,6 @@ class TabbedBrowsingTest {
verifySaveCollection() verifySaveCollection()
}.closeAllTabs { }.closeAllTabs {
verifyNoTabsOpened() verifyNoTabsOpened()
}.openHomeScreen {
} }
// Repeat for Private Tabs // Repeat for Private Tabs
@ -134,8 +144,6 @@ class TabbedBrowsingTest {
verifyCloseAllTabsButton() verifyCloseAllTabsButton()
}.closeAllTabs { }.closeAllTabs {
verifyNoTabsOpened() 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.isDisplayed
import androidx.test.espresso.matcher.ViewMatchers.withEffectiveVisibility import androidx.test.espresso.matcher.ViewMatchers.withEffectiveVisibility
import androidx.test.espresso.matcher.ViewMatchers.withId import androidx.test.espresso.matcher.ViewMatchers.withId
import androidx.test.espresso.matcher.ViewMatchers.withResourceName
import androidx.test.espresso.matcher.ViewMatchers.withText import androidx.test.espresso.matcher.ViewMatchers.withText
import androidx.test.platform.app.InstrumentationRegistry import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.By import androidx.test.uiautomator.By
@ -144,6 +145,8 @@ class BrowserRobot {
fun verifyEnhancedTrackingProtectionSwitch() = assertEnhancedTrackingProtectionSwitch() fun verifyEnhancedTrackingProtectionSwitch() = assertEnhancedTrackingProtectionSwitch()
fun clickEnhancedTrackingProtectionSwitchOffOn() = onView(withResourceName("switch_widget")).click()
fun verifyProtectionSettingsButton() = assertProtectionSettingsButton() fun verifyProtectionSettingsButton() = assertProtectionSettingsButton()
fun verifyEnhancedTrackingOptions() { fun verifyEnhancedTrackingOptions() {
@ -188,6 +191,8 @@ class BrowserRobot {
fun clickEnhancedTrackingProtectionPanel() = enhancedTrackingProtectionPanel().click() fun clickEnhancedTrackingProtectionPanel() = enhancedTrackingProtectionPanel().click()
fun verifyEnhancedTrackingProtectionPanelNotVisible() = assertEnhancedTrackingProtectionPanelNotVisible()
fun clickContextOpenLinkInNewTab() { fun clickContextOpenLinkInNewTab() {
val mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()) val mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
mDevice.waitNotNull( mDevice.waitNotNull(
@ -420,6 +425,11 @@ private fun assertNavURLBar() = navURLBar()
fun enhancedTrackingProtectionPanel() = onView(withId(R.id.mozac_browser_toolbar_tracking_protection_indicator)) fun enhancedTrackingProtectionPanel() = onView(withId(R.id.mozac_browser_toolbar_tracking_protection_indicator))
private fun assertEnhancedTrackingProtectionPanelNotVisible() {
enhancedTrackingProtectionPanel()
.check(matches(withEffectiveVisibility(Visibility.GONE)))
}
private fun assertEnhancedTrackingProtectionSwitch() { private fun assertEnhancedTrackingProtectionSwitch() {
withText(R.id.trackingProtectionSwitch) withText(R.id.trackingProtectionSwitch)
.matches(withEffectiveVisibility(Visibility.VISIBLE)) .matches(withEffectiveVisibility(Visibility.VISIBLE))

View File

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

View File

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

View File

@ -7,6 +7,7 @@ package org.mozilla.fenix.ui.robots
import androidx.preference.R import androidx.preference.R
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import androidx.test.espresso.Espresso.onView import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.Espresso.pressBack
import androidx.test.espresso.assertion.ViewAssertions.matches import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.contrib.RecyclerViewActions import androidx.test.espresso.contrib.RecyclerViewActions
import androidx.test.espresso.matcher.ViewMatchers.hasDescendant 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.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.UiDevice import androidx.test.uiautomator.UiDevice
import org.hamcrest.CoreMatchers.allOf import org.hamcrest.CoreMatchers.allOf
import org.hamcrest.CoreMatchers.not
import org.mozilla.fenix.helpers.assertIsChecked import org.mozilla.fenix.helpers.assertIsChecked
import org.mozilla.fenix.helpers.click import org.mozilla.fenix.helpers.click
import org.mozilla.fenix.helpers.isChecked import org.mozilla.fenix.helpers.isChecked
import org.mozilla.fenix.helpers.isEnabled
/** /**
* Implementation of Robot Pattern for the settings Enhanced Tracking Protection sub menu. * Implementation of Robot Pattern for the settings Enhanced Tracking Protection sub menu.
@ -44,8 +47,12 @@ class SettingsSubMenuEnhancedTrackingProtectionRobot {
fun verifyEnhancedTrackingProtectionOptions() = assertEnhancedTrackingProtectionOptions() fun verifyEnhancedTrackingProtectionOptions() = assertEnhancedTrackingProtectionOptions()
fun verifyEnhancedTrackingProtectionOptionsGrayedOut() = assertEnhancedTrackingProtectionOptionsGrayedOut()
fun verifyEnhancedTrackingProtectionDefaults() = assertEnhancedTrackingProtectionDefaults() fun verifyEnhancedTrackingProtectionDefaults() = assertEnhancedTrackingProtectionDefaults()
fun clickEnhancedTrackingProtectionDefaults() = onView(withResourceName("switch_widget")).click()
fun verifyRadioButtonDefaults() = assertRadioButtonDefaults() fun verifyRadioButtonDefaults() = assertRadioButtonDefaults()
fun verifyEnhancedTrackingProtectionProtectionSubMenuItems() { fun verifyEnhancedTrackingProtectionProtectionSubMenuItems() {
@ -61,6 +68,16 @@ class SettingsSubMenuEnhancedTrackingProtectionRobot {
class Transition { class Transition {
val mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())!! 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 { fun goBack(interact: SettingsRobot.() -> Unit): SettingsRobot.Transition {
goBackButton().click() goBackButton().click()
@ -141,6 +158,31 @@ private fun assertEnhancedTrackingProtectionOptions() {
.check(matches(withEffectiveVisibility(Visibility.VISIBLE))) .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() { private fun assertEnhancedTrackingProtectionDefaults() {
onView(withResourceName("switch_widget")).check( onView(withResourceName("switch_widget")).check(
matches( matches(

View File

@ -253,12 +253,11 @@ class ThreeDotMenuMainRobot {
return BrowserRobot.Transition() return BrowserRobot.Transition()
} }
fun closeAllTabs(interact: TabDrawerRobot.() -> Unit): TabDrawerRobot.Transition { fun closeAllTabs(interact: HomeScreenRobot.() -> Unit): HomeScreenRobot.Transition {
// mDevice.waitNotNull(Until.findObject(By.text("Close all tabs")), waitingTime)
closeAllTabsButton().click() closeAllTabsButton().click()
TabDrawerRobot().interact() HomeScreenRobot().interact()
return TabDrawerRobot.Transition() return HomeScreenRobot.Transition()
} }
fun openFindInPage(interact: FindInPageRobot.() -> Unit): FindInPageRobot.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.lib.crash.handler.CrashHandlerService
import mozilla.components.service.sync.logins.GeckoLoginStorageDelegate import mozilla.components.service.sync.logins.GeckoLoginStorageDelegate
import org.mozilla.fenix.Config 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.GeckoRuntime
import org.mozilla.geckoview.GeckoRuntimeSettings import org.mozilla.geckoview.GeckoRuntimeSettings
@ -48,9 +48,10 @@ object GeckoProvider {
.debugLogging(Config.channel.isDebug) .debugLogging(Config.channel.isDebug)
.build() .build()
if (!Settings.getInstance(context).shouldUseAutoSize) { val settings = context.components.settings
if (!settings.shouldUseAutoSize) {
runtimeSettings.automaticFontSizeAdjustment = false runtimeSettings.automaticFontSizeAdjustment = false
val fontSize = Settings.getInstance(context).fontSizeFactor val fontSize = settings.fontSizeFactor
runtimeSettings.fontSizeFactor = fontSize 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.lib.crash.handler.CrashHandlerService
import mozilla.components.service.sync.logins.GeckoLoginStorageDelegate import mozilla.components.service.sync.logins.GeckoLoginStorageDelegate
import org.mozilla.fenix.Config 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.GeckoRuntime
import org.mozilla.geckoview.GeckoRuntimeSettings import org.mozilla.geckoview.GeckoRuntimeSettings
@ -48,9 +48,10 @@ object GeckoProvider {
.aboutConfigEnabled(true) .aboutConfigEnabled(true)
.build() .build()
if (!Settings.getInstance(context).shouldUseAutoSize) { val settings = context.components.settings
if (!settings.shouldUseAutoSize) {
runtimeSettings.automaticFontSizeAdjustment = false runtimeSettings.automaticFontSizeAdjustment = false
val fontSize = Settings.getInstance(context).fontSizeFactor val fontSize = settings.fontSizeFactor
runtimeSettings.fontSizeFactor = fontSize runtimeSettings.fontSizeFactor = fontSize
} }

View File

@ -7,13 +7,10 @@ package org.mozilla.fenix
enum class ReleaseChannel { enum class ReleaseChannel {
FenixDebug, FenixDebug,
FenixNightly,
FenixBeta,
FenixProduction, FenixProduction,
FennecProduction, FennecProduction,
FennecBeta, FennecBeta;
FennecNightly;
val isReleased: Boolean val isReleased: Boolean
get() = when (this) { get() = when (this) {
@ -33,8 +30,6 @@ enum class ReleaseChannel {
val isReleaseOrBeta: Boolean val isReleaseOrBeta: Boolean
get() = when (this) { get() = when (this) {
FenixProduction -> true
FenixBeta -> true
FennecProduction -> true FennecProduction -> true
FennecBeta -> true FennecBeta -> true
else -> false else -> false
@ -43,14 +38,11 @@ enum class ReleaseChannel {
val isBeta: Boolean val isBeta: Boolean
get() = when (this) { get() = when (this) {
FennecBeta -> true FennecBeta -> true
FenixBeta -> true
else -> false else -> false
} }
val isNightlyOrDebug: Boolean val isNightlyOrDebug: Boolean
get() = when (this) { get() = when (this) {
FenixNightly -> true
FennecNightly -> true
FenixDebug -> true FenixDebug -> true
FenixProduction -> true FenixProduction -> true
else -> false else -> false
@ -66,12 +58,9 @@ enum class ReleaseChannel {
object Config { object Config {
val channel = when (BuildConfig.BUILD_TYPE) { val channel = when (BuildConfig.BUILD_TYPE) {
"fenixProduction" -> ReleaseChannel.FenixProduction "fenixProduction" -> ReleaseChannel.FenixProduction
"fenixBeta" -> ReleaseChannel.FenixBeta
"fenixNightly" -> ReleaseChannel.FenixNightly
"debug" -> ReleaseChannel.FenixDebug "debug" -> ReleaseChannel.FenixDebug
"fennecProduction" -> ReleaseChannel.FennecProduction "fennecProduction" -> ReleaseChannel.FennecProduction
"fennecBeta" -> ReleaseChannel.FennecBeta "fennecBeta" -> ReleaseChannel.FennecBeta
"fennecNightly" -> ReleaseChannel.FennecNightly
// Builds for local performance analysis, recording benchmarks, automation, etc. // Builds for local performance analysis, recording benchmarks, automation, etc.
// This should be treated like a released channel because we want to test // 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( private val fennecChannels: List<ReleaseChannel> = listOf(
ReleaseChannel.FennecNightly,
ReleaseChannel.FennecBeta, ReleaseChannel.FennecBeta,
ReleaseChannel.FennecProduction 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.PerformanceActivityLifecycleCallbacks
import org.mozilla.fenix.session.VisibilityLifecycleCallback import org.mozilla.fenix.session.VisibilityLifecycleCallback
import org.mozilla.fenix.utils.BrowsersCache import org.mozilla.fenix.utils.BrowsersCache
import org.mozilla.fenix.utils.Settings
/** /**
*The main application class for Fenix. Records data to measure initialization performance. *The main application class for Fenix. Records data to measure initialization performance.
@ -355,8 +354,7 @@ open class FenixApplication : LocaleAwareApplication() {
_, engineSession, url -> _, engineSession, url ->
val shouldCreatePrivateSession = val shouldCreatePrivateSession =
components.core.sessionManager.selectedSession?.private components.core.sessionManager.selectedSession?.private
?: Settings.instance?.openLinksInAPrivateTab ?: components.settings.openLinksInAPrivateTab
?: false
val session = Session(url, shouldCreatePrivateSession) val session = Session(url, shouldCreatePrivateSession)
components.core.sessionManager.add(session, true, engineSession) 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.AppBarConfiguration
import androidx.navigation.ui.NavigationUI import androidx.navigation.ui.NavigationUI
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import com.google.android.gms.tasks.Tasks.call
import kotlinx.android.synthetic.main.activity_home.* import kotlinx.android.synthetic.main.activity_home.*
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers 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.browser.browsingmode.DefaultBrowsingModeManager
import org.mozilla.fenix.components.metrics.BreadcrumbsRecorder import org.mozilla.fenix.components.metrics.BreadcrumbsRecorder
import org.mozilla.fenix.components.metrics.Event import org.mozilla.fenix.components.metrics.Event
import org.mozilla.fenix.trackingprotectionexceptions.TrackingProtectionExceptionsFragmentDirections
import org.mozilla.fenix.ext.alreadyOnDestination import org.mozilla.fenix.ext.alreadyOnDestination
import org.mozilla.fenix.ext.components import org.mozilla.fenix.ext.components
import org.mozilla.fenix.ext.nav 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.SettingsFragmentDirections
import org.mozilla.fenix.settings.TrackingProtectionFragmentDirections import org.mozilla.fenix.settings.TrackingProtectionFragmentDirections
import org.mozilla.fenix.settings.about.AboutFragmentDirections 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.LoginDetailFragmentDirections
import org.mozilla.fenix.settings.logins.fragment.SavedLoginsAuthFragmentDirections
import org.mozilla.fenix.settings.search.AddSearchEngineFragmentDirections import org.mozilla.fenix.settings.search.AddSearchEngineFragmentDirections
import org.mozilla.fenix.settings.search.EditCustomSearchEngineFragmentDirections import org.mozilla.fenix.settings.search.EditCustomSearchEngineFragmentDirections
import org.mozilla.fenix.share.AddNewDeviceFragmentDirections 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.tabtray.TabTrayDialogFragment
import org.mozilla.fenix.theme.DefaultThemeManager import org.mozilla.fenix.theme.DefaultThemeManager
import org.mozilla.fenix.theme.ThemeManager import org.mozilla.fenix.theme.ThemeManager
import org.mozilla.fenix.trackingprotectionexceptions.TrackingProtectionExceptionsFragmentDirections
import org.mozilla.fenix.utils.BrowsersCache import org.mozilla.fenix.utils.BrowsersCache
import org.mozilla.fenix.utils.RunWhenReadyQueue import org.mozilla.fenix.utils.RunWhenReadyQueue
@ -575,7 +574,7 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity {
} }
protected open fun createBrowsingModeManager(initialMode: BrowsingMode): BrowsingModeManager { protected open fun createBrowsingModeManager(initialMode: BrowsingMode): BrowsingModeManager {
return DefaultBrowsingModeManager(initialMode) { newMode -> return DefaultBrowsingModeManager(initialMode, components.settings) { newMode ->
themeManager.currentTheme = 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.R
import org.mozilla.fenix.ext.components import org.mozilla.fenix.ext.components
import org.mozilla.fenix.ext.showToolbar import org.mozilla.fenix.ext.showToolbar
import org.mozilla.fenix.utils.Settings
/** /**
* An activity to show the details of a installed add-on. * An activity to show the details of a installed add-on.
@ -191,8 +190,7 @@ class InstalledAddonDetailsFragment : Fragment() {
val components = it.context.components val components = it.context.components
val shouldCreatePrivateSession = val shouldCreatePrivateSession =
components.core.store.state.selectedTab?.content?.private components.core.store.state.selectedTab?.content?.private
?: Settings.instance?.openLinksInAPrivateTab ?: components.settings.openLinksInAPrivateTab
?: false
if (shouldCreatePrivateSession) { if (shouldCreatePrivateSession) {
components.useCases.tabsUseCases.addPrivateTab(settingUrl) components.useCases.tabsUseCases.addPrivateTab(settingUrl)

View File

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

View File

@ -36,6 +36,7 @@ interface BrowsingModeManager {
*/ */
class DefaultBrowsingModeManager( class DefaultBrowsingModeManager(
private var _mode: BrowsingMode, private var _mode: BrowsingMode,
private val settings: Settings,
private val modeDidChange: (BrowsingMode) -> Unit private val modeDidChange: (BrowsingMode) -> Unit
) : BrowsingModeManager { ) : BrowsingModeManager {
@ -44,6 +45,6 @@ class DefaultBrowsingModeManager(
set(value) { set(value) {
_mode = value _mode = value
modeDidChange(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.R
import org.mozilla.fenix.components.SearchWidgetCreator import org.mozilla.fenix.components.SearchWidgetCreator
import org.mozilla.fenix.components.metrics.Event 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.settings
import org.mozilla.fenix.utils.Settings import org.mozilla.fenix.utils.Settings
/** /**
@ -29,27 +28,29 @@ import org.mozilla.fenix.utils.Settings
*/ */
class SearchWidgetCFR( class SearchWidgetCFR(
private val context: Context, private val context: Context,
private val settings: Settings,
private val metrics: MetricController,
private val getToolbar: () -> View private val getToolbar: () -> View
) { ) {
fun displayIfNecessary() { fun displayIfNecessary() {
if (!context.settings().isInSearchWidgetExperiment || if (settings.isInSearchWidgetExperiment &&
!context.settings().shouldDisplaySearchWidgetCFR() || settings.shouldDisplaySearchWidgetCFR() &&
isShown !isShown
) { return } ) {
isShown = true
isShown = true showSearchWidgetCFR()
showSearchWidgetCFR() }
} }
@Suppress("MagicNumber", "InflateParams") @Suppress("InflateParams")
private fun showSearchWidgetCFR() { private fun showSearchWidgetCFR() {
context.settings().incrementSearchWidgetCFRDisplayed() settings.incrementSearchWidgetCFRDisplayed()
val searchWidgetCFRDialog = Dialog(context) val searchWidgetCFRDialog = Dialog(context)
val layout = LayoutInflater.from(context) val layout = LayoutInflater.from(context)
.inflate(R.layout.search_widget_cfr, null) .inflate(R.layout.search_widget_cfr, null)
val isBottomToolbar = Settings.getInstance(context).shouldUseBottomToolbar val isBottomToolbar = settings.shouldUseBottomToolbar
layout.drop_down_triangle.isGone = isBottomToolbar layout.drop_down_triangle.isGone = isBottomToolbar
layout.pop_up_triangle.isVisible = isBottomToolbar layout.pop_up_triangle.isVisible = isBottomToolbar
@ -63,16 +64,16 @@ class SearchWidgetCFR(
} }
layout.cfr_neg_button.setOnClickListener { layout.cfr_neg_button.setOnClickListener {
context.components.analytics.metrics.track(Event.SearchWidgetCFRNotNowPressed) metrics.track(Event.SearchWidgetCFRNotNowPressed)
searchWidgetCFRDialog.dismiss() searchWidgetCFRDialog.dismiss()
context.settings().manuallyDismissSearchWidgetCFR() settings.manuallyDismissSearchWidgetCFR()
} }
layout.cfr_pos_button.setOnClickListener { layout.cfr_pos_button.setOnClickListener {
context.components.analytics.metrics.track(Event.SearchWidgetCFRAddWidgetPressed) metrics.track(Event.SearchWidgetCFRAddWidgetPressed)
SearchWidgetCreator.createSearchWidget(context) SearchWidgetCreator.createSearchWidget(context)
searchWidgetCFRDialog.dismiss() searchWidgetCFRDialog.dismiss()
context.settings().manuallyDismissSearchWidgetCFR() settings.manuallyDismissSearchWidgetCFR()
} }
searchWidgetCFRDialog.apply { searchWidgetCFRDialog.apply {
@ -90,16 +91,16 @@ class SearchWidgetCFR(
searchWidgetCFRDialog.setOnCancelListener { searchWidgetCFRDialog.setOnCancelListener {
isShown = false isShown = false
context.components.analytics.metrics.track(Event.SearchWidgetCFRCanceled) metrics.track(Event.SearchWidgetCFRCanceled)
} }
searchWidgetCFRDialog.setOnDismissListener { searchWidgetCFRDialog.setOnDismissListener {
isShown = false isShown = false
context.settings().incrementSearchWidgetCFRDismissed() settings.incrementSearchWidgetCFRDismissed()
} }
searchWidgetCFRDialog.show() searchWidgetCFRDialog.show()
context.components.analytics.metrics.track(Event.SearchWidgetCFRDisplayed) metrics.track(Event.SearchWidgetCFRDisplayed)
} }
companion object { companion object {

View File

@ -10,6 +10,7 @@ import org.mozilla.fenix.home.Tab
/** /**
* Diff callback for comparing tab lists with selected state. * Diff callback for comparing tab lists with selected state.
*/ */
@Suppress("LongParameterList")
internal class TabDiffUtil( internal class TabDiffUtil(
private val old: List<Tab>, private val old: List<Tab>,
private val new: 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" val baseUrl = "https://sentry.prod.mozaws.net/operations"
return when (Config.channel) { return when (Config.channel) {
ReleaseChannel.FenixProduction -> "$baseUrl/fenix" ReleaseChannel.FenixProduction -> "$baseUrl/fenix"
ReleaseChannel.FenixBeta -> "$baseUrl/fenix-beta"
ReleaseChannel.FenixNightly -> "$baseUrl/fenix-nightly"
ReleaseChannel.FennecProduction -> "$baseUrl/fenix-fennec" ReleaseChannel.FennecProduction -> "$baseUrl/fenix-fennec"
ReleaseChannel.FennecBeta -> "$baseUrl/fenix-fennec-beta" ReleaseChannel.FennecBeta -> "$baseUrl/fenix-fennec-beta"
ReleaseChannel.FennecNightly -> "$baseUrl/fenix-fennec-nightly"
else -> null else -> null
} }
} }

View File

@ -40,6 +40,7 @@ import org.mozilla.fenix.ext.settings
import org.mozilla.fenix.sync.SyncedTabsIntegration import org.mozilla.fenix.sync.SyncedTabsIntegration
import org.mozilla.fenix.utils.Mockable import org.mozilla.fenix.utils.Mockable
import org.mozilla.fenix.utils.RunWhenReadyQueue 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 * 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( private val telemetryAccountObserver = TelemetryAccountObserver(
context, context.settings(),
context.components.analytics.metrics context.components.analytics.metrics
) )
@ -178,8 +179,8 @@ class BackgroundServices(
} }
@VisibleForTesting(otherwise = PRIVATE) @VisibleForTesting(otherwise = PRIVATE)
class TelemetryAccountObserver( internal class TelemetryAccountObserver(
private val context: Context, private val settings: Settings,
private val metricController: MetricController private val metricController: MetricController
) : AccountObserver { ) : AccountObserver {
override fun onAuthenticated(account: OAuthAccount, authType: AuthType) { override fun onAuthenticated(account: OAuthAccount, authType: AuthType) {
@ -211,12 +212,12 @@ class TelemetryAccountObserver(
metricController.track(Event.SyncAuthOtherExternal) metricController.track(Event.SyncAuthOtherExternal)
} }
// Used by Leanplum as a context variable. // Used by Leanplum as a context variable.
context.settings().fxaSignedIn = true settings.fxaSignedIn = true
} }
override fun onLoggedOut() { override fun onLoggedOut() {
metricController.track(Event.SyncAuthSignOut) metricController.track(Event.SyncAuthSignOut)
// Used by Leanplum as a context variable. // 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.HomeActivity
import org.mozilla.fenix.utils.ClipboardHandler import org.mozilla.fenix.utils.ClipboardHandler
import org.mozilla.fenix.utils.Mockable import org.mozilla.fenix.utils.Mockable
import org.mozilla.fenix.utils.Settings
import org.mozilla.fenix.wifi.WifiConnectionMonitor import org.mozilla.fenix.wifi.WifiConnectionMonitor
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
@ -107,4 +108,6 @@ class Components(private val context: Context) {
val performance by lazy { PerformanceComponent() } val performance by lazy { PerformanceComponent() }
val push by lazy { Push(context, analytics.crashReporter) } val push by lazy { Push(context, analytics.crashReporter) }
val wifiConnectionMonitor by lazy { WifiConnectionMonitor(context.getSystemService()!!) } 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 com.adjust.sdk.LogLevel
import org.mozilla.fenix.BuildConfig import org.mozilla.fenix.BuildConfig
import org.mozilla.fenix.Config import org.mozilla.fenix.Config
import org.mozilla.fenix.ReleaseChannel
import org.mozilla.fenix.ext.settings import org.mozilla.fenix.ext.settings
class AdjustMetricsService(private val application: Application) : MetricsService { class AdjustMetricsService(private val application: Application) : MetricsService {
@ -23,7 +22,7 @@ class AdjustMetricsService(private val application: Application) : MetricsServic
if ((BuildConfig.ADJUST_TOKEN.isNullOrBlank())) { if ((BuildConfig.ADJUST_TOKEN.isNullOrBlank())) {
Log.i(LOGTAG, "No adjust token defined") 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") 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.browser.state.store.BrowserStore
import mozilla.components.concept.storage.BookmarksStorage import mozilla.components.concept.storage.BookmarksStorage
import mozilla.components.support.ktx.android.content.getColorFromAttr import mozilla.components.support.ktx.android.content.getColorFromAttr
import org.mozilla.fenix.R
import org.mozilla.fenix.FeatureFlags import org.mozilla.fenix.FeatureFlags
import org.mozilla.fenix.HomeActivity import org.mozilla.fenix.HomeActivity
import org.mozilla.fenix.R
import org.mozilla.fenix.browser.browsingmode.BrowsingMode import org.mozilla.fenix.browser.browsingmode.BrowsingMode
import org.mozilla.fenix.ext.asActivity import org.mozilla.fenix.ext.asActivity
import org.mozilla.fenix.ext.components import org.mozilla.fenix.ext.components
import org.mozilla.fenix.ext.settings import org.mozilla.fenix.ext.settings
import org.mozilla.fenix.theme.ThemeManager 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. * 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 // Predicates that are called once, during screen init
val shouldShowSaveToCollection = (context.asActivity() as? HomeActivity) val shouldShowSaveToCollection = (context.asActivity() as? HomeActivity)
?.browsingModeManager?.mode == BrowsingMode.Normal ?.browsingModeManager?.mode == BrowsingMode.Normal
val shouldDeleteDataOnQuit = Settings.getInstance(context) val shouldDeleteDataOnQuit = context.components.settings
.shouldDeleteBrowsingDataOnQuit .shouldDeleteBrowsingDataOnQuit
val menuItems = listOfNotNull( val menuItems = listOfNotNull(

View File

@ -12,13 +12,10 @@ import android.view.ViewGroup
import androidx.annotation.StringRes import androidx.annotation.StringRes
import mozilla.components.browser.search.SearchEngineManager import mozilla.components.browser.search.SearchEngineManager
import mozilla.components.support.locale.LocaleManager 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.FenixApplication
import org.mozilla.fenix.components.Components import org.mozilla.fenix.components.Components
import org.mozilla.fenix.components.metrics.MetricController import org.mozilla.fenix.components.metrics.MetricController
import org.mozilla.fenix.settings.advanced.getSelectedLocale import org.mozilla.fenix.settings.advanced.getSelectedLocale
import org.mozilla.fenix.utils.Settings
import java.lang.String.format import java.lang.String.format
import java.util.Locale import java.util.Locale
@ -60,8 +57,7 @@ fun Context.getPreferenceKey(@StringRes resourceId: Int): String =
fun Context.getRootView(): View? = fun Context.getRootView(): View? =
asActivity()?.window?.decorView?.findViewById<View>(android.R.id.content) as? ViewGroup asActivity()?.window?.decorView?.findViewById<View>(android.R.id.content) as? ViewGroup
fun Context.settings(isCrashReportEnabledInBuild: Boolean = BuildConfig.CRASH_REPORTING && Config.channel.isReleased) = fun Context.settings() = components.settings
Settings.getInstance(this, isCrashReportEnabledInBuild)
/** /**
* Used to catch IllegalArgumentException that is thrown when * 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. // the CFR in.
view.toolbar_wrapper.doOnLayout { view.toolbar_wrapper.doOnLayout {
if (!browsingModeManager.mode.isPrivate) { 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.components
import org.mozilla.fenix.ext.settings import org.mozilla.fenix.ext.settings
import org.mozilla.fenix.theme.ThemeManager import org.mozilla.fenix.theme.ThemeManager
import org.mozilla.fenix.utils.Settings
import org.mozilla.fenix.whatsnew.WhatsNew import org.mozilla.fenix.whatsnew.WhatsNew
class HomeMenu( class HomeMenu(
@ -153,40 +152,29 @@ class HomeMenu(
null 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) { if (shouldUseBottomToolbar) {
listOfNotNull( menuItems.reversed()
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) }
}
} else { } else {
listOfNotNull( menuItems
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) }
}
} }
} }

View File

@ -128,7 +128,7 @@ interface SessionControlController {
fun handleCreateCollection() fun handleCreateCollection()
} }
@SuppressWarnings("TooManyFunctions", "LargeClass") @SuppressWarnings("TooManyFunctions", "LargeClass", "LongParameterList")
class DefaultSessionControlController( class DefaultSessionControlController(
private val activity: HomeActivity, private val activity: HomeActivity,
private val engine: Engine, 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.components.metrics.Event.OnboardingToolbarPosition.Position
import org.mozilla.fenix.ext.asActivity import org.mozilla.fenix.ext.asActivity
import org.mozilla.fenix.ext.components import org.mozilla.fenix.ext.components
import org.mozilla.fenix.ext.settings
import org.mozilla.fenix.onboarding.OnboardingRadioButton import org.mozilla.fenix.onboarding.OnboardingRadioButton
import org.mozilla.fenix.utils.view.addToRadioGroup import org.mozilla.fenix.utils.view.addToRadioGroup
class OnboardingToolbarPositionPickerViewHolder(view: View) : RecyclerView.ViewHolder(view) { class OnboardingToolbarPositionPickerViewHolder(view: View) : RecyclerView.ViewHolder(view) {
private val metrics = view.context.components.analytics.metrics
init { init {
val radioTopToolbar = view.toolbar_top_radio_button val radioTopToolbar = view.toolbar_top_radio_button
val radioBottomToolbar = view.toolbar_bottom_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) radioTopToolbar.addIllustration(view.toolbar_top_image)
radioBottomToolbar.addIllustration(view.toolbar_bottom_image) radioBottomToolbar.addIllustration(view.toolbar_bottom_image)
radio = if (view.context.settings().shouldUseBottomToolbar) { val settings = view.context.components.settings
radio = if (settings.shouldUseBottomToolbar) {
radioBottomToolbar radioBottomToolbar
} else { } else {
radioTopToolbar radioTopToolbar
@ -35,28 +37,24 @@ class OnboardingToolbarPositionPickerViewHolder(view: View) : RecyclerView.ViewH
radio.updateRadioValue(true) radio.updateRadioValue(true)
radioBottomToolbar.onClickListener { radioBottomToolbar.onClickListener {
itemView.context.components.analytics.metrics metrics.track(Event.OnboardingToolbarPosition(Position.BOTTOM))
.track(Event.OnboardingToolbarPosition(Position.BOTTOM))
itemView.context.asActivity()?.recreate() itemView.context.asActivity()?.recreate()
} }
view.toolbar_bottom_image.setOnClickListener { view.toolbar_bottom_image.setOnClickListener {
itemView.context.components.analytics.metrics metrics.track(Event.OnboardingToolbarPosition(Position.BOTTOM))
.track(Event.OnboardingToolbarPosition(Position.BOTTOM))
radioBottomToolbar.performClick() radioBottomToolbar.performClick()
} }
radioTopToolbar.onClickListener { radioTopToolbar.onClickListener {
itemView.context.components.analytics.metrics metrics.track(Event.OnboardingToolbarPosition(Position.TOP))
.track(Event.OnboardingToolbarPosition(Position.TOP))
itemView.context.asActivity()?.recreate() itemView.context.asActivity()?.recreate()
} }
view.toolbar_top_image.setOnClickListener { view.toolbar_top_image.setOnClickListener {
itemView.context.components.analytics.metrics metrics.track(Event.OnboardingToolbarPosition(Position.TOP))
.track(Event.OnboardingToolbarPosition(Position.TOP))
radioTopToolbar.performClick() radioTopToolbar.performClick()
} }
} }

View File

@ -37,17 +37,17 @@ class OnboardingTrackingProtectionViewHolder(view: View) : RecyclerView.ViewHold
isChecked = view.context.settings().shouldUseTrackingProtection isChecked = view.context.settings().shouldUseTrackingProtection
setOnCheckedChangeListener { _, isChecked -> setOnCheckedChangeListener { _, isChecked ->
updateTrackingProtectionSetting(isChecked) updateTrackingProtectionSetting(isChecked)
updateRadioGroupState(view, isChecked) updateRadioGroupState(isChecked)
} }
} }
setupRadioGroup(view, trackingProtectionToggle.isChecked) setupRadioGroup(trackingProtectionToggle.isChecked)
updateRadioGroupState(view, 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) addToRadioGroup(standardTrackingProtection, strictTrackingProtection)
@ -58,56 +58,20 @@ class OnboardingTrackingProtectionViewHolder(view: View) : RecyclerView.ViewHold
standardTrackingProtection.onClickListener { standardTrackingProtection.onClickListener {
updateTrackingProtectionPolicy() updateTrackingProtectionPolicy()
view.context.components.analytics.metrics itemView.context.components.analytics.metrics
.track(Event.OnboardingTrackingProtection(Setting.STANDARD)) .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 { strictTrackingProtection.onClickListener {
updateTrackingProtectionPolicy() updateTrackingProtectionPolicy()
view.context.components.analytics.metrics itemView.context.components.analytics.metrics
.track(Event.OnboardingTrackingProtection(Setting.STRICT)) .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 standardTrackingProtection.isEnabled = isChecked
strictTrackingProtection.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) { private fun updateTrackingProtectionSetting(enabled: Boolean) {

View File

@ -47,7 +47,7 @@ interface BookmarkController {
fun handleBackPressed() fun handleBackPressed()
} }
@SuppressWarnings("TooManyFunctions") @SuppressWarnings("TooManyFunctions", "LongParameterList")
class DefaultBookmarkController( class DefaultBookmarkController(
private val activity: HomeActivity, private val activity: HomeActivity,
private val navController: NavController, 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.requireComponents
import org.mozilla.fenix.ext.toShortUrl import org.mozilla.fenix.ext.toShortUrl
import org.mozilla.fenix.library.LibraryPageFragment import org.mozilla.fenix.library.LibraryPageFragment
import org.mozilla.fenix.tabtray.TabTrayDialogFragment
import org.mozilla.fenix.utils.allowUndo import org.mozilla.fenix.utils.allowUndo
/** /**
@ -207,14 +208,14 @@ class BookmarkFragment : LibraryPageFragment<BookmarkNode>(), UserInteractionHan
R.id.open_bookmarks_in_new_tabs_multi_select -> { R.id.open_bookmarks_in_new_tabs_multi_select -> {
openItemsInNewTab { node -> node.url } openItemsInNewTab { node -> node.url }
navigate(BookmarkFragmentDirections.actionGlobalTabTrayDialogFragment()) showTabTray()
metrics?.track(Event.OpenedBookmarksInNewTabs) metrics?.track(Event.OpenedBookmarksInNewTabs)
true true
} }
R.id.open_bookmarks_in_private_tabs_multi_select -> { R.id.open_bookmarks_in_private_tabs_multi_select -> {
openItemsInNewTab(private = true) { node -> node.url } openItemsInNewTab(private = true) { node -> node.url }
navigate(BookmarkFragmentDirections.actionGlobalTabTrayDialogFragment()) showTabTray()
metrics?.track(Event.OpenedBookmarksInPrivateTabs) metrics?.track(Event.OpenedBookmarksInPrivateTabs)
true true
} }
@ -237,6 +238,11 @@ class BookmarkFragment : LibraryPageFragment<BookmarkNode>(), UserInteractionHan
} }
} }
private fun showTabTray() {
invokePendingDeletion()
TabTrayDialogFragment.show(parentFragmentManager)
}
private fun navigate(directions: NavDirections) { private fun navigate(directions: NavDirections) {
invokePendingDeletion() invokePendingDeletion()
findNavController().nav( 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.showToolbar
import org.mozilla.fenix.ext.toShortUrl import org.mozilla.fenix.ext.toShortUrl
import org.mozilla.fenix.library.LibraryPageFragment import org.mozilla.fenix.library.LibraryPageFragment
import org.mozilla.fenix.tabtray.TabTrayDialogFragment
import org.mozilla.fenix.utils.allowUndo import org.mozilla.fenix.utils.allowUndo
@SuppressWarnings("TooManyFunctions", "LargeClass") @SuppressWarnings("TooManyFunctions", "LargeClass")
@ -184,9 +185,7 @@ class HistoryFragment : LibraryPageFragment<HistoryItem>(), UserInteractionHandl
selectedItem.url selectedItem.url
} }
navigate( showTabTray()
HistoryFragmentDirections.actionGlobalTabTrayDialogFragment()
)
true true
} }
R.id.open_history_in_private_tabs_multi_select -> { R.id.open_history_in_private_tabs_multi_select -> {
@ -199,14 +198,18 @@ class HistoryFragment : LibraryPageFragment<HistoryItem>(), UserInteractionHandl
browsingModeManager.mode = BrowsingMode.Private browsingModeManager.mode = BrowsingMode.Private
supportActionBar?.hide() supportActionBar?.hide()
} }
navigate(
HistoryFragmentDirections.actionGlobalTabTrayDialogFragment() showTabTray()
)
true true
} }
else -> super.onOptionsItemSelected(item) else -> super.onOptionsItemSelected(item)
} }
private fun showTabTray() {
invokePendingDeletion()
TabTrayDialogFragment.show(parentFragmentManager)
}
private fun getMultiSelectSnackBarMessage(historyItems: Set<HistoryItem>): String { private fun getMultiSelectSnackBarMessage(historyItems: Set<HistoryItem>): String {
return if (historyItems.size > 1) { return if (historyItems.size > 1) {
getString(R.string.history_delete_multiple_items_snackbar) getString(R.string.history_delete_multiple_items_snackbar)

View File

@ -5,12 +5,16 @@
package org.mozilla.fenix.onboarding package org.mozilla.fenix.onboarding
import android.content.Context import android.content.Context
import android.text.SpannableString
import android.text.SpannableStringBuilder
import android.util.AttributeSet import android.util.AttributeSet
import android.widget.ImageView import android.widget.ImageView
import androidx.appcompat.widget.AppCompatRadioButton import androidx.appcompat.widget.AppCompatRadioButton
import androidx.core.content.edit import androidx.core.content.edit
import androidx.core.content.withStyledAttributes import androidx.core.content.withStyledAttributes
import org.mozilla.fenix.R 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.ext.settings
import org.mozilla.fenix.utils.view.GroupableRadioButton import org.mozilla.fenix.utils.view.GroupableRadioButton
import org.mozilla.fenix.utils.view.uncheckAll import org.mozilla.fenix.utils.view.uncheckAll
@ -23,6 +27,8 @@ class OnboardingRadioButton(
private var illustration: ImageView? = null private var illustration: ImageView? = null
private var clickListener: (() -> Unit)? = null private var clickListener: (() -> Unit)? = null
var key: Int = 0 var key: Int = 0
var title: Int = 0
var description: Int = 0
init { init {
context.withStyledAttributes( context.withStyledAttributes(
@ -31,6 +37,9 @@ class OnboardingRadioButton(
0, 0 0, 0
) { ) {
key = getResourceId(R.styleable.OnboardingRadioButton_onboardingKey, 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() toggleRadioGroups()
clickListener?.invoke() 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) { override fun updateRadioValue(isChecked: Boolean) {
@ -69,4 +100,9 @@ class OnboardingRadioButton(
radioGroups.uncheckAll() 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.Intent
import android.content.IntentFilter import android.content.IntentFilter
import android.os.BatteryManager import android.os.BatteryManager
import org.mozilla.fenix.ext.components
import org.mozilla.fenix.onboarding.FenixOnboarding import org.mozilla.fenix.onboarding.FenixOnboarding
import org.mozilla.fenix.utils.Settings
import android.provider.Settings as AndroidSettings import android.provider.Settings as AndroidSettings
/** /**
@ -67,13 +67,13 @@ object Performance {
* Disables the tracking protection popup. However, TP is still on. * Disables the tracking protection popup. However, TP is still on.
*/ */
private fun disableTrackingProtectionPopups(context: Context) { private fun disableTrackingProtectionPopups(context: Context) {
Settings.getInstance(context).isOverrideTPPopupsForPerformanceTest = true context.components.settings.isOverrideTPPopupsForPerformanceTest = true
} }
/** /**
* Disables the first time PWA popup. * Disables the first time PWA popup.
*/ */
private fun disableFirstTimePWAPopup(context: Context) { 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 android.util.Base64
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.MainScope
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import mozilla.components.concept.engine.Engine import mozilla.components.concept.engine.Engine
import mozilla.components.concept.engine.webpush.WebPushDelegate import mozilla.components.concept.engine.webpush.WebPushDelegate
@ -22,7 +22,8 @@ import mozilla.components.support.base.log.logger.Logger
*/ */
class WebPushEngineIntegration( class WebPushEngineIntegration(
private val engine: Engine, private val engine: Engine,
private val pushFeature: AutoPushFeature private val pushFeature: AutoPushFeature,
private val coroutineScope: CoroutineScope = MainScope()
) : AutoPushFeature.Observer { ) : AutoPushFeature.Observer {
private var handler: WebPushHandler? = null private var handler: WebPushHandler? = null
@ -39,13 +40,13 @@ class WebPushEngineIntegration(
} }
override fun onMessageReceived(scope: PushScope, message: ByteArray?) { override fun onMessageReceived(scope: PushScope, message: ByteArray?) {
CoroutineScope(Dispatchers.Main).launch { coroutineScope.launch {
handler?.onPushMessage(scope, message) handler?.onPushMessage(scope, message)
} }
} }
override fun onSubscriptionChanged(scope: PushScope) { override fun onSubscriptionChanged(scope: PushScope) {
CoroutineScope(Dispatchers.Main).launch { coroutineScope.launch {
handler?.onSubscriptionChanged(scope) 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.concept.engine.EngineSession
import mozilla.components.feature.awesomebar.provider.BookmarksStorageSuggestionProvider import mozilla.components.feature.awesomebar.provider.BookmarksStorageSuggestionProvider
import mozilla.components.feature.awesomebar.provider.HistoryStorageSuggestionProvider 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.SearchSuggestionProvider
import mozilla.components.feature.awesomebar.provider.SessionSuggestionProvider import mozilla.components.feature.awesomebar.provider.SessionSuggestionProvider
import mozilla.components.feature.search.SearchUseCases 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.SearchEngineSource
import org.mozilla.fenix.search.SearchFragmentState 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 * View that contains and configures the BrowserAwesomeBar
*/ */
@ -88,7 +44,8 @@ class AwesomeBarView(
private val shortcutsEnginePickerProvider: ShortcutsSuggestionProvider private val shortcutsEnginePickerProvider: ShortcutsSuggestionProvider
private val bookmarksStorageSuggestionProvider: BookmarksStorageSuggestionProvider private val bookmarksStorageSuggestionProvider: BookmarksStorageSuggestionProvider
private val defaultSearchSuggestionProvider: SearchSuggestionProvider 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 var providersInUse = mutableSetOf<AwesomeBar.SuggestionProvider>()
private val loadUrlUseCase = object : SessionUseCases.LoadUrlUseCase { private val loadUrlUseCase = object : SessionUseCases.LoadUrlUseCase {
@ -141,7 +98,8 @@ class AwesomeBarView(
val draw = getDrawable(context, R.drawable.ic_link)!! val draw = getDrawable(context, R.drawable.ic_link)!!
draw.colorFilter = createBlendModeColorFilterCompat(primaryTextColor, SRC_IN) 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 = sessionProvider =
SessionSuggestionProvider( SessionSuggestionProvider(
context.resources, context.resources,
@ -167,8 +125,9 @@ class AwesomeBarView(
engineForSpeculativeConnects engineForSpeculativeConnects
) )
val searchDrawable = getDrawable(context, R.drawable.ic_search)!! val searchBitmap = getDrawable(context, R.drawable.ic_search)!!.apply {
searchDrawable.colorFilter = createBlendModeColorFilterCompat(primaryTextColor, SRC_IN) colorFilter = createBlendModeColorFilterCompat(primaryTextColor, SRC_IN)
}.toBitmap()
defaultSearchSuggestionProvider = defaultSearchSuggestionProvider =
SearchSuggestionProvider( SearchSuggestionProvider(
@ -178,9 +137,20 @@ class AwesomeBarView(
fetchClient = components.core.client, fetchClient = components.core.client,
mode = SearchSuggestionProvider.Mode.MULTIPLE_SUGGESTIONS, mode = SearchSuggestionProvider.Mode.MULTIPLE_SUGGESTIONS,
limit = 3, limit = 3,
icon = searchDrawable.toBitmap(), icon = searchBitmap,
showDescription = false, showDescription = false,
engine = engineForSpeculativeConnects engine = engineForSpeculativeConnects,
filterExactMatch = true
)
defaultSearchActionProvider =
SearchActionProvider(
searchEngineGetter = suspend {
components.search.searchEngineManager.getDefaultSearchEngineAsync(context)
},
searchUseCase = searchUseCase,
icon = searchBitmap,
showDescription = false
) )
shortcutsEnginePickerProvider = shortcutsEnginePickerProvider =
@ -248,9 +218,7 @@ class AwesomeBarView(
} }
if (state.showSearchSuggestions) { if (state.showSearchSuggestions) {
getSelectedSearchSuggestionProvider(state)?.let { providersToAdd.addAll(getSelectedSearchSuggestionProvider(state))
providersToAdd.add(it)
}
} }
if (!isBrowsingModePrivate()) { if (!isBrowsingModePrivate()) {
@ -274,9 +242,7 @@ class AwesomeBarView(
} }
if (!state.showSearchSuggestions) { if (!state.showSearchSuggestions) {
getSelectedSearchSuggestionProvider(state)?.let { providersToRemove.addAll(getSelectedSearchSuggestionProvider(state))
providersToRemove.add(it)
}
} }
if (isBrowsingModePrivate()) { if (isBrowsingModePrivate()) {
@ -291,9 +257,12 @@ class AwesomeBarView(
?: false ?: false
} }
private fun getSelectedSearchSuggestionProvider(state: SearchFragmentState): SearchSuggestionProvider? { private fun getSelectedSearchSuggestionProvider(state: SearchFragmentState): List<AwesomeBar.SuggestionProvider> {
return when (state.searchEngineSource) { return when (state.searchEngineSource) {
is SearchEngineSource.Default -> defaultSearchSuggestionProvider is SearchEngineSource.Default -> listOf(
defaultSearchActionProvider,
defaultSearchSuggestionProvider
)
is SearchEngineSource.Shortcut -> getSuggestionProviderForEngine( is SearchEngineSource.Shortcut -> getSuggestionProviderForEngine(
state.searchEngineSource.searchEngine state.searchEngineSource.searchEngine
) )
@ -307,26 +276,38 @@ class AwesomeBarView(
view.addProviders(shortcutsEnginePickerProvider) view.addProviders(shortcutsEnginePickerProvider)
} }
private fun getSuggestionProviderForEngine(engine: SearchEngine): SearchSuggestionProvider? { private fun getSuggestionProviderForEngine(engine: SearchEngine): List<AwesomeBar.SuggestionProvider> {
return searchSuggestionProviderMap.getOrPut(engine) { return searchSuggestionProviderMap.getOrPut(engine) {
val context = container.context val context = container.context
val components = context.components val components = context.components
val primaryTextColor = context.getColorFromAttr(R.attr.primaryText) val primaryTextColor = context.getColorFromAttr(R.attr.primaryText)
val draw = getDrawable(context, R.drawable.ic_search) val searchBitmap = getDrawable(context, R.drawable.ic_search)?.apply {
draw?.colorFilter = createBlendModeColorFilterCompat(primaryTextColor, SRC_IN) colorFilter = createBlendModeColorFilterCompat(primaryTextColor, SRC_IN)
}?.toBitmap()
val engineForSpeculativeConnects = if (!isBrowsingModePrivate()) components.core.engine else null val engineForSpeculativeConnects =
if (!isBrowsingModePrivate()) components.core.engine else null
SearchSuggestionProvider( val searchEngine =
components.search.provider.installedSearchEngines(context).list.find { it.name == engine.name } components.search.provider.installedSearchEngines(context).list.find { it.name == engine.name }
?: components.search.provider.getDefaultEngine(context), ?: components.search.provider.getDefaultEngine(context)
shortcutSearchUseCase,
components.core.client, listOf(
limit = 3, SearchActionProvider(
mode = SearchSuggestionProvider.Mode.MULTIPLE_SUGGESTIONS, searchEngineGetter = suspend { searchEngine },
icon = draw?.toBitmap(), searchUseCase = shortcutSearchUseCase,
engine = engineForSpeculativeConnects 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.content.Context
import android.graphics.Bitmap import android.graphics.Bitmap
import android.graphics.drawable.BitmapDrawable import android.graphics.drawable.BitmapDrawable
import androidx.annotation.VisibleForTesting
import androidx.appcompat.content.res.AppCompatResources import androidx.appcompat.content.res.AppCompatResources
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import mozilla.components.browser.domains.autocomplete.ShippedDomainsProvider import mozilla.components.browser.domains.autocomplete.ShippedDomainsProvider
@ -56,7 +57,8 @@ class ToolbarView(
engine: Engine engine: Engine
) { ) {
private var isInitialized = false @VisibleForTesting
internal var isInitialized = false
init { init {
view.apply { view.apply {

View File

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

View File

@ -16,8 +16,10 @@ import androidx.lifecycle.LifecycleCoroutineScope
import com.google.android.material.bottomsheet.BottomSheetBehavior import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.google.android.material.tabs.TabLayout import com.google.android.material.tabs.TabLayout
import kotlinx.android.extensions.LayoutContainer 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.view.*
import kotlinx.android.synthetic.main.component_tabstray_fab.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.Dispatchers.Main
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@ -36,7 +38,7 @@ import org.mozilla.fenix.ext.settings
/** /**
* View that contains and configures the BrowserAwesomeBar * View that contains and configures the BrowserAwesomeBar
*/ */
@Suppress("LongParameterList") @Suppress("LongParameterList", "TooManyFunctions", "LargeClass")
class TabTrayView( class TabTrayView(
private val container: ViewGroup, private val container: ViewGroup,
private val interactor: TabTrayInteractor, private val interactor: TabTrayInteractor,
@ -245,6 +247,17 @@ class TabTrayView(
View.VISIBLE View.VISIBLE
} }
view.tab_tray_overflow.isVisible = !hasNoTabs 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.View
import android.view.accessibility.AccessibilityNodeInfo import android.view.accessibility.AccessibilityNodeInfo
import android.widget.ImageButton import android.widget.ImageButton
import android.widget.ImageView
import android.widget.TextView import android.widget.TextView
import androidx.annotation.VisibleForTesting import androidx.annotation.VisibleForTesting
import androidx.appcompat.widget.AppCompatImageButton import androidx.appcompat.widget.AppCompatImageButton
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.core.view.doOnNextLayout
import mozilla.components.browser.state.state.MediaState import mozilla.components.browser.state.state.MediaState
import mozilla.components.browser.tabstray.TabViewHolder import mozilla.components.browser.tabstray.TabViewHolder
import mozilla.components.browser.tabstray.thumbnail.TabThumbnailView 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.pauseIfPlaying
import mozilla.components.feature.media.ext.playIfPaused import mozilla.components.feature.media.ext.playIfPaused
import mozilla.components.support.base.observer.Observable 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.images.loader.ImageLoader
import mozilla.components.support.ktx.kotlin.tryGetHostFromUrl import mozilla.components.support.ktx.kotlin.tryGetHostFromUrl
import org.mozilla.fenix.R 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.removeTouchDelegate
import org.mozilla.fenix.ext.showAndEnable import org.mozilla.fenix.ext.showAndEnable
import org.mozilla.fenix.ext.toTab import org.mozilla.fenix.ext.toTab
import kotlin.math.max
/** /**
* A RecyclerView ViewHolder implementation for "tab" items. * A RecyclerView ViewHolder implementation for "tab" items.
@ -73,10 +74,7 @@ class TabTrayViewHolder(
if (tab.thumbnail != null) { if (tab.thumbnail != null) {
thumbnailView.setImageBitmap(tab.thumbnail) thumbnailView.setImageBitmap(tab.thumbnail)
} else { } else {
// Make sure we have the view's dimensions so we can load the image at the correct size loadIntoThumbnailView(thumbnailView, tab.id)
thumbnailView.doOnNextLayout {
imageLoader.loadIntoView(thumbnailView, tab.id)
}
} }
// Media state // Media state
@ -182,6 +180,14 @@ class TabTrayViewHolder(
closeView.context.getString(R.string.close_tab_title, title) 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) { internal fun updateAccessibilityRowIndex(item: View, newIndex: Int) {
item.setAccessibilityDelegate(object : View.AccessibilityDelegate() { item.setAccessibilityDelegate(object : View.AccessibilityDelegate() {
override fun onInitializeAccessibilityNodeInfo( 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 mozilla.components.browser.session.Session
import org.mozilla.fenix.R import org.mozilla.fenix.R
import org.mozilla.fenix.components.metrics.Event 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.ext.increaseTapArea
import org.mozilla.fenix.utils.Settings import org.mozilla.fenix.utils.Settings
@ -32,6 +32,7 @@ import org.mozilla.fenix.utils.Settings
class TrackingProtectionOverlay( class TrackingProtectionOverlay(
private val context: Context, private val context: Context,
private val settings: Settings, private val settings: Settings,
private val metrics: MetricController,
private val getToolbar: () -> View private val getToolbar: () -> View
) : Session.Observer { ) : Session.Observer {
@ -54,7 +55,7 @@ class TrackingProtectionOverlay(
override fun onTouchEvent(event: MotionEvent): Boolean { override fun onTouchEvent(event: MotionEvent): Boolean {
if (event.action == MotionEvent.ACTION_DOWN) { if (event.action == MotionEvent.ACTION_DOWN) {
context.components.analytics.metrics.track(Event.ContextualHintETPOutsideTap) metrics.track(Event.ContextualHintETPOutsideTap)
} }
return super.onTouchEvent(event) return super.onTouchEvent(event)
} }
@ -62,7 +63,7 @@ class TrackingProtectionOverlay(
val layout = LayoutInflater.from(context) val layout = LayoutInflater.from(context)
.inflate(R.layout.tracking_protection_onboarding_popup, null) .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.drop_down_triangle.isGone = isBottomToolbar
layout.pop_up_triangle.isVisible = isBottomToolbar layout.pop_up_triangle.isVisible = isBottomToolbar
@ -76,13 +77,13 @@ class TrackingProtectionOverlay(
val closeButton = layout.findViewById<ImageView>(R.id.close_onboarding) val closeButton = layout.findViewById<ImageView>(R.id.close_onboarding)
closeButton.increaseTapArea(BUTTON_INCREASE_DPS) closeButton.increaseTapArea(BUTTON_INCREASE_DPS)
closeButton.setOnClickListener { closeButton.setOnClickListener {
context.components.analytics.metrics.track(Event.ContextualHintETPDismissed) metrics.track(Event.ContextualHintETPDismissed)
trackingOnboardingDialog.dismiss() trackingOnboardingDialog.dismiss()
} }
val res = context.resources val res = context.resources
val triangleWidthPx = res.getDimension(R.dimen.tp_onboarding_triangle_height) val triangleWidthPx = res.getDimension(R.dimen.cfr_triangle_height)
val triangleMarginStartPx = res.getDimension(R.dimen.tp_onboarding_triangle_margin_start) val triangleMarginStartPx = res.getDimension(R.dimen.cfr_triangle_margin_edge)
val toolbar = getToolbar() val toolbar = getToolbar()
val trackingProtectionIcon: View = val trackingProtectionIcon: View =
@ -115,12 +116,12 @@ class TrackingProtectionOverlay(
val etpShield = val etpShield =
getToolbar().findViewById<View>(R.id.mozac_browser_toolbar_tracking_protection_indicator) getToolbar().findViewById<View>(R.id.mozac_browser_toolbar_tracking_protection_indicator)
trackingOnboardingDialog.message.setOnClickListener { trackingOnboardingDialog.message.setOnClickListener {
context.components.analytics.metrics.track(Event.ContextualHintETPInsideTap) metrics.track(Event.ContextualHintETPInsideTap)
trackingOnboardingDialog.dismiss() trackingOnboardingDialog.dismiss()
etpShield.performClick() etpShield.performClick()
} }
context.components.analytics.metrics.track(Event.ContextualHintETPDisplayed) metrics.track(Event.ContextualHintETPDisplayed)
trackingOnboardingDialog.show() trackingOnboardingDialog.show()
settings.incrementTrackingProtectionOnboardingCount() settings.incrementTrackingProtectionOnboardingCount()
} }

View File

@ -33,9 +33,9 @@ import org.mozilla.fenix.ext.components
import org.mozilla.fenix.ext.getPreferenceKey import org.mozilla.fenix.ext.getPreferenceKey
import org.mozilla.fenix.settings.PhoneFeature import org.mozilla.fenix.settings.PhoneFeature
import org.mozilla.fenix.settings.deletebrowsingdata.DeleteBrowsingDataOnQuitType 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.SavedLoginsSortingStrategyMenu
import org.mozilla.fenix.settings.logins.SortingStrategy import org.mozilla.fenix.settings.logins.SortingStrategy
import org.mozilla.fenix.settings.logins.fragment.SavedLoginsFragment
import org.mozilla.fenix.settings.registerOnSharedPreferenceChangeListener import org.mozilla.fenix.settings.registerOnSharedPreferenceChangeListener
import java.security.InvalidParameterException 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. * A simple wrapper for SharedPreferences that makes reading preference a little bit easier.
* @param appContext Reference to application context.
*/ */
@Suppress("LargeClass", "TooManyFunctions") @Suppress("LargeClass", "TooManyFunctions")
class Settings private constructor( class Settings(private val appContext: Context) : PreferencesHolder {
context: Context,
private val isCrashReportEnabledInBuild: Boolean
) : PreferencesHolder {
companion object { companion object {
const val showLoginsSecureWarningSyncMaxCount = 1 const val showLoginsSecureWarningSyncMaxCount = 1
const val showLoginsSecureWarningMaxCount = 1 const val showLoginsSecureWarningMaxCount = 1
@ -88,24 +87,11 @@ class Settings private constructor(
ASK_TO_ALLOW_INT -> AutoplayAction.BLOCKED ASK_TO_ALLOW_INT -> AutoplayAction.BLOCKED
else -> throw InvalidParameterException("$this is not a valid SitePermissionsRules.AutoplayAction") 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 = override val preferences: SharedPreferences =
appContext.getSharedPreferences(FENIX_PREFERENCES, MODE_PRIVATE) appContext.getSharedPreferences(FENIX_PREFERENCES, MODE_PRIVATE)
@ -372,7 +358,7 @@ class Settings private constructor(
appContext.getPreferenceKey(R.string.pref_key_tracking_protection_standard_option), appContext.getPreferenceKey(R.string.pref_key_tracking_protection_standard_option),
false false
).apply() ).apply()
appContext?.components?.let { appContext.components.let {
val policy = it.core.trackingProtectionPolicyFactory val policy = it.core.trackingProtectionPolicyFactory
.createTrackingProtectionPolicy() .createTrackingProtectionPolicy()
it.useCases.settingsUseCases.updateTrackingProtection.invoke(policy) 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_width="0dp"
android:layout_height="80dp" android:layout_height="80dp"
android:background="@color/foundation_normal_theme" android:background="@color/foundation_normal_theme"
app:tabIndicatorColor="@color/accent_normal_theme" app:layout_constraintStart_toStartOf="parent"
app:tabIconTint="@color/tab_icon"
app:tabRippleColor="@android:color/transparent"
app:tabGravity="fill"
app:layout_constraintWidth_percent="0.5"
app:layout_constraintTop_toBottomOf="@+id/handle" 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 <com.google.android.material.tabs.TabItem
android:id="@+id/default_tab_item" android:id="@+id/default_tab_item"
android:layout_height="match_parent"
android:layout_width="0dp" android:layout_width="0dp"
android:icon="@drawable/ic_tabs" android:layout_height="match_parent"
android:contentDescription="@string/tab_header_label" /> android:contentDescription="@string/tab_header_label"
android:layout="@layout/tabs_tray_tab_counter"
app:tabIconTint="@color/tab_icon" />
<com.google.android.material.tabs.TabItem <com.google.android.material.tabs.TabItem
android:id="@+id/private_tab_item" android:id="@+id/private_tab_item"
android:layout_height="match_parent"
android:layout_width="0dp" android:layout_width="0dp"
android:icon="@drawable/ic_private_browsing" android:layout_height="match_parent"
android:contentDescription="@string/tabs_header_private_tabs_title" /> android:contentDescription="@string/tabs_header_private_tabs_title"
android:icon="@drawable/ic_private_browsing" />
</com.google.android.material.tabs.TabLayout> </com.google.android.material.tabs.TabLayout>

View File

@ -25,7 +25,7 @@
android:background="?android:attr/selectableItemBackground" android:background="?android:attr/selectableItemBackground"
android:button="@null" android:button="@null"
app:drawableStartCompat="?android:attr/listChoiceIndicatorSingle" 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:paddingTop="@dimen/radio_button_preference_vertical"
android:paddingStart="@dimen/radio_button_preference_horizontal" android:paddingStart="@dimen/radio_button_preference_horizontal"
android:paddingEnd="@dimen/radio_button_preference_horizontal" android:paddingEnd="@dimen/radio_button_preference_horizontal"
@ -40,7 +40,7 @@
android:background="?android:attr/selectableItemBackground" android:background="?android:attr/selectableItemBackground"
android:button="@null" android:button="@null"
app:drawableStartCompat="?android:attr/listChoiceIndicatorSingle" 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:paddingTop="@dimen/radio_button_preference_vertical"
android:paddingStart="@dimen/radio_button_preference_horizontal" android:paddingStart="@dimen/radio_button_preference_horizontal"
android:paddingEnd="@dimen/radio_button_preference_horizontal" android:paddingEnd="@dimen/radio_button_preference_horizontal"

View File

@ -24,7 +24,7 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:background="?android:attr/selectableItemBackground" android:background="?android:attr/selectableItemBackground"
android:button="@null" 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:paddingStart="@dimen/radio_button_preference_horizontal"
android:paddingTop="@dimen/radio_button_preference_vertical" android:paddingTop="@dimen/radio_button_preference_vertical"
android:paddingEnd="@dimen/radio_button_preference_horizontal" android:paddingEnd="@dimen/radio_button_preference_horizontal"
@ -39,7 +39,7 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:background="?android:attr/selectableItemBackground" android:background="?android:attr/selectableItemBackground"
android:button="@null" 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:paddingStart="@dimen/radio_button_preference_horizontal"
android:paddingTop="@dimen/radio_button_preference_vertical" android:paddingTop="@dimen/radio_button_preference_vertical"
android:paddingEnd="@dimen/radio_button_preference_horizontal" android:paddingEnd="@dimen/radio_button_preference_horizontal"
@ -54,7 +54,7 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:background="?android:attr/selectableItemBackground" android:background="?android:attr/selectableItemBackground"
android:button="@null" 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:paddingStart="@dimen/radio_button_preference_horizontal"
android:paddingTop="@dimen/radio_button_preference_vertical" android:paddingTop="@dimen/radio_button_preference_vertical"
android:paddingEnd="@dimen/radio_button_preference_horizontal" android:paddingEnd="@dimen/radio_button_preference_horizontal"
@ -68,7 +68,7 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:background="?android:attr/selectableItemBackground" android:background="?android:attr/selectableItemBackground"
android:button="@null" 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:paddingStart="@dimen/radio_button_preference_horizontal"
android:paddingTop="@dimen/radio_button_preference_vertical" android:paddingTop="@dimen/radio_button_preference_vertical"
android:paddingEnd="@dimen/radio_button_preference_horizontal" 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/. --> - 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" <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/onboarding_card" android:id="@+id/onboarding_card"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
@ -140,34 +141,18 @@
android:layout_marginStart="16dp" android:layout_marginStart="16dp"
android:layout_marginTop="16dp" android:layout_marginTop="16dp"
android:layout_marginBottom="16dp" android:layout_marginBottom="16dp"
android:background="@android:color/transparent"
android:contentDescription="@string/onboarding_theme_automatic_title" android:contentDescription="@string/onboarding_theme_automatic_title"
android:foreground="@drawable/rounded_ripple"
android:paddingStart="0dp" android:paddingStart="0dp"
android:paddingEnd="8dp" android:paddingEnd="8dp"
android:theme="@style/Checkable.Colored" android:theme="@style/Checkable.Colored"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/divider" 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> </androidx.constraintlayout.widget.ConstraintLayout>

View File

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

View File

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

View File

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

View File

@ -25,8 +25,8 @@
<androidx.cardview.widget.CardView <androidx.cardview.widget.CardView
android:id="@+id/mozac_browser_tabstray_card" android:id="@+id/mozac_browser_tabstray_card"
android:layout_width="92dp" android:layout_width="@dimen/tab_tray_thumbnail_width"
android:layout_height="69dp" android:layout_height="@dimen/tab_tray_thumbnail_height"
android:layout_marginStart="16dp" android:layout_marginStart="16dp"
android:layout_marginTop="8dp" android:layout_marginTop="8dp"
android:backgroundTint="?tabTrayThumbnailItemBackground" 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 <ImageView
android:visibility="gone" android:visibility="gone"
android:id="@+id/drop_down_triangle" android:id="@+id/drop_down_triangle"
android:layout_width="@dimen/tp_onboarding_triangle_width" android:layout_width="@dimen/cfr_triangle_width"
android:layout_height="@dimen/tp_onboarding_triangle_height" android:layout_height="@dimen/cfr_triangle_height"
android:layout_marginStart="@dimen/tp_onboarding_triangle_margin_start" android:layout_marginStart="@dimen/cfr_triangle_margin_edge"
android:importantForAccessibility="no" android:importantForAccessibility="no"
android:rotation="0" android:rotation="0"
app:srcCompat="@drawable/ic_triangle" app:srcCompat="@drawable/ic_cfr_triangle"
app:tint="#0250BB"
android:layout_gravity="start" /> android:layout_gravity="start" />
<androidx.constraintlayout.widget.ConstraintLayout <androidx.constraintlayout.widget.ConstraintLayout
@ -36,7 +37,7 @@
android:focusable="true" android:focusable="true"
android:contentDescription="@string/onboarding_close" android:contentDescription="@string/onboarding_close"
app:srcCompat="@drawable/ic_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_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" /> app:layout_constraintTop_toTopOf="parent" />
@ -60,11 +61,12 @@
<ImageView <ImageView
android:id="@+id/pop_up_triangle" android:id="@+id/pop_up_triangle"
android:layout_width="16dp" android:layout_width="@dimen/cfr_triangle_width"
android:layout_height="@dimen/tp_onboarding_triangle_height" android:layout_height="@dimen/cfr_triangle_height"
android:layout_marginStart="16dp" android:layout_marginStart="@dimen/cfr_triangle_margin_edge"
android:importantForAccessibility="no" android:importantForAccessibility="no"
android:rotation="180" android:rotation="180"
app:srcCompat="@drawable/ic_triangle" app:srcCompat="@drawable/ic_cfr_triangle"
app:tint="#0250BB"
android:layout_gravity="start" /> android:layout_gravity="start" />
</LinearLayout> </LinearLayout>

View File

@ -78,7 +78,7 @@
<!-- Browser menu button that opens the addon manager --> <!-- Browser menu button that opens the addon manager -->
<string name="browser_menu_add_ons">Askouezhioù</string> <string name="browser_menu_add_ons">Askouezhioù</string>
<!-- Text displayed when there are no add-ons to be shown --> <!-- 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 --> <!-- Browser menu button that sends a user to help articles -->
<string name="browser_menu_help">Skoazell</string> <string name="browser_menu_help">Skoazell</string>
<!-- Browser menu button that sends a to a the what's new article --> <!-- 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 --> <!-- Text shown when user enters empty device name -->
<string name="empty_device_name_error">Anv an trevnad nʼhall ket bezañ goullo.</string> <string name="empty_device_name_error">Anv an trevnad nʼhall ket bezañ goullo.</string>
<!-- Label indicating that sync is in progress --> <!-- 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 --> <!-- 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> <string name="sync_failed_summary">Goubredañ cʼhwitet. Berzh diwezhañ: %s</string>
@ -390,7 +390,7 @@
<!-- Preference for using top toolbar --> <!-- Preference for using top toolbar -->
<string name="preference_top_toolbar">Krecʼh</string> <string name="preference_top_toolbar">Krecʼh</string>
<!-- Preference for using bottom toolbar --> <!-- Preference for using bottom toolbar -->
<string name="preference_bottom_toolbar">Diaz</string> <string name="preference_bottom_toolbar">Traoñ</string>
<!-- Theme Preferences --> <!-- Theme Preferences -->
<!-- Preference for using light theme --> <!-- Preference for using light theme -->
@ -398,7 +398,7 @@
<!-- Preference for using dark theme --> <!-- Preference for using dark theme -->
<string name="preference_dark_theme">Teñval</string> <string name="preference_dark_theme">Teñval</string>
<!-- Preference for using using dark or light theme automatically set by battery --> <!-- 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 --> <!-- Preference for using following device theme -->
<string name="preference_follow_device_theme">Mont gant neuz ar benveg</string> <string name="preference_follow_device_theme">Mont gant neuz ar benveg</string>
@ -474,7 +474,7 @@
<!-- Open tabs menu item to share all tabs --> <!-- Open tabs menu item to share all tabs -->
<string name="tabs_menu_share_tabs">Rannañ an ivinelloù</string> <string name="tabs_menu_share_tabs">Rannañ an ivinelloù</string>
<!-- Open tabs menu item to save tabs to collection --> <!-- 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 --> <!-- Content description (not visible, for screen readers etc.): Opens the tab menu when pressed -->
<string name="tab_menu">Lañser an ivinell</string> <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 --> <!-- 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> <string name="collection_menu_button_content_description">Lañser an dastumadoù</string>
<!-- No Open Tabs Message Header --> <!-- 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 --> <!-- 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> <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 --> <!-- 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 --> <!-- 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> <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 --> <!-- 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 --> <!-- Title for the site permissions item in Delete browsing data -->
<string name="preferences_delete_browsing_data_site_permissions">Aotreoù al lechienn</string> <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> <string name="browser_toolbar_url_copied_to_clipboard_snackbar">URL eilet er golver</string>
<!-- Title text for the Add To Homescreen dialog --> <!-- 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 --> <!-- Cancel button text for the Add to Homescreen dialog -->
<string name="add_to_homescreen_cancel">Nullañ</string> <string name="add_to_homescreen_cancel">Nullañ</string>
<!-- Add button text for the Add to Homescreen dialog --> <!-- Add button text for the Add to Homescreen dialog -->
@ -1365,7 +1365,7 @@
<!-- Bookmark deletion confirmation --> <!-- Bookmark deletion confirmation -->
<string name="bookmark_deletion_confirmation">Ha fellout a ra deocʼh dilemel ar sined-mañ ?</string> <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 --> <!-- 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 <!-- text shown before the issuer name to indicate who its verified by, parameter is the name of
the certificate authority that verified the ticket--> the certificate authority that verified the ticket-->
<string name="certificate_info_verified_by">Gwiriet gant:%1$s</string> <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 --> <!-- text for firefox preview moving tip header. "Firefox Nightly" is intentionally hardcoded -->
<string name="tip_firefox_preview_moved_header_preview_installed">Firefox Nightly हलले आहे</string> <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 --> <!-- Onboarding -->
<!-- Text for onboarding welcome message <!-- Text for onboarding welcome message
The first parameter is the name of the app (e.g. Firefox Preview) --> 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> <string name="onboarding_firefox_account_sync_is_on">सिंक चालू आहे</string>
<!-- text to display in the snackbar if automatic sign-in fails. user may try again --> <!-- text to display in the snackbar if automatic sign-in fails. user may try again -->
<string name="onboarding_firefox_account_automatic_signin_failed">साइन इन करण्यात अयशस्वी</string> <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 --> <!-- text for tracking protection radio button option for strict level of blocking -->
<string name="onboarding_tracking_protection_strict_button">कठोर (सुचवलेले)</string> <string name="onboarding_tracking_protection_strict_button">कठोर (सुचवलेले)</string>
<!-- text for the toolbar position card header <!-- text for the toolbar position card header

View File

@ -16,6 +16,13 @@
<!-- No Open Tabs Message Description --> <!-- No Open Tabs Message Description -->
<string name="no_open_tabs_description">Filele deschise vor fi afișate aici.</string> <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) --> <!-- 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> <string name="about_content">%1$s este realizat de Mozilla.</string>
@ -38,6 +45,9 @@
<!-- Text for the negative button --> <!-- Text for the negative button -->
<string name="cfr_neg_button_text">Nu, mulțumesc</string> <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 --> <!-- Text for the positive button -->
<string name="search_widget_cfr_pos_button_text">Adaugă widget</string> <string name="search_widget_cfr_pos_button_text">Adaugă widget</string>
<!-- Text for the negative button --> <!-- Text for the negative button -->
@ -110,6 +120,8 @@
<string name="browser_menu_powered_by2">Cu tehnologie %1$s</string> <string name="browser_menu_powered_by2">Cu tehnologie %1$s</string>
<!-- Browser menu button to put the current page in reader mode --> <!-- Browser menu button to put the current page in reader mode -->
<string name="browser_menu_read">Mod de lectură</string> <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 --> <!-- Browser menu button to open the current page in an external app -->
<string name="browser_menu_open_app_link">Deschide în aplicație</string> <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 --> <!-- 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> <string name="preferences_show_search_shortcuts">Afișează comenzile rapide pentru căutări</string>
<!-- Preference title for switch preference to show search suggestions --> <!-- Preference title for switch preference to show search suggestions -->
<string name="preferences_show_search_suggestions">Afișează sugestii de căutare</string> <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 --> <!-- 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> <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 --> <!-- Preference title for switch preference to show a clipboard suggestion when searching -->
@ -367,6 +381,9 @@
<!-- Preference for removing FxA account --> <!-- Preference for removing FxA account -->
<string name="preferences_sync_remove_account">Șterge contul</string> <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 --> <!-- Button to open camera for pairing -->
<string name="pair_open_camera">Cameră deschisă</string> <string name="pair_open_camera">Cameră deschisă</string>
<!-- Button to cancel pairing --> <!-- Button to cancel pairing -->
@ -408,6 +425,8 @@
<!-- Option in Library to open History page --> <!-- Option in Library to open History page -->
<string name="library_history">Istoric</string> <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 --> <!-- Option in Library to open Reading List -->
<string name="library_reading_list">Listă de lectură</string> <string name="library_reading_list">Listă de lectură</string>
<!-- Menu Item Label for Search in Library --> <!-- Menu Item Label for Search in Library -->
@ -430,6 +449,24 @@
<string name="add_tab">Adaugă fila</string> <string name="add_tab">Adaugă fila</string>
<!-- Content description (not visible, for screen readers etc.): Add tab button. Adds a news tab when pressed --> <!-- 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> <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 --> <!-- Content description (not visible, for screen readers etc.): Close tab button. Closes the current session when pressed -->
<string name="close_tab">Închide fila</string> <string name="close_tab">Închide fila</string>
<!-- Content description (not visible, for screen readers etc.): Close tab <title> button. First parameter is tab title --> <!-- 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> <string name="tabs_menu_close_all_tabs">Închide toate filele</string>
<!-- Open tabs menu item to share all tabs --> <!-- Open tabs menu item to share all tabs -->
<string name="tabs_menu_share_tabs">Partajează filele</string> <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 --> <!-- Content description (not visible, for screen readers etc.): Opens the tab menu when pressed -->
<string name="tab_menu">Meniu de file</string> <string name="tab_menu">Meniu de file</string>
<!-- Tab menu item to share the tab --> <!-- Tab menu item to share the tab -->
@ -463,6 +502,9 @@
<!-- Text for the menu button to remove a top site --> <!-- Text for the menu button to remove a top site -->
<string name="remove_top_site">Șterge</string> <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 --> <!-- History -->
<!-- Text for the button to clear all history --> <!-- Text for the button to clear all history -->
<string name="history_delete_all">Șterge istoricul</string> <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 --> <!-- 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> <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 --> <!-- Title for the "select tabs" step of the collection creator -->
<string name="create_collection_select_tabs">Selectează filele</string> <string name="create_collection_select_tabs">Selectează filele</string>
<!-- Title for the "select collection" step of the collection creator --> <!-- 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--> <!-- Button to save currently selected tabs in the "select tabs" step of the collection creator-->
<string name="create_collection_save">Salvează</string> <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--> <!-- 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> <string name="create_collection_default_name">Colecția %d</string>
@ -791,11 +840,15 @@
<!-- Title for Accessibility Text Size Scaling Preference --> <!-- Title for Accessibility Text Size Scaling Preference -->
<string name="preference_accessibility_font_size_title">Dimensiunea fontului</string> <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 --> <!-- 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> <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 --> <!-- Title for the Delete browsing data preference -->
<string name="preferences_delete_browsing_data">Șterge datele de navigare</string> <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 --> <!-- 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> <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> <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. --> <!-- 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> <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 --> <!-- Action item in menu for the Delete browsing data on quit feature -->
<string name="delete_browsing_data_on_quit_action">Închide</string> <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 <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> 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 --> <!-- 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> <string name="tip_firefox_preview_moved_header_preview_installed">Firefox Nightly s-a mutat</string>
<!-- text for firefox preview moving tip description --> <!-- text for firefox preview moving tip description -->
@ -903,8 +957,21 @@
<string name="onboarding_firefox_account_sync_is_on">Sync este activat</string> <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 --> <!-- 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> <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 --> <!-- text for tracking protection radio button option for strict level of blocking -->
<string name="onboarding_tracking_protection_strict_button">Strictă (recomandat)</string> <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 <!-- text for the toolbar position card header
In English this is an idiom for "choose a side as in an argument or fight" 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 --> 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> <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 --> <!-- Text displayed that links to website about enhanced tracking protection -->
<string name="preference_enhanced_tracking_protection_explanation_learn_more">Află mai multe</string> <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 --> <!-- 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> <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 --> <!-- Preference for enhanced tracking protection for the strict protection settings -->
<string name="preference_enhanced_tracking_protection_strict">Strictă</string> <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 --> <!-- 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> <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 --> <!-- Preference for enhanced tracking protection for the custom protection settings -->
<string name="preference_enhanced_tracking_protection_custom">Personalizat</string> <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 --> <!-- 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> <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 --> <!-- 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 --> <!-- 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> <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 --> <!-- 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> <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 --> <!-- Enhanced Tracking Protection message that protection is currently off for this site -->
@ -1069,6 +1146,8 @@
<!-- About page link text to open support link --> <!-- About page link text to open support link -->
<string name="about_support">Asistență</string> <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 --> <!-- About page link text to open privacy notice link -->
<string name="about_privacy_notice">Politică de confidențialitate</string> <string name="about_privacy_notice">Politică de confidențialitate</string>
<!-- About page link text to open know your rights link --> <!-- About page link text to open know your rights link -->
@ -1204,6 +1283,13 @@
<!-- Summary for Accessibility Force Enable Zoom Preference --> <!-- 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> <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 --> <!-- Title of the Add search engine screen -->
<string name="search_engine_add_custom_search_engine_title">Adaugă un motor de căutare</string> <string name="search_engine_add_custom_search_engine_title">Adaugă un motor de căutare</string>
<!-- Title of the Edit search engine screen --> <!-- Title of the Edit search engine screen -->
@ -1296,11 +1382,57 @@
<string name="certificate_info_verified_by">Verificat de: %1$s</string> <string name="certificate_info_verified_by">Verificat de: %1$s</string>
<!-- Login overflow menu delete button --> <!-- Login overflow menu delete button -->
<string name="login_menu_delete_button">Șterge</string> <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 --> <!-- Message in delete confirmation dialog for logins -->
<string name="login_deletion_confirmation">Sigur vrei să ștergi aceste date de autentificare?</string> <string name="login_deletion_confirmation">Sigur vrei să ștergi aceste date de autentificare?</string>
<!-- Positive action of a dialog asking to delete --> <!-- Positive action of a dialog asking to delete -->
<string name="dialog_delete_positive">Șterge</string> <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. --> <!-- Confirmation dialog button text when top sites limit is reached. -->
<string name="top_sites_max_limit_confirmation_button">Ok, am înțeles</string> <string name="top_sites_max_limit_confirmation_button">Ok, am înțeles</string>

View File

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

View File

@ -28,7 +28,7 @@
<dimen name="radio_button_padding_vertical">12dp</dimen> <dimen name="radio_button_padding_vertical">12dp</dimen>
<dimen name="radio_button_preference_height">48dp</dimen> <dimen name="radio_button_preference_height">48dp</dimen>
<dimen name="radio_button_preference_horizontal">16dp</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="radio_button_preference_vertical">12dp</dimen>
<dimen name="phone_feature_label_recommended_text_size">14sp</dimen> <dimen name="phone_feature_label_recommended_text_size">14sp</dimen>
<dimen name="site_permissions_exceptions_item_text_size">18sp</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_height">48dp</dimen>
<dimen name="context_menu_x_offset">8dp</dimen> <dimen name="context_menu_x_offset">8dp</dimen>
<dimen name="tp_onboarding_width">256dp</dimen> <dimen name="tp_onboarding_width">256dp</dimen>
<dimen name="tp_onboarding_triangle_height">16dp</dimen> <!-- Dimensions for the CFR (Contextual Feature Recommendation) tooltip triangle. -->
<dimen name="tp_onboarding_triangle_width">16dp</dimen> <dimen name="cfr_triangle_height">16dp</dimen>
<dimen name="tp_onboarding_triangle_margin_start">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> <dimen name="tab_counter_box_width_height">24dp</dimen>
@ -155,6 +156,8 @@
<!-- Tabs Tray --> <!-- Tabs Tray -->
<dimen name="tab_tray_top_offset">40dp</dimen> <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 --> <!-- Saved Logins Fragment -->
<dimen name="saved_logins_sort_menu_dropdown_chevron_icon_margin_start">10dp</dimen> <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 --> <!-- Label for the secret settings preference -->
<string name="preferences_debug_settings">Secret Settings</string> <string name="preferences_debug_settings">Secret Settings</string>
<string name="preferences_debug_settings_enable_tab_tray">Use New Tab Tray</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> </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> <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. --> <!-- Confirmation dialog button text when top sites limit is reached. -->
<string name="top_sites_max_limit_confirmation_button">OK, Got It</string> <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> </resources>

View File

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

View File

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

View File

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

View File

@ -7,6 +7,7 @@ package org.mozilla.fenix.components
import android.content.Context import android.content.Context
import io.mockk.mockk import io.mockk.mockk
import org.mozilla.fenix.utils.ClipboardHandler import org.mozilla.fenix.utils.ClipboardHandler
import org.mozilla.fenix.utils.Settings
class TestComponents(private val context: Context) : Components(context) { class TestComponents(private val context: Context) : Components(context) {
override val backgroundServices by lazy { 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 analytics by lazy { Analytics(context) }
override val clipboardHandler by lazy { ClipboardHandler(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 @Test
fun handleToolbarNewTabPress() = runBlockingTest { fun handleToolbarNewTabPress() = runBlockingTest {
val browsingModeManager: BrowsingModeManager = DefaultBrowsingModeManager(BrowsingMode.Private) {} val browsingModeManager: BrowsingModeManager = DefaultBrowsingModeManager(
BrowsingMode.Private,
mockk(relaxed = true)
) {}
val item = TabCounterMenuItem.NewTab(false) val item = TabCounterMenuItem.NewTab(false)
every { activity.browsingModeManager } returns browsingModeManager every { activity.browsingModeManager } returns browsingModeManager
@ -556,7 +559,10 @@ class DefaultBrowserToolbarControllerTest {
@Test @Test
fun handleToolbarNewPrivateTabPress() = runBlockingTest { fun handleToolbarNewPrivateTabPress() = runBlockingTest {
val browsingModeManager: BrowsingModeManager = DefaultBrowsingModeManager(BrowsingMode.Normal) {} val browsingModeManager: BrowsingModeManager = DefaultBrowsingModeManager(
BrowsingMode.Normal,
mockk(relaxed = true)
) {}
val item = TabCounterMenuItem.NewTab(true) val item = TabCounterMenuItem.NewTab(true)
every { activity.browsingModeManager } returns browsingModeManager 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.LayoutInflater
import android.view.View import android.view.View
import io.mockk.every import io.mockk.every
import io.mockk.mockk
import kotlinx.android.synthetic.main.onboarding_toolbar_position_picker.view.* import kotlinx.android.synthetic.main.onboarding_toolbar_position_picker.view.*
import mozilla.components.support.test.robolectric.testContext import mozilla.components.support.test.robolectric.testContext
import org.junit.After
import org.junit.Assert.assertFalse import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue import org.junit.Assert.assertTrue
import org.junit.Before import org.junit.Before
import org.junit.Test import org.junit.Test
import org.junit.runner.RunWith import org.junit.runner.RunWith
import org.mozilla.fenix.ext.components
import org.mozilla.fenix.helpers.FenixRobolectricTestRunner import org.mozilla.fenix.helpers.FenixRobolectricTestRunner
import org.mozilla.fenix.utils.Settings import org.mozilla.fenix.utils.Settings
@ -27,16 +26,10 @@ class OnboardingToolbarPositionPickerViewHolderTest {
@Before @Before
fun setup() { fun setup() {
val components = testContext.components
view = LayoutInflater.from(testContext) view = LayoutInflater.from(testContext)
.inflate(OnboardingToolbarPositionPickerViewHolder.LAYOUT_ID, null) .inflate(OnboardingToolbarPositionPickerViewHolder.LAYOUT_ID, null)
settings = mockk(relaxed = true) settings = components.settings
Settings.instance = settings
}
@After
fun teardown() {
Settings.instance = null
} }
@Test @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.helpers.FenixRobolectricTestRunner
import org.mozilla.fenix.settings.SupportUtils import org.mozilla.fenix.settings.SupportUtils
import org.mozilla.fenix.utils.Settings import org.mozilla.fenix.utils.Settings
import org.mozilla.fenix.whatsnew.clear
@ExperimentalCoroutinesApi @ExperimentalCoroutinesApi
@RunWith(FenixRobolectricTestRunner::class) @RunWith(FenixRobolectricTestRunner::class)
@ -48,10 +47,10 @@ class DefaultSearchControllerTest {
private val searchEngine: SearchEngine = mockk(relaxed = true) private val searchEngine: SearchEngine = mockk(relaxed = true)
private val metrics: MetricController = mockk(relaxed = true) private val metrics: MetricController = mockk(relaxed = true)
private val sessionManager: SessionManager = 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 val clearToolbarFocus: (() -> Unit) = mockk(relaxed = true)
private lateinit var controller: DefaultSearchController private lateinit var controller: DefaultSearchController
private lateinit var settings: Settings
@Before @Before
fun setUp() { fun setUp() {
@ -60,6 +59,7 @@ class DefaultSearchControllerTest {
every { store.state.searchEngineSource.searchEngine } returns searchEngine every { store.state.searchEngineSource.searchEngine } returns searchEngine
every { activity.metrics } returns metrics every { activity.metrics } returns metrics
every { activity.components.core.sessionManager } returns sessionManager every { activity.components.core.sessionManager } returns sessionManager
every { activity.components.settings } returns settings
controller = DefaultSearchController( controller = DefaultSearchController(
activity = activity, activity = activity,
@ -67,8 +67,6 @@ class DefaultSearchControllerTest {
navController = navController, navController = navController,
clearToolbarFocus = clearToolbarFocus clearToolbarFocus = clearToolbarFocus
) )
settings = testContext.settings().apply { testContext.settings().clear() }
} }
@Test @Test
@ -154,10 +152,7 @@ class DefaultSearchControllerTest {
@Test @Test
fun `show search shortcuts when setting enabled AND query empty`() { fun `show search shortcuts when setting enabled AND query empty`() {
val text = "" val text = ""
testContext.settings().preferences every { settings.shouldShowSearchShortcuts } returns true
.edit()
.putBoolean(testContext.getString(R.string.pref_key_show_search_shortcuts), true)
.apply()
controller.handleTextChanged(text) controller.handleTextChanged(text)
@ -168,10 +163,7 @@ class DefaultSearchControllerTest {
fun `show search shortcuts when setting enabled AND query equals url`() { fun `show search shortcuts when setting enabled AND query equals url`() {
val text = "mozilla.org" val text = "mozilla.org"
every { store.state.url } returns "mozilla.org" every { store.state.url } returns "mozilla.org"
testContext.settings().preferences every { settings.shouldShowSearchShortcuts } returns true
.edit()
.putBoolean(testContext.getString(R.string.pref_key_show_search_shortcuts), true)
.apply()
controller.handleTextChanged(text) controller.handleTextChanged(text)
@ -189,10 +181,7 @@ class DefaultSearchControllerTest {
@Test @Test
fun `do not show search shortcuts when setting disabled AND query empty AND url not matching query`() { fun `do not show search shortcuts when setting disabled AND query empty AND url not matching query`() {
testContext.settings().preferences every { settings.shouldShowSearchShortcuts } returns false
.edit()
.putBoolean(testContext.getString(R.string.pref_key_show_search_shortcuts), false)
.apply()
assertFalse(testContext.settings().shouldShowSearchShortcuts) assertFalse(testContext.settings().shouldShowSearchShortcuts)
@ -205,10 +194,7 @@ class DefaultSearchControllerTest {
@Test @Test
fun `do not show search shortcuts when setting disabled AND query non-empty`() { fun `do not show search shortcuts when setting disabled AND query non-empty`() {
testContext.settings().preferences every { settings.shouldShowSearchShortcuts } returns false
.edit()
.putBoolean(testContext.getString(R.string.pref_key_show_search_shortcuts), false)
.apply()
assertFalse(testContext.settings().shouldShowSearchShortcuts) assertFalse(testContext.settings().shouldShowSearchShortcuts)

View File

@ -6,33 +6,58 @@ package org.mozilla.fenix.search.toolbar
import android.content.Context import android.content.Context
import androidx.appcompat.view.ContextThemeWrapper import androidx.appcompat.view.ContextThemeWrapper
import androidx.core.graphics.drawable.toBitmap
import io.mockk.MockKAnnotations import io.mockk.MockKAnnotations
import io.mockk.Runs import io.mockk.Runs
import io.mockk.every import io.mockk.every
import io.mockk.impl.annotations.MockK import io.mockk.impl.annotations.MockK
import io.mockk.just import io.mockk.just
import io.mockk.mockk
import io.mockk.slot import io.mockk.slot
import io.mockk.spyk import io.mockk.spyk
import io.mockk.verify import io.mockk.verify
import mozilla.components.browser.toolbar.BrowserToolbar import mozilla.components.browser.toolbar.BrowserToolbar
import mozilla.components.browser.toolbar.edit.EditToolbar
import mozilla.components.concept.engine.Engine import mozilla.components.concept.engine.Engine
import mozilla.components.concept.toolbar.Toolbar import mozilla.components.concept.toolbar.Toolbar
import mozilla.components.support.test.robolectric.testContext import mozilla.components.support.test.robolectric.testContext
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue import org.junit.Assert.assertTrue
import org.junit.Before import org.junit.Before
import org.junit.Test import org.junit.Test
import org.junit.runner.RunWith import org.junit.runner.RunWith
import org.mozilla.fenix.R import org.mozilla.fenix.R
import org.mozilla.fenix.components.metrics.Event
import org.mozilla.fenix.helpers.FenixRobolectricTestRunner import org.mozilla.fenix.helpers.FenixRobolectricTestRunner
import org.mozilla.fenix.search.SearchEngineSource
import org.mozilla.fenix.search.SearchFragmentState
@RunWith(FenixRobolectricTestRunner::class) @RunWith(FenixRobolectricTestRunner::class)
class ToolbarViewTest { class ToolbarViewTest {
@MockK(relaxed = true) private lateinit var interactor: ToolbarInteractor @MockK(relaxed = true) private lateinit var interactor: ToolbarInteractor
@MockK private lateinit var engine: Engine @MockK private lateinit var engine: Engine
private lateinit var context: Context private lateinit var context: Context
private lateinit var toolbar: BrowserToolbar 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 @Before
fun setup() { fun setup() {
@ -72,6 +97,69 @@ class ToolbarViewTest {
assertTrue(toolbar.private) 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( private fun buildToolbarView(isPrivate: Boolean) = ToolbarView(
context, context,
interactor, interactor,

View File

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

View File

@ -6,7 +6,9 @@ package org.mozilla.fenix.trackingprotection
import android.content.Context import android.content.Context
import android.view.View import android.view.View
import io.mockk.MockKAnnotations
import io.mockk.every import io.mockk.every
import io.mockk.impl.annotations.MockK
import io.mockk.mockk import io.mockk.mockk
import io.mockk.spyk import io.mockk.spyk
import io.mockk.verify import io.mockk.verify
@ -16,28 +18,27 @@ import org.junit.Before
import org.junit.Test import org.junit.Test
import org.junit.runner.RunWith import org.junit.runner.RunWith
import org.mozilla.fenix.R 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.helpers.FenixRobolectricTestRunner
import org.mozilla.fenix.utils.Settings
@RunWith(FenixRobolectricTestRunner::class) @RunWith(FenixRobolectricTestRunner::class)
class TrackingProtectionOverlayTest { class TrackingProtectionOverlayTest {
private lateinit var context: Context private lateinit var context: Context
private lateinit var settings: Settings @MockK(relaxed = true) private lateinit var settings: Settings
private lateinit var toolbar: View @MockK(relaxed = true) private lateinit var metrics: MetricController
private lateinit var icon: View @MockK(relaxed = true) private lateinit var toolbar: View
private lateinit var session: Session @MockK(relaxed = true) private lateinit var icon: View
private lateinit var overlay: TrackingProtectionOverlay @MockK(relaxed = true) private lateinit var session: Session
@MockK(relaxed = true) private lateinit var overlay: TrackingProtectionOverlay
@Before @Before
fun setup() { fun setup() {
MockKAnnotations.init(this)
context = spyk(testContext) 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 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 package org.mozilla.fenix.utils
import org.mozilla.fenix.helpers.FenixRobolectricTestRunner
import mozilla.components.feature.sitepermissions.SitePermissionsRules import mozilla.components.feature.sitepermissions.SitePermissionsRules
import mozilla.components.feature.sitepermissions.SitePermissionsRules.Action.ALLOWED import mozilla.components.feature.sitepermissions.SitePermissionsRules.Action.ALLOWED
import mozilla.components.feature.sitepermissions.SitePermissionsRules.Action.ASK_TO_ALLOW 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.Before
import org.junit.Test import org.junit.Test
import org.junit.runner.RunWith import org.junit.runner.RunWith
import org.mozilla.fenix.ext.clearAndCommit import org.mozilla.fenix.helpers.FenixRobolectricTestRunner
import org.mozilla.fenix.ext.settings
import org.mozilla.fenix.settings.PhoneFeature import org.mozilla.fenix.settings.PhoneFeature
import org.mozilla.fenix.settings.deletebrowsingdata.DeleteBrowsingDataOnQuitType import org.mozilla.fenix.settings.deletebrowsingdata.DeleteBrowsingDataOnQuitType
@ -27,9 +25,18 @@ class SettingsTest {
lateinit var settings: Settings 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 @Before
fun setUp() { fun setUp() {
settings = testContext.settings().apply(Settings::clear) settings = Settings(testContext)
} }
@Test @Test
@ -103,28 +110,6 @@ class SettingsTest {
assertEquals("Mozilla", settings.defaultSearchEngineName) 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 @Test
fun isRemoteDebuggingEnabled() { fun isRemoteDebuggingEnabled() {
// When just created // When just created
@ -451,7 +436,7 @@ class SettingsTest {
// When just created // When just created
// Then // Then
assertEquals( assertEquals(
defaultPermissions(), defaultPermissions,
settings.getSitePermissionsCustomSettingsRules() settings.getSitePermissionsCustomSettingsRules()
) )
} }
@ -463,7 +448,7 @@ class SettingsTest {
// Then // Then
assertEquals( assertEquals(
defaultPermissions().copy(camera = BLOCKED), defaultPermissions.copy(camera = BLOCKED),
settings.getSitePermissionsCustomSettingsRules() settings.getSitePermissionsCustomSettingsRules()
) )
} }
@ -475,7 +460,7 @@ class SettingsTest {
// Then // Then
assertEquals( assertEquals(
defaultPermissions().copy(notification = BLOCKED), defaultPermissions.copy(notification = BLOCKED),
settings.getSitePermissionsCustomSettingsRules() settings.getSitePermissionsCustomSettingsRules()
) )
} }
@ -487,7 +472,7 @@ class SettingsTest {
// Then // Then
assertEquals( assertEquals(
defaultPermissions().copy(location = BLOCKED), defaultPermissions.copy(location = BLOCKED),
settings.getSitePermissionsCustomSettingsRules() settings.getSitePermissionsCustomSettingsRules()
) )
} }
@ -499,7 +484,7 @@ class SettingsTest {
// Then // Then
assertEquals( assertEquals(
defaultPermissions().copy(microphone = BLOCKED), defaultPermissions.copy(microphone = BLOCKED),
settings.getSitePermissionsCustomSettingsRules() settings.getSitePermissionsCustomSettingsRules()
) )
} }
@ -509,7 +494,7 @@ class SettingsTest {
settings.setSitePermissionsPhoneFeatureAction(PhoneFeature.AUTOPLAY_AUDIBLE, ALLOWED) settings.setSitePermissionsPhoneFeatureAction(PhoneFeature.AUTOPLAY_AUDIBLE, ALLOWED)
assertEquals( assertEquals(
defaultPermissions().copy(autoplayAudible = ALLOWED), defaultPermissions.copy(autoplayAudible = ALLOWED),
settings.getSitePermissionsCustomSettingsRules() settings.getSitePermissionsCustomSettingsRules()
) )
} }
@ -519,25 +504,8 @@ class SettingsTest {
settings.setSitePermissionsPhoneFeatureAction(PhoneFeature.AUTOPLAY_INAUDIBLE, ALLOWED) settings.setSitePermissionsPhoneFeatureAction(PhoneFeature.AUTOPLAY_INAUDIBLE, ALLOWED)
assertEquals( assertEquals(
defaultPermissions().copy(autoplayInaudible = ALLOWED), defaultPermissions.copy(autoplayInaudible = ALLOWED),
settings.getSitePermissionsCustomSettingsRules() 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 package org.mozilla.fenix.whatsnew
import org.mozilla.fenix.helpers.FenixRobolectricTestRunner import androidx.preference.PreferenceManager
import mozilla.components.support.test.robolectric.testContext import mozilla.components.support.test.robolectric.testContext
import org.junit.Assert import org.junit.Assert
import org.junit.Before import org.junit.Before
import org.junit.Test import org.junit.Test
import org.junit.runner.RunWith import org.junit.runner.RunWith
import org.mozilla.fenix.ext.clearAndCommit import org.mozilla.fenix.ext.clearAndCommit
import org.mozilla.fenix.utils.Settings import org.mozilla.fenix.helpers.FenixRobolectricTestRunner
@RunWith(FenixRobolectricTestRunner::class) @RunWith(FenixRobolectricTestRunner::class)
class WhatsNewStorageTest { class WhatsNewStorageTest {
private lateinit var storage: SharedPreferenceWhatsNewStorage private lateinit var storage: SharedPreferenceWhatsNewStorage
private lateinit var settings: Settings
@Before @Before
fun setUp() { fun setUp() {
storage = SharedPreferenceWhatsNewStorage(testContext) storage = SharedPreferenceWhatsNewStorage(testContext)
settings = Settings.getInstance(testContext) PreferenceManager.getDefaultSharedPreferences(testContext).clearAndCommit()
.apply(Settings::clear)
} }
@Test @Test
@ -57,7 +56,3 @@ class WhatsNewStorageTest {
const val DAY_IN_MILLIS = 3600 * 1000 * 24 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 * 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/. */ * 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 mozilla.components.support.test.robolectric.testContext
import org.junit.Assert.assertEquals import org.junit.Assert.assertEquals
import org.junit.Before import org.junit.Before
import org.junit.Test import org.junit.Test
import org.junit.runner.RunWith import org.junit.runner.RunWith
import org.mozilla.fenix.ext.settings import org.mozilla.fenix.ext.clearAndCommit
import org.mozilla.fenix.utils.Settings import org.mozilla.fenix.helpers.FenixRobolectricTestRunner
@RunWith(FenixRobolectricTestRunner::class) @RunWith(FenixRobolectricTestRunner::class)
class WhatsNewTest { class WhatsNewTest {
private lateinit var storage: SharedPreferenceWhatsNewStorage private lateinit var storage: SharedPreferenceWhatsNewStorage
private lateinit var settings: Settings
@Before @Before
fun setup() { fun setup() {
storage = SharedPreferenceWhatsNewStorage(testContext) storage = SharedPreferenceWhatsNewStorage(testContext)
settings = testContext.settings().apply(Settings::clear) PreferenceManager.getDefaultSharedPreferences(testContext).clearAndCommit()
WhatsNew.wasUpdatedRecently = null WhatsNew.wasUpdatedRecently = null
} }

View File

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

View File

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

View File

@ -3,5 +3,5 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
object AndroidComponents { 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 leanplum = "5.4.0"
const val osslicenses_plugin = "0.9.5" const val osslicenses_plugin = "0.9.5"
const val osslicenses_library = "17.0.0" 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_appcompat = "1.2.0-rc01"
const val androidx_biometric = "1.1.0-alpha01" 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' # - 'NotificationReport'
# - 'FindingsReport' # - 'FindingsReport'
# - 'BuildFailureReport' # - 'BuildFailureReport'
console-reports:
active: true
exclude:
# - 'HtmlOutputReport' # - 'HtmlOutputReport'
- 'PlainOutputReport' - 'PlainOutputReport'
- 'XmlOutputReport' - 'XmlOutputReport'
@ -34,6 +30,8 @@ console-reports:
comments: comments:
active: true active: true
excludes: "**/*Test.kt, **/*Spec.kt, **/test/**, **/androidTest/**" excludes: "**/*Test.kt, **/*Spec.kt, **/test/**, **/androidTest/**"
AbsentOrWrongFileLicense:
active: true
CommentOverPrivateFunction: CommentOverPrivateFunction:
active: false active: false
CommentOverPrivateProperty: 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. 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). Additional metrics or pings defined by Fenix are documented in the [Glean SDK autogenerated docs](metrics.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.
## Leanplum ## Leanplum
See [here](https://github.com/mozilla-mobile/fenix/blob/master/docs/mma.md) for details on Leanplum usage in Firefox Preview. 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", "ast",
"az", "az",
"be", "be",
"bg",
"bn", "bn",
"br", "br",
"bs", "bs",
@ -58,10 +59,13 @@ locales = [
"lt", "lt",
"ml", "ml",
"mr", "mr",
"ms",
"my", "my",
"nb-NO", "nb-NO",
"ne-NP",
"nl", "nl",
"nn-NO", "nn-NO",
"nv",
"oc", "oc",
"pa-IN", "pa-IN",
"pl", "pl",
@ -83,6 +87,7 @@ locales = [
"trs", "trs",
"uk", "uk",
"ur", "ur",
"uz",
"vec", "vec",
"vi", "vi",
"zh-CN", "zh-CN",

View File

@ -45,22 +45,14 @@ if (localProperties != null) {
if (appServicesLocalPath != null) { if (appServicesLocalPath != null) {
log("Enabling automatic publication of application-services from: $appServicesLocalPath") log("Enabling automatic publication of application-services from: $appServicesLocalPath")
def publishAppServicesCmd = ["./automation/publish_to_maven_local_if_modified.py"] // Windows can't execute .py files directly, so we assume a "manually installed" python,
// Application-services doesn't build on native Windows. However, it still makes sense to // which comes with a "py" launcher and respects the shebang line to specify the version.
// enable these workflows on Windows, even if it isn't quote as automatic as elsewhere - def publishAppServicesCmd = [];
// 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...
if (System.properties['os.name'].toLowerCase().contains('windows')) { if (System.properties['os.name'].toLowerCase().contains('windows')) {
log('NOTE: The autoPublish workflows do not work on native windows.'); publishAppServicesCmd << "py";
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 << "./automation/publish_to_maven_local_if_modified.py";
runCmd(publishAppServicesCmd, appServicesLocalPath, "Published application-services for local development.", false)
} else { } else {
log("Disabled auto-publication of application-services. Enable it by settings '$settingAppServicesPath' in local.properties") 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) String androidComponentsLocalPath = localProperties.getProperty(settingAndroidComponentsPath)
if (androidComponentsLocalPath != null) { 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") log("Enabling automatic publication of android-components from: $androidComponentsLocalPath")
// As above, hacks to execute .py files on Windows.
def publishAcCmd = []; def publishAcCmd = [];
if (System.properties['os.name'].toLowerCase().contains('windows')) { if (System.properties['os.name'].toLowerCase().contains('windows')) {
publishAcCmd << "py"; publishAcCmd << "py";

View File

@ -82,7 +82,7 @@ job-defaults:
- '--app=fenix' - '--app=fenix'
- '--browsertime' - '--browsertime'
- '--cold' - '--cold'
- '--binary=org.mozilla.fenix.nightly' - '--binary=org.mozilla.fenix'
- '--activity=org.mozilla.fenix.IntentReceiverActivity' - '--activity=org.mozilla.fenix.IntentReceiverActivity'
- '--download-symbols=ondemand' - '--download-symbols=ondemand'
- '--browsertime-node=$MOZ_FETCHES_DIR/node/bin/node' - '--browsertime-node=$MOZ_FETCHES_DIR/node/bin/node'
@ -232,3 +232,15 @@ jobs:
test-name: google-search-restaurants test-name: google-search-restaurants
treeherder: treeherder:
symbol: 'Btime(tp6m-28-c)' 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: treeherder:
symbol: forPerformanceTest(B) 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: nightly-simulation:
attributes: attributes:
nightly: false nightly: false
@ -112,47 +101,22 @@ jobs:
include-shippable-secrets: true include-shippable-secrets: true
run: run:
geckoview-engine: geckoNightly geckoview-engine: geckoNightly
gradle-build-type: fennecNightly gradle-build-type: fenixProduction
treeherder: treeherder:
symbol: nightlySim(B) symbol: nightlySim(B)
beta: nightly:
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:
attributes: attributes:
nightly: true nightly: true
include-nightly-version: true include-nightly-version: true
include-shippable-secrets: true include-shippable-secrets: true
run: run:
geckoview-engine: geckoNightly geckoview-engine: geckoNightly
# XXX `fenixProduction` is now the new nightly.
gradle-build-type: fenixProduction gradle-build-type: fenixProduction
run-on-tasks-for: [github-release] run-on-tasks-for: []
treeherder: treeherder:
symbol: production(B) symbol: nightly(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)
fennec-beta: fennec-beta:
attributes: attributes:

View File

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

View File

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

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