diff --git a/.cron.yml b/.cron.yml index 0ee4b916a..3e9ae015f 100644 --- a/.cron.yml +++ b/.cron.yml @@ -10,7 +10,7 @@ jobs: treeherder-symbol: Nd target-tasks-method: nightly when: - - {hour: 18, minute: 0} + - {hour: 5, minute: 0} # This is a temporary hook in order to not overload Google Play. # See bug 1628413 for more context. - name: nightly-on-google-play @@ -19,7 +19,7 @@ jobs: treeherder-symbol: Nd-gp target-tasks-method: nightly-on-google-play when: - - {hour: 6, minute: 0} + - {hour: 17, minute: 0} - name: fennec-production job: type: decision-task @@ -31,7 +31,7 @@ jobs: type: decision-task treeherder-symbol: bump-ac target-tasks-method: bump_android_components - when: [{hour: 14, minute: 0}] + when: [{hour: 15, minute: 30}] - name: screenshots job: type: decision-task diff --git a/app/src/androidTest/java/org/mozilla/fenix/ui/TabbedBrowsingTest.kt b/app/src/androidTest/java/org/mozilla/fenix/ui/TabbedBrowsingTest.kt index 02a270524..7ff893c41 100644 --- a/app/src/androidTest/java/org/mozilla/fenix/ui/TabbedBrowsingTest.kt +++ b/app/src/androidTest/java/org/mozilla/fenix/ui/TabbedBrowsingTest.kt @@ -262,11 +262,11 @@ class TabbedBrowsingTest { }.openTabTray { verifyNoTabsOpened() verifyNewTabButton() - verifyTabTrayOverflowMenu(false) + verifyTabTrayOverflowMenu(true) }.toggleToPrivateTabs { verifyNoTabsOpened() verifyNewTabButton() - verifyTabTrayOverflowMenu(false) + verifyTabTrayOverflowMenu(true) } } diff --git a/app/src/debug/java/org/mozilla/fenix/DebugFenixApplication.kt b/app/src/debug/java/org/mozilla/fenix/DebugFenixApplication.kt index 7f5c86762..7825870b0 100644 --- a/app/src/debug/java/org/mozilla/fenix/DebugFenixApplication.kt +++ b/app/src/debug/java/org/mozilla/fenix/DebugFenixApplication.kt @@ -9,12 +9,11 @@ import androidx.preference.PreferenceManager import leakcanary.AppWatcher import leakcanary.LeakCanary import org.mozilla.fenix.ext.getPreferenceKey -import org.mozilla.fenix.ext.resetPoliciesAfter class DebugFenixApplication : FenixApplication() { override fun setupLeakCanary() { - val isEnabled = StrictMode.allowThreadDiskReads().resetPoliciesAfter { + val isEnabled = components.strictMode.resetAfter(StrictMode.allowThreadDiskReads()) { PreferenceManager.getDefaultSharedPreferences(this) .getBoolean(getPreferenceKey(R.string.pref_key_leakcanary), true) } diff --git a/app/src/main/java/org/mozilla/fenix/FenixApplication.kt b/app/src/main/java/org/mozilla/fenix/FenixApplication.kt index 734661367..3ea661a6c 100644 --- a/app/src/main/java/org/mozilla/fenix/FenixApplication.kt +++ b/app/src/main/java/org/mozilla/fenix/FenixApplication.kt @@ -40,11 +40,9 @@ import mozilla.components.support.rusthttp.RustHttpConfig import mozilla.components.support.rustlog.RustLog import mozilla.components.support.utils.logElapsedTime import mozilla.components.support.webextensions.WebExtensionSupport -import org.mozilla.fenix.StrictModeManager.enableStrictMode import org.mozilla.fenix.components.Components import org.mozilla.fenix.components.metrics.MetricServiceType import org.mozilla.fenix.ext.components -import org.mozilla.fenix.ext.resetPoliciesAfter import org.mozilla.fenix.ext.settings import org.mozilla.fenix.perf.StorageStatsMetrics import org.mozilla.fenix.perf.StartupTimeline @@ -126,11 +124,11 @@ open class FenixApplication : LocaleAwareApplication(), Provider { val megazordSetup = setupMegazord() setDayNightTheme() - enableStrictMode(true) + components.strictMode.enableStrictMode(true) warmBrowsersCache() // Make sure the engine is initialized and ready to use. - StrictMode.allowThreadDiskReads().resetPoliciesAfter { + components.strictMode.resetAfter(StrictMode.allowThreadDiskReads()) { components.core.engine.warmUp() } initializeWebExtensionSupport() @@ -451,7 +449,7 @@ open class FenixApplication : LocaleAwareApplication(), Provider { applicationContext.resources.configuration.uiMode = config.uiMode // random StrictMode onDiskRead violation even when Fenix is not running in the background. - StrictMode.allowThreadDiskReads().resetPoliciesAfter { + components.strictMode.resetAfter(StrictMode.allowThreadDiskReads()) { super.onConfigurationChanged(config) } } diff --git a/app/src/main/java/org/mozilla/fenix/HomeActivity.kt b/app/src/main/java/org/mozilla/fenix/HomeActivity.kt index b1109f73f..333fb7ba5 100644 --- a/app/src/main/java/org/mozilla/fenix/HomeActivity.kt +++ b/app/src/main/java/org/mozilla/fenix/HomeActivity.kt @@ -73,7 +73,6 @@ import org.mozilla.fenix.ext.breadcrumb import org.mozilla.fenix.ext.components import org.mozilla.fenix.ext.metrics import org.mozilla.fenix.ext.nav -import org.mozilla.fenix.ext.resetPoliciesAfter import org.mozilla.fenix.ext.settings import org.mozilla.fenix.home.HomeFragmentDirections import org.mozilla.fenix.home.intent.CrashReporterIntentProcessor @@ -150,9 +149,9 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity { private lateinit var navigationToolbar: Toolbar final override fun onCreate(savedInstanceState: Bundle?) { - StrictModeManager.changeStrictModePolicies(supportFragmentManager) + components.strictMode.attachListenerToDisablePenaltyDeath(supportFragmentManager) // There is disk read violations on some devices such as samsung and pixel for android 9/10 - StrictMode.allowThreadDiskReads().resetPoliciesAfter { + components.strictMode.resetAfter(StrictMode.allowThreadDiskReads()) { super.onCreate(savedInstanceState) } @@ -757,7 +756,7 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity { } override fun attachBaseContext(base: Context) { - StrictMode.allowThreadDiskReads().resetPoliciesAfter { + base.components.strictMode.resetAfter(StrictMode.allowThreadDiskReads()) { super.attachBaseContext(base) } } diff --git a/app/src/main/java/org/mozilla/fenix/IntentReceiverActivity.kt b/app/src/main/java/org/mozilla/fenix/IntentReceiverActivity.kt index 8b42b5533..3e84fa22a 100644 --- a/app/src/main/java/org/mozilla/fenix/IntentReceiverActivity.kt +++ b/app/src/main/java/org/mozilla/fenix/IntentReceiverActivity.kt @@ -15,7 +15,6 @@ import org.mozilla.fenix.components.IntentProcessorType import org.mozilla.fenix.components.getType import org.mozilla.fenix.components.metrics.Event import org.mozilla.fenix.ext.components -import org.mozilla.fenix.ext.resetPoliciesAfter import org.mozilla.fenix.ext.settings import org.mozilla.fenix.perf.StartupTimeline import org.mozilla.fenix.shortcut.NewTabShortcutIntentProcessor @@ -28,7 +27,7 @@ class IntentReceiverActivity : Activity() { @VisibleForTesting override fun onCreate(savedInstanceState: Bundle?) { // StrictMode violation on certain devices such as Samsung - StrictMode.allowThreadDiskReads().resetPoliciesAfter { + components.strictMode.resetAfter(StrictMode.allowThreadDiskReads()) { super.onCreate(savedInstanceState) } @@ -68,7 +67,7 @@ class IntentReceiverActivity : Activity() { ) } // StrictMode violation on certain devices such as Samsung - StrictMode.allowThreadDiskReads().resetPoliciesAfter { + components.strictMode.resetAfter(StrictMode.allowThreadDiskReads()) { startActivity(intent) } finish() // must finish() after starting the other activity diff --git a/app/src/main/java/org/mozilla/fenix/StrictModeManager.kt b/app/src/main/java/org/mozilla/fenix/StrictModeManager.kt index b624b097f..b52faf9b3 100644 --- a/app/src/main/java/org/mozilla/fenix/StrictModeManager.kt +++ b/app/src/main/java/org/mozilla/fenix/StrictModeManager.kt @@ -8,32 +8,31 @@ import android.os.Build import android.os.StrictMode import androidx.fragment.app.Fragment import androidx.fragment.app.FragmentManager +import mozilla.components.support.ktx.android.os.resetAfter + +private const val MANUFACTURE_HUAWEI: String = "HUAWEI" +private const val MANUFACTURE_ONE_PLUS: String = "OnePlus" /** * Manages strict mode settings for the application. */ -object StrictModeManager { +class StrictModeManager(config: Config) { + + // This is public so it can be used by inline functions. + val isEnabledByBuildConfig = config.channel.isDebug /*** * Enables strict mode for debug purposes. meant to be run only in the main process. * @param setPenaltyDeath boolean value to decide setting the penaltyDeath as a penalty. - * @param setPenaltyDialog boolean value to decide setting the dialog box as a penalty. - * Note: dialog penalty cannot be set with penaltyDeath */ - fun enableStrictMode(setPenaltyDeath: Boolean, setPenaltyDialog: Boolean = false) { - if (Config.channel.isDebug) { + fun enableStrictMode(setPenaltyDeath: Boolean) { + if (isEnabledByBuildConfig) { val threadPolicy = StrictMode.ThreadPolicy.Builder() .detectAll() .penaltyLog() if (setPenaltyDeath && Build.MANUFACTURER !in strictModeExceptionList) { threadPolicy.penaltyDeath() } - - // dialog penalty cannot be set with penaltyDeath - if (!setPenaltyDeath && setPenaltyDialog) { - threadPolicy.penaltyDialog() - } - StrictMode.setThreadPolicy(threadPolicy.build()) val builder = StrictMode.VmPolicy.Builder() @@ -47,33 +46,48 @@ object StrictModeManager { builder.detectContentUriWithoutPermission() } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { - if (setPenaltyDeath || setPenaltyDialog) { - builder.permitNonSdkApiUsage() - } else { - builder.detectNonSdkApiUsage() - } + builder.detectNonSdkApiUsage() } StrictMode.setVmPolicy(builder.build()) } } /** - * Revert strict mode to disable penalty. Tied to fragment lifecycle since strict mode + * Revert strict mode to disable penalty based on fragment lifecycle since strict mode * needs to switch to penalty logs. Using the fragment life cycle allows decoupling from any * specific fragment. */ - fun changeStrictModePolicies(fragmentManager: FragmentManager) { + fun attachListenerToDisablePenaltyDeath(fragmentManager: FragmentManager) { fragmentManager.registerFragmentLifecycleCallbacks(object : FragmentManager.FragmentLifecycleCallbacks() { override fun onFragmentResumed(fm: FragmentManager, f: Fragment) { - enableStrictMode(setPenaltyDeath = false, setPenaltyDialog = false) + enableStrictMode(setPenaltyDeath = false) fm.unregisterFragmentLifecycleCallbacks(this) } }, false) } - private const val MANUFACTURE_HUAWEI: String = "HUAWEI" - private const val MANUFACTURE_ONE_PLUS: String = "OnePlus" + /** + * Runs the given [functionBlock] and sets the given [StrictMode.ThreadPolicy] after its + * completion when in a build configuration that has StrictMode enabled. If StrictMode is + * not enabled, simply runs the [functionBlock]. + * + * This function is written in the style of [AutoCloseable.use]. + * + * This is significantly less convenient to run than when it was written as an extension function + * on [StrictMode.ThreadPolicy] but I think this is okay: it shouldn't be easy to ignore StrictMode. + * + * @return the value returned by [functionBlock]. + */ + inline fun resetAfter(policy: StrictMode.ThreadPolicy, functionBlock: () -> R): R { + // Calling resetAfter takes 1-2ms (unknown device) so we only execute it if StrictMode can + // actually be enabled. https://github.com/mozilla-mobile/fenix/issues/11617 + return if (isEnabledByBuildConfig) { + policy.resetAfter(functionBlock) + } else { + functionBlock() + } + } /** * There are certain manufacturers that have custom font classes for the OS systems. diff --git a/app/src/main/java/org/mozilla/fenix/browser/BaseBrowserFragment.kt b/app/src/main/java/org/mozilla/fenix/browser/BaseBrowserFragment.kt index 2a0d7ddfe..97d85f197 100644 --- a/app/src/main/java/org/mozilla/fenix/browser/BaseBrowserFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/browser/BaseBrowserFragment.kt @@ -261,9 +261,7 @@ abstract class BaseBrowserFragment : Fragment(), UserInteractionHandler, Session ) }, onCloseTab = { closedSession -> - val tab = store.state.findTab(closedSession.id) - ?: return@DefaultBrowserToolbarController - val isSelected = tab.id == context.components.core.store.state.selectedTabId + val tab = store.state.findTab(closedSession.id) ?: return@DefaultBrowserToolbarController val snackbarMessage = if (tab.content.private) { requireContext().getString(R.string.snackbar_private_tab_closed) @@ -276,12 +274,9 @@ abstract class BaseBrowserFragment : Fragment(), UserInteractionHandler, Session snackbarMessage, requireContext().getString(R.string.snackbar_deleted_undo), { - sessionManager.add( - closedSession, - isSelected, - engineSessionState = tab.engineState.engineSessionState - ) + requireComponents.useCases.tabsUseCases.undo.invoke() }, + paddedForBottomToolbar = true, operation = { } ) } diff --git a/app/src/main/java/org/mozilla/fenix/browser/BrowserFragment.kt b/app/src/main/java/org/mozilla/fenix/browser/BrowserFragment.kt index ebd51b122..1642afe09 100644 --- a/app/src/main/java/org/mozilla/fenix/browser/BrowserFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/browser/BrowserFragment.kt @@ -39,7 +39,6 @@ import org.mozilla.fenix.ext.components import org.mozilla.fenix.ext.nav import org.mozilla.fenix.ext.navigateSafe import org.mozilla.fenix.ext.requireComponents -import org.mozilla.fenix.ext.resetPoliciesAfter import org.mozilla.fenix.ext.settings import org.mozilla.fenix.shortcut.PwaOnboardingObserver import org.mozilla.fenix.trackingprotection.TrackingProtectionOverlay @@ -110,7 +109,7 @@ class BrowserFragment : BaseBrowserFragment(), UserInteractionHandler { ) readerViewFeature.set( - feature = StrictMode.allowThreadDiskReads().resetPoliciesAfter { + feature = components.strictMode.resetAfter(StrictMode.allowThreadDiskReads()) { ReaderViewFeature( context, components.core.engine, diff --git a/app/src/main/java/org/mozilla/fenix/browser/ToolbarGestureHandler.kt b/app/src/main/java/org/mozilla/fenix/browser/ToolbarGestureHandler.kt index 6c975a075..63d196f59 100644 --- a/app/src/main/java/org/mozilla/fenix/browser/ToolbarGestureHandler.kt +++ b/app/src/main/java/org/mozilla/fenix/browser/ToolbarGestureHandler.kt @@ -90,27 +90,27 @@ class ToolbarGestureHandler( when (getDestination()) { is Destination.Tab -> { // Restrict the range of motion for the views so you can't start a swipe in one direction - // then move your finger far enough in the other direction and make the content visually - // start sliding off screen the other way. + // then move your finger far enough or in the other direction and make the content visually + // start sliding off screen. tabPreview.translationX = when (gestureDirection) { GestureDirection.RIGHT_TO_LEFT -> min( windowWidth.toFloat() + previewOffset, tabPreview.translationX - distanceX - ) + ).coerceAtLeast(0f) GestureDirection.LEFT_TO_RIGHT -> max( -windowWidth.toFloat() - previewOffset, tabPreview.translationX - distanceX - ) + ).coerceAtMost(0f) } contentLayout.translationX = when (gestureDirection) { GestureDirection.RIGHT_TO_LEFT -> min( 0f, contentLayout.translationX - distanceX - ) + ).coerceAtLeast(-windowWidth.toFloat() - previewOffset) GestureDirection.LEFT_TO_RIGHT -> max( 0f, contentLayout.translationX - distanceX - ) + ).coerceAtMost(windowWidth.toFloat() + previewOffset) } } is Destination.None -> { diff --git a/app/src/main/java/org/mozilla/fenix/collections/CollectionCreationFragment.kt b/app/src/main/java/org/mozilla/fenix/collections/CollectionCreationFragment.kt index 4e15537f7..bb09b0d9c 100644 --- a/app/src/main/java/org/mozilla/fenix/collections/CollectionCreationFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/collections/CollectionCreationFragment.kt @@ -9,23 +9,15 @@ import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup -import androidx.annotation.VisibleForTesting import androidx.fragment.app.DialogFragment import androidx.lifecycle.lifecycleScope import androidx.navigation.fragment.navArgs import kotlinx.android.synthetic.main.fragment_create_collection.view.* import kotlinx.coroutines.ExperimentalCoroutinesApi -import mozilla.components.browser.state.selector.findTab -import mozilla.components.browser.state.state.BrowserState -import mozilla.components.browser.state.state.TabSessionState -import mozilla.components.lib.publicsuffixlist.PublicSuffixList import mozilla.components.lib.state.ext.consumeFrom import org.mozilla.fenix.R import org.mozilla.fenix.components.StoreProvider -import org.mozilla.fenix.ext.getMediaStateForSession import org.mozilla.fenix.ext.requireComponents -import org.mozilla.fenix.ext.toShortUrl -import org.mozilla.fenix.home.Tab @ExperimentalCoroutinesApi class CollectionCreationFragment : DialogFragment() { @@ -47,27 +39,16 @@ class CollectionCreationFragment : DialogFragment() { val view = inflater.inflate(R.layout.fragment_create_collection, container, false) val args: CollectionCreationFragmentArgs by navArgs() - val store = requireComponents.core.store - val publicSuffixList = requireComponents.publicSuffixList - val tabs = store.state.getTabs(args.tabIds, publicSuffixList) - val selectedTabs = if (args.selectedTabIds != null) { - store.state.getTabs(args.selectedTabIds, publicSuffixList).toSet() - } else { - if (tabs.size == 1) setOf(tabs.first()) else emptySet() - } - - val tabCollections = requireComponents.core.tabCollectionStorage.cachedTabCollections - val selectedTabCollection = args.selectedTabCollectionId - .let { id -> tabCollections.firstOrNull { it.id == id } } - collectionCreationStore = StoreProvider.get(this) { CollectionCreationStore( - CollectionCreationState( - tabs = tabs, - selectedTabs = selectedTabs, + createInitialCollectionCreationState( + browserState = requireComponents.core.store.state, + tabCollectionStorage = requireComponents.core.tabCollectionStorage, + publicSuffixList = requireComponents.publicSuffixList, saveCollectionStep = args.saveCollectionStep, - tabCollections = tabCollections, - selectedTabCollection = selectedTabCollection + tabIds = args.tabIds, + selectedTabIds = args.selectedTabIds, + selectedTabCollectionId = args.selectedTabCollectionId ) ) } @@ -110,31 +91,3 @@ class CollectionCreationFragment : DialogFragment() { return dialog } } - -@VisibleForTesting -internal fun BrowserState.getTabs( - tabIds: Array?, - publicSuffixList: PublicSuffixList -): List { - return tabIds - ?.mapNotNull { id -> findTab(id) } - ?.map { it.toTab(this, publicSuffixList) } - .orEmpty() -} - -private fun TabSessionState.toTab( - state: BrowserState, - publicSuffixList: PublicSuffixList, - selected: Boolean? = null -): Tab { - val url = readerState.activeUrl ?: content.url - return Tab( - sessionId = this.id, - url = url, - hostname = url.toShortUrl(publicSuffixList), - title = content.title, - selected = selected, - icon = content.icon, - mediaState = state.getMediaStateForSession(this.id) - ) -} diff --git a/app/src/main/java/org/mozilla/fenix/collections/CollectionCreationStore.kt b/app/src/main/java/org/mozilla/fenix/collections/CollectionCreationStore.kt index f15ccf963..c9fffedb8 100644 --- a/app/src/main/java/org/mozilla/fenix/collections/CollectionCreationStore.kt +++ b/app/src/main/java/org/mozilla/fenix/collections/CollectionCreationStore.kt @@ -4,11 +4,19 @@ package org.mozilla.fenix.collections +import androidx.annotation.VisibleForTesting +import mozilla.components.browser.state.selector.findTab +import mozilla.components.browser.state.state.BrowserState +import mozilla.components.browser.state.state.TabSessionState import mozilla.components.feature.tab.collections.TabCollection +import mozilla.components.lib.publicsuffixlist.PublicSuffixList import mozilla.components.lib.state.Action import mozilla.components.lib.state.State import mozilla.components.lib.state.Store import org.mozilla.fenix.collections.CollectionCreationAction.StepChanged +import org.mozilla.fenix.components.TabCollectionStorage +import org.mozilla.fenix.ext.getMediaStateForSession +import org.mozilla.fenix.ext.toShortUrl import org.mozilla.fenix.home.Tab class CollectionCreationStore( @@ -43,6 +51,62 @@ data class CollectionCreationState( val defaultCollectionNumber: Int = 1 ) : State +@Suppress("LongParameterList") +fun createInitialCollectionCreationState( + browserState: BrowserState, + tabCollectionStorage: TabCollectionStorage, + publicSuffixList: PublicSuffixList, + saveCollectionStep: SaveCollectionStep, + tabIds: Array?, + selectedTabIds: Array?, + selectedTabCollectionId: Long +): CollectionCreationState { + val tabs = browserState.getTabs(tabIds, publicSuffixList) + val selectedTabs = if (selectedTabIds != null) { + browserState.getTabs(selectedTabIds, publicSuffixList).toSet() + } else { + if (tabs.size == 1) setOf(tabs.first()) else emptySet() + } + + val tabCollections = tabCollectionStorage.cachedTabCollections + val selectedTabCollection = tabCollections.firstOrNull { it.id == selectedTabCollectionId } + + return CollectionCreationState( + tabs = tabs, + selectedTabs = selectedTabs, + saveCollectionStep = saveCollectionStep, + tabCollections = tabCollections, + selectedTabCollection = selectedTabCollection + ) +} + +@VisibleForTesting +internal fun BrowserState.getTabs( + tabIds: Array?, + publicSuffixList: PublicSuffixList +): List { + return tabIds + ?.mapNotNull { id -> findTab(id) } + ?.map { it.toTab(this, publicSuffixList) } + .orEmpty() +} + +private fun TabSessionState.toTab( + state: BrowserState, + publicSuffixList: PublicSuffixList +): Tab { + val url = readerState.activeUrl ?: content.url + return Tab( + sessionId = this.id, + url = url, + hostname = url.toShortUrl(publicSuffixList), + title = content.title, + selected = null, + icon = content.icon, + mediaState = state.getMediaStateForSession(this.id) + ) +} + sealed class CollectionCreationAction : Action { object AddAllTabs : CollectionCreationAction() object RemoveAllTabs : CollectionCreationAction() diff --git a/app/src/main/java/org/mozilla/fenix/components/AccountAbnormalities.kt b/app/src/main/java/org/mozilla/fenix/components/AccountAbnormalities.kt index 92fb160bb..bd6c0196c 100644 --- a/app/src/main/java/org/mozilla/fenix/components/AccountAbnormalities.kt +++ b/app/src/main/java/org/mozilla/fenix/components/AccountAbnormalities.kt @@ -16,7 +16,7 @@ import mozilla.components.concept.sync.OAuthAccount import mozilla.components.lib.crash.CrashReporter import mozilla.components.service.fxa.manager.FxaAccountManager import mozilla.components.support.base.log.logger.Logger -import org.mozilla.fenix.ext.resetPoliciesAfter +import org.mozilla.fenix.StrictModeManager import kotlin.coroutines.CoroutineContext /** @@ -57,6 +57,7 @@ internal abstract class AbnormalFxaEvent : Exception() { class AccountAbnormalities( context: Context, private val crashReporter: CrashReporter, + strictMode: StrictModeManager, private val coroutineContext: CoroutineContext = Dispatchers.IO ) : AccountObserver { companion object { @@ -79,14 +80,14 @@ class AccountAbnormalities( private val hadAccountPrior: Boolean init { - val prefPair = StrictMode.allowThreadDiskReads().resetPoliciesAfter { + val prefPair = strictMode.resetAfter(StrictMode.allowThreadDiskReads()) { val p = context.getSharedPreferences(PREF_FXA_ABNORMALITIES, Context.MODE_PRIVATE) val a = p.getBoolean(KEY_HAS_ACCOUNT, false) Pair(p, a) } prefs = prefPair.first hadAccountPrior = prefPair.second -} + } /** * Once [accountManager] is initialized, queries it to detect abnormal account states. diff --git a/app/src/main/java/org/mozilla/fenix/components/Analytics.kt b/app/src/main/java/org/mozilla/fenix/components/Analytics.kt index 120a27ac1..9f9bd201b 100644 --- a/app/src/main/java/org/mozilla/fenix/components/Analytics.kt +++ b/app/src/main/java/org/mozilla/fenix/components/Analytics.kt @@ -18,6 +18,7 @@ import org.mozilla.fenix.Config import org.mozilla.fenix.HomeActivity import org.mozilla.fenix.R import org.mozilla.fenix.ReleaseChannel +import org.mozilla.fenix.StrictModeManager import org.mozilla.fenix.components.metrics.AdjustMetricsService import org.mozilla.fenix.components.metrics.GleanMetricsService import org.mozilla.fenix.components.metrics.LeanplumMetricsService @@ -34,7 +35,8 @@ import org.mozilla.geckoview.BuildConfig.MOZ_UPDATE_CHANNEL */ @Mockable class Analytics( - private val context: Context + private val context: Context, + strictMode: StrictModeManager ) { val crashReporter: CrashReporter by lazy { val services = mutableListOf() @@ -84,7 +86,10 @@ class Analytics( ) } - val leanplumMetricsService by lazy { LeanplumMetricsService(context as Application) } + val leanplumMetricsService by lazy { LeanplumMetricsService( + context as Application, + strictMode + ) } val metrics: MetricController by lazy { MetricController.create( diff --git a/app/src/main/java/org/mozilla/fenix/components/BackgroundServices.kt b/app/src/main/java/org/mozilla/fenix/components/BackgroundServices.kt index c2e92057e..61d6c08cd 100644 --- a/app/src/main/java/org/mozilla/fenix/components/BackgroundServices.kt +++ b/app/src/main/java/org/mozilla/fenix/components/BackgroundServices.kt @@ -36,6 +36,7 @@ import mozilla.components.service.sync.logins.SyncableLoginsStorage import mozilla.components.support.utils.RunWhenReadyQueue import org.mozilla.fenix.Config import org.mozilla.fenix.R +import org.mozilla.fenix.StrictModeManager import org.mozilla.fenix.components.metrics.Event import org.mozilla.fenix.components.metrics.MetricController import org.mozilla.fenix.ext.components @@ -57,7 +58,8 @@ class BackgroundServices( historyStorage: Lazy, bookmarkStorage: Lazy, passwordsStorage: Lazy, - remoteTabsStorage: Lazy + remoteTabsStorage: Lazy, + strictMode: StrictModeManager ) { // Allows executing tasks which depend on the account manager, but do not need to eagerly initialize it. val accountManagerAvailableQueue = RunWhenReadyQueue() @@ -105,7 +107,7 @@ class BackgroundServices( context.components.analytics.metrics ) - val accountAbnormalities = AccountAbnormalities(context, crashReporter) + val accountAbnormalities = AccountAbnormalities(context, crashReporter, strictMode) val accountManager by lazy { makeAccountManager(context, serverConfig, deviceConfig, syncConfig) } diff --git a/app/src/main/java/org/mozilla/fenix/components/Components.kt b/app/src/main/java/org/mozilla/fenix/components/Components.kt index bbedb955f..49af228f8 100644 --- a/app/src/main/java/org/mozilla/fenix/components/Components.kt +++ b/app/src/main/java/org/mozilla/fenix/components/Components.kt @@ -16,7 +16,9 @@ import mozilla.components.feature.addons.update.DefaultAddonUpdater import mozilla.components.lib.publicsuffixlist.PublicSuffixList import mozilla.components.support.migration.state.MigrationStore import io.github.forkmaintainers.iceraven.components.PagedAddonCollectionProvider +import org.mozilla.fenix.Config import org.mozilla.fenix.HomeActivity +import org.mozilla.fenix.StrictModeManager import org.mozilla.fenix.components.metrics.AppStartupTelemetry import org.mozilla.fenix.ext.settings import org.mozilla.fenix.utils.ClipboardHandler @@ -40,11 +42,12 @@ class Components(private val context: Context) { core.lazyHistoryStorage, core.lazyBookmarksStorage, core.lazyPasswordsStorage, - core.lazyRemoteTabsStorage + core.lazyRemoteTabsStorage, + strictMode ) } val services by lazy { Services(context, backgroundServices.accountManager) } - val core by lazy { Core(context, analytics.crashReporter) } + val core by lazy { Core(context, analytics.crashReporter, strictMode) } val search by lazy { Search(context) } val useCases by lazy { UseCases( @@ -113,13 +116,14 @@ class Components(private val context: Context) { addonCollectionProvider.setCollectionName(addonsCollection) } - val analytics by lazy { Analytics(context) } + val analytics by lazy { Analytics(context, strictMode) } val publicSuffixList by lazy { PublicSuffixList(context) } val clipboardHandler by lazy { ClipboardHandler(context) } val migrationStore by lazy { MigrationStore() } val performance by lazy { PerformanceComponent() } val push by lazy { Push(context, analytics.crashReporter) } val wifiConnectionMonitor by lazy { WifiConnectionMonitor(context as Application) } + val strictMode by lazy { StrictModeManager(Config) } val settings by lazy { Settings(context) } diff --git a/app/src/main/java/org/mozilla/fenix/components/Core.kt b/app/src/main/java/org/mozilla/fenix/components/Core.kt index fbec09a5d..69033a78d 100644 --- a/app/src/main/java/org/mozilla/fenix/components/Core.kt +++ b/app/src/main/java/org/mozilla/fenix/components/Core.kt @@ -20,6 +20,7 @@ import mozilla.components.browser.session.Session import mozilla.components.browser.session.SessionManager import mozilla.components.browser.session.engine.EngineMiddleware import mozilla.components.browser.session.storage.SessionStorage +import mozilla.components.browser.session.undo.UndoMiddleware import mozilla.components.browser.state.action.RecentlyClosedAction import mozilla.components.browser.state.state.BrowserState import mozilla.components.browser.state.store.BrowserStore @@ -59,9 +60,9 @@ import org.mozilla.fenix.AppRequestInterceptor import org.mozilla.fenix.Config import org.mozilla.fenix.HomeActivity import org.mozilla.fenix.R +import org.mozilla.fenix.StrictModeManager import org.mozilla.fenix.downloads.DownloadService import org.mozilla.fenix.ext.components -import org.mozilla.fenix.ext.resetPoliciesAfter import org.mozilla.fenix.ext.settings import org.mozilla.fenix.media.MediaService import org.mozilla.fenix.search.telemetry.ads.AdsTelemetry @@ -69,13 +70,18 @@ import org.mozilla.fenix.search.telemetry.incontent.InContentTelemetry import org.mozilla.fenix.settings.SupportUtils import org.mozilla.fenix.settings.advanced.getSelectedLocale import org.mozilla.fenix.utils.Mockable +import org.mozilla.fenix.utils.getUndoDelay import java.util.concurrent.TimeUnit /** * Component group for all core browser functionality. */ @Mockable -class Core(private val context: Context, private val crashReporter: CrashReporting) { +class Core( + private val context: Context, + private val crashReporter: CrashReporting, + strictMode: StrictModeManager +) { /** * The browser engine component initialized based on the build * configuration (see build variants). @@ -146,13 +152,18 @@ class Core(private val context: Context, private val crashReporter: CrashReporti MediaMiddleware(context, MediaService::class.java), DownloadMiddleware(context, DownloadService::class.java), ReaderViewMiddleware(), - ThumbnailsMiddleware(thumbnailStorage) + ThumbnailsMiddleware(thumbnailStorage), + UndoMiddleware(::lookupSessionManager, context.getUndoDelay()) ) + EngineMiddleware.create(engine, ::findSessionById) ).also { it.dispatch(RecentlyClosedAction.InitializeRecentlyClosedState) } } + private fun lookupSessionManager(): SessionManager { + return sessionManager + } + private fun findSessionById(tabId: String): Session? { return sessionManager.findSessionById(tabId) } @@ -261,7 +272,11 @@ class Core(private val context: Context, private val crashReporter: CrashReporti val bookmarksStorage by lazy { lazyBookmarksStorage.value } val passwordsStorage by lazy { lazyPasswordsStorage.value } - val tabCollectionStorage by lazy { TabCollectionStorage(context, sessionManager) } + val tabCollectionStorage by lazy { TabCollectionStorage( + context, + sessionManager, + strictMode + ) } /** * A storage component for persisting thumbnail images of tabs. @@ -273,7 +288,7 @@ class Core(private val context: Context, private val crashReporter: CrashReporti val topSitesStorage by lazy { val defaultTopSites = mutableListOf>() - StrictMode.allowThreadDiskReads().resetPoliciesAfter { + strictMode.resetAfter(StrictMode.allowThreadDiskReads()) { if (!context.settings().defaultTopSitesAdded) { defaultTopSites.add( Pair( @@ -362,6 +377,6 @@ class Core(private val context: Context, private val crashReporter: CrashReporti private const val KEY_STRENGTH = 256 private const val KEY_STORAGE_NAME = "core_prefs" private const val PASSWORDS_KEY = "passwords" - private const val RECENTLY_CLOSED_MAX = 5 + private const val RECENTLY_CLOSED_MAX = 10 } } diff --git a/app/src/main/java/org/mozilla/fenix/components/TabCollectionStorage.kt b/app/src/main/java/org/mozilla/fenix/components/TabCollectionStorage.kt index 10523589d..2ff36f047 100644 --- a/app/src/main/java/org/mozilla/fenix/components/TabCollectionStorage.kt +++ b/app/src/main/java/org/mozilla/fenix/components/TabCollectionStorage.kt @@ -19,8 +19,8 @@ import mozilla.components.feature.tab.collections.TabCollection import mozilla.components.feature.tab.collections.TabCollectionStorage import mozilla.components.support.base.observer.Observable import mozilla.components.support.base.observer.ObserverRegistry +import org.mozilla.fenix.StrictModeManager import org.mozilla.fenix.ext.components -import org.mozilla.fenix.ext.resetPoliciesAfter import org.mozilla.fenix.ext.toShortUrl import org.mozilla.fenix.home.sessioncontrol.viewholders.CollectionViewHolder import org.mozilla.fenix.utils.Mockable @@ -29,6 +29,7 @@ import org.mozilla.fenix.utils.Mockable class TabCollectionStorage( private val context: Context, private val sessionManager: SessionManager, + strictMode: StrictModeManager, private val delegate: Observable = ObserverRegistry() ) : Observable by delegate { @@ -56,7 +57,7 @@ class TabCollectionStorage( var cachedTabCollections = listOf() private val collectionStorage by lazy { - StrictMode.allowThreadDiskReads().resetPoliciesAfter { + strictMode.resetAfter(StrictMode.allowThreadDiskReads()) { TabCollectionStorage(context, sessionManager) } } diff --git a/app/src/main/java/org/mozilla/fenix/components/metrics/LeanplumMetricsService.kt b/app/src/main/java/org/mozilla/fenix/components/metrics/LeanplumMetricsService.kt index d3dc2b392..3012e1aa1 100644 --- a/app/src/main/java/org/mozilla/fenix/components/metrics/LeanplumMetricsService.kt +++ b/app/src/main/java/org/mozilla/fenix/components/metrics/LeanplumMetricsService.kt @@ -22,8 +22,8 @@ import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import mozilla.components.support.locale.LocaleManager import org.mozilla.fenix.BuildConfig +import org.mozilla.fenix.StrictModeManager import org.mozilla.fenix.components.metrics.MozillaProductDetector.MozillaProducts -import org.mozilla.fenix.ext.resetPoliciesAfter import org.mozilla.fenix.ext.settings import org.mozilla.fenix.home.intent.DeepLinkIntentProcessor import java.util.Locale @@ -58,6 +58,7 @@ private val Event.name: String? class LeanplumMetricsService( private val application: Application, + strictMode: StrictModeManager, private val deviceIdGenerator: () -> String = { randomUUID().toString() } ) : MetricsService, DeepLinkIntentProcessor.DeepLinkVerifier { val scope = CoroutineScope(Dispatchers.IO) @@ -83,7 +84,7 @@ class LeanplumMetricsService( override val type = MetricServiceType.Marketing private val token = Token(LeanplumId, LeanplumToken) - private val preferences = StrictMode.allowThreadDiskReads().resetPoliciesAfter { + private val preferences = strictMode.resetAfter(StrictMode.allowThreadDiskReads()) { application.getSharedPreferences(PREFERENCE_NAME, MODE_PRIVATE) } diff --git a/app/src/main/java/org/mozilla/fenix/ext/StrictMode.kt b/app/src/main/java/org/mozilla/fenix/ext/StrictMode.kt deleted file mode 100644 index ecb73dc39..000000000 --- a/app/src/main/java/org/mozilla/fenix/ext/StrictMode.kt +++ /dev/null @@ -1,26 +0,0 @@ -/* 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.os.StrictMode -import org.mozilla.fenix.Config - -/** - * Runs the given [functionBlock] and sets the ThreadPolicy after its completion in Debug mode. - * Otherwise simply runs the [functionBlock] - * This function is written in the style of [AutoCloseable.use]. - * @return the value returned by [functionBlock]. - */ -inline fun StrictMode.ThreadPolicy.resetPoliciesAfter(functionBlock: () -> R): R { - return if (Config.channel.isDebug) { - try { - functionBlock() - } finally { - StrictMode.setThreadPolicy(this) - } - } else { - functionBlock() - } -} diff --git a/app/src/main/java/org/mozilla/fenix/home/HomeFragment.kt b/app/src/main/java/org/mozilla/fenix/home/HomeFragment.kt index 6df4a4131..97fb25474 100644 --- a/app/src/main/java/org/mozilla/fenix/home/HomeFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/home/HomeFragment.kt @@ -91,8 +91,6 @@ import org.mozilla.fenix.ext.hideToolbar import org.mozilla.fenix.ext.metrics import org.mozilla.fenix.ext.nav import org.mozilla.fenix.ext.requireComponents -import org.mozilla.fenix.ext.resetPoliciesAfter -import org.mozilla.fenix.ext.sessionsOfType import org.mozilla.fenix.ext.settings import org.mozilla.fenix.home.sessioncontrol.DefaultSessionControlController import org.mozilla.fenix.home.sessioncontrol.SessionControlInteractor @@ -149,7 +147,7 @@ class HomeFragment : Fragment() { get() = requireComponents.core.store private val onboarding by lazy { - StrictMode.allowThreadDiskReads().resetPoliciesAfter { + requireComponents.strictMode.resetAfter(StrictMode.allowThreadDiskReads()) { FenixOnboarding(requireContext()) } } @@ -199,7 +197,7 @@ class HomeFragment : Fragment() { expandedCollections = emptySet(), mode = currentMode.getCurrentMode(), topSites = components.core.topSitesStorage.cachedTopSites, - tip = StrictMode.allowThreadDiskReads().resetPoliciesAfter { + tip = components.strictMode.resetAfter(StrictMode.allowThreadDiskReads()) { FenixTipManager( listOf( MasterPasswordTipProvider( @@ -469,17 +467,10 @@ class HomeFragment : Fragment() { } private fun removeAllTabsAndShowSnackbar(sessionCode: String) { - val tabs = sessionManager.sessionsOfType(private = sessionCode == ALL_PRIVATE_TABS).toList() - val selectedIndex = sessionManager - .selectedSession?.let { sessionManager.sessions.indexOf(it) } - ?: SessionManager.NO_SELECTION - - val snapshot = tabs - .map(sessionManager::createSessionSnapshot) - .let { SessionManager.Snapshot(it, selectedIndex) } - - tabs.forEach { - requireComponents.useCases.tabsUseCases.removeTab(it) + if (sessionCode == ALL_PRIVATE_TABS) { + sessionManager.removePrivateSessions() + } else { + sessionManager.removeNormalSessions() } val snackbarMessage = if (sessionCode == ALL_PRIVATE_TABS) { @@ -493,7 +484,7 @@ class HomeFragment : Fragment() { snackbarMessage, requireContext().getString(R.string.snackbar_deleted_undo), { - sessionManager.restore(snapshot) + requireComponents.useCases.tabsUseCases.undo.invoke() }, operation = { }, anchorView = snackbarAnchorView @@ -501,38 +492,29 @@ class HomeFragment : Fragment() { } private fun removeTabAndShowSnackbar(sessionId: String) { - sessionManager.findSessionById(sessionId)?.let { session -> - val snapshot = sessionManager.createSessionSnapshot(session) - val state = store.state.findTab(sessionId)?.engineState?.engineSessionState - val isSelected = - session.id == requireComponents.core.store.state.selectedTabId ?: false + val tab = store.state.findTab(sessionId) ?: return - requireComponents.useCases.tabsUseCases.removeTab(sessionId) - - val snackbarMessage = if (snapshot.session.private) { - requireContext().getString(R.string.snackbar_private_tab_closed) - } else { - requireContext().getString(R.string.snackbar_tab_closed) - } + requireComponents.useCases.tabsUseCases.removeTab(sessionId) - viewLifecycleOwner.lifecycleScope.allowUndo( - requireView(), - snackbarMessage, - requireContext().getString(R.string.snackbar_deleted_undo), - { - sessionManager.add( - snapshot.session, - isSelected, - engineSessionState = state - ) - findNavController().navigate( - HomeFragmentDirections.actionGlobalBrowser(null) - ) - }, - operation = { }, - anchorView = snackbarAnchorView - ) + val snackbarMessage = if (tab.content.private) { + requireContext().getString(R.string.snackbar_private_tab_closed) + } else { + requireContext().getString(R.string.snackbar_tab_closed) } + + viewLifecycleOwner.lifecycleScope.allowUndo( + requireView(), + snackbarMessage, + requireContext().getString(R.string.snackbar_deleted_undo), + { + requireComponents.useCases.tabsUseCases.undo.invoke() + findNavController().navigate( + HomeFragmentDirections.actionGlobalBrowser(null) + ) + }, + operation = { }, + anchorView = snackbarAnchorView + ) } override fun onDestroyView() { @@ -555,7 +537,7 @@ class HomeFragment : Fragment() { collections = components.core.tabCollectionStorage.cachedTabCollections, mode = currentMode.getCurrentMode(), topSites = components.core.topSitesStorage.cachedTopSites, - tip = StrictMode.allowThreadDiskReads().resetPoliciesAfter { + tip = components.strictMode.resetAfter(StrictMode.allowThreadDiskReads()) { FenixTipManager( listOf( MasterPasswordTipProvider( diff --git a/app/src/main/java/org/mozilla/fenix/settings/SettingsFragment.kt b/app/src/main/java/org/mozilla/fenix/settings/SettingsFragment.kt index 6f0719487..df29c2916 100644 --- a/app/src/main/java/org/mozilla/fenix/settings/SettingsFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/settings/SettingsFragment.kt @@ -6,13 +6,16 @@ package org.mozilla.fenix.settings import android.annotation.SuppressLint import android.content.ActivityNotFoundException +import android.content.DialogInterface import android.content.Intent import android.net.Uri import android.os.Build import android.os.Bundle import android.os.Handler -import android.provider.Settings +import android.view.LayoutInflater import android.widget.Toast +import androidx.annotation.VisibleForTesting +import androidx.appcompat.app.AlertDialog import androidx.lifecycle.lifecycleScope import androidx.navigation.NavDirections import androidx.navigation.findNavController @@ -20,6 +23,8 @@ import androidx.navigation.fragment.navArgs import androidx.preference.Preference import androidx.preference.PreferenceFragmentCompat import androidx.recyclerview.widget.RecyclerView +import kotlinx.android.synthetic.main.amo_collection_override_dialog.view.* +import kotlinx.android.synthetic.main.fragment_installed_add_on_details.view.* import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.delay import kotlinx.coroutines.launch @@ -28,6 +33,7 @@ import mozilla.components.concept.sync.AuthType import mozilla.components.concept.sync.OAuthAccount import mozilla.components.concept.sync.Profile import mozilla.components.support.ktx.android.content.getColorFromAttr +import mozilla.components.support.ktx.android.view.showKeyboard import org.mozilla.fenix.BrowserDirection import org.mozilla.fenix.Config import org.mozilla.fenix.FeatureFlags @@ -43,6 +49,7 @@ import org.mozilla.fenix.ext.requireComponents import org.mozilla.fenix.ext.settings import org.mozilla.fenix.ext.showToolbar import org.mozilla.fenix.settings.account.AccountUiView +import org.mozilla.fenix.utils.Settings import kotlin.system.exitProcess @Suppress("LargeClass", "TooManyFunctions") @@ -304,6 +311,41 @@ class SettingsFragment : PreferenceFragmentCompat() { resources.getString(R.string.pref_key_debug_settings) -> { SettingsFragmentDirections.actionSettingsFragmentToSecretSettingsFragment() } + resources.getString(R.string.pref_key_override_amo_collection) -> { + val context = requireContext() + val dialogView = LayoutInflater.from(context).inflate(R.layout.amo_collection_override_dialog, null) + + AlertDialog.Builder(context).apply { + setTitle(context.getString(R.string.preferences_customize_amo_collection)) + setView(dialogView) + setNegativeButton(R.string.customize_addon_collection_cancel) { dialog: DialogInterface, _ -> + dialog.cancel() + } + + setPositiveButton(R.string.customize_addon_collection_ok) { _, _ -> + context.settings().overrideAmoUser = dialogView.custom_amo_user.text.toString() + context.settings().overrideAmoCollection = dialogView.custom_amo_collection.text.toString() + + Toast.makeText( + context, + getString(R.string.toast_customize_addon_collection_done), + Toast.LENGTH_LONG + ).show() + + Handler().postDelayed({ + exitProcess(0) + }, AMO_COLLECTION_OVERRIDE_EXIT_DELAY) + } + + dialogView.custom_amo_collection.setText(context.settings().overrideAmoCollection) + dialogView.custom_amo_user.setText(context.settings().overrideAmoUser) + dialogView.custom_amo_user.requestFocus() + dialogView.custom_amo_user.showKeyboard() + create() + }.show() + + null + } else -> null } directions?.let { navigateFromSettings(directions) } @@ -374,12 +416,14 @@ class SettingsFragment : PreferenceFragmentCompat() { findPreference( getPreferenceKey(R.string.pref_key_debug_settings) )?.isVisible = requireContext().settings().showSecretDebugMenuThisSession + + setupAmoCollectionOverridePreference(requireContext().settings()) } private fun getClickListenerForMakeDefaultBrowser(): Preference.OnPreferenceClickListener { return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { Preference.OnPreferenceClickListener { - val intent = Intent(Settings.ACTION_MANAGE_DEFAULT_APPS_SETTINGS) + val intent = Intent(android.provider.Settings.ACTION_MANAGE_DEFAULT_APPS_SETTINGS) startActivity(intent) true } @@ -446,8 +490,23 @@ class SettingsFragment : PreferenceFragmentCompat() { } } + @VisibleForTesting + internal fun setupAmoCollectionOverridePreference(settings: Settings) { + val preferenceAmoCollectionOverride = + findPreference(getPreferenceKey(R.string.pref_key_override_amo_collection)) + + val show = (Config.channel.isNightlyOrDebug && ( + settings.amoCollectionOverrideConfigured() || settings.showSecretDebugMenuThisSession) + ) + preferenceAmoCollectionOverride?.apply { + isVisible = show + summary = settings.overrideAmoCollection.ifEmpty { null } + } + } + companion object { private const val SCROLL_INDICATOR_DELAY = 10L private const val FXA_SYNC_OVERRIDE_EXIT_DELAY = 2000L + private const val AMO_COLLECTION_OVERRIDE_EXIT_DELAY = 3000L } } diff --git a/app/src/main/java/org/mozilla/fenix/settings/advanced/LocaleSettingsFragment.kt b/app/src/main/java/org/mozilla/fenix/settings/advanced/LocaleSettingsFragment.kt index 2696115b1..4a307d29d 100644 --- a/app/src/main/java/org/mozilla/fenix/settings/advanced/LocaleSettingsFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/settings/advanced/LocaleSettingsFragment.kt @@ -17,7 +17,6 @@ import kotlinx.android.synthetic.main.fragment_locale_settings.view.* import kotlinx.coroutines.ExperimentalCoroutinesApi import mozilla.components.lib.state.ext.consumeFrom import mozilla.components.support.ktx.android.view.hideKeyboard -import mozilla.components.support.locale.LocaleManager import org.mozilla.fenix.R import org.mozilla.fenix.components.StoreProvider import org.mozilla.fenix.ext.showToolbar @@ -40,7 +39,11 @@ class LocaleSettingsFragment : Fragment() { ): View? { val view = inflater.inflate(R.layout.fragment_locale_settings, container, false) - store = getStore() + store = StoreProvider.get(this) { + LocaleSettingsStore( + createInitialLocaleSettingsState(requireContext()) + ) + } interactor = LocaleSettingsInteractor( controller = DefaultLocaleSettingsController( activity = requireActivity(), @@ -88,19 +91,4 @@ class LocaleSettingsFragment : Fragment() { localeView.update(it) } } - - private fun getStore(): LocaleSettingsStore { - val supportedLocales = LocaleManager.getSupportedLocales() - val selectedLocale = LocaleManager.getSelectedLocale(requireContext()) - - return StoreProvider.get(this) { - LocaleSettingsStore( - LocaleSettingsState( - supportedLocales, - supportedLocales, - selectedLocale - ) - ) - } - } } diff --git a/app/src/main/java/org/mozilla/fenix/settings/advanced/LocaleSettingsStore.kt b/app/src/main/java/org/mozilla/fenix/settings/advanced/LocaleSettingsStore.kt index 98dc4ce66..59655c1b9 100644 --- a/app/src/main/java/org/mozilla/fenix/settings/advanced/LocaleSettingsStore.kt +++ b/app/src/main/java/org/mozilla/fenix/settings/advanced/LocaleSettingsStore.kt @@ -4,9 +4,11 @@ package org.mozilla.fenix.settings.advanced +import android.content.Context import mozilla.components.lib.state.Action import mozilla.components.lib.state.State import mozilla.components.lib.state.Store +import mozilla.components.support.locale.LocaleManager import java.util.Locale class LocaleSettingsStore( @@ -27,6 +29,16 @@ data class LocaleSettingsState( val selectedLocale: Locale ) : State +fun createInitialLocaleSettingsState(context: Context): LocaleSettingsState { + val supportedLocales = LocaleManager.getSupportedLocales() + + return LocaleSettingsState( + supportedLocales, + supportedLocales, + selectedLocale = LocaleManager.getSelectedLocale(context) + ) +} + /** * Actions to dispatch through the `LocaleSettingsStore` to modify `LocaleSettingsState` through the reducer. */ diff --git a/app/src/main/java/org/mozilla/fenix/settings/logins/LoginsFragmentStore.kt b/app/src/main/java/org/mozilla/fenix/settings/logins/LoginsFragmentStore.kt index ea130f07e..9ab138c30 100644 --- a/app/src/main/java/org/mozilla/fenix/settings/logins/LoginsFragmentStore.kt +++ b/app/src/main/java/org/mozilla/fenix/settings/logins/LoginsFragmentStore.kt @@ -10,6 +10,7 @@ import mozilla.components.concept.storage.Login import mozilla.components.lib.state.Action import mozilla.components.lib.state.State import mozilla.components.lib.state.Store +import org.mozilla.fenix.utils.Settings /** * Class representing a parcelable saved logins item @@ -80,6 +81,16 @@ data class LoginsListState( val duplicateLogins: List ) : State +fun createInitialLoginsListState(settings: Settings) = LoginsListState( + isLoading = true, + loginList = emptyList(), + filteredItems = emptyList(), + searchedForText = null, + sortingStrategy = settings.savedLoginsSortingStrategy, + highlightedItem = settings.savedLoginsMenuHighlightedItem, + duplicateLogins = emptyList() // assume on load there are no dupes +) + /** * Handles changes in the saved logins list, including updates and filtering. */ diff --git a/app/src/main/java/org/mozilla/fenix/settings/logins/fragment/EditLoginFragment.kt b/app/src/main/java/org/mozilla/fenix/settings/logins/fragment/EditLoginFragment.kt index c65654fe0..62a624169 100644 --- a/app/src/main/java/org/mozilla/fenix/settings/logins/fragment/EditLoginFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/settings/logins/fragment/EditLoginFragment.kt @@ -33,11 +33,11 @@ import org.mozilla.fenix.ext.requireComponents import org.mozilla.fenix.ext.settings import org.mozilla.fenix.settings.logins.LoginsAction import org.mozilla.fenix.settings.logins.LoginsFragmentStore -import org.mozilla.fenix.settings.logins.LoginsListState import org.mozilla.fenix.settings.logins.SavedLogin -import org.mozilla.fenix.settings.logins.togglePasswordReveal import org.mozilla.fenix.settings.logins.controller.SavedLoginsStorageController +import org.mozilla.fenix.settings.logins.createInitialLoginsListState import org.mozilla.fenix.settings.logins.interactor.EditLoginInteractor +import org.mozilla.fenix.settings.logins.togglePasswordReveal /** * Displays the editable saved login information for a single website @@ -69,15 +69,7 @@ class EditLoginFragment : Fragment(R.layout.fragment_edit_login) { loginsFragmentStore = StoreProvider.get(this) { LoginsFragmentStore( - LoginsListState( - isLoading = true, - loginList = listOf(), - filteredItems = listOf(), - searchedForText = null, - sortingStrategy = requireContext().settings().savedLoginsSortingStrategy, - highlightedItem = requireContext().settings().savedLoginsMenuHighlightedItem, - duplicateLogins = listOf() - ) + createInitialLoginsListState(requireContext().settings()) ) } diff --git a/app/src/main/java/org/mozilla/fenix/settings/logins/fragment/LoginDetailFragment.kt b/app/src/main/java/org/mozilla/fenix/settings/logins/fragment/LoginDetailFragment.kt index ed452e33b..7120c0fe0 100644 --- a/app/src/main/java/org/mozilla/fenix/settings/logins/fragment/LoginDetailFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/settings/logins/fragment/LoginDetailFragment.kt @@ -38,9 +38,9 @@ import org.mozilla.fenix.ext.settings import org.mozilla.fenix.ext.showToolbar import org.mozilla.fenix.ext.simplifiedUrl import org.mozilla.fenix.settings.logins.LoginsFragmentStore -import org.mozilla.fenix.settings.logins.LoginsListState import org.mozilla.fenix.settings.logins.SavedLogin import org.mozilla.fenix.settings.logins.controller.SavedLoginsStorageController +import org.mozilla.fenix.settings.logins.createInitialLoginsListState import org.mozilla.fenix.settings.logins.interactor.LoginDetailInteractor import org.mozilla.fenix.settings.logins.togglePasswordReveal import org.mozilla.fenix.settings.logins.view.LoginDetailView @@ -68,15 +68,7 @@ class LoginDetailFragment : Fragment(R.layout.fragment_login_detail) { val view = inflater.inflate(R.layout.fragment_login_detail, container, false) savedLoginsStore = StoreProvider.get(this) { LoginsFragmentStore( - LoginsListState( - isLoading = true, - loginList = listOf(), - filteredItems = listOf(), - searchedForText = null, - sortingStrategy = requireContext().settings().savedLoginsSortingStrategy, - highlightedItem = requireContext().settings().savedLoginsMenuHighlightedItem, - duplicateLogins = listOf() // assume on load there are no dupes - ) + createInitialLoginsListState(requireContext().settings()) ) } loginDetailView = LoginDetailView( diff --git a/app/src/main/java/org/mozilla/fenix/settings/logins/fragment/SavedLoginsFragment.kt b/app/src/main/java/org/mozilla/fenix/settings/logins/fragment/SavedLoginsFragment.kt index fe2431ae0..dc50cc445 100644 --- a/app/src/main/java/org/mozilla/fenix/settings/logins/fragment/SavedLoginsFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/settings/logins/fragment/SavedLoginsFragment.kt @@ -35,11 +35,11 @@ import org.mozilla.fenix.ext.settings import org.mozilla.fenix.ext.showToolbar import org.mozilla.fenix.settings.logins.LoginsAction import org.mozilla.fenix.settings.logins.LoginsFragmentStore -import org.mozilla.fenix.settings.logins.LoginsListState import org.mozilla.fenix.settings.logins.SavedLoginsSortingStrategyMenu import org.mozilla.fenix.settings.logins.SortingStrategy import org.mozilla.fenix.settings.logins.controller.LoginsListController import org.mozilla.fenix.settings.logins.controller.SavedLoginsStorageController +import org.mozilla.fenix.settings.logins.createInitialLoginsListState import org.mozilla.fenix.settings.logins.interactor.SavedLoginsInteractor import org.mozilla.fenix.settings.logins.view.SavedLoginsListView @@ -77,15 +77,7 @@ class SavedLoginsFragment : Fragment() { val view = inflater.inflate(R.layout.fragment_saved_logins, container, false) savedLoginsStore = StoreProvider.get(this) { LoginsFragmentStore( - LoginsListState( - isLoading = true, - loginList = listOf(), - filteredItems = listOf(), - searchedForText = null, - sortingStrategy = requireContext().settings().savedLoginsSortingStrategy, - highlightedItem = requireContext().settings().savedLoginsMenuHighlightedItem, - duplicateLogins = listOf() // assume on load there are no dupes - ) + createInitialLoginsListState(requireContext().settings()) ) } diff --git a/app/src/main/java/org/mozilla/fenix/share/ShareController.kt b/app/src/main/java/org/mozilla/fenix/share/ShareController.kt index 2c1f44888..e3c1a7310 100644 --- a/app/src/main/java/org/mozilla/fenix/share/ShareController.kt +++ b/app/src/main/java/org/mozilla/fenix/share/ShareController.kt @@ -4,6 +4,7 @@ package org.mozilla.fenix.share +import android.content.ActivityNotFoundException import android.content.Context import android.content.Intent import android.content.Intent.ACTION_SEND @@ -98,13 +99,19 @@ class DefaultShareController( setClassName(app.packageName, app.activityName) } + @Suppress("TooGenericExceptionCaught") val result = try { context.startActivity(intent) ShareController.Result.SUCCESS - } catch (e: SecurityException) { - snackbar.setText(context.getString(R.string.share_error_snackbar)) - snackbar.show() - ShareController.Result.SHARE_ERROR + } catch (e: Exception) { + when (e) { + is SecurityException, is ActivityNotFoundException -> { + snackbar.setText(context.getString(R.string.share_error_snackbar)) + snackbar.show() + ShareController.Result.SHARE_ERROR + } + else -> throw e + } } dismiss(result) } diff --git a/app/src/main/java/org/mozilla/fenix/tabtray/TabTrayDialogFragment.kt b/app/src/main/java/org/mozilla/fenix/tabtray/TabTrayDialogFragment.kt index f97ef9055..455b073ee 100644 --- a/app/src/main/java/org/mozilla/fenix/tabtray/TabTrayDialogFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/tabtray/TabTrayDialogFragment.kt @@ -266,10 +266,7 @@ class TabTrayDialogFragment : AppCompatDialogFragment(), UserInteractionHandler private fun showUndoSnackbarForTab(sessionId: String) { val store = requireComponents.core.store - val sessionManager = requireComponents.core.sessionManager - val tab = requireComponents.core.store.state.findTab(sessionId) ?: return - val session = sessionManager.findSessionById(sessionId) ?: return // Check if this is the last tab of this session type val isLastOpenTab = @@ -279,8 +276,6 @@ class TabTrayDialogFragment : AppCompatDialogFragment(), UserInteractionHandler return } - val isSelected = sessionId == requireComponents.core.store.state.selectedTabId ?: false - val snackbarMessage = if (tab.content.private) { getString(R.string.snackbar_private_tab_closed) } else { @@ -292,12 +287,8 @@ class TabTrayDialogFragment : AppCompatDialogFragment(), UserInteractionHandler snackbarMessage, getString(R.string.snackbar_deleted_undo), { - sessionManager.add( - session, - isSelected, - engineSessionState = tab.engineState.engineSessionState - ) - _tabTrayView?.scrollToTab(session.id) + requireComponents.useCases.tabsUseCases.undo.invoke() + _tabTrayView?.scrollToTab(tab.id) }, operation = { }, elevation = ELEVATION, diff --git a/app/src/main/java/org/mozilla/fenix/tabtray/TabTrayView.kt b/app/src/main/java/org/mozilla/fenix/tabtray/TabTrayView.kt index 4dc2a1ff9..f1ae010bd 100644 --- a/app/src/main/java/org/mozilla/fenix/tabtray/TabTrayView.kt +++ b/app/src/main/java/org/mozilla/fenix/tabtray/TabTrayView.kt @@ -131,7 +131,8 @@ class TabTrayView( private var tabsTouchHelper: TabsTouchHelper private val collectionsButtonAdapter = SaveToCollectionsButtonAdapter(interactor, isPrivate) - private val syncedTabsController = SyncedTabsController(lifecycleOwner, view, store, concatAdapter) + private val syncedTabsController = + SyncedTabsController(lifecycleOwner, view, store, concatAdapter) private val syncedTabsFeature = ViewBoundFeatureWrapper() private var hasLoaded = false @@ -288,7 +289,10 @@ class TabTrayView( } tabTrayItemMenu = - TabTrayItemMenu(view.context, { view.tab_layout.selectedTabPosition == 0 }) { + TabTrayItemMenu( + view.context, + { tabs.isNotEmpty() && view.tab_layout.selectedTabPosition == 0 }, + { tabs.isNotEmpty() }) { when (it) { is TabTrayItemMenu.Item.ShareAllTabs -> interactor.onShareTabsClicked( isPrivateModeSelected @@ -571,7 +575,6 @@ class TabTrayView( } else { View.VISIBLE } - view.tab_tray_overflow.isVisible = !hasNoTabs counter_text.text = updateTabCounter(browserState.normalTabs.size) updateTabCounterContentDescription(browserState.normalTabs.size) @@ -775,6 +778,7 @@ class TabTrayView( class TabTrayItemMenu( private val context: Context, private val shouldShowSaveToCollection: () -> Boolean, + private val hasOpenTabs: () -> Boolean, private val onItemTapped: (Item) -> Unit = {} ) { @@ -804,7 +808,7 @@ class TabTrayItemMenu( ) { context.components.analytics.metrics.track(Event.TabsTrayShareAllTabsPressed) onItemTapped.invoke(Item.ShareAllTabs) - }, + }.apply { visible = hasOpenTabs }, SimpleBrowserMenuItem( context.getString(R.string.tab_tray_menu_tab_settings), @@ -826,7 +830,7 @@ class TabTrayItemMenu( ) { context.components.analytics.metrics.track(Event.TabsTrayCloseAllTabsPressed) onItemTapped.invoke(Item.CloseAllTabs) - } + }.apply { visible = hasOpenTabs } ) } } diff --git a/app/src/main/java/org/mozilla/fenix/utils/Settings.kt b/app/src/main/java/org/mozilla/fenix/utils/Settings.kt index 1810b159c..75f5a6edd 100644 --- a/app/src/main/java/org/mozilla/fenix/utils/Settings.kt +++ b/app/src/main/java/org/mozilla/fenix/utils/Settings.kt @@ -896,6 +896,20 @@ class Settings(private val appContext: Context) : PreferencesHolder { default = "" ) + var overrideAmoUser by stringPreference( + appContext.getPreferenceKey(R.string.pref_key_override_amo_user), + default = "" + ) + + var overrideAmoCollection by stringPreference( + appContext.getPreferenceKey(R.string.pref_key_override_amo_collection), + default = "" + ) + + fun amoCollectionOverrideConfigured(): Boolean { + return overrideAmoUser.isNotEmpty() || overrideAmoCollection.isNotEmpty() + } + val topSitesSize by intPreference( appContext.getPreferenceKey(R.string.pref_key_top_sites_size), default = 0 diff --git a/app/src/main/java/org/mozilla/fenix/utils/Undo.kt b/app/src/main/java/org/mozilla/fenix/utils/Undo.kt index 26dbf59ee..7ba20701b 100644 --- a/app/src/main/java/org/mozilla/fenix/utils/Undo.kt +++ b/app/src/main/java/org/mozilla/fenix/utils/Undo.kt @@ -4,6 +4,7 @@ package org.mozilla.fenix.utils +import android.content.Context import android.view.View import androidx.appcompat.widget.ContentFrameLayout import androidx.core.view.updatePadding @@ -19,6 +20,18 @@ import java.util.concurrent.atomic.AtomicBoolean internal const val UNDO_DELAY = 3000L internal const val ACCESSIBLE_UNDO_DELAY = 15000L +/** + * Get the recommended time an "undo" action should be available until it can automatically be + * dismissed. The delay may be different based on the accessibility settings of the device. + */ +fun Context.getUndoDelay(): Long { + return if (settings().accessibilityServicesEnabled) { + ACCESSIBLE_UNDO_DELAY + } else { + UNDO_DELAY + } +} + /** * Runs [operation] after giving user time (see [UNDO_DELAY]) to cancel it. * In case of cancellation, [onCancel] is executed. @@ -47,6 +60,7 @@ fun CoroutineScope.allowUndo( // writing a volatile variable. val requestedUndo = AtomicBoolean(false) + @Suppress("ComplexCondition") fun showUndoSnackbar() { val snackbar = FenixSnackbar .make( @@ -69,6 +83,7 @@ fun CoroutineScope.allowUndo( val shouldUseBottomToolbar = view.context.settings().shouldUseBottomToolbar val toolbarHeight = view.resources.getDimensionPixelSize(R.dimen.browser_toolbar_height) + val dynamicToolbarEnabled = view.context.settings().isDynamicToolbarEnabled snackbar.view.updatePadding( bottom = if ( @@ -79,7 +94,7 @@ fun CoroutineScope.allowUndo( // can't intelligently position the snackbar on the upper most view. // Ideally we should not pass ContentFrameLayout in, but it's the only // way to display snackbars through a fragment transition. - view is ContentFrameLayout + (view is ContentFrameLayout || !dynamicToolbarEnabled) ) { toolbarHeight } else { @@ -92,13 +107,7 @@ fun CoroutineScope.allowUndo( // Wait a bit, and if user didn't request cancellation, proceed with // requested operation and hide the snackbar. launch { - val lengthToDelay = if (view.context.settings().accessibilityServicesEnabled) { - ACCESSIBLE_UNDO_DELAY - } else { - UNDO_DELAY - } - - delay(lengthToDelay) + delay(view.context.getUndoDelay()) if (!requestedUndo.get()) { snackbar.dismiss() diff --git a/app/src/main/java/org/mozilla/fenix/whatsnew/WhatsNew.kt b/app/src/main/java/org/mozilla/fenix/whatsnew/WhatsNew.kt index a536f593a..dd586ab47 100644 --- a/app/src/main/java/org/mozilla/fenix/whatsnew/WhatsNew.kt +++ b/app/src/main/java/org/mozilla/fenix/whatsnew/WhatsNew.kt @@ -6,7 +6,7 @@ package org.mozilla.fenix.whatsnew import android.content.Context import android.os.StrictMode -import org.mozilla.fenix.ext.resetPoliciesAfter +import org.mozilla.fenix.ext.components // This file is a modified port from Focus Android @@ -70,7 +70,7 @@ class WhatsNew private constructor(private val storage: WhatsNewStorage) { fun shouldHighlightWhatsNew(context: Context): Boolean { return shouldHighlightWhatsNew( ContextWhatsNewVersion(context), - StrictMode.allowThreadDiskReads().resetPoliciesAfter { + context.components.strictMode.resetAfter(StrictMode.allowThreadDiskReads()) { SharedPreferenceWhatsNewStorage(context) } ) diff --git a/app/src/main/res/layout/amo_collection_override_dialog.xml b/app/src/main/res/layout/amo_collection_override_dialog.xml new file mode 100644 index 000000000..b913795b4 --- /dev/null +++ b/app/src/main/res/layout/amo_collection_override_dialog.xml @@ -0,0 +1,32 @@ + + + + + + + + + diff --git a/app/src/main/res/layout/component_tabstray_bottom.xml b/app/src/main/res/layout/component_tabstray_bottom.xml index 39dfcdb39..5fef10255 100644 --- a/app/src/main/res/layout/component_tabstray_bottom.xml +++ b/app/src/main/res/layout/component_tabstray_bottom.xml @@ -158,7 +158,7 @@ android:background="?android:attr/selectableItemBackgroundBorderless" android:contentDescription="@string/open_tabs_menu" android:visibility="visible" - app:tint="@color/accent_normal_theme" + app:tint="@color/tab_tray_heading_icon_menu_normal_theme" app:layout_constraintBottom_toBottomOf="@id/tab_layout" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toTopOf="@id/tab_layout" diff --git a/app/src/main/res/values-be/strings.xml b/app/src/main/res/values-be/strings.xml index 17d3923fd..51759aa1a 100644 --- a/app/src/main/res/values-be/strings.xml +++ b/app/src/main/res/values-be/strings.xml @@ -142,6 +142,8 @@ Усталяваць Сінхранізаваныя карткі + + Сінхранізаваць ізноў Знайсці на старонцы @@ -264,6 +266,8 @@ Адкрываць спасылкі ў прыватнай картцы Дазволіць здымкі экрана ў прыватным рэжыме + + Калі гэта дазволена, прыватныя карткі таксама будуць бачныя, калі адкрыта некалькі праграм Дадаць ярлык прыватнага аглядання @@ -460,6 +464,11 @@ Пацягніце, каб абнавіць + + Пракруціце, каб схаваць панэль інструментаў + + Пасуньце ўбок панэль інструментаў, каб пераключыць карткі + Сеансы @@ -1278,6 +1287,9 @@ Назва ярлыка + + Вы можаце лёгка дадаць гэты вэб-сайт на хатні экран вашай прылады, каб мець да яго імгненны доступ і аглядаць хутчэй, нібыта гэта асобная праграма. + Лагіны і паролі @@ -1348,8 +1360,12 @@ Сайт скапіяваны ў буфер абмену Капіяваць пароль + + Ачысціць пароль Капіяваць імя карыстальніка + + Ачысціць імя карыстальніка Капіяваць сайт diff --git a/app/src/main/res/values-br/strings.xml b/app/src/main/res/values-br/strings.xml index c17606fdb..999037aaa 100644 --- a/app/src/main/res/values-br/strings.xml +++ b/app/src/main/res/values-br/strings.xml @@ -140,6 +140,8 @@ Staliañ Ivinelloù goubredet + + Adgoubredañ Kavout er bajennad @@ -260,6 +262,8 @@ Digeriñ an ereoù en un ivinell brevez Aotren an tapadennoù-skramm er merdeiñ prevez + + Mard eo aotreet, an ivinelloù prevez a vo hewel pa vo meur a arload digor Ouzhpennañ ur verradenn merdeiñ prevez @@ -280,6 +284,8 @@ Neuz Degemer + + Jestroù Personelaat @@ -314,8 +320,12 @@ Klask er roll istor Klask er sinedoù + + Klask en ivinelloù goubredet Arventennoù ar gont + + Leuniañ an ereoù ent emgefreek Digeriñ ereoù en arloadoù @@ -452,6 +462,16 @@ Mont gant neuz ar benveg + + + Sachit da azgrenaat + + Dibunit da guzhat ar varrenn-ostilhoù + + Riklit ar varrenn-ostilhoù war ar c’hostez evit cheñch ivinell + + Rikli ar ar varrenn-ostilhoù war-zu an nec’h evit digeriñ ivinelloù + Estezioù @@ -1039,7 +1059,7 @@ - Kennasket hoc’h evel %s war ur merdeer Firefox all war ar pellgomzer-mañ. Fellout a ra deoc’h kennaskañ gant ar gont-mañ? + Kennasket oc’h evel %s war ur merdeer Firefox all war an trevnad-mañ. Fellout a ra deoc’h kennaskañ gant ar gont-mañ? Ya, kennaskit ac’hanon @@ -1129,6 +1149,8 @@ Kennaskit gant ho kamera Ober gant ur chomlec’h postel kentoc’h + + Krouit unan evit goubredañ Firefox etre an trevnadoù.]]> Firefox a baouezo da c’houbredañ gant ho kont, met ne vo ket dilamet ho roadennoù merdeiñ war an trevnad-mañ. @@ -1237,6 +1259,11 @@ The first parameter is the app name --> %s | Levraouegoù digor o zarzh + + Heulierien adheñchañ + + Skarzhañ an toupinoù lakaet gant adheñchadurioù etrezek lec’hiennoù heuliañ anavezet + Skor @@ -1279,8 +1306,9 @@ Kenderc’hel etrezek al lec’hienn Anv ar verradenn + - Gallout a rit ouzhpennañ al lec’hienn d’ho pennbajenn en un doare aes evit gallout he haeziñ ha merdeiñ buanoc’h, evel un arload. + Gallout a rit ouzhpennañ al lec’hienn-mañ da bennbajenn ho trevnad evit mont war-eeun ha merdeiñ primoc’h evel ma vefe un arload. Titouroù kennaskañ @@ -1351,8 +1379,12 @@ Eilet eo bet al lec’hienn er golver Eilañ ar ger-tremen + + Skarzhañ ar ger-tremen Eilañ an anv arveriad + + Skarzhañ an anv arveriad Eilañ al lec’hienn @@ -1505,7 +1537,7 @@ Un titour kennaskañ gant an anv arveriad-mañ a zo dioutañ endeo. - + Kennaskañ un trevnad all. Adkennaskit mar plij ganeoc’h. @@ -1526,7 +1558,7 @@ Tizhet eo bet ar vevenn lec’hiennoù - Evit ouzhpennañ ul lec’hienn gwellañ nevez e rankit dilemel unan all. Stokit ’pad pell amzer war ul lec’hienn ha dibabit Dilemel. + Evit ouzhpennañ ul lec’hienn gwellañ eo ret deoc’h dilemel unan. Stokit ha dalc’hit al lec’hienn ha dibabit dilemel. Mat, komprenet am eus @@ -1536,7 +1568,7 @@ Dilemel - Tennit gounid eus %s. @@ -1544,4 +1576,9 @@ Dastumit ar pezh a zo pouezus evidoc‘h Strollit ar cʼhlaskoù, al lecʼhiennoù hag an ivinelloù heñvel evit mont daveto buanocʼh. + + Kennasket hoc’h evel %s war ur merdeer Firefox all war ar pellgomzer-mañ. Fellout a ra deoc’h kennaskañ gant ar gont-mañ? + + Gallout a rit ouzhpennañ al lec’hienn d’ho pennbajenn en un doare aes evit gallout he haeziñ ha merdeiñ buanoc’h, evel un arload. + diff --git a/app/src/main/res/values-cak/strings.xml b/app/src/main/res/values-cak/strings.xml index 3e6e4efe7..ca83f39fc 100644 --- a/app/src/main/res/values-cak/strings.xml +++ b/app/src/main/res/values-cak/strings.xml @@ -151,6 +151,8 @@ Tiyak Ximon taq ruwi\' + + Tixim chik Tikanöx pa ruxaq @@ -277,6 +279,8 @@ Tiya\' q\'ij richin nichap ruwa pa ichinan okem pa k\'amaya\'l + + We ya\'on q\'ij, xketz\'et chuqa\' ri ichinan taq ruwi\' toq e jaqäl k\'ïy chokoy Titz\'aqatisäx choj okem pa ri ichinan okem pa k\'amaya\'l @@ -297,6 +301,8 @@ Wachinel Tikirib\'äl + + B\'anoj paläj Tichinäx @@ -334,8 +340,12 @@ Tikanöx runatab\'al okem pa k\'amaya\'l Kekanöx taq yaketal + + Kekanöx ri taq ruwi\' eximon Kinuk\'ulem Rub\'i\' Taqoya\'l + + Titz\'aqatisäx ruyon URLs Kejaq taq ximonel pa taq chokoy @@ -476,6 +486,17 @@ Tojqäx ri ruwachinel oyonib\'äl + + + Tiqirirëx richin nik\'ex + + Taq\'axaj richin nawewaj ri ajkajtz\'ik + + + Tiq\'axäx ri ajkajtz\'ik chi taq ruchi\' richin nijal ruwi\' + + Tiq\'axäx ri ajkatz\'ik ajsik richin yejaq taq ruwi\' + Taq Moloj @@ -1174,6 +1195,8 @@ Tatikirisaj molojri\'ïl rik\'in ri elesäy awachib\'al Tawokisaj ri taqoya\'l + + Tatz\'uku\' jun richin naxïm Firefox pa taq okisab\'äl.]]> Firefox man xtuxïm ta chik ri rub\'i\' ataqoya\'l, man xkeyuj ta ri taq rutzij awokem pa re oyonib\'äl re\'. @@ -1286,6 +1309,11 @@ The first parameter is the app name --> %s | OSS taq wujb\'äl + + Kechojmïx chik Ojqanela\' + + Yeruyüj ri taq cookies echojmirisan pan ajk\'amaya\'l taq ruxaq ojqanem etaman kiwa. + Tob\'äl @@ -1589,6 +1617,8 @@ Achi\'el: \nhttps://www.google.com/search?q=%s Xaq\'i\' ruchi\' ri jutaqil taq ruxaq + + Richin natz\'aqatisaj jun k\'ak\'a\' nimaläj ruxaq, tayuju\' jun. Tapitz\'a\' ri ruxaq chuqa\' tacha\' tiyuj. ÜTZ, Wetaman Chik @@ -1598,7 +1628,7 @@ Achi\'el: \nhttps://www.google.com/search?q=%s Tiyuj - Ütz tawokisaj ri %s. diff --git a/app/src/main/res/values-co/strings.xml b/app/src/main/res/values-co/strings.xml index b87301ee5..4375967a7 100644 --- a/app/src/main/res/values-co/strings.xml +++ b/app/src/main/res/values-co/strings.xml @@ -1174,6 +1174,8 @@ Cunnettatevi cù u vostru appareghju-fotò Impiegà piuttostu un messaghju elettronicu + + Createne unu per sincrunizà i vostri apparechji.]]> Firefox ùn si sincruniserà più cù u vostru contu, ma ùn squasserà alcunu datu di navigazione nant’à st’apparechju. @@ -1281,6 +1283,11 @@ The first parameter is the app name --> %s | Bibliuteche di fonte aperta + + Frattighjatori da ridirezzione + + Squasseghja i canistrelli definiti da ridirezzione ver di siti web cunnisciuti per u spiunagiu. + Assistenza diff --git a/app/src/main/res/values-el/strings.xml b/app/src/main/res/values-el/strings.xml index a6eba5850..f2ce32b57 100644 --- a/app/src/main/res/values-el/strings.xml +++ b/app/src/main/res/values-el/strings.xml @@ -85,11 +85,15 @@ Απόρριψη + + Απαιτείται πρόσβαση στην κάμερα. Μεταβείτε στις Ρυθμίσεις Android, πατήστε "Δικαιώματα" και πατήστε "Να επιτρέπεται". Μετάβαση στις ρυθμίσεις Απόρριψη + + Ρυθμίστε τις ανοιχτές καρτέλες ώστε να κλείνουν αυτόματα αυτές που δεν έχουν προβληθεί την προηγούμενη ημέρα, εβδομάδα ή μήνα. Εμφάνιση επιλογών @@ -265,6 +269,8 @@ Άνοιγμα συνδέσμων σε ιδιωτική καρτέλα Να επιτρέπονται στιγμιότυπα στην ιδιωτική περιήγηση + + Αν επιτρέπεται, οι ιδιωτικές καρτέλες θα είναι επίσης ορατές όταν είναι ανοιχτές πολλές εφαρμογές Προσθήκη συντόμευσης ιδιωτικής περιήγησης @@ -398,16 +404,25 @@ Η προστασία από καταγραφή είναι ανενεργή για αυτές τις ιστοσελίδες Ενεργοποίηση για όλες τις σελίδες + + Οι εξαιρέσεις σας επιτρέπουν να απενεργοποιήσετε την προστασία από καταγραφή σε συγκεκριμένες σελίδες. Μάθετε περισσότερα + + Γενικά ανενεργή, μεταβείτε στις Ρυθμίσεις για ενεργοποίηση. + Τηλεμετρία Δεδομένα χρήσης και τεχνικά δεδομένα + + Αποστέλλει πληροφορίες επιδόσεων, χρήσης, υλικού συσκευής και εξατομίκευσης του προγράμματος περιήγησης στη Mozilla για βελτίωση του %1$s Δεδομένα μάρκετινγκ + + Αποστέλλει δεδομένα σχετικά με τις λειτουργίες που χρησιμοποιείτε στο %1$s με το Leanplum, την εταιρεία μάρκετινγκ για κινητές συσκευές. Πειράματα @@ -457,6 +472,16 @@ Χρήση θέματος συσκευής + + + Τράβηγμα για ανανέωση + + Κύλιση για απόκρυψη γραμμής εργαλείων + + Ολίσθηση γραμμής εργαλείων προς τα πλάγια για εναλλαγή καρτελών + + Ολίσθηση γραμμής εργαλείων προς τα πάνω για άνοιγμα καρτελών + Συνεδρίες @@ -1076,10 +1101,14 @@ Οι ρυθμίσεις απορρήτου και ασφάλειας αποκλείουν ιχνηλάτες, κακόβουλο λογισμικό και εταιρείες που σας ακολουθούν. Τυπική (προεπιλογή) + + Φραγή λιγότερων ιχνηλατών. Οι σελίδες θα φορτώνονται κανονικά. Αυστηρή (προτείνεται) Αυστηρή + + Φραγή περισσότερων ιχνηλατών, διαφημίσεων και αναδυόμενων παραθύρων. Οι σελίδες φορτώνονται ταχύτερα, αλλά ορισμένα χαρακτηριστικά ενδέχεται να μην λειτουργούν. @@ -1113,6 +1142,8 @@ Επιλογή θέματος + + Εξοικονόμηση μπαταρίας και προστασία ματιών με τη σκοτεινή λειτουργία. Αυτόματο @@ -1141,6 +1172,8 @@ Χρήση email + + Δημιουργήστε έναν για συγχρονισμό του Firefox μεταξύ συσκευών.]]> Το Firefox θα σταματήσει να συγχρονίζεται με το λογαριασμό σας, αλλά δεν θα διαγράψει τα δεδομένα περιήγησης από αυτή τη συσκευή. @@ -1214,12 +1247,20 @@ Περιορίζει την ικανότητα των κοινωνικών δικτύων να παρακολουθούν τη δραστηριότητά σας στο διαδίκτυο. Cookies ιχνηλάτησης μεταξύ ιστοσελίδων + + Αποκλείει τα cookies που χρησιμοποιούν τα δίκτυα διαφημίσεων και οι εταιρείες ανάλυσης δεδομένων για τη συλλογή δεδομένων περιήγησής σας από πολλαπλές ιστοσελίδες. Cryptominers + + Εμποδίζει τα κακόβουλα σενάρια από το να προσπελάσουν τη συσκευή σας για εξόρυξη ψηφιακού νομίσματος. Fingerprinters + + Διακόπτει τη συλλογή μοναδικών, αναγνωριστικών δεδομένων της συσκευής σας που μπορούν να χρησιμοποιηθούν για σκοπούς καταγραφής. Περιεχόμενο καταγραφής + + Διακόπτει τη φόρτωση εξωτερικών διαφημίσεων, βίντεο και άλλου περιεχομένου που περιέχει κώδικα καταγραφής. Ίσως επηρεάσει τη λειτουργικότητα μερικών ιστοσελίδων. Κάθε φορά που η ασπίδα είναι μωβ, το %s έχει αποκλείσει ιχνηλάτες σε μια σελίδα. Πατήστε για περισσότερες πληροφορίες. @@ -1242,6 +1283,12 @@ The first parameter is the app name --> %s | Βιβλιοθήκες OSS + + Ανακατεύθυνση ιχνηλατών + + + Διαγράφει τα cookies που τοποθετούνται από ανακατευθύνσεις σε γνωστές ιστοσελίδες καταγραφής. + Υποστήριξη @@ -1289,6 +1336,9 @@ Όνομα συντόμευσης + + Μπορείτε εύκολα να προσθέσετε αυτή την ιστοσελίδα στην αρχική οθόνη για άμεση πρόσβαση και ταχύτερη περιήγηση, σαν να ήταν εφαρμογή. + Συνδέσεις και κωδικοί πρόσβασης @@ -1373,6 +1423,10 @@ Απόκρυψη κωδικού πρόσβασης Ξεκλειδώστε για να δείτε τις αποθηκευμένες συνδέσεις σας + + Προστασία στοιχείων σύνδεσης + + Ορίστε ένα μοτίβο κλειδώματος συσκευής, ένα ΡΙΝ ή έναν κωδικό πρόσβασης για προστασία των αποθηκευμένων στοιχείων σύνδεσης, σε περίπτωση που κάποιος τρίτος αποκτήσει πρόσβαση στη συσκευή σας. Αργότερα diff --git a/app/src/main/res/values-es-rES/strings.xml b/app/src/main/res/values-es-rES/strings.xml index e2d9d896a..07c28a644 100644 --- a/app/src/main/res/values-es-rES/strings.xml +++ b/app/src/main/res/values-es-rES/strings.xml @@ -1196,6 +1196,8 @@ Inicia sesión con tu cámara Usa el correo electrónico + + Crea una para sincronizar Firefox entre dispositivos.]]> Firefox dejará de sincronizarse con tu cuenta, pero no eliminará ninguno de tus datos de navegación en este dispositivo. @@ -1305,6 +1307,11 @@ The first parameter is the app name --> %s | Bibliotecas OSS + + Rastreadores de redirección + + Borra las cookies establecidas por redirecciones a sitios web de rastreo conocidos. + Ayuda diff --git a/app/src/main/res/values-eu/strings.xml b/app/src/main/res/values-eu/strings.xml index daa0e29b3..0b761f135 100644 --- a/app/src/main/res/values-eu/strings.xml +++ b/app/src/main/res/values-eu/strings.xml @@ -147,6 +147,8 @@ Instalatu Sinkronizatutako fitxak + + Sinkronizatu berriro Bilatu orrian @@ -292,6 +294,8 @@ Itxura Hasierako pantaila + + Keinuak Pertsonalizatu @@ -326,9 +330,13 @@ Bilatu nabigatze-historia Bilatu laster-markak + + Bilatu sinkronizatutako fitxak Kontu-ezarpenak + + Osatu automatikoki URLak Ireki loturak aplikazioetan @@ -471,6 +479,9 @@ Jarraitu gailuaren itxura + + Korritu tresna-barra ezkutatzeko + Saioak @@ -1061,10 +1072,6 @@ Hasi sinkronizatzen laster-markak, historia eta gehiago zure Firefox kontua erabiliz. Argibide gehiago - - %s bezala saioa hasita duzu telefono honetako beste Firefox nabigatzaile batean. Kontu honekin saioa hasi nahi duzu? Bai, hasi saioa @@ -1308,9 +1315,6 @@ Lasterbidearen izena - - Modu errazean gehi dezakezu webgune hau zure telefonoaren hasierako pantailan berehalako sarbidea izan eta aplikazio-moduko esperientziarekin azkarrago nabigatzeko. - Saio-hasierak eta pasahitzak @@ -1537,7 +1541,7 @@ Erabiltzaile-izen hori badago lehendik ere - + Konektatu beste gailu bat. Autentifikatu berriro mesedez. @@ -1557,8 +1561,6 @@ Gune erabilienen mugara iritsi da - - Gune erabilienetako bat gehitzeko, kendu aurretik dagoen bat. Sakatu eta mantendu gunea eta hautatu kentzeko aukera. Ados, ulertuta @@ -1568,7 +1570,7 @@ Kendu - Atera %s(r)i ahalik eta zuku gehiena. @@ -1576,4 +1578,9 @@ Bildu zuretzat garrantzizkoa dena Multzokatu antzerako bilaketak, guneak eta fitxak sarbide azkarrago baterako. + + %s bezala saioa hasita duzu telefono honetako beste Firefox nabigatzaile batean. Kontu honekin saioa hasi nahi duzu? + + Modu errazean gehi dezakezu webgune hau zure telefonoaren hasierako pantailan berehalako sarbidea izan eta aplikazio-moduko esperientziarekin azkarrago nabigatzeko. + diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 06553b266..0e7af75b4 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -1199,6 +1199,8 @@ Cependant, il peut être moins stable. Téléchargez la version bêta de notre n Connectez-vous avec votre appareil photo Utiliser plutôt une adresse électronique + + Créez-en un pour synchroniser Firefox entre vos appareils.]]> Firefox ne se synchronisera plus avec votre compte, mais ne supprimera aucune de vos données de navigation sur cet appareil. @@ -1306,6 +1308,11 @@ Cependant, il peut être moins stable. Téléchargez la version bêta de notre n The first parameter is the app name --> %s | Bibliothèques open source + + Traqueurs par redirection + + Efface les cookies définis par redirection vers des sites web connus pour le pistage. + Assistance diff --git a/app/src/main/res/values-fy-rNL/strings.xml b/app/src/main/res/values-fy-rNL/strings.xml index 6bc3feeff..accb6da0d 100644 --- a/app/src/main/res/values-fy-rNL/strings.xml +++ b/app/src/main/res/values-fy-rNL/strings.xml @@ -1163,6 +1163,8 @@ Meld jo oan mei jo kamera E-mail brûke + + Meitsje der ien oan om Firefox tusken apparaten te syngronisearjen.]]> Firefox stoppet de syngronisaasje mei jo account, mar sil gjin sneupgegevens op dit apparaat fuortsmite. @@ -1271,6 +1273,11 @@ The first parameter is the app name --> %s | OSS-biblioteken + + Trochliedingstrackers + + Wisket cookies dy\'t ynsteld binne troch trochliedingen nei bekende trackingwebsites. + Stipe diff --git a/app/src/main/res/values-hr/strings.xml b/app/src/main/res/values-hr/strings.xml index 9638ae8d2..5e74c0523 100644 --- a/app/src/main/res/values-hr/strings.xml +++ b/app/src/main/res/values-hr/strings.xml @@ -1171,6 +1171,8 @@ Prijavi se pomoću kamere Umjesto toga koristi e-poštu + + Stvori ga i sinkroniziraj Firefox među uređajima.]]> Firefox će prestati sinkronizirati s tvojim računom, ali neće izbrisati podatke o tvom pregledavanju na ovom uređaju. @@ -1281,6 +1283,11 @@ The first parameter is the app name --> %s | OSS biblioteke + + Pratitelji preusmjeravanja + + Čisti kolačiće koje postavljaju preusmjeravanja na poznate stranice za praćenje. + Podrška diff --git a/app/src/main/res/values-in/strings.xml b/app/src/main/res/values-in/strings.xml index aef23d915..836e57e12 100644 --- a/app/src/main/res/values-in/strings.xml +++ b/app/src/main/res/values-in/strings.xml @@ -147,6 +147,8 @@ Pasang Tab yang disinkronkan + + Sinkron ulang Temukan di laman @@ -275,6 +277,8 @@ Buka tautan di tab pribadi Izinkan tangkapan layar di penjelajahan pribadi + + Jika diizinkan, tab pribadi juga akan terlihat saat beberapa aplikasi terbuka Tambahkan pintasan penjelajahan pribadi @@ -295,6 +299,8 @@ Tema Beranda + + Gestur Ubahsuai @@ -329,9 +335,13 @@ Cari riwayat jelajah Cari markah + + Cari tab tersinkron Setelan akun + + URL lengkapi-otomatis Buka tautan di aplikasi @@ -474,6 +484,16 @@ Ikuti tema peranti + + + Tarik untuk menyegarkan + + Gulir untuk menyembunyikan bilah alat + + Geser bilah alat ke samping untuk beralih tab + + Geser bilah alat ke atas untuk membuka tab + Sesi @@ -1072,7 +1092,7 @@ - Anda masuk sebagai %s di peramban Firefox lainnya pada ponsel ini. Apakah Anda ingin masuk dengan akun tersebut? + Anda masuk sebagai %s di peramban Firefox lainnya pada perangkat ini. Apakah Anda ingin masuk dengan akun tersebut? Ya, masukkan saya @@ -1161,6 +1181,8 @@ Masuk dengan kamera Anda Gunakan surel saja + + Buat satu untuk menyinkronkan Firefox antar perangkat.]]> Firefox akan berhenti menyinkronkan dengan akun Anda, tetapi tidak akan menghapus data penjelajahan Anda di peranti ini. @@ -1269,6 +1291,11 @@ The first parameter is the app name --> %s | Pustaka OSS + + Alihkan Pelacak + + Menghapus kuki yang ditetapkan oleh pengalihan ke situs web pelacakan yang dikenal. + Dukungan @@ -1315,7 +1342,7 @@ Nama pintasan - Anda dapat dengan mudah menambahkan situs web ini ke layar Beranda ponsel Anda untuk mendapatkan akses instan dan penjelajahan lebih cepat seperti menggunakan aplikasi. + Anda dapat dengan mudah menambahkan situs web ini ke layar Beranda perangkat Anda untuk mendapatkan akses instan dan penjelajahan lebih cepat seperti menggunakan aplikasi. Info masuk dan kata sandi @@ -1385,8 +1412,12 @@ Situs disalin ke clipboard Salin sandi + + Hapus sandi Salin nama pengguna + + Hapus nama pengguna Salin situs @@ -1541,7 +1572,7 @@ Sebuah info masuk dengan nama pengguna tersebut sudah ada - + Hubungkan perangkat lain Harap autentikasi ulang. @@ -1562,7 +1593,7 @@ Batas situs teratas tercapai - Untuk menambahkan situs teratas, hapus salah satu. Tekan lama situs yang dimaksud dan pilih hapus. + Untuk menambahkan situs teratas, hapus salah satu. Tekan lama situs yang dimaksud dan pilih hapus. Oke, Paham! @@ -1572,7 +1603,7 @@ Hapus - Dapatkan hasil maksimal dari %s. @@ -1580,4 +1611,9 @@ Kumpulkan hal-hal yang penting bagi Anda Kelompokkan pencarian, situs, dan tab yang serupa agar dapat diakses cepat nanti. + + Anda masuk sebagai %s di peramban Firefox lainnya pada ponsel ini. Apakah Anda ingin masuk dengan akun tersebut? + + Anda dapat dengan mudah menambahkan situs web ini ke layar Beranda ponsel Anda untuk mendapatkan akses instan dan penjelajahan lebih cepat seperti menggunakan aplikasi. + diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index 5dcd282e2..8580a4752 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -1198,6 +1198,8 @@ Accedi con la fotocamera Accedi con l’email + + Creane uno per sincronizzare Firefox tra vari dispositivi.]]> I dati di navigazione non verranno più sincronizzati con l’account Firefox, ma non saranno rimossi da questo dispositivo. @@ -1305,6 +1307,12 @@ The first parameter is the app name --> %s | Librerie OSS + + Traccianti di reindirizzamento + + + Elimina i cookie impostati per reindirizzare a siti web noti per il tracciamento. + Supporto diff --git a/app/src/main/res/values-ka/strings.xml b/app/src/main/res/values-ka/strings.xml index 47bd23666..2d77fd703 100644 --- a/app/src/main/res/values-ka/strings.xml +++ b/app/src/main/res/values-ka/strings.xml @@ -311,7 +311,7 @@ შემმუშავებლის ხელსაწყოები - დაშორებული გამართვა USB-ით + დაშორებული USB-გამართვა საძიებო სისტემების ჩვენება @@ -336,7 +336,7 @@ ბმულების გახსნა პროგრამებში - ჩამოტვირთვის გარეშე მმართველი + გარეშე მმართველი ჩამოტვირთვების დამატებები @@ -992,7 +992,7 @@ გამოხვალთ ანგარიშებიდან უმეტეს საიტზე - შენახული სურათებისა და ფაილების ასლები + სურათები და ფაილების კეში გაათავისუფლებს მეხსიერებას @@ -1163,6 +1163,8 @@ შედით კამერის დახმარებით ელფოსტის გამოყენება სანაცვლოდ + + შექმენით და დაასინქრონეთ Firefox მოწყობილობებზე.]]> Firefox შეწყვეტს ამ ანგარიშის სინქრონიზაციას, მაგრამ ამ მოწყობილობიდან დათვალიერების მონაცემები არ წაიშლება. @@ -1172,7 +1174,7 @@ გაუქმება - ნაგულისხმევი საქაღალდეების ჩასწორება ვერ ხერხდება + ნაგულისხმევი საქაღალდეების ჩასწორება ვერ მოხერხდება @@ -1273,6 +1275,11 @@ The first parameter is the app name --> %s | OSS-ბიბლიოთეკები + + გადამმისამართებელი მეთვალყურეები + + ასუფთავებს გადამისამართებით მიღებულ ფუნთუშებს, ცნობილი მეთვალყურეებისგან. + მხარდაჭერა diff --git a/app/src/main/res/values-kab/strings.xml b/app/src/main/res/values-kab/strings.xml index eaf98fed3..1e4953421 100644 --- a/app/src/main/res/values-kab/strings.xml +++ b/app/src/main/res/values-kab/strings.xml @@ -148,6 +148,8 @@ Tiktiwin tigejdanin yuzzlen ur nṣeḥḥi ara Sebded Iccaren yemtawin + + Ales amtawi Nadi deg usebter @@ -270,6 +272,8 @@ Tiktiwin tigejdanin yuzzlen ur nṣeḥḥi ara Ldi iseɣwan deg iccer uslig Sireg tuṭṭfa n ugdil deg tunigin tusligt + + Ma yella yettusireg, accaren usligen ad d-banen ula d nutni ma yili aṭas n yisnasen i yeldin Rnu anegzum i tunigin tusligin @@ -290,6 +294,8 @@ Tiktiwin tigejdanin yuzzlen ur nṣeḥḥi ara Asentel Asebter agejdan + + Isillifen Sagen @@ -324,9 +330,13 @@ Tiktiwin tigejdanin yuzzlen ur nṣeḥḥi ara Nadi azray n tunigin Nadi ticraḍ n yisebtar + + Nadi accaren yemtawin Iɣewwaṛen n umiḍan + + Tacaṛt tawurmant n URLs Ldi iseɣwan deg isnasen @@ -467,6 +477,17 @@ Tiktiwin tigejdanin yuzzlen ur nṣeḥḥi ara Ḍfer asntel n ibnek + + + Zuɣer i usesfer + + Drurem i wakken ad teffreḍ agalis n yifecka + + + Err agalis n yifecka deg tama i ubeddel n waccaren + + Err agalis n yifecka deg usawen i twaledyawt n waccaren + Tiɣimiyen @@ -1057,6 +1078,10 @@ Tiktiwin tigejdanin yuzzlen ur nṣeḥḥi ara Bdu amtawi n ticraḍ, awalen uffiren, akked waṭas-nniḍen s umiḍan n Firefox. Issin ugar + + Aql-ak·akem teqqneḍ d %s ɣef yiminig-nniḍen n Firefox s yibenk-a. Tebɣiḍ ad teqqneḍ s umiḍan-a? Ih, qqen-iyi @@ -1148,6 +1173,8 @@ Tiktiwin tigejdanin yuzzlen ur nṣeḥḥi ara Qqen s tkamirat-ik Seqdec tansa n yimayl deg umḍiq-is + + Rnu yiwen i umtaw n Firefox gar yibenkan.]]> Firefox ad iseḥbes amtawi d umiḍan-inek, acukan ur ittekkes ara isefka-inek n tunigin seg uselkim-a. @@ -1258,6 +1285,12 @@ Tiktiwin tigejdanin yuzzlen ur nṣeḥḥi ara The first parameter is the app name --> %s | timkerḍiyin OSS + + Welleh i yineḍfaren + + + Yesfaḍay inagan n tuqqna yettusbadun s yiwellhen n yismal web n uḍfar yettwassnen. + Tallelt @@ -1302,6 +1335,9 @@ Tiktiwin tigejdanin yuzzlen ur nṣeḥḥi ara Isem n unegzum + + Tzemreḍ s sshal ad ternuḍ asmel-a web ɣer ugdil agejdan n yibenk-inek·inem i wakken ad tesɛuḍ anekcum askudan d tunigin taruradt s termt i icuban asnas. + Inekcam d wawalen uffiren @@ -1374,6 +1410,8 @@ Tiktiwin tigejdanin yuzzlen ur nṣeḥḥi ara Sfeḍ awal uffir Nɣel isem n useqdac + + Sfeḍ isem n useqdac Nɣel asmel @@ -1552,6 +1590,8 @@ Tiktiwin tigejdanin yuzzlen ur nṣeḥḥi ara Tewwḍeḍ ɣer talast n usmel + + I tmerna n usmel afellay amaynut, kkes yiwen. Nnal ɣef usmel-nni war aḥbas syen fren "kkes". IH, awi-t-id @@ -1561,7 +1601,7 @@ Tiktiwin tigejdanin yuzzlen ur nṣeḥḥi ara Kkes - Awi %s amaynut akk. diff --git a/app/src/main/res/values-lt/strings.xml b/app/src/main/res/values-lt/strings.xml index 0c0693f1c..e0da01ebe 100644 --- a/app/src/main/res/values-lt/strings.xml +++ b/app/src/main/res/values-lt/strings.xml @@ -144,6 +144,8 @@ Įdiegti Sinchronizuotos kortelės + + Sinchronizuoti iš naujo Rasti tinklalapyje @@ -325,6 +327,8 @@ Ieškoti naršymo žurnale Ieškoti adresyne + + Ieškoti tarp sinchronizuotų kortelių Paskyros nuostatos @@ -1166,6 +1170,8 @@ Prisijunkite su savo kamera Naudoti el. paštą + + Susikurkite, norėdami sinchronizuoti „Firefox“ tarp įrenginių.]]> „Firefox“ nebesinchronizuos duomenų su jūsų paskyra, tačiau šiame įrenginyje esančių naršymo duomenų nepašalins. @@ -1274,6 +1280,11 @@ The first parameter is the app name --> „%s“ | Atvirosios bibliotekos + + Nukreipimų stebėjimo elementai + + Išvalo slapukus, įrašomus nukreipiant į žinomas stebėjimo svetaines. + Žinynas diff --git a/app/src/main/res/values-night/colors.xml b/app/src/main/res/values-night/colors.xml index 5f0f08d11..0190bf370 100644 --- a/app/src/main/res/values-night/colors.xml +++ b/app/src/main/res/values-night/colors.xml @@ -65,6 +65,7 @@ @color/tab_tray_item_media_background_dark_theme @color/tab_tray_heading_icon_dark_theme @color/tab_tray_heading_icon_inactive_dark_theme + @color/tab_tray_heading_icon_menu_dark_theme @color/tab_tray_item_thumbnail_background_dark_theme @color/tab_tray_item_thumbnail_icon_dark_theme @color/tab_tray_selected_mask_dark_theme diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml index 6a692d417..3014f98d2 100644 --- a/app/src/main/res/values-nl/strings.xml +++ b/app/src/main/res/values-nl/strings.xml @@ -1175,6 +1175,8 @@ Meld u aan met uw camera E-mail gebruiken + + Maak er een aan om Firefox tussen apparaten te synchroniseren.]]> Firefox stopt de synchronisatie met uw account, maar zal geen surfgegevens op dit apparaat verwijderen. @@ -1282,6 +1284,11 @@ The first parameter is the app name --> %s | OSS-bibliotheken + + Doorleidingstrackers + + Wist cookies die zijn ingesteld door doorgeleidingen naar bekende trackingwebsites. + Ondersteuning diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index a1f5c3cea..011f6c979 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -1171,6 +1171,8 @@ Zaloguj się za pomocą aparatu Użyj adresu e-mail + + Utwórz je, aby synchronizować Firefoksa między urządzeniami.]]> Firefox zatrzyma synchronizację z tym kontem, ale nie usunie danych przeglądania na tym urządzeniu. @@ -1279,6 +1281,11 @@ The first parameter is the app name --> %s | Biblioteki open source + + Elementy śledzące przez przekierowania + + Czyści ciasteczka ustawione przez przekierowania do znanych witryn śledzących użytkowników. + Pomoc diff --git a/app/src/main/res/values-pt-rPT/strings.xml b/app/src/main/res/values-pt-rPT/strings.xml index 39e6acdc9..aecebd5f2 100644 --- a/app/src/main/res/values-pt-rPT/strings.xml +++ b/app/src/main/res/values-pt-rPT/strings.xml @@ -1167,6 +1167,8 @@ Inicie sessão com a sua câmara Como alternativa, utilizar o e-mail + + Crie uma conta para sincronizar o Firefox entre os dispositivos.]]> O Firefox irá parar a sincronização com a sua conta, mas não irá apagar quaisquer dados de navegação seus neste dispositivo. @@ -1277,6 +1279,11 @@ The first parameter is the app name --> %s | Bibliotecas de código aberto + + Rastreadores de redirecionamento + + Limpa as cookies definidas por redirecionamentos para sites de rastreamento conhecidos. + Apoio diff --git a/app/src/main/res/values-rm/strings.xml b/app/src/main/res/values-rm/strings.xml index 0d92a239b..632759601 100644 --- a/app/src/main/res/values-rm/strings.xml +++ b/app/src/main/res/values-rm/strings.xml @@ -142,6 +142,8 @@ Installar Tabs sincronisads + + Resincronisar Tschertgar en la pagina @@ -263,6 +265,8 @@ Avrir colliaziuns en in tab privat Permetter maletgs dal visur en il modus privat + + Sche permess, èn tabs privats era visibels en cas che pliras apps èn avertas Agiuntar ina scursanida al modus privat @@ -283,6 +287,8 @@ Design Pagina iniziala + + Gests Persunalisar @@ -318,8 +324,12 @@ Tschertgar en la cronologia da navigaziun Tschertgar en ils segnapaginas + + Tschertgar en ils tabs sincronisads Parameters dal conto + + Cumplettar automaticamain URLs Avrir colliaziuns en apps @@ -456,6 +466,16 @@ Tenor il design tschernì sin l\'apparat + + + Trair per actualisar + + Scrollar per zuppentar la trav d\'utensils + + Stritgar da la vart la trav d\'utensils per midar tab + + Stritgar ensi la trav d\'utensils per avrir tabs + Sesidas @@ -1045,7 +1065,7 @@ - Ti es annunzià sco %s en in auter navigatur da Firefox sin quest telefonin. Vuls ti t\'annunziar cun quest conto? + Ti es annunzià sco %s en in auter navigatur da Firefox sin quest apparat. Vuls ti t\'annunziar cun quest conto? Gea, m\'annunziar @@ -1137,6 +1157,8 @@ T\'annunzia cun tia camera Utilisar l\'e-mail + + Creescha in per sincronisar Firefox tranter differents apparats.]]> Firefox chala da sincronisar cun tes conto, ma las datas da navigaziun restan sin quest apparat. @@ -1295,9 +1317,6 @@ Num da la scursanida - - Ti pos agiuntar a moda simpla questa website al visur da partenza da tes telefonin per avair access direct e navigar pli svelt, sco sch\'i fiss ina app. - Infurmaziuns d\'annunzia e pleds-clav @@ -1526,7 +1545,7 @@ Datas d\'annunzia cun quest num d\'utilisader existan gia - + Colliar in auter apparat. Re-autentifitgescha per plaschair. @@ -1546,8 +1565,6 @@ Cuntanschì la limita da paginas preferidas - - Per agiuntar ina nova pagina preferida stos ti l\'emprim allontanar in\'autra. Smatga ditg sin ina pagina e tscherna «Allontanar». OK, chapì @@ -1557,7 +1574,7 @@ Allontanar - Rabla ora il maximum da %s. @@ -1565,4 +1582,9 @@ Rimna las chaussas che t\'èn impurtantas Gruppescha retschertgas, paginas e tabs sumegliants per pli tard pudair acceder pli svelt. - + + Ti es annunzià sco %s en in auter navigatur da Firefox sin quest telefonin. Vuls ti t\'annunziar cun quest conto? + + Ti pos agiuntar a moda simpla questa website al visur da partenza da tes telefonin per avair access direct e navigar pli svelt, sco sch\'i fiss ina app. + + diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 331fe062e..ccd990fe1 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -155,6 +155,8 @@ Установить Облачные вкладки + + Повторная синхронизация Найти на странице @@ -1219,6 +1221,8 @@ Войти с помощью камеры Использовать электронную почту + + Создайте его, чтобы синхронизировать Firefox между устройствами.]]> Firefox прекратит синхронизацию с вашим аккаунтом, но не будет удалять ничего из ваших данных веб-сёрфинга на этом устройстве. @@ -1326,6 +1330,11 @@ The first parameter is the app name --> %s | Свободные библиотеки + + Трекеры перенаправлений + + Удаляет куки, установленные в ходе перенаправлений на известные веб-сайты с отслеживанием. + Поддержка diff --git a/app/src/main/res/values-sq/strings.xml b/app/src/main/res/values-sq/strings.xml index 758cef642..e784e6f8d 100644 --- a/app/src/main/res/values-sq/strings.xml +++ b/app/src/main/res/values-sq/strings.xml @@ -143,6 +143,8 @@ Instaloje Skeda të njëkohësuara + + Rinjëkohëso Gjej në faqe @@ -324,6 +326,8 @@ Kërkoni në historik shfletimi Kërkoni te faqerojtësit + + Kërko në skeda të njëkohësuara Rregullime llogarie @@ -1158,6 +1162,8 @@ Hyni me kamerën tuaj Më mirë përdorni email + + Krijoni një të tillë që të njëkohësoni Firefox-in mes pajisjesh.]]> Firefox-i do të reshtë së njëkohësuari me llogarinë tuaj, por s’do të fshijë ndonjë nga të dhënat e shfletimit tuaj në këtë pajisje. @@ -1267,6 +1273,11 @@ The first parameter is the app name --> %s | Biblioteka OSS + + Gjurmues Ridrejtimesh + + Spastron cookies të depozituara nga ridrejtime për te sajte të njohur për gjurmim. + Asistencë diff --git a/app/src/main/res/values-sr/strings.xml b/app/src/main/res/values-sr/strings.xml index 7d7e04ff1..a8a5ea97d 100644 --- a/app/src/main/res/values-sr/strings.xml +++ b/app/src/main/res/values-sr/strings.xml @@ -1164,6 +1164,8 @@ Пријава преко камере Користи адресу е-поште + + Направите га за синхронизацију Firefox-а међу уређајима.]]> Firefox ће престати са снихронизацијом вашег налога али неће обрисати ваше податке прегледања на овом уређају. @@ -1277,6 +1279,11 @@ The first parameter is the app name --> %s | библиотеке отвореног кода + + Преусмерите пратиоце + + Брише колачиће постављене преусмеравањем на познате страница за праћење. + Подршка diff --git a/app/src/main/res/values-tg/strings.xml b/app/src/main/res/values-tg/strings.xml index f7b2b6379..ffeb619d1 100644 --- a/app/src/main/res/values-tg/strings.xml +++ b/app/src/main/res/values-tg/strings.xml @@ -832,6 +832,9 @@ Кукиҳо + + Нест кардани маълумоти баррасӣ ҳангоми баромад + Бекор кардан diff --git a/app/src/main/res/values-ur/strings.xml b/app/src/main/res/values-ur/strings.xml index 61cdc7eba..a2c21bf0c 100644 --- a/app/src/main/res/values-ur/strings.xml +++ b/app/src/main/res/values-ur/strings.xml @@ -76,6 +76,19 @@ ابھی نہیں + + سیٹنگز پر جائیں + + برخاست کریں + + + سیٹنگز پر جائیں + + برخاست کریں + + + برخاست کریں + نیا ٹیب @@ -472,6 +485,10 @@ بند کریں + + + ٹیب بند کریں + ٹیبس کھولیں @@ -502,9 +519,9 @@ مجموعہ سے ٹیب کو ہٹا دیں - ٹیب بند کر دیں + ٹیب بند کریں - %s ٹیب بند کر دیں + %s ٹیب بند کریں ٹیبز کا مینو کھولیں @@ -594,7 +611,7 @@ تباہی کی رپورٹ Mozilla کو ارسال کریں - ٹیب بند کر دیں + ٹیب بند کریں ٹیب بحال کریں @@ -743,10 +760,6 @@ مجموعہ کا مینو - - آپ کے لئے اہم چیزیں جمع کریں - - ایک طرح کی تلاشیں ، سائٹیں اور ٹیبز کو بعد میں فوری رسائی کے لئے گروہ بندی کریں۔ ٹیب کو منتخب کریں @@ -983,8 +996,8 @@ دیکھیں نیا کیا ہے یہاں جوابات حاصل کریں - - %s کا زیادہ سے زیادہ فائدہ اٹھائیں۔ + + مزید سیکھیں ہاں ، مجھے سائن ان کریں @@ -1427,7 +1440,7 @@ اس صارف نام کے ساتھ لاگ ان پہلے سے موجود ہے - + ایک اور آلہ جوڑیں۔ برائے مہربانی دوبارہ توثیق کریں۔ @@ -1446,9 +1459,18 @@ اعلٰی سائٹ کی حد پوری ہو گئی - - نئی ٹاپ سائٹ شامل کرنے کے لئے ، ایک سائٹ کو ہٹائیں۔ سائٹ پر دیر تک دبائیں اور ہٹانا منتخب کریں۔ ٹھیک ہے سمجھ گیا - + + ہٹائیں + + + %s کا زیادہ سے زیادہ فائدہ اٹھائیں۔ + + + آپ کے لئے اہم چیزیں جمع کریں + + ایک طرح کی تلاشیں ، سائٹیں اور ٹیبز کو بعد میں فوری رسائی کے لئے گروہ بندی کریں۔ + diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index fb96fd10e..89963e642 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -75,6 +75,7 @@ #312A65 @color/ink_20 @color/ink_20_48a + @color/accent_normal_theme @color/photonLightGrey10 @color/photonLightGrey60 @color/violet_70_12a @@ -141,7 +142,8 @@ @color/photonDarkGrey10 #9059FF @color/violet_50 - @color/violet_50_48a + @color/photonLightGrey05 + @color/photonLightGrey05 @color/photonDarkGrey50 @color/photonDarkGrey05 @color/violet_50_32a @@ -264,6 +266,7 @@ @color/tab_tray_item_media_background_light_theme @color/tab_tray_heading_icon_light_theme @color/tab_tray_heading_icon_inactive_light_theme + @color/tab_tray_heading_icon_menu_light_theme @color/tab_tray_item_thumbnail_background_light_theme @color/tab_tray_item_thumbnail_icon_light_theme @color/tab_tray_selected_mask_light_theme diff --git a/app/src/main/res/values/preference_keys.xml b/app/src/main/res/values/preference_keys.xml index a68590d38..efbceadc1 100644 --- a/app/src/main/res/values/preference_keys.xml +++ b/app/src/main/res/values/preference_keys.xml @@ -35,6 +35,8 @@ pref_key_delete_browsing_data_on_quit_categories pref_key_last_known_mode_private pref_key_addons + pref_key_override_amo_user + pref_key_override_amo_collection pref_key_last_maintenance pref_key_help pref_key_rate diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index fb6521369..c4ec4faf4 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -347,6 +347,20 @@ Notifications + + + Custom Add-on collection + + OK + + Cancel + + Collection name + + Collection owner (User ID) + + Add-on collection modified. Quitting the application to apply changes… + Sync now diff --git a/app/src/main/res/xml/preferences.xml b/app/src/main/res/xml/preferences.xml index 11104f2f6..8a99bb133 100644 --- a/app/src/main/res/xml/preferences.xml +++ b/app/src/main/res/xml/preferences.xml @@ -131,6 +131,11 @@ android:key="@string/pref_key_addons" android:title="@string/preferences_addons" /> + + mockIntentProcessor(): T { diff --git a/app/src/test/java/org/mozilla/fenix/StrictModeManagerTest.kt b/app/src/test/java/org/mozilla/fenix/StrictModeManagerTest.kt index 560787384..f4f3e21a8 100644 --- a/app/src/test/java/org/mozilla/fenix/StrictModeManagerTest.kt +++ b/app/src/test/java/org/mozilla/fenix/StrictModeManagerTest.kt @@ -11,13 +11,13 @@ import io.mockk.confirmVerified import io.mockk.every import io.mockk.impl.annotations.MockK import io.mockk.mockk -import io.mockk.mockkObject import io.mockk.mockkStatic import io.mockk.slot -import io.mockk.unmockkObject import io.mockk.unmockkStatic import io.mockk.verify import org.junit.After +import org.junit.Assert.assertEquals +import org.junit.Assert.fail import org.junit.Before import org.junit.Test import org.junit.runner.RunWith @@ -26,48 +26,91 @@ import org.mozilla.fenix.helpers.FenixRobolectricTestRunner @RunWith(FenixRobolectricTestRunner::class) class StrictModeManagerTest { + private lateinit var debugManager: StrictModeManager + private lateinit var releaseManager: StrictModeManager + @MockK(relaxUnitFun = true) private lateinit var fragmentManager: FragmentManager @Before fun setup() { MockKAnnotations.init(this) mockkStatic(StrictMode::class) - mockkObject(Config) + + // These tests log a warning that mockk couldn't set the backing field of Config.channel + // but it doesn't seem to impact their correctness so I'm ignoring it. + val debugConfig: Config = mockk { every { channel } returns ReleaseChannel.Debug } + debugManager = StrictModeManager(debugConfig) + + val releaseConfig: Config = mockk { every { channel } returns ReleaseChannel.Release } + releaseManager = StrictModeManager(releaseConfig) } @After fun teardown() { unmockkStatic(StrictMode::class) - unmockkObject(Config) } @Test - fun `test enableStrictMode in release`() { - every { Config.channel } returns ReleaseChannel.Release - StrictModeManager.enableStrictMode(false) - + fun `GIVEN we're in a release build WHEN we enable strict mode THEN we don't set policies`() { + releaseManager.enableStrictMode(false) verify(exactly = 0) { StrictMode.setThreadPolicy(any()) } verify(exactly = 0) { StrictMode.setVmPolicy(any()) } } @Test - fun `test enableStrictMode in debug`() { - every { Config.channel } returns ReleaseChannel.Debug - StrictModeManager.enableStrictMode(false) - + fun `GIVEN we're in a debug build WHEN we enable strict mode THEN we set policies`() { + debugManager.enableStrictMode(false) verify { StrictMode.setThreadPolicy(any()) } verify { StrictMode.setVmPolicy(any()) } } @Test - fun `test changeStrictModePolicies`() { + fun `GIVEN we're in a debug build WHEN we attach a listener THEN we attach to the fragment lifecycle and detach when onFragmentResumed is called`() { val callbacks = slot() - StrictModeManager.changeStrictModePolicies(fragmentManager) + debugManager.attachListenerToDisablePenaltyDeath(fragmentManager) verify { fragmentManager.registerFragmentLifecycleCallbacks(capture(callbacks), false) } confirmVerified(fragmentManager) callbacks.captured.onFragmentResumed(fragmentManager, mockk()) verify { fragmentManager.unregisterFragmentLifecycleCallbacks(callbacks.captured) } } + + @Test + fun `GIVEN we're in a release build WHEN resetAfter is called THEN we return the value from the function block`() { + val expected = "Hello world" + val actual = releaseManager.resetAfter(StrictMode.allowThreadDiskReads()) { expected } + assertEquals(expected, actual) + } + + @Test + fun `GIVEN we're in a debug build WHEN resetAfter is called THEN we return the value from the function block`() { + val expected = "Hello world" + val actual = debugManager.resetAfter(StrictMode.allowThreadDiskReads()) { expected } + assertEquals(expected, actual) + } + + @Test + fun `GIVEN we're in a release build WHEN resetAfter is called THEN the old policy is not set`() { + releaseManager.resetAfter(StrictMode.allowThreadDiskReads()) { "" } + verify(exactly = 0) { StrictMode.setThreadPolicy(any()) } + } + + @Test + fun `GIVEN we're in a debug build WHEN resetAfter is called THEN the old policy is set`() { + val expectedPolicy = StrictMode.allowThreadDiskReads() + debugManager.resetAfter(expectedPolicy) { "" } + verify { StrictMode.setThreadPolicy(expectedPolicy) } + } + + @Test + fun `GIVEN we're in a debug build WHEN resetAfter is called and an exception is thrown from the function THEN the old policy is set`() { + val expectedPolicy = StrictMode.allowThreadDiskReads() + try { + debugManager.resetAfter(expectedPolicy) { throw IllegalStateException() } + @Suppress("UNREACHABLE_CODE") fail("Expected previous method to throw.") + } catch (e: IllegalStateException) { /* Do nothing */ } + + verify { StrictMode.setThreadPolicy(expectedPolicy) } + } } diff --git a/app/src/test/java/org/mozilla/fenix/collections/CollectionCreationFragmentTest.kt b/app/src/test/java/org/mozilla/fenix/collections/CollectionCreationFragmentTest.kt index 913590f0d..bd04dc4ef 100644 --- a/app/src/test/java/org/mozilla/fenix/collections/CollectionCreationFragmentTest.kt +++ b/app/src/test/java/org/mozilla/fenix/collections/CollectionCreationFragmentTest.kt @@ -10,13 +10,10 @@ import io.mockk.impl.annotations.MockK import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.ExperimentalCoroutinesApi import mozilla.components.browser.state.state.BrowserState -import mozilla.components.browser.state.state.ReaderState import mozilla.components.browser.state.state.createTab -import mozilla.components.feature.tab.collections.Tab import mozilla.components.lib.publicsuffixlist.PublicSuffixList import mozilla.components.support.test.robolectric.createAddedTestFragment import mozilla.components.support.test.robolectric.testContext -import org.junit.Assert.assertEquals import org.junit.Assert.assertNotNull import org.junit.Assert.assertNull import org.junit.Assert.assertTrue @@ -31,9 +28,6 @@ private const val SESSION_ID_MOZILLA = "0" private const val URL_BCC = "www.bcc.co.uk" private const val SESSION_ID_BCC = "1" -private const val SESSION_ID_BAD_1 = "not a real session id" -private const val SESSION_ID_BAD_2 = "definitely not a real session id" - @ExperimentalCoroutinesApi @RunWith(FenixRobolectricTestRunner::class) class CollectionCreationFragmentTest { @@ -71,63 +65,4 @@ class CollectionCreationFragmentTest { fragment.dismiss() assertNull(fragment.dialog) } - - @Test - fun `GIVEN tabs are present in state WHEN getTabs is called THEN tabs will be returned`() { - val tabs = state.getTabs(arrayOf(SESSION_ID_MOZILLA, SESSION_ID_BCC), publicSuffixList) - - val hosts = tabs.map { it.hostname } - - assertEquals(URL_MOZILLA, hosts[0]) - assertEquals(URL_BCC, hosts[1]) - } - - @Test - fun `GIVEN some tabs are present in state WHEN getTabs is called THEN only valid tabs will be returned`() { - val tabs = state.getTabs(arrayOf(SESSION_ID_MOZILLA, SESSION_ID_BAD_1), publicSuffixList) - - val hosts = tabs.map { it.hostname } - - assertEquals(URL_MOZILLA, hosts[0]) - assertEquals(1, hosts.size) - } - - @Test - fun `GIVEN tabs are not present in state WHEN getTabs is called THEN an empty list will be returned`() { - val tabs = state.getTabs(arrayOf(SESSION_ID_BAD_1, SESSION_ID_BAD_2), publicSuffixList) - - assertEquals(emptyList(), tabs) - } - - @Test - fun `WHEN getTabs is called will null tabIds THEN an empty list will be returned`() { - val tabs = state.getTabs(null, publicSuffixList) - - assertEquals(emptyList(), tabs) - } - - @Test - fun `toTab uses active reader URL`() { - val tabWithoutReaderState = createTab(url = "https://example.com", id = "1") - - val tabWithInactiveReaderState = createTab(url = "https://blog.mozilla.org", id = "2", - readerState = ReaderState(active = false, activeUrl = null) - ) - - val tabWithActiveReaderState = createTab(url = "moz-extension://123", id = "3", - readerState = ReaderState(active = true, activeUrl = "https://blog.mozilla.org/123") - ) - - val state = BrowserState( - tabs = listOf(tabWithoutReaderState, tabWithInactiveReaderState, tabWithActiveReaderState) - ) - val tabs = state.getTabs( - arrayOf(tabWithoutReaderState.id, tabWithInactiveReaderState.id, tabWithActiveReaderState.id), - publicSuffixList - ) - - assertEquals(tabWithoutReaderState.content.url, tabs[0].url) - assertEquals(tabWithInactiveReaderState.content.url, tabs[1].url) - assertEquals("https://blog.mozilla.org/123", tabs[2].url) - } } diff --git a/app/src/test/java/org/mozilla/fenix/collections/CollectionCreationStoreTest.kt b/app/src/test/java/org/mozilla/fenix/collections/CollectionCreationStoreTest.kt index e6d42072d..3415bc4ca 100644 --- a/app/src/test/java/org/mozilla/fenix/collections/CollectionCreationStoreTest.kt +++ b/app/src/test/java/org/mozilla/fenix/collections/CollectionCreationStoreTest.kt @@ -4,17 +4,54 @@ package org.mozilla.fenix.collections +import io.mockk.MockKAnnotations +import io.mockk.every +import io.mockk.impl.annotations.MockK import io.mockk.mockk +import kotlinx.coroutines.CompletableDeferred +import kotlinx.coroutines.ExperimentalCoroutinesApi +import mozilla.components.browser.state.state.BrowserState +import mozilla.components.browser.state.state.ReaderState +import mozilla.components.browser.state.state.createTab +import mozilla.components.lib.publicsuffixlist.PublicSuffixList import mozilla.components.support.test.ext.joinBlocking import org.junit.Assert.assertEquals +import org.junit.Before import org.junit.Test import org.junit.runner.RunWith +import org.mozilla.fenix.components.TabCollectionStorage import org.mozilla.fenix.helpers.FenixRobolectricTestRunner import org.mozilla.fenix.home.Tab +private const val URL_MOZILLA = "www.mozilla.org" +private const val SESSION_ID_MOZILLA = "0" +private const val URL_BCC = "www.bcc.co.uk" +private const val SESSION_ID_BCC = "1" + +private const val SESSION_ID_BAD_1 = "not a real session id" +private const val SESSION_ID_BAD_2 = "definitely not a real session id" + +@ExperimentalCoroutinesApi @RunWith(FenixRobolectricTestRunner::class) class CollectionCreationStoreTest { + @MockK private lateinit var tabCollectionStorage: TabCollectionStorage + @MockK(relaxed = true) private lateinit var publicSuffixList: PublicSuffixList + + private val sessionMozilla = createTab(URL_MOZILLA, id = SESSION_ID_MOZILLA) + private val sessionBcc = createTab(URL_BCC, id = SESSION_ID_BCC) + private val state = BrowserState( + tabs = listOf(sessionMozilla, sessionBcc) + ) + + @Before + fun before() { + MockKAnnotations.init(this) + every { tabCollectionStorage.cachedTabCollections } returns emptyList() + every { publicSuffixList.stripPublicSuffix(URL_MOZILLA) } returns CompletableDeferred(URL_MOZILLA) + every { publicSuffixList.stripPublicSuffix(URL_BCC) } returns CompletableDeferred(URL_BCC) + } + @Test fun `select and deselect all tabs`() { val tabs = listOf(mockk(), mockk()) @@ -73,4 +110,121 @@ class CollectionCreationStoreTest { assertEquals(SaveCollectionStep.RenameCollection, store.state.saveCollectionStep) assertEquals(3, store.state.defaultCollectionNumber) } + + @Test + fun `GIVEN no selected tab ids WHEN create initial state THEN only tab will be selected`() { + val result = createInitialCollectionCreationState( + browserState = state, + tabCollectionStorage = tabCollectionStorage, + publicSuffixList = publicSuffixList, + saveCollectionStep = SaveCollectionStep.NameCollection, + tabIds = arrayOf(SESSION_ID_MOZILLA), + selectedTabIds = null, + selectedTabCollectionId = 0 + ) + + assertEquals(SaveCollectionStep.NameCollection, result.saveCollectionStep) + assertEquals(1, result.tabs.size) + assertEquals(SESSION_ID_MOZILLA, result.tabs[0].sessionId) + assertEquals(1, result.selectedTabs.size) + assertEquals(SESSION_ID_MOZILLA, result.selectedTabs.first().sessionId) + } + + @Test + fun `GIVEN no selected tab ids WHEN create initial state with many tabs THEN nothing will be selected`() { + val result = createInitialCollectionCreationState( + browserState = state, + tabCollectionStorage = tabCollectionStorage, + publicSuffixList = publicSuffixList, + saveCollectionStep = SaveCollectionStep.NameCollection, + tabIds = arrayOf(SESSION_ID_MOZILLA, SESSION_ID_BCC), + selectedTabIds = null, + selectedTabCollectionId = 0 + ) + + assertEquals(SaveCollectionStep.NameCollection, result.saveCollectionStep) + assertEquals(2, result.tabs.size) + assertEquals(SESSION_ID_MOZILLA, result.tabs[0].sessionId) + assertEquals(SESSION_ID_BCC, result.tabs[1].sessionId) + assertEquals(0, result.selectedTabs.size) + } + + @Test + fun `GIVEN selected tab ids WHEN create initial state THEN select tabs`() { + val result = createInitialCollectionCreationState( + browserState = state, + tabCollectionStorage = tabCollectionStorage, + publicSuffixList = publicSuffixList, + saveCollectionStep = SaveCollectionStep.RenameCollection, + tabIds = arrayOf(SESSION_ID_MOZILLA, SESSION_ID_BCC), + selectedTabIds = arrayOf(SESSION_ID_BCC), + selectedTabCollectionId = 0 + ) + + assertEquals(SaveCollectionStep.RenameCollection, result.saveCollectionStep) + assertEquals(2, result.tabs.size) + assertEquals(SESSION_ID_MOZILLA, result.tabs[0].sessionId) + assertEquals(SESSION_ID_BCC, result.tabs[1].sessionId) + assertEquals(1, result.selectedTabs.size) + assertEquals(SESSION_ID_BCC, result.selectedTabs.first().sessionId) + } + + @Test + fun `GIVEN tabs are present in state WHEN getTabs is called THEN tabs will be returned`() { + val tabs = state.getTabs(arrayOf(SESSION_ID_MOZILLA, SESSION_ID_BCC), publicSuffixList) + + val hosts = tabs.map { it.hostname } + + assertEquals(URL_MOZILLA, hosts[0]) + assertEquals(URL_BCC, hosts[1]) + } + + @Test + fun `GIVEN some tabs are present in state WHEN getTabs is called THEN only valid tabs will be returned`() { + val tabs = state.getTabs(arrayOf(SESSION_ID_MOZILLA, SESSION_ID_BAD_1), publicSuffixList) + + val hosts = tabs.map { it.hostname } + + assertEquals(URL_MOZILLA, hosts[0]) + assertEquals(1, hosts.size) + } + + @Test + fun `GIVEN tabs are not present in state WHEN getTabs is called THEN an empty list will be returned`() { + val tabs = state.getTabs(arrayOf(SESSION_ID_BAD_1, SESSION_ID_BAD_2), publicSuffixList) + + assertEquals(emptyList(), tabs) + } + + @Test + fun `WHEN getTabs is called will null tabIds THEN an empty list will be returned`() { + val tabs = state.getTabs(null, publicSuffixList) + + assertEquals(emptyList(), tabs) + } + + @Test + fun `toTab uses active reader URL`() { + val tabWithoutReaderState = createTab(url = "https://example.com", id = "1") + + val tabWithInactiveReaderState = createTab(url = "https://blog.mozilla.org", id = "2", + readerState = ReaderState(active = false, activeUrl = null) + ) + + val tabWithActiveReaderState = createTab(url = "moz-extension://123", id = "3", + readerState = ReaderState(active = true, activeUrl = "https://blog.mozilla.org/123") + ) + + val state = BrowserState( + tabs = listOf(tabWithoutReaderState, tabWithInactiveReaderState, tabWithActiveReaderState) + ) + val tabs = state.getTabs( + arrayOf(tabWithoutReaderState.id, tabWithInactiveReaderState.id, tabWithActiveReaderState.id), + publicSuffixList + ) + + assertEquals(tabWithoutReaderState.content.url, tabs[0].url) + assertEquals(tabWithInactiveReaderState.content.url, tabs[1].url) + assertEquals("https://blog.mozilla.org/123", tabs[2].url) + } } diff --git a/app/src/test/java/org/mozilla/fenix/components/AccountAbnormalitiesTest.kt b/app/src/test/java/org/mozilla/fenix/components/AccountAbnormalitiesTest.kt index 22a8df8ed..c69007c10 100644 --- a/app/src/test/java/org/mozilla/fenix/components/AccountAbnormalitiesTest.kt +++ b/app/src/test/java/org/mozilla/fenix/components/AccountAbnormalitiesTest.kt @@ -25,7 +25,7 @@ class AccountAbnormalitiesTest { val crashReporter: CrashReporter = mockk() // no account present - val accountAbnormalities = AccountAbnormalities(testContext, crashReporter) + val accountAbnormalities = AccountAbnormalities(testContext, crashReporter, mockk(relaxed = true)) try { accountAbnormalities.userRequestedLogout() @@ -52,7 +52,7 @@ class AccountAbnormalitiesTest { val crashReporter: CrashReporter = mockk(relaxed = true) val accountManager: FxaAccountManager = mockk(relaxed = true) - val accountAbnormalities = AccountAbnormalities(testContext, crashReporter, this.coroutineContext) + val accountAbnormalities = AccountAbnormalities(testContext, crashReporter, mockk(relaxed = true), this.coroutineContext) accountAbnormalities.accountManagerStarted(accountManager) // Logout action must be preceded by auth. @@ -65,7 +65,7 @@ class AccountAbnormalitiesTest { val crashReporter: CrashReporter = mockk(relaxed = true) val accountManager: FxaAccountManager = mockk(relaxed = true) - val accountAbnormalities = AccountAbnormalities(testContext, crashReporter, this.coroutineContext) + val accountAbnormalities = AccountAbnormalities(testContext, crashReporter, mockk(relaxed = true), this.coroutineContext) accountAbnormalities.accountManagerStarted(accountManager) accountAbnormalities.onAuthenticated(mockk(), mockk()) @@ -83,7 +83,7 @@ class AccountAbnormalitiesTest { val crashReporter: CrashReporter = mockk(relaxed = true) val accountManager: FxaAccountManager = mockk(relaxed = true) - val accountAbnormalities = AccountAbnormalities(testContext, crashReporter, this.coroutineContext) + val accountAbnormalities = AccountAbnormalities(testContext, crashReporter, mockk(relaxed = true), this.coroutineContext) accountAbnormalities.accountManagerStarted(accountManager) // User didn't request this logout. @@ -96,7 +96,7 @@ class AccountAbnormalitiesTest { val crashReporter: CrashReporter = mockk(relaxed = true) val accountManager: FxaAccountManager = mockk(relaxed = true) - val accountAbnormalities = AccountAbnormalities(testContext, crashReporter, this.coroutineContext) + val accountAbnormalities = AccountAbnormalities(testContext, crashReporter, mockk(relaxed = true), this.coroutineContext) accountAbnormalities.accountManagerStarted(accountManager) accountAbnormalities.onAuthenticated(mockk(), mockk()) @@ -104,7 +104,7 @@ class AccountAbnormalitiesTest { every { accountManager.authenticatedAccount() } returns null // Pretend we restart, and instantiate a new middleware instance. - val accountAbnormalities2 = AccountAbnormalities(testContext, crashReporter, this.coroutineContext) + val accountAbnormalities2 = AccountAbnormalities(testContext, crashReporter, mockk(relaxed = true), this.coroutineContext) // mock accountManager doesn't have an account, but we expect it to have one since we // were authenticated before our "restart". accountAbnormalities2.accountManagerStarted(accountManager) @@ -117,7 +117,7 @@ class AccountAbnormalitiesTest { val crashReporter: CrashReporter = mockk() val accountManager: FxaAccountManager = mockk(relaxed = true) - val accountAbnormalities = AccountAbnormalities(testContext, crashReporter, this.coroutineContext) + val accountAbnormalities = AccountAbnormalities(testContext, crashReporter, mockk(relaxed = true), this.coroutineContext) accountAbnormalities.accountManagerStarted(accountManager) // We saw an auth event, then user requested a logout. diff --git a/app/src/test/java/org/mozilla/fenix/components/TestComponents.kt b/app/src/test/java/org/mozilla/fenix/components/TestComponents.kt index 1d31eaf56..a2d1f92c5 100644 --- a/app/src/test/java/org/mozilla/fenix/components/TestComponents.kt +++ b/app/src/test/java/org/mozilla/fenix/components/TestComponents.kt @@ -28,7 +28,7 @@ class TestComponents(private val context: Context) : Components(context) { ) } override val intentProcessors by lazy { mockk(relaxed = true) } - override val analytics by lazy { Analytics(context) } + override val analytics by lazy { Analytics(context, strictMode) } override val clipboardHandler by lazy { ClipboardHandler(context) } diff --git a/app/src/test/java/org/mozilla/fenix/components/TestCore.kt b/app/src/test/java/org/mozilla/fenix/components/TestCore.kt index fad4bd4b9..70dccaebd 100644 --- a/app/src/test/java/org/mozilla/fenix/components/TestCore.kt +++ b/app/src/test/java/org/mozilla/fenix/components/TestCore.kt @@ -17,7 +17,11 @@ import mozilla.components.concept.fetch.Client import mozilla.components.feature.pwa.WebAppShortcutManager import mozilla.components.feature.top.sites.DefaultTopSitesStorage -class TestCore(context: Context, crashReporter: CrashReporting) : Core(context, crashReporter) { +class TestCore(context: Context, crashReporter: CrashReporting) : Core( + context, + crashReporter, + mockk() +) { override val engine = mockk(relaxed = true) { every { this@mockk getProperty "settings" } returns mockk(relaxed = true) diff --git a/app/src/test/java/org/mozilla/fenix/components/metrics/LeanplumMetricsServiceTest.kt b/app/src/test/java/org/mozilla/fenix/components/metrics/LeanplumMetricsServiceTest.kt index 6027a100a..ea0e90302 100644 --- a/app/src/test/java/org/mozilla/fenix/components/metrics/LeanplumMetricsServiceTest.kt +++ b/app/src/test/java/org/mozilla/fenix/components/metrics/LeanplumMetricsServiceTest.kt @@ -5,6 +5,7 @@ package org.mozilla.fenix.components.metrics import android.content.Context.MODE_PRIVATE +import io.mockk.mockk import mozilla.components.support.test.robolectric.testContext import org.junit.Assert.assertEquals import org.junit.Assert.assertNull @@ -30,10 +31,18 @@ class LeanplumMetricsServiceTest { assertNull(sharedPreferences.getString("LP_DEVICE_ID", null)) - val leanplumMetricService = LeanplumMetricsService(testContext.application, idGenerator) + val leanplumMetricService = LeanplumMetricsService( + testContext.application, + mockk(relaxed = true), + idGenerator + ) assertEquals("TEST_DEVICE_ID", leanplumMetricService.deviceId) - val leanplumMetricService2 = LeanplumMetricsService(testContext.application, idGenerator) + val leanplumMetricService2 = LeanplumMetricsService( + testContext.application, + mockk(relaxed = true), + idGenerator + ) assertEquals("TEST_DEVICE_ID", leanplumMetricService2.deviceId) assertEquals(1, callCount) diff --git a/app/src/test/java/org/mozilla/fenix/ext/StrictModeTest.kt b/app/src/test/java/org/mozilla/fenix/ext/StrictModeTest.kt deleted file mode 100644 index 26854c46d..000000000 --- a/app/src/test/java/org/mozilla/fenix/ext/StrictModeTest.kt +++ /dev/null @@ -1,79 +0,0 @@ -/* 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.os.StrictMode -import io.mockk.Runs -import io.mockk.every -import io.mockk.just -import io.mockk.mockk -import io.mockk.mockkObject -import io.mockk.mockkStatic -import io.mockk.unmockkObject -import io.mockk.unmockkStatic -import io.mockk.verify -import org.junit.After -import org.junit.Assert.assertEquals -import org.junit.Assert.assertNotNull -import org.junit.Before -import org.junit.Test -import org.junit.runner.RunWith -import org.mozilla.fenix.Config -import org.mozilla.fenix.ReleaseChannel -import org.mozilla.fenix.helpers.FenixRobolectricTestRunner - -@RunWith(FenixRobolectricTestRunner::class) -class StrictModeTest { - - private lateinit var threadPolicy: StrictMode.ThreadPolicy - private lateinit var functionBlock: () -> String - - @Before - fun setup() { - threadPolicy = StrictMode.ThreadPolicy.LAX - functionBlock = mockk() - mockkStatic(StrictMode::class) - mockkObject(Config) - - every { StrictMode.setThreadPolicy(threadPolicy) } just Runs - every { functionBlock() } returns "Hello world" - } - - @After - fun teardown() { - unmockkStatic(StrictMode::class) - unmockkObject(Config) - } - - @Test - fun `runs function block in release`() { - every { Config.channel } returns ReleaseChannel.Release - assertEquals("Hello world", threadPolicy.resetPoliciesAfter(functionBlock)) - verify(exactly = 0) { StrictMode.setThreadPolicy(any()) } - } - - @Test - fun `runs function block in debug`() { - every { Config.channel } returns ReleaseChannel.Debug - assertEquals("Hello world", threadPolicy.resetPoliciesAfter(functionBlock)) - verify { StrictMode.setThreadPolicy(threadPolicy) } - } - - @Test - fun `sets thread policy even if function throws`() { - every { Config.channel } returns ReleaseChannel.Debug - every { functionBlock() } throws IllegalStateException() - var exception: IllegalStateException? = null - - try { - threadPolicy.resetPoliciesAfter(functionBlock) - } catch (e: IllegalStateException) { - exception = e - } - - verify { StrictMode.setThreadPolicy(threadPolicy) } - assertNotNull(exception) - } -} diff --git a/app/src/test/java/org/mozilla/fenix/settings/SettingsFragmentTest.kt b/app/src/test/java/org/mozilla/fenix/settings/SettingsFragmentTest.kt new file mode 100644 index 000000000..8a6d2ad64 --- /dev/null +++ b/app/src/test/java/org/mozilla/fenix/settings/SettingsFragmentTest.kt @@ -0,0 +1,76 @@ +/* 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.settings + +import androidx.fragment.app.FragmentActivity +import androidx.preference.Preference +import io.mockk.every +import io.mockk.mockk +import org.junit.Assert.assertFalse +import org.junit.Assert.assertNotNull +import org.junit.Assert.assertTrue +import org.junit.Test +import org.junit.runner.RunWith +import org.mozilla.fenix.R +import org.mozilla.fenix.ext.getPreferenceKey +import org.mozilla.fenix.helpers.FenixRobolectricTestRunner +import org.mozilla.fenix.utils.Settings +import org.robolectric.Robolectric + +@RunWith(FenixRobolectricTestRunner::class) +class SettingsFragmentTest { + + @Test + fun `Add-on collection override pref is visible if debug menu active`() { + val settingsFragment = SettingsFragment() + val activity = Robolectric.buildActivity(FragmentActivity::class.java).create().get() + + activity.supportFragmentManager.beginTransaction() + .add(settingsFragment, "test") + .commitNow() + + val preferenceAmoCollectionOverride = settingsFragment.findPreference( + settingsFragment.getPreferenceKey(R.string.pref_key_override_amo_collection) + ) + + settingsFragment.setupAmoCollectionOverridePreference(mockk(relaxed = true)) + assertNotNull(preferenceAmoCollectionOverride) + assertFalse(preferenceAmoCollectionOverride!!.isVisible) + + val settings: Settings = mockk(relaxed = true) + every { settings.showSecretDebugMenuThisSession } returns true + settingsFragment.setupAmoCollectionOverridePreference(settings) + assertTrue(preferenceAmoCollectionOverride.isVisible) + } + + @Test + fun `Add-on collection override pref is visible if already configured`() { + val settingsFragment = SettingsFragment() + val activity = Robolectric.buildActivity(FragmentActivity::class.java).create().get() + + activity.supportFragmentManager.beginTransaction() + .add(settingsFragment, "test") + .commitNow() + + val preferenceAmoCollectionOverride = settingsFragment.findPreference( + settingsFragment.getPreferenceKey(R.string.pref_key_override_amo_collection) + ) + + settingsFragment.setupAmoCollectionOverridePreference(mockk(relaxed = true)) + assertNotNull(preferenceAmoCollectionOverride) + assertFalse(preferenceAmoCollectionOverride!!.isVisible) + + val settings: Settings = mockk(relaxed = true) + every { settings.showSecretDebugMenuThisSession } returns false + + every { settings.amoCollectionOverrideConfigured() } returns false + settingsFragment.setupAmoCollectionOverridePreference(settings) + assertFalse(preferenceAmoCollectionOverride.isVisible) + + every { settings.amoCollectionOverrideConfigured() } returns true + settingsFragment.setupAmoCollectionOverridePreference(settings) + assertTrue(preferenceAmoCollectionOverride.isVisible) + } +} diff --git a/app/src/test/java/org/mozilla/fenix/settings/logins/LoginsFragmentStoreTest.kt b/app/src/test/java/org/mozilla/fenix/settings/logins/LoginsFragmentStoreTest.kt index dbf41c2bd..3fe11eeb2 100644 --- a/app/src/test/java/org/mozilla/fenix/settings/logins/LoginsFragmentStoreTest.kt +++ b/app/src/test/java/org/mozilla/fenix/settings/logins/LoginsFragmentStoreTest.kt @@ -4,6 +4,7 @@ package org.mozilla.fenix.settings.logins +import io.mockk.every import io.mockk.mockk import mozilla.components.concept.storage.Login import mozilla.components.support.test.ext.joinBlocking @@ -12,6 +13,7 @@ import org.junit.Assert.assertFalse import org.junit.Assert.assertNull import org.junit.Assert.assertTrue import org.junit.Test +import org.mozilla.fenix.utils.Settings class LoginsFragmentStoreTest { @@ -34,6 +36,26 @@ class LoginsFragmentStoreTest { duplicateLogins = listOf() ) + @Test + fun `create initial state`() { + val settings = mockk() + every { settings.savedLoginsSortingStrategy } returns SortingStrategy.LastUsed + every { settings.savedLoginsMenuHighlightedItem } returns SavedLoginsSortingStrategyMenu.Item.LastUsedSort + + assertEquals( + LoginsListState( + isLoading = true, + loginList = emptyList(), + filteredItems = emptyList(), + searchedForText = null, + sortingStrategy = SortingStrategy.LastUsed, + highlightedItem = SavedLoginsSortingStrategyMenu.Item.LastUsedSort, + duplicateLogins = emptyList() + ), + createInitialLoginsListState(settings) + ) + } + @Test fun `convert login to saved login`() { val login = Login( diff --git a/app/src/test/java/org/mozilla/fenix/share/ShareControllerTest.kt b/app/src/test/java/org/mozilla/fenix/share/ShareControllerTest.kt index b538412c8..2c27fb755 100644 --- a/app/src/test/java/org/mozilla/fenix/share/ShareControllerTest.kt +++ b/app/src/test/java/org/mozilla/fenix/share/ShareControllerTest.kt @@ -5,6 +5,7 @@ package org.mozilla.fenix.share import android.app.Activity +import android.content.ActivityNotFoundException import android.content.Context import android.content.Intent import androidx.navigation.NavController @@ -143,6 +144,31 @@ class ShareControllerTest { } } + @Test + fun `handleShareToApp should dismiss with an error start when a ActivityNotFoundException occurs`() { + val appPackageName = "package" + val appClassName = "activity" + val appShareOption = AppShareOption("app", mockk(), appPackageName, appClassName) + val shareIntent = slot() + // Our share Intent uses `FLAG_ACTIVITY_NEW_TASK` but when resolving the startActivity call + // needed for capturing the actual Intent used the `slot` one doesn't have this flag so we + // need to use an Activity Context. + val activityContext: Context = mockk() + val testController = DefaultShareController(activityContext, shareSubject, shareData, mockk(), + snackbar, mockk(), mockk(), testCoroutineScope, dismiss) + every { activityContext.startActivity(capture(shareIntent)) } throws ActivityNotFoundException() + every { activityContext.getString(R.string.share_error_snackbar) } returns "Cannot share to this app" + + testController.handleShareToApp(appShareOption) + + verifyOrder { + activityContext.startActivity(shareIntent.captured) + snackbar.setText("Cannot share to this app") + snackbar.show() + dismiss(ShareController.Result.SHARE_ERROR) + } + } + @Test @Suppress("DeferredResultUnused") fun `handleShareToDevice should share to account device, inform callbacks and dismiss`() { diff --git a/app/src/test/java/org/mozilla/fenix/utils/SettingsTest.kt b/app/src/test/java/org/mozilla/fenix/utils/SettingsTest.kt index 9b5861c99..d05850b70 100644 --- a/app/src/test/java/org/mozilla/fenix/utils/SettingsTest.kt +++ b/app/src/test/java/org/mozilla/fenix/utils/SettingsTest.kt @@ -576,4 +576,34 @@ class SettingsTest { settings.getSitePermissionsCustomSettingsRules() ) } + + @Test + fun overrideAmoCollection() { + // When just created + // Then + assertEquals("", settings.overrideAmoCollection) + assertFalse(settings.amoCollectionOverrideConfigured()) + + // When + settings.overrideAmoCollection = "testCollection" + + // Then + assertEquals("testCollection", settings.overrideAmoCollection) + assertTrue(settings.amoCollectionOverrideConfigured()) + } + + @Test + fun overrideAmoUser() { + // When just created + // Then + assertEquals("", settings.overrideAmoUser) + assertFalse(settings.amoCollectionOverrideConfigured()) + + // When + settings.overrideAmoUser = "testAmoUser" + + // Then + assertEquals("testAmoUser", settings.overrideAmoUser) + assertTrue(settings.amoCollectionOverrideConfigured()) + } }