diff --git a/README.md b/README.md index f3b395c43..c017a7a2c 100644 --- a/README.md +++ b/README.md @@ -115,6 +115,8 @@ To make it easier to triage, we have these issue requirements: Please keep in mind that even though a feature you have in mind may seem like a small ask, as a small team, we have to prioritize our planned work and every new feature adds complexity and maintenance and may take up design, research, product, and engineering time. We appreciate everyone’s passion but we will not be able to incorporate every feature request or even fix every bug. That being said, just because we haven't replied, doesn't mean we don't care about the issue, please be patient with our response times as we're very busy. +## License + 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 diff --git a/app/build.gradle b/app/build.gradle index 3efd92820..56083f372 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -14,7 +14,6 @@ import com.android.build.OutputFile import groovy.json.JsonOutput import org.gradle.internal.logging.text.StyledTextOutput.Style import org.gradle.internal.logging.text.StyledTextOutputFactory -import org.mozilla.fenix.gradle.tasks.LintUnitTestRunner import static org.gradle.api.tasks.testing.TestResult.ResultType @@ -617,8 +616,6 @@ task buildTranslationArray { android.defaultConfig.buildConfigField "String[]", "SUPPORTED_LOCALE_ARRAY", foundLocalesString } -tasks.register('lintUnitTestRunner', LintUnitTestRunner) - afterEvaluate { // Format test output. Ported from AC #2401 diff --git a/app/src/androidTest/java/org/mozilla/fenix/ui/robots/HistoryRobot.kt b/app/src/androidTest/java/org/mozilla/fenix/ui/robots/HistoryRobot.kt index 85cfd7fe9..72d62ec06 100644 --- a/app/src/androidTest/java/org/mozilla/fenix/ui/robots/HistoryRobot.kt +++ b/app/src/androidTest/java/org/mozilla/fenix/ui/robots/HistoryRobot.kt @@ -42,7 +42,7 @@ class HistoryRobot { fun verifyVisitedTimeTitle() { mDevice.waitNotNull( Until.findObject( - By.text("Last 24 hours") + By.text("Today") ), waitingTime ) @@ -99,7 +99,7 @@ class HistoryRobot { } fun openThreeDotMenu(interact: ThreeDotMenuHistoryItemRobot.() -> Unit): - ThreeDotMenuHistoryItemRobot.Transition { + ThreeDotMenuHistoryItemRobot.Transition { threeDotMenu().click() @@ -143,7 +143,7 @@ private fun assertEmptyHistoryView() = .check(matches(withText("No history here"))) private fun assertVisitedTimeTitle() = - onView(withId(R.id.header_title)).check(matches(withText("Last 24 hours"))) + onView(withId(R.id.header_title)).check(matches(withText("Today"))) private fun assertTestPageTitle(title: String) = testPageTitle() .check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE))) diff --git a/app/src/main/java/com/leanplum/Leanplum.java b/app/src/main/java/com/leanplum/Leanplum.java index ed808e911..70a6151fe 100644 --- a/app/src/main/java/com/leanplum/Leanplum.java +++ b/app/src/main/java/com/leanplum/Leanplum.java @@ -88,4 +88,8 @@ public class Leanplum { public static void track(String event, double value, String info) { } + + public static String getDeviceId() { return "stub"; } + + public static String getUserId() { return "stub"; } } diff --git a/app/src/main/java/org/mozilla/fenix/HomeActivity.kt b/app/src/main/java/org/mozilla/fenix/HomeActivity.kt index 333fb7ba5..e73f6a8e5 100644 --- a/app/src/main/java/org/mozilla/fenix/HomeActivity.kt +++ b/app/src/main/java/org/mozilla/fenix/HomeActivity.kt @@ -32,7 +32,6 @@ import kotlinx.android.synthetic.main.activity_home.* import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers.IO -import kotlinx.coroutines.Dispatchers.Main import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.Job import kotlinx.coroutines.delay @@ -281,16 +280,6 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity { } settings().wasDefaultBrowserOnLastResume = settings().isDefaultBrowser() - - if (!settings().manuallyCloseTabs) { - val toClose = components.core.store.state.tabs.filter { - (System.currentTimeMillis() - it.lastAccess) > settings().getTabTimeout() - } - // Removal needs to happen on the main thread. - lifecycleScope.launch(Main) { - toClose.forEach { components.useCases.tabsUseCases.removeTab(it.id) } - } - } } } 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 9f9bd201b..120a27ac1 100644 --- a/app/src/main/java/org/mozilla/fenix/components/Analytics.kt +++ b/app/src/main/java/org/mozilla/fenix/components/Analytics.kt @@ -18,7 +18,6 @@ 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 @@ -35,8 +34,7 @@ import org.mozilla.geckoview.BuildConfig.MOZ_UPDATE_CHANNEL */ @Mockable class Analytics( - private val context: Context, - strictMode: StrictModeManager + private val context: Context ) { val crashReporter: CrashReporter by lazy { val services = mutableListOf() @@ -86,10 +84,7 @@ class Analytics( ) } - val leanplumMetricsService by lazy { LeanplumMetricsService( - context as Application, - strictMode - ) } + val leanplumMetricsService by lazy { LeanplumMetricsService(context as Application) } val metrics: MetricController by lazy { MetricController.create( 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 49af228f8..6c743f86d 100644 --- a/app/src/main/java/org/mozilla/fenix/components/Components.kt +++ b/app/src/main/java/org/mozilla/fenix/components/Components.kt @@ -30,7 +30,11 @@ import java.util.concurrent.TimeUnit private const val DAY_IN_MINUTES = 24 * 60L /** - * Provides access to all components. + * Provides access to all components. This class is an implementation of the Service Locator + * pattern, which helps us manage the dependencies in our app. + * + * Note: these aren't just "components" from "android-components": they're any "component" that + * can be considered a building block of our app. */ @Mockable class Components(private val context: Context) { @@ -116,7 +120,7 @@ class Components(private val context: Context) { addonCollectionProvider.setCollectionName(addonsCollection) } - val analytics by lazy { Analytics(context, strictMode) } + val analytics by lazy { Analytics(context) } val publicSuffixList by lazy { PublicSuffixList(context) } val clipboardHandler by lazy { ClipboardHandler(context) } val migrationStore by lazy { MigrationStore() } 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 69033a78d..4d76715be 100644 --- a/app/src/main/java/org/mozilla/fenix/components/Core.kt +++ b/app/src/main/java/org/mozilla/fenix/components/Core.kt @@ -77,6 +77,7 @@ import java.util.concurrent.TimeUnit * Component group for all core browser functionality. */ @Mockable +@Suppress("LargeClass") class Core( private val context: Context, private val crashReporter: CrashReporting, @@ -218,6 +219,18 @@ class Core( .periodicallyInForeground(interval = 30, unit = TimeUnit.SECONDS) .whenGoingToBackground() .whenSessionsChange() + + // Now that we have restored our previous state (if there's one) let's remove timed out tabs + if (!context.settings().manuallyCloseTabs) { + store.state.tabs.filter { + (System.currentTimeMillis() - it.lastAccess) > context.settings().getTabTimeout() + }.forEach { + val session = sessionManager.findSessionById(it.id) + if (session != null) { + sessionManager.remove(session) + } + } + } } WebNotificationFeature( @@ -272,11 +285,13 @@ class Core( val bookmarksStorage by lazy { lazyBookmarksStorage.value } val passwordsStorage by lazy { lazyPasswordsStorage.value } - val tabCollectionStorage by lazy { TabCollectionStorage( - context, - sessionManager, - strictMode - ) } + val tabCollectionStorage by lazy { + TabCollectionStorage( + context, + sessionManager, + strictMode + ) + } /** * A storage component for persisting thumbnail images of tabs. 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 3012e1aa1..79526cd92 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 @@ -5,11 +5,8 @@ package org.mozilla.fenix.components.metrics import android.app.Application -import android.content.Context.MODE_PRIVATE import android.net.Uri -import android.os.StrictMode import android.util.Log -import androidx.annotation.VisibleForTesting import com.leanplum.Leanplum import com.leanplum.LeanplumActivityHelper import com.leanplum.annotations.Parser @@ -22,7 +19,6 @@ 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.settings import org.mozilla.fenix.home.intent.DeepLinkIntentProcessor @@ -57,9 +53,7 @@ private val Event.name: String? } class LeanplumMetricsService( - private val application: Application, - strictMode: StrictModeManager, - private val deviceIdGenerator: () -> String = { randomUUID().toString() } + private val application: Application ) : MetricsService, DeepLinkIntentProcessor.DeepLinkVerifier { val scope = CoroutineScope(Dispatchers.IO) var leanplumJob: Job? = null @@ -84,22 +78,6 @@ class LeanplumMetricsService( override val type = MetricServiceType.Marketing private val token = Token(LeanplumId, LeanplumToken) - private val preferences = strictMode.resetAfter(StrictMode.allowThreadDiskReads()) { - application.getSharedPreferences(PREFERENCE_NAME, MODE_PRIVATE) - } - - @VisibleForTesting - internal val deviceId by lazy { - var deviceId = preferences.getString(DEVICE_ID_KEY, null) - - if (deviceId == null) { - deviceId = deviceIdGenerator.invoke() - preferences.edit().putString(DEVICE_ID_KEY, deviceId).apply() - } - - deviceId - } - @Suppress("ComplexMethod") override fun start() { @@ -107,7 +85,7 @@ class LeanplumMetricsService( Leanplum.setIsTestModeEnabled(false) Leanplum.setApplicationContext(application) - Leanplum.setDeviceId(deviceId) + Leanplum.setDeviceId(randomUUID().toString()) Parser.parseVariables(application) leanplumJob = scope.launch { @@ -171,6 +149,8 @@ class LeanplumMetricsService( LeanplumInternal.setCalledStart(true) LeanplumInternal.setHasStarted(true) LeanplumInternal.setStartedInBackground(true) + Log.i(LOGTAG, "Started Leanplum with deviceId ${Leanplum.getDeviceId()}" + + " and userId ${Leanplum.getUserId()}") } } } @@ -185,7 +165,7 @@ class LeanplumMetricsService( // We compare the local Leanplum device ID against the "uid" query parameter and only // accept deep links where both values match. val uid = deepLink.getQueryParameter("uid") - return uid == deviceId + return uid == Leanplum.getDeviceId() } override fun stop() { diff --git a/app/src/main/java/org/mozilla/fenix/downloads/DynamicDownloadDialog.kt b/app/src/main/java/org/mozilla/fenix/downloads/DynamicDownloadDialog.kt index 826a477e6..be571cc84 100644 --- a/app/src/main/java/org/mozilla/fenix/downloads/DynamicDownloadDialog.kt +++ b/app/src/main/java/org/mozilla/fenix/downloads/DynamicDownloadDialog.kt @@ -11,7 +11,7 @@ import kotlinx.android.extensions.LayoutContainer import kotlinx.android.synthetic.main.download_dialog_layout.view.* import mozilla.components.browser.state.state.content.DownloadState import mozilla.components.feature.downloads.AbstractFetchDownloadService -import mozilla.components.feature.downloads.toMegabyteString +import mozilla.components.feature.downloads.toMegabyteOrKilobyteString import org.mozilla.fenix.R import org.mozilla.fenix.components.metrics.Event import org.mozilla.fenix.ext.metrics @@ -86,7 +86,7 @@ class DynamicDownloadDialog( } else { val titleText = container.context.getString( R.string.mozac_feature_downloads_completed_notification_text2 - ) + " (${downloadState.contentLength?.toMegabyteString()})" + ) + " (${downloadState.contentLength?.toMegabyteOrKilobyteString()})" view.download_dialog_title.text = titleText 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 72c55c61a..5b50105ac 100644 --- a/app/src/main/java/org/mozilla/fenix/home/HomeFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/home/HomeFragment.kt @@ -622,7 +622,8 @@ class HomeFragment : Fragment() { dialog.cancel() } setPositiveButton(R.string.tab_collection_dialog_positive) { dialog: DialogInterface, _ -> - viewLifecycleOwner.lifecycleScope.launch(IO) { + // Use fragment's lifecycle; the view may be gone by the time dialog is interacted with. + lifecycleScope.launch(IO) { context.components.core.tabCollectionStorage.removeCollection(tabCollection) context.components.analytics.metrics.track(Event.CollectionRemoved) }.invokeOnCompletion { diff --git a/app/src/main/java/org/mozilla/fenix/library/bookmarks/BookmarkFragment.kt b/app/src/main/java/org/mozilla/fenix/library/bookmarks/BookmarkFragment.kt index 18c19943e..974551b44 100644 --- a/app/src/main/java/org/mozilla/fenix/library/bookmarks/BookmarkFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/library/bookmarks/BookmarkFragment.kt @@ -6,6 +6,7 @@ package org.mozilla.fenix.library.bookmarks import android.content.DialogInterface import android.os.Bundle +import android.text.SpannableString import android.view.LayoutInflater import android.view.Menu import android.view.MenuInflater @@ -49,6 +50,7 @@ import org.mozilla.fenix.ext.components import org.mozilla.fenix.ext.minus import org.mozilla.fenix.ext.nav import org.mozilla.fenix.ext.requireComponents +import org.mozilla.fenix.ext.setTextColor import org.mozilla.fenix.ext.toShortUrl import org.mozilla.fenix.library.LibraryPageFragment import org.mozilla.fenix.utils.allowUndo @@ -185,6 +187,11 @@ class BookmarkFragment : LibraryPageFragment(), UserInteractionHan inflater.inflate(R.menu.bookmarks_select_multi_not_item, menu) } else { inflater.inflate(R.menu.bookmarks_select_multi, menu) + + menu.findItem(R.id.delete_bookmarks_multi_select).title = + SpannableString(getString(R.string.bookmark_menu_delete_button)).apply { + setTextColor(requireContext(), R.attr.destructive) + } } } } @@ -365,7 +372,8 @@ class BookmarkFragment : LibraryPageFragment(), UserInteractionHan pendingBookmarkDeletionJob = getDeleteOperation(Event.RemoveBookmarkFolder) dialog.dismiss() val snackbarMessage = getRemoveBookmarksSnackBarMessage(selected, containsFolders = true) - viewLifecycleOwner.lifecycleScope.allowUndo( + // Use fragment's lifecycle; the view may be gone by the time dialog is interacted with. + lifecycleScope.allowUndo( requireView(), snackbarMessage, getString(R.string.bookmark_undo_deletion), diff --git a/app/src/main/java/org/mozilla/fenix/library/bookmarks/edit/EditBookmarkFragment.kt b/app/src/main/java/org/mozilla/fenix/library/bookmarks/edit/EditBookmarkFragment.kt index c7de31c64..db2e3f71b 100644 --- a/app/src/main/java/org/mozilla/fenix/library/bookmarks/edit/EditBookmarkFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/library/bookmarks/edit/EditBookmarkFragment.kt @@ -182,7 +182,8 @@ class EditBookmarkFragment : Fragment(R.layout.fragment_edit_bookmark) { dialog.cancel() } setPositiveButton(R.string.tab_collection_dialog_positive) { dialog: DialogInterface, _ -> - viewLifecycleOwner.lifecycleScope.launch(IO) { + // Use fragment's lifecycle; the view may be gone by the time dialog is interacted with. + lifecycleScope.launch(IO) { requireComponents.core.bookmarksStorage.deleteNode(args.guidToEdit) requireComponents.analytics.metrics.track(Event.RemoveBookmark) diff --git a/app/src/main/java/org/mozilla/fenix/library/downloads/viewholders/DownloadsListItemViewHolder.kt b/app/src/main/java/org/mozilla/fenix/library/downloads/viewholders/DownloadsListItemViewHolder.kt index c367ca674..c5f6bbbbd 100644 --- a/app/src/main/java/org/mozilla/fenix/library/downloads/viewholders/DownloadsListItemViewHolder.kt +++ b/app/src/main/java/org/mozilla/fenix/library/downloads/viewholders/DownloadsListItemViewHolder.kt @@ -8,12 +8,12 @@ import android.view.View import androidx.recyclerview.widget.RecyclerView import kotlinx.android.synthetic.main.download_list_item.view.* import kotlinx.android.synthetic.main.library_site_item.view.* +import mozilla.components.feature.downloads.toMegabyteOrKilobyteString import org.mozilla.fenix.R import org.mozilla.fenix.ext.hideAndDisable import org.mozilla.fenix.library.SelectionHolder import org.mozilla.fenix.library.downloads.DownloadInteractor import org.mozilla.fenix.library.downloads.DownloadItem -import mozilla.components.feature.downloads.toMegabyteString import org.mozilla.fenix.ext.getIcon class DownloadsListItemViewHolder( @@ -29,7 +29,7 @@ class DownloadsListItemViewHolder( ) { itemView.download_layout.visibility = View.VISIBLE itemView.download_layout.titleView.text = item.fileName - itemView.download_layout.urlView.text = item.size.toLong().toMegabyteString() + itemView.download_layout.urlView.text = item.size.toLong().toMegabyteOrKilobyteString() itemView.download_layout.setSelectionInteractor(item, selectionHolder, downloadInteractor) itemView.download_layout.changeSelected(item in selectionHolder.selectedItems) diff --git a/app/src/main/java/org/mozilla/fenix/library/history/HistoryAdapter.kt b/app/src/main/java/org/mozilla/fenix/library/history/HistoryAdapter.kt index 7a0b33b1a..e82bac914 100644 --- a/app/src/main/java/org/mozilla/fenix/library/history/HistoryAdapter.kt +++ b/app/src/main/java/org/mozilla/fenix/library/history/HistoryAdapter.kt @@ -17,10 +17,11 @@ import java.util.Calendar import java.util.Date enum class HistoryItemTimeGroup { - Today, ThisWeek, ThisMonth, Older; + Today, Yesterday, ThisWeek, ThisMonth, Older; fun humanReadable(context: Context): String = when (this) { - Today -> context.getString(R.string.history_24_hours) + Today -> context.getString(R.string.history_today) + Yesterday -> context.getString(R.string.history_yesterday) ThisWeek -> context.getString(R.string.history_7_days) ThisMonth -> context.getString(R.string.history_30_days) Older -> context.getString(R.string.history_older) @@ -81,11 +82,14 @@ class HistoryAdapter(private val historyInteractor: HistoryInteractor) : companion object { private const val zeroDays = 0 + private const val oneDay = 1 private const val sevenDays = 7 private const val thirtyDays = 30 - private val oneDayAgo = getDaysAgo(zeroDays).time + private val zeroDaysAgo = getDaysAgo(zeroDays).time + private val oneDayAgo = getDaysAgo(oneDay).time private val sevenDaysAgo = getDaysAgo(sevenDays).time private val thirtyDaysAgo = getDaysAgo(thirtyDays).time + private val yesterdayRange = LongRange(oneDayAgo, zeroDaysAgo) private val lastWeekRange = LongRange(sevenDaysAgo, oneDayAgo) private val lastMonthRange = LongRange(thirtyDaysAgo, sevenDaysAgo) @@ -99,6 +103,7 @@ class HistoryAdapter(private val historyInteractor: HistoryInteractor) : private fun timeGroupForHistoryItem(item: HistoryItem): HistoryItemTimeGroup { return when { DateUtils.isToday(item.visitedAt) -> HistoryItemTimeGroup.Today + yesterdayRange.contains(item.visitedAt) -> HistoryItemTimeGroup.Yesterday lastWeekRange.contains(item.visitedAt) -> HistoryItemTimeGroup.ThisWeek lastMonthRange.contains(item.visitedAt) -> HistoryItemTimeGroup.ThisMonth else -> HistoryItemTimeGroup.Older diff --git a/app/src/main/java/org/mozilla/fenix/library/history/HistoryFragment.kt b/app/src/main/java/org/mozilla/fenix/library/history/HistoryFragment.kt index f4714615a..ee4f50180 100644 --- a/app/src/main/java/org/mozilla/fenix/library/history/HistoryFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/library/history/HistoryFragment.kt @@ -8,6 +8,7 @@ import android.content.ClipboardManager import android.content.Context.CLIPBOARD_SERVICE import android.content.DialogInterface import android.os.Bundle +import android.text.SpannableString import android.view.LayoutInflater import android.view.Menu import android.view.MenuInflater @@ -42,6 +43,7 @@ import org.mozilla.fenix.components.metrics.Event import org.mozilla.fenix.ext.components import org.mozilla.fenix.ext.nav import org.mozilla.fenix.ext.requireComponents +import org.mozilla.fenix.ext.setTextColor import org.mozilla.fenix.ext.showToolbar import org.mozilla.fenix.ext.toShortUrl import org.mozilla.fenix.library.LibraryPageFragment @@ -166,7 +168,13 @@ class HistoryFragment : LibraryPageFragment(), UserInteractionHandl } inflater.inflate(menuRes, menu) + menu.findItem(R.id.share_history_multi_select)?.isVisible = true + + menu.findItem(R.id.delete_history_multi_select)?.title = + SpannableString(getString(R.string.bookmark_menu_delete_button)).apply { + setTextColor(requireContext(), R.attr.destructive) + } } override fun onOptionsItemSelected(item: MenuItem): Boolean = when (item.itemId) { @@ -267,7 +275,8 @@ class HistoryFragment : LibraryPageFragment(), UserInteractionHandl } setPositiveButton(R.string.delete_browsing_data_prompt_allow) { dialog: DialogInterface, _ -> historyStore.dispatch(HistoryFragmentAction.EnterDeletionMode) - viewLifecycleOwner.lifecycleScope.launch(IO) { + // Use fragment's lifecycle; the view may be gone by the time dialog is interacted with. + lifecycleScope.launch(IO) { requireComponents.analytics.metrics.track(Event.HistoryAllItemsRemoved) requireComponents.core.store.dispatch(RecentlyClosedAction.RemoveAllClosedTabAction) requireComponents.core.historyStorage.deleteEverything() diff --git a/app/src/main/java/org/mozilla/fenix/settings/account/AccountSettingsFragment.kt b/app/src/main/java/org/mozilla/fenix/settings/account/AccountSettingsFragment.kt index 8ccc5ff87..45e73998d 100644 --- a/app/src/main/java/org/mozilla/fenix/settings/account/AccountSettingsFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/settings/account/AccountSettingsFragment.kt @@ -233,7 +233,8 @@ class AccountSettingsFragment : PreferenceFragmentCompat() { setNegativeButton(getString(R.string.logins_warning_dialog_later)) { _: DialogInterface, _ -> SyncEnginesStorage(context).setStatus(SyncEngine.Passwords, newValue) - viewLifecycleOwner.lifecycleScope.launch { + // Use fragment's lifecycle; the view may be gone by the time dialog is interacted with. + lifecycleScope.launch { context.components.backgroundServices.accountManager.syncNow(SyncReason.EngineChange) } } diff --git a/app/src/main/java/org/mozilla/fenix/settings/deletebrowsingdata/DeleteBrowsingDataFragment.kt b/app/src/main/java/org/mozilla/fenix/settings/deletebrowsingdata/DeleteBrowsingDataFragment.kt index 77f867494..95732ce21 100644 --- a/app/src/main/java/org/mozilla/fenix/settings/deletebrowsingdata/DeleteBrowsingDataFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/settings/deletebrowsingdata/DeleteBrowsingDataFragment.kt @@ -142,7 +142,7 @@ class DeleteBrowsingDataFragment : Fragment(R.layout.fragment_delete_browsing_da private fun deleteSelected() { startDeletion() - viewLifecycleOwner.lifecycleScope.launch(IO) { + lifecycleScope.launch(IO) { getCheckboxes().mapIndexed { i, v -> if (v.isChecked) { when (i) { diff --git a/app/src/main/java/org/mozilla/fenix/settings/logins/controller/SavedLoginsStorageController.kt b/app/src/main/java/org/mozilla/fenix/settings/logins/controller/SavedLoginsStorageController.kt index bd1bbe96a..5257c73f8 100644 --- a/app/src/main/java/org/mozilla/fenix/settings/logins/controller/SavedLoginsStorageController.kt +++ b/app/src/main/java/org/mozilla/fenix/settings/logins/controller/SavedLoginsStorageController.kt @@ -30,7 +30,7 @@ import org.mozilla.fenix.settings.logins.mapToSavedLogin */ open class SavedLoginsStorageController( private val passwordsStorage: SyncableLoginsStorage, - private val viewLifecycleScope: CoroutineScope, + private val lifecycleScope: CoroutineScope, private val navController: NavController, private val loginsFragmentStore: LoginsFragmentStore, private val ioDispatcher: CoroutineDispatcher = Dispatchers.IO @@ -40,7 +40,7 @@ open class SavedLoginsStorageController( fun delete(loginId: String) { var deleteLoginJob: Deferred? = null - val deleteJob = viewLifecycleScope.launch(ioDispatcher) { + val deleteJob = lifecycleScope.launch(ioDispatcher) { deleteLoginJob = async { passwordsStorage.delete(loginId) } @@ -58,7 +58,7 @@ open class SavedLoginsStorageController( fun save(loginId: String, usernameText: String, passwordText: String) { var saveLoginJob: Deferred? = null - viewLifecycleScope.launch(ioDispatcher) { + lifecycleScope.launch(ioDispatcher) { saveLoginJob = async { // must retrieve from storage to get the httpsRealm and formActionOrigin val oldLogin = passwordsStorage.get(loginId) @@ -124,7 +124,7 @@ open class SavedLoginsStorageController( fun findPotentialDuplicates(loginId: String) { var deferredLogin: Deferred>? = null - val fetchLoginJob = viewLifecycleScope.launch(ioDispatcher) { + val fetchLoginJob = lifecycleScope.launch(ioDispatcher) { deferredLogin = async { val login = getLogin(loginId) passwordsStorage.getPotentialDupesIgnoringUsername(login!!) @@ -150,7 +150,7 @@ open class SavedLoginsStorageController( fun fetchLoginDetails(loginId: String) { var deferredLogin: Deferred>? = null - val fetchLoginJob = viewLifecycleScope.launch(ioDispatcher) { + val fetchLoginJob = lifecycleScope.launch(ioDispatcher) { deferredLogin = async { passwordsStorage.list() } @@ -178,7 +178,7 @@ open class SavedLoginsStorageController( fun handleLoadAndMapLogins() { var deferredLogins: Deferred>? = null - val fetchLoginsJob = viewLifecycleScope.launch(ioDispatcher) { + val fetchLoginsJob = lifecycleScope.launch(ioDispatcher) { deferredLogins = async { passwordsStorage.list() } 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 62a624169..fccf33aa9 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 @@ -76,7 +76,7 @@ class EditLoginFragment : Fragment(R.layout.fragment_edit_login) { interactor = EditLoginInteractor( SavedLoginsStorageController( passwordsStorage = requireContext().components.core.passwordsStorage, - viewLifecycleScope = viewLifecycleOwner.lifecycleScope, + lifecycleScope = lifecycleScope, navController = findNavController(), loginsFragmentStore = loginsFragmentStore ) 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 7120c0fe0..63fe12c87 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 @@ -86,7 +86,7 @@ class LoginDetailFragment : Fragment(R.layout.fragment_login_detail) { interactor = LoginDetailInteractor( SavedLoginsStorageController( passwordsStorage = requireContext().components.core.passwordsStorage, - viewLifecycleScope = viewLifecycleOwner.lifecycleScope, + lifecycleScope = lifecycleScope, navController = findNavController(), loginsFragmentStore = savedLoginsStore ) diff --git a/app/src/main/java/org/mozilla/fenix/settings/logins/fragment/SavedLoginsAuthFragment.kt b/app/src/main/java/org/mozilla/fenix/settings/logins/fragment/SavedLoginsAuthFragment.kt index 4246beccd..9a2d70b6b 100644 --- a/app/src/main/java/org/mozilla/fenix/settings/logins/fragment/SavedLoginsAuthFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/settings/logins/fragment/SavedLoginsAuthFragment.kt @@ -16,6 +16,8 @@ import android.provider.Settings.ACTION_SECURITY_SETTINGS import android.util.Log import androidx.appcompat.app.AlertDialog import androidx.biometric.BiometricManager +import androidx.biometric.BiometricManager.Authenticators.BIOMETRIC_WEAK +import androidx.biometric.BiometricManager.Authenticators.DEVICE_CREDENTIAL import androidx.biometric.BiometricPrompt import androidx.core.content.ContextCompat import androidx.core.content.getSystemService @@ -98,7 +100,7 @@ class SavedLoginsAuthFragment : PreferenceFragmentCompat() { promptInfo = BiometricPrompt.PromptInfo.Builder() .setTitle(getString(R.string.logins_biometric_prompt_message)) - .setDeviceCredentialAllowed(true) + .setAllowedAuthenticators(BIOMETRIC_WEAK or DEVICE_CREDENTIAL) .build() } @@ -154,7 +156,7 @@ class SavedLoginsAuthFragment : PreferenceFragmentCompat() { private fun canUseBiometricPrompt(context: Context): Boolean { return if (SDK_INT >= M) { val manager = BiometricManager.from(context) - val canAuthenticate = manager.canAuthenticate() + val canAuthenticate = manager.canAuthenticate(BIOMETRIC_WEAK) val hardwareUnavailable = canAuthenticate == BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE || canAuthenticate == BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE 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 dc50cc445..130e856a6 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 @@ -92,7 +92,7 @@ class SavedLoginsFragment : Fragment() { savedLoginsStorageController = SavedLoginsStorageController( passwordsStorage = requireContext().components.core.passwordsStorage, - viewLifecycleScope = viewLifecycleOwner.lifecycleScope, + lifecycleScope = viewLifecycleOwner.lifecycleScope, navController = findNavController(), loginsFragmentStore = savedLoginsStore ) diff --git a/app/src/main/java/org/mozilla/fenix/settings/sitepermissions/SitePermissionsDetailsExceptionsFragment.kt b/app/src/main/java/org/mozilla/fenix/settings/sitepermissions/SitePermissionsDetailsExceptionsFragment.kt index 9ad2a5f1a..8e68407b2 100644 --- a/app/src/main/java/org/mozilla/fenix/settings/sitepermissions/SitePermissionsDetailsExceptionsFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/settings/sitepermissions/SitePermissionsDetailsExceptionsFragment.kt @@ -92,7 +92,8 @@ class SitePermissionsDetailsExceptionsFragment : PreferenceFragmentCompat() { } private fun clearSitePermissions() { - viewLifecycleOwner.lifecycleScope.launch(IO) { + // Use fragment's lifecycle; the view may be gone by the time dialog is interacted with. + lifecycleScope.launch(IO) { requireContext().components.core.permissionStorage.deleteSitePermissions(sitePermissions) withContext(Main) { requireView().findNavController().popBackStack() 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 455b073ee..d1c4367d8 100644 --- a/app/src/main/java/org/mozilla/fenix/tabtray/TabTrayDialogFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/tabtray/TabTrayDialogFragment.kt @@ -423,7 +423,7 @@ class TabTrayDialogFragment : AppCompatDialogFragment(), UserInteractionHandler AlertDialog.Builder(it).setTitle(R.string.tab_tray_add_new_collection) .setView(customLayout).setPositiveButton(android.R.string.ok) { dialog, _ -> - viewLifecycleOwner.lifecycleScope.launch(Dispatchers.IO) { + lifecycleScope.launch(Dispatchers.IO) { tabCollectionStorage.createCollection( collectionNameEditText.text.toString(), sessionList 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 f1ae010bd..3e35f9534 100644 --- a/app/src/main/java/org/mozilla/fenix/tabtray/TabTrayView.kt +++ b/app/src/main/java/org/mozilla/fenix/tabtray/TabTrayView.kt @@ -290,9 +290,15 @@ class TabTrayView( tabTrayItemMenu = TabTrayItemMenu( - view.context, - { tabs.isNotEmpty() && view.tab_layout.selectedTabPosition == 0 }, - { tabs.isNotEmpty() }) { + context = view.context, + shouldShowSaveToCollection = { tabs.isNotEmpty() && view.tab_layout.selectedTabPosition == 0 }, + hasOpenTabs = { + if (isPrivateModeSelected) { + view.context.components.core.store.state.privateTabs.isNotEmpty() + } else { + view.context.components.core.store.state.normalTabs.isNotEmpty() + } + }) { when (it) { is TabTrayItemMenu.Item.ShareAllTabs -> interactor.onShareTabsClicked( isPrivateModeSelected diff --git a/app/src/main/res/layout/component_tabstray_bottom.xml b/app/src/main/res/layout/component_tabstray_bottom.xml index 5fef10255..2bb984ddc 100644 --- a/app/src/main/res/layout/component_tabstray_bottom.xml +++ b/app/src/main/res/layout/component_tabstray_bottom.xml @@ -111,6 +111,7 @@ android:id="@+id/tab_layout" android:layout_width="0dp" android:layout_height="80dp" + app:tabMaxWidth="0dp" android:background="@color/foundation_normal_theme" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" diff --git a/app/src/main/res/values-an/strings.xml b/app/src/main/res/values-an/strings.xml index bcd5a8c52..03adf45d3 100644 --- a/app/src/main/res/values-an/strings.xml +++ b/app/src/main/res/values-an/strings.xml @@ -143,6 +143,8 @@ Instalar Pestanyas sincronizadas + + Resincronizar Mirar en a pachina @@ -466,6 +468,11 @@ Desplazar pa amagar la barra de ferramientas + + Eslizar la barra de ferramientas enta los costaus pa cambiar de pestanya + + Eslizar la barra de ferramientas enta alto pa ubrir las pestanyas + Sesions @@ -1351,8 +1358,12 @@ Puesto copiau a lo portafuellas Copiar clau + + Borrar la clau Copiar nombre d’usuario + + Borrar lo nombre d\'usuario Copiar puesto @@ -1523,6 +1534,8 @@ S’ha arribau a lo limite de puestos principals. + + Pa anyadir un nuevo puesto principal, has de borrar-ne belatro. Toca y mantiene pretau lo puesto y tría borrar. Vale, entendiu diff --git a/app/src/main/res/values-ast/strings.xml b/app/src/main/res/values-ast/strings.xml index 0a03c69e6..aada1ca02 100644 --- a/app/src/main/res/values-ast/strings.xml +++ b/app/src/main/res/values-ast/strings.xml @@ -405,6 +405,8 @@ Comparte los datos tocante a les carauterístiques qu\'uses en %1$s con Leanplum, el nuesu fornidor de marketing móvil. Esperimentos + + Permite que Mozilla instale y recueya datos pa carauterístiques esperimentales Informador de casques @@ -1079,6 +1081,8 @@ Aniciu de sesión cola cámara Usar una direición de corréu + + Crea una pa sincronizar Firefox ente preseos.]]> Firefox va dexar de sincronizase cola to cuenta mas nun va desaniciar nengún datu d\'esti preséu. @@ -1187,6 +1191,12 @@ The first parameter is the app name --> %s | Biblioteques OSS + + Redireiciones de rastrexadores + + + Llimpia les cookies creaes poles redireiciones a sitios web conocíos que rastrexen. + Sofitu @@ -1465,6 +1475,9 @@ Aniciar sesión pa sincronizar + + Nun hai llingüetes abiertes + Algamóse la llende de sitios destacaos diff --git a/app/src/main/res/values-be/strings.xml b/app/src/main/res/values-be/strings.xml index 51759aa1a..f2264b2b5 100644 --- a/app/src/main/res/values-be/strings.xml +++ b/app/src/main/res/values-be/strings.xml @@ -423,6 +423,9 @@ Служба месцазнаходжання Mozilla + + Справаздача аб здароўі %s + Уключыць сінхранізацыю @@ -469,6 +472,9 @@ Пасуньце ўбок панэль інструментаў, каб пераключыць карткі + + Пасуньце ўверх панэль інструментаў, каб адкрыць карткі + Сеансы @@ -1142,6 +1148,8 @@ Увайдзіце з дапамогай камеры Выкарыстаць электронную пошту + + Стварыце яго, каб сінхранізаваць Firefox паміж прыладамі.]]> Firefox спыніць сінхранізацыю з вашым уліковым запісам, але не выдаліць дадзеныя аглядання на гэтай прыладзе. @@ -1424,6 +1432,8 @@ Падрабязней + + Інфармацыя аб дададзенай пошукавай сістэме Спасылка на падрабязныя звесткі @@ -1545,6 +1555,8 @@ Дасягнуты ліміт папулярных сайтаў + + Каб дадаць новы топ-сайт, выдаліце якісь іншы. Націсніце на сайт і ўтрымлівайце, пасля абярыце "Выдаліць". OK, зразумела diff --git a/app/src/main/res/values-bg/strings.xml b/app/src/main/res/values-bg/strings.xml index c9abdbcf0..04a0934d2 100644 --- a/app/src/main/res/values-bg/strings.xml +++ b/app/src/main/res/values-bg/strings.xml @@ -21,12 +21,44 @@ Поверителните раздели ще бъдат показани тук. + + 1 отворен раздел. Докоснете за превключване на раздели. + + %1$s отворени раздели. Докоснете за превключване между тях. + + %1$d избрани + + Създаване на списък + + Име + + Избор на списък + + Изход от режим на множествен избор + + Запазване на избраните раздели в списък + + + Отментат %1$s + + Неотментат %1$s + + Излизане от режим на множествен избор + + Режим на множествен избор, изберете раздели за добавяне към списък + + + Избран + %1$s е произведен от @fork-maintainers. Вие сте в поверителен прозорец + + %1$s изчиства историята на търсенето и разглеждането, когато ги затворите или излезете от приложението. Въпреки че това не ви прави анонимни за уеб сайтовете или доставчиците на интернет услуги, улеснява запазването на анонимността на действия ви в мрежата, от останалите ползващи същото устройство. Разпространени легенди относно поверителното разглеждане Изтриване на сесията @@ -48,6 +80,26 @@ Не сега + + + Може да настроите Firefox автоматично да отваря препратки в приложения + + Към настройки + + Прекратяване + + + Към настройки + + Прекратяване + + + Настройте отворените раздели да бъдат автоматично затваряни ако не са преглеждани през последния ден, седмица или месец. + + Настройки + + Прекратяване + Нов раздел @@ -98,7 +150,7 @@ Нов раздел - Добавяне в списък + Добавяне към списък Споделяне @@ -140,14 +192,12 @@ Сканиране - - Преки пътища + + Търсеща машина Настройки на търсачките - - Търсене с - Този път търсете с: + Този път търсете с: Използване на препратка от буфера @@ -202,7 +252,7 @@ Банкови карти и адреси - Задаване като стандартен четец + Четец по подразбиране Разширени @@ -216,13 +266,19 @@ Поверително разглеждане - Отваряне препратките в поверителен раздел + Отваряне препратки в поверителен раздел Снимки на екрана в поверителен режим + + Ако е включено, поверителните раздели ще са видими от отворените приложения Добавяне на икона за поверителен режим Достъпност + + Личен сървър за Firefox Account + + Личен сървър за Sync Сметка @@ -231,8 +287,12 @@ Лента с инструменти Тема + + Начален екран + + Жестове - Персонализиране + Външен вид Синхронизирайте отметки, пароли и други с вашия Firefox Account @@ -251,27 +311,36 @@ Развойни инструменти Отдалечено отстраняване на дефекти през USB - - Преки пътища за търсене + + Показване на търсещи машини Показване на предложения Показване на гласово търсене - Показване в поверителен режим + В поверителен режим също - Показване на предложения от буфера + Предложения от буфера - Търсене на историята на разглеждане + В история на разглеждане - Търсене в отметките + В отметките + + В синхронизираните раздели Настройки на сметката + + Автоматично довършване на адреси Отваряне на препратки в приложения + + Външно приложение за изтегляния Добавки + + Известия + Синхронизиране @@ -347,8 +416,12 @@ Споделя данни за използването, производителността, хардуера, настройките на четеца с Mozilla, за да ни помогне да направим %1$s по-добър Маркетингови данни + + Споделя данни за използваните от вас възможности на %1$s чрез Leanplum, нашият партньор за мобилен маркетинг. Експерименти + + Позволява на Mozilla да инсталира експериментални възможности и да събира данни за тях Доклади за срив @@ -374,7 +447,7 @@ firefox.com/pair]]> - Отваряне на камерата + Отваряне на камера Отказ @@ -391,10 +464,14 @@ Тъмна - Зададена от приложението за пестене на батерия + Зададена от приложение за пестене на батерия Следва темата на устройството + + + Издърпайте за презареждане + Сесии @@ -427,6 +504,32 @@ Затваряне + + Последно затворени раздели + + Цялата история + + %d раздела + + %d раздел + + + Няма затворени раздели + + + + Затваряне на раздели + + Ръчно + + След един ден + + След една седмица + + След един месец + Отворени раздели @@ -443,9 +546,13 @@ Отворени раздели - Добавяне в списък + Добавяне към списък Споделяне на всички раздели + + Последно затворени раздели + + Настройки на раздели Затваряне на всички раздели @@ -481,17 +588,242 @@ Заглавна пиктограма на текущото меню на сесия + + Добавяне към списък + + Изтриване на списък + + Преименуване на списък + + Отваряне на раздели + + Премахване + + Премахване от историята + + %1$s (поверителен режим) + + Запазване + + + + Изчистване на история + + Сигурни ли сте, че желаете да изтриете историята на разглеждане? + + Историята е изчистена + + Изтрито %1$s + + Изчистване + + Копиране + + Споделяне + + Отваряне в раздел + + Отваряне в поверителен раздел + + Изтриване + + %1$d избрани + + Изтриване на %1$d записа + + + Последните 24 часа + + Последните 7 дни + + Последните 30 дни + + + По-стари + + Липсва история + + + + Няма изтегляния + + %1$d избрани + + + + Извинете. %1$s не можа да зареди страницата. + + Oпитайте да възстановите или затворите раздела. + + Изпращане на доклад за срива до Mozilla + + Затваряне на раздел + + Възстановяване на раздел + + + Настройки на сесия + + + Споделяне на сесия + + + + Меню отметки + + Промяна на отметка + + Избор на папка + + Наистина ли искате да изтриете папката? + + %s ще изтрие избраните елементи. + + Изтрита %1$s + + Добавяне на папка + + Отметката е създадена. + + Отметката е запазена! + + ПРОМЕНЯНЕ + + Променяне + + Избиране + + Копиране + + Споделяне + + Отваряне в нов раздел + + Отваряне в поверителен раздел + + Изтриване + + Запазване + + %1$d избрани + + Промяна на отметка + + Промяна на папка + + Влезте, за да видите синхронизираните отметки + + URL + + ПАПКА + + ИМЕ + + Добавяне на папка + + Избиране на папка + + Заглавието е задължително + + Неправилен адрес + + + Липсват отметки + + Изтрита %1$s + + Отметките са изтрити + + Изтриване на избраните папки + + ОТМЕНЯНЕ + + + + Права + + Към настройките Списък с бързи настройки + + Препоръчително + + Управление на права на страница + + Изчистване на правата + + + Изчистване на правото + + Изчистване на правата на всички страници + + Автоматично възпроизвеждане + + Камера + + Микрофон + + Местоположение + + Известие + + Винаги да пита + + Забраняване + + Разрешаване + + Забранено от Android + + Изключения + + + Включено + + Изключено + + Разрешаване на звук и видео + + + Включено + + Изключено + + + + Списъци Меню Списъци + + Съберете важните за вас неща.\nГрупирайте търсения, страници и раздели за бърз достъп по-късно. + + Избиране на раздели + + Създаване на списък Избиране на всички + + Отменяне всички + + Изберете раздели за запазване + + %d раздела избрани + + %d раздел избран Разделите са запазени! + + Колекция запазена! Разделът е запазен! @@ -499,14 +831,112 @@ Запазване + + Преглед + + + Списък %d + + + + Изпращане и споделяне + + Споделяне Споделяне + + Споделяне на препратка + + Всички действия + + Последно използвани + + Вписване в Sync + + Изпращане до всички устройства + + Повторно свързване със Sync + + Без мрежа + + Добавяне на устройство + + За да изпращате раздели е необходимо да бъдете вписани във Firefox на поне едно устройство. + + + Разбрах + + Невъзможно е споделяне с това приложение + + Изпращане към устройство + + Няма свързани устройства + + Повече за изпращане на раздели… + + Добавяне на устройство… + + + + Поверителна сесия + + Изчистване на поверителни раздели + + Затваряне на поверителни раздели + + Отваряне + + Изчистване и отваряне Разрешете %1$s да отвори %2$s + + Изтриване на %1$s? + + Изтриване + + Отказ + + + Режим на цял екран + + Адресът е копиран + + + Отворени раздели + + %d раздела + + %d адреса + + История + + %d страници + + Бисквитки + + Изход + + + Отказ + + Изтриване + + + Преминете към новият Nightly + + + Firefox Nightly е преместен + + + + Здравейте от %s! Запознайте се с %s @@ -517,17 +947,21 @@ Имате въпроси относно новия дизайн на %s? Искате ли да разберете какви са промените? Получете отговори тук - - Извлечете максимума от %s - + + Синхронизирайте отметки, пароли и други чрез вашата сметка във Firefox. + + Научете повече + + Вписване… + + Вписване във Firefox Автоматична поверителност Настройките за поверителност и сигурност спират проследяващ и злонамерен код, както и компании, които ви преследват. - Стандартна (по подразбиране) + Стандартна (подразбиране) Спира малко проследявания. Страниците зареждат нормално. @@ -536,42 +970,311 @@ Строга Спира повече проследявания, реклами и изскачащи прозорци. Страниците зареждат по-бързо, но може да не работят. + + Изберете страна + + Изпробвайте работа с една ръка, с лента инструменти отдолу, или я преместете отгоре. + + Разглеждайте поверително + + За да отворите поверителен раздел: докоснете иконата %s. + + За да се отваря поверителен раздел всеки път: променете настройките за поверително разглеждане. + + Отваряне на настройки + + Поверителност + + Проектирали сме %s така, че да ви предоставя контрол върху това, което споделяте както онлайн, така и с нас. + + + Бележка за поверителността Затваряне + + Започнете да разглеждате + Изберете тема + + Запазете батерия и зрението си с избиране на тъмна тема. + + Автоматичнa + + Приспособява се към настройките на устройството + + Тъмна тема + + Светла тема + + + Разделите са изпратени! + + Разделът е изпратен! + + НОВ ОПИТ + + Сканиране на код + + + Отказ + + + + Настройки на защитата + + Подобрена защита от проследяване - Преглеждайте без да бъдете следени + Разглеждайте без да сте следени - Пазете вашите данни само ваши. %s ви предпазва от много от най-разпространените проследявания, които следват вашите действия онлайн. + Пазете вашите данни лични. %s ви предпазва от най-разпространените проследявания, които дебнат действията ви онлайн. Научете повече + + Стандартна (подразбиране) + + Спира малко проследявания. Страниците зареждат нормално. + + Строга + + Спира повече проследявания, реклами и изскачащи прозорци. Страниците зареждат по-бързо, но може да не работят. + + По избор + + Изберете какво проследяване и кои скриптове да спрете. + + + Бисквитки + + Проследяване в различни сайтове и социални медии + + Бисквитки от непосетени страници + + Всички бисквитки от трети страни (може наруши работата на страници) + + Всички бисквитки (ще наруши работата на страници) + + Проследяващо съдържание + + Във всички раздели + + Само в поверителни раздели + + Добиване на криптовалути + + Снемане на цифров отпечатък + + Разрешено + + Проследяване от социални мрежи + + Ограничава възможността социалните мрежи да проследяват вашата активност в Мрежата. + + Бисквитки за следене в различни сайтове + + Спира бисквитки, които компаниите за реклама и анализ използват за събиране на ваши данни от посещения в множество страници. + + Добиване на криптовалути + + Предотвратява зловредни скриптове, придобиващи достъп до устройството ви, с цел добив на цифрова валута. + + Снемане на цифров отпечатък + + Предотвратява събирането на уникални данни за обратно установяване, които могат да бъдат използвани за проследяване. + + Проследяващо съдържание + + Спира зареждането на външни реклами, видео и друго съдържание, съдържащо проследяващ код. Може да засегне някои страници. + + Всеки път, когато щитът е лилав, %s е спрял проследяване на сайт. Докоснете за повече информация. + + Защитите са ВКЛЮЧЕНИ за този сайт + + Защитите са ИЗКЛЮЧЕНИ за този сайт + + Подобрената защита от проследяване е изключена за тези сайтове Връщане назад + + %s | библиотеки с отворен код + + + Поддръжка + + Политика на поверителност + + Знайте правата си + + Лицензна информация + + Използвани библиотеки + + Меню за отстраняване на грешки: %1$d оставащи натискания до включване + Меню за отстраняване на грешки включено + + + 1 раздел + + %d раздела + + + + Копиране + + Поставяне и посещаване + + Поставяне + + Адресът е копиран в системния буфер + + + Добавяне към началния екран + + Отказ + + Добавяне + + Към страницата + + Име на пряк път + + + Регистрации и пароли + + Запазване на регистрации и пароли + + Питане за запазване + + Никога + + Автоматично попълване + + Синхронизиране на регистрации + + Включено + + Изключено + + Повторно свързване + + Вписване в Sync + + Запазени регистрации + + Тук се показват нещата, които запазвате или синхронизирате във %s. Научете повече за Sync. + + Изключения + + Тук се показват регистрации и пароли, които не са запазени. + + Търсене на регистрация + + Азбучен ред + + Последно използвани + + Страница + + Потребителско име + + Парола + + Въведете отново своя PIN + + Отключете, за да видите запазените регистрации + + Връзката е незащитена. Въведените тук данни за вход могат да бъдат компрометирани. Научете повече Меню за сортиране на регистрации + + Добавяне на търсеща машина + + Промяна на търсеща машина Добавяне Запазване + + Променяне + + Премахване + + + Друга + + Име + + Низ за търсене, който да се използва + + Заменете заявката с „%s“. Пример:\nhttps://www.google.com/search?q=%s Научете повече + + Данни за търсещата машина на потребителя Препратка научете повече + + Въведете име на търсещата машина + + Търсеща машина с име „%s“ вече съществува. + + + Уверете се, че низът за търсене съвпада с примера + + Грешка при свързване с „%s“ + + %s е създадена + + %s е запазена + + %s е изтрита + + + Добре дошли в един чисто нов %s + + + + Добавете друго на устройство. + + Няма отворени раздели във Firefox на други ваши устройства. + + Вижте разделите от други ваши устройства. + + Вписване в Sync + + + Няма отворени раздели + + + Добре, разбрах + + + Премахване + + + Извлечете максимума от %s + diff --git a/app/src/main/res/values-co/strings.xml b/app/src/main/res/values-co/strings.xml index 4375967a7..d125a1542 100644 --- a/app/src/main/res/values-co/strings.xml +++ b/app/src/main/res/values-co/strings.xml @@ -1175,7 +1175,7 @@ Impiegà piuttostu un messaghju elettronicu - Createne unu per sincrunizà i vostri apparechji.]]> + Createne unu per sincrunizà Firefox trà i vostri apparechji.]]> Firefox ùn si sincruniserà più cù u vostru contu, ma ùn squasserà alcunu datu di navigazione nant’à st’apparechju. @@ -1284,7 +1284,7 @@ %s | Bibliuteche di fonte aperta - Frattighjatori da ridirezzione + Perseguitatori da ridirezzione Squasseghja i canistrelli definiti da ridirezzione ver di siti web cunnisciuti per u spiunagiu. diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml index 42a5d13e9..bc2725803 100644 --- a/app/src/main/res/values-cs/strings.xml +++ b/app/src/main/res/values-cs/strings.xml @@ -100,7 +100,7 @@ Zobrazit možnosti - Zavřít + Ne, děkuji @@ -146,6 +146,8 @@ Nainstalovat Synchronizované panely + + Synchronizovat znovu Najít na stránce @@ -272,6 +274,8 @@ Otevírat odkazy v anonymních panelech Povolit pořizování snímků obrazovky v anonymním prohlížení + + Pokud toto povolíte, obsah panelů bude viditelný i v zobrazení otevřených aplikací Vytvořit zkratku pro anonymní prohlížení @@ -328,6 +332,8 @@ Našeptávat z historie prohlížení Našeptávat ze záložek + + Hledat v synchronizovaných panelech Nastavení účtu @@ -1172,6 +1178,8 @@ Přihlásit se pomocí fotoaparátu Použít raději e-mail + + Vytvořte si ho a synchronizujte svůj Firefox mezi zařízeními.]]> Firefox ukončí synchronizaci s vaším účtem, ale nesmaže z tohoto zařízení žádná vaše data. @@ -1279,6 +1287,11 @@ The first parameter is the app name --> %s | OSS knihovny + + Sledující přesměrování + + Maže cookies nastavené během přesměrování známými sledujícími servery. + Podpora diff --git a/app/src/main/res/values-da/strings.xml b/app/src/main/res/values-da/strings.xml index e1ab532de..7e8a0c4da 100644 --- a/app/src/main/res/values-da/strings.xml +++ b/app/src/main/res/values-da/strings.xml @@ -144,6 +144,8 @@ Installer Synkroniserede faneblade + + Synkroniser igen Find på siden @@ -265,6 +267,8 @@ Åbn links i et privat faneblad Tillad skærmbilleder i privat browsing + + Hvis du tillader det, vil private faneblade også være synlige, når flere apps er åbne Tilføj genvej til privat browsing @@ -285,6 +289,8 @@ Tema Startside + + Bevægelser Tilpas @@ -319,8 +325,12 @@ Søg i browserhistorik Søg i bogmærker + + Søg i synkroniserede faneblade Kontoindstillinger + + Autofuldfør adresser Åbn links i apps @@ -456,6 +466,16 @@ Samme som enhedens tema + + + Træk for at genindlæse + + Scroll for at skjule værktøjslinje + + Stryg værktøjslinje sidelæns for at skifte faneblade + + Stryg værktøjslinje op for at åbne faneblade + Sessioner @@ -1040,6 +1060,10 @@ Begynd at synkronisere bogmærker, adgangskoder m.m. med din Firefox-konto. Læs mere + + Du er logget ind som %s i en anden version af Firefox på denne enhed. Vil du logge ind med denne konto? Ja, log mig ind @@ -1129,6 +1153,8 @@ Log in med dit kamera Brug mail i stedet + + Opret en for at synkronisere Firefox mellem enheder.]]> Firefox vil ikke længere synkronisere med din konto, men sletter ikke dine browserdata på denne enhed. @@ -1236,6 +1262,12 @@ The first parameter is the app name --> %s | OSS-biblioteker + + Sporing via omdirigeringer + + + Rydder cookies, der sættes af omdirigeringer til websteder, der er kendt for at spore deres brugere. + Hjælp @@ -1280,6 +1312,9 @@ Genvejsnavn + + Du kan nemt føje dette websted til din enheds startskærm for at have hurtig adgang til det og browse hurtigere med en app-lignende oplevelse. + Logins og adgangskoder @@ -1348,8 +1383,12 @@ Websted er kopieret til udklipsholder Kopier adgangskode + + Ryd adgangskode Kopier brugernavn + + Ryd brugernavn Kopier websted @@ -1523,6 +1562,8 @@ Grænsen for Mest besøgte websider er nået + + Fjern en webside for at erstatte den med en ny. Tryk og hold på websiden, og vælg så Fjern. Ok, forstået diff --git a/app/src/main/res/values-el/strings.xml b/app/src/main/res/values-el/strings.xml index f2ce32b57..54aa2d285 100644 --- a/app/src/main/res/values-el/strings.xml +++ b/app/src/main/res/values-el/strings.xml @@ -41,12 +41,19 @@ Αποθήκευση επιλεγμένων καρτελών στη συλλογή + + Επιλέχθηκε το "%1$s" + + Αποεπιλέχθηκε το "%1$s" Τέλος λειτουργίας πολλαπλής επιλογής Σε λειτουργία πολλαπλών επιλογών, επιλέξτε καρτέλες για αποθήκευση σε συλλογή + + Επιλέχθηκε + Το %1$s αναπτύσσεται από τη @fork-maintainers. diff --git a/app/src/main/res/values-es-rAR/strings.xml b/app/src/main/res/values-es-rAR/strings.xml index 9d6aaf1d0..64f97d035 100644 --- a/app/src/main/res/values-es-rAR/strings.xml +++ b/app/src/main/res/values-es-rAR/strings.xml @@ -813,7 +813,7 @@ Activado - Desactivado + Desactivada Permitir audio y video @@ -828,7 +828,7 @@ Activado - Desactivado + Desactivada @@ -1220,7 +1220,7 @@ Qué es lo que está bloqueado por la protección de rastreo estricta - Personalizado + Personalizada Elegí qué rastreadores y secuencias de comandos querés bloquear. diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index 558c1db48..8debe1c95 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -72,6 +72,8 @@ Descartar + + Se necesita acceso a la cámara. Ve a los ajustes de Android, toca en permisos y luego en permitir. Ir a ajustes @@ -255,6 +257,8 @@ Abrir enlaces en una pestaña privada Permitir capturas de pantalla en navegación privada + + Si está permitido, las pestañas privadas también serán visibles cuando hayan varias aplicaciones abiertas Agregar acceso directo a la navegación privada @@ -545,6 +549,8 @@ Compartir todas las pestañas Pestañas cerradas recientemente + + Ajustes de pestañas Cerrar todas las pestañas @@ -1562,6 +1568,8 @@ Límite de sitios frecuentes alcanzado + + Para añadir un nuevo sitio frecuente, elimina uno. Toca y mantén presionado el sitio y selecciona eliminar. Vale, entendido diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml index 3014f98d2..ae522a192 100644 --- a/app/src/main/res/values-nl/strings.xml +++ b/app/src/main/res/values-nl/strings.xml @@ -481,11 +481,11 @@ Trek om te vernieuwen - Scroll om de werkbalk te verbergen + Scrollen om de werkbalk te verbergen - Veeg de werkbalk opzij om van tabblad te wisselen + De werkbalk opzij vegen om van tabblad te wisselen - Veeg de werkbalk omhoog om tabbladen te openen + De werkbalk omhoog vegen om tabbladen te openen diff --git a/app/src/main/res/values-nn-rNO/strings.xml b/app/src/main/res/values-nn-rNO/strings.xml index 0a2a11e2e..1d8889c41 100644 --- a/app/src/main/res/values-nn-rNO/strings.xml +++ b/app/src/main/res/values-nn-rNO/strings.xml @@ -1287,6 +1287,11 @@ The first parameter is the app name --> %s | OSS-bibliotek + + Omdirigeringssporarar + + Fjernar infokapslar stilte inn av omdirigeringar til kjende sporingsnettstadar. + Brukarstøtte diff --git a/app/src/main/res/values-oc/strings.xml b/app/src/main/res/values-oc/strings.xml index a6b4468a0..9d084d56f 100644 --- a/app/src/main/res/values-oc/strings.xml +++ b/app/src/main/res/values-oc/strings.xml @@ -77,6 +77,9 @@ Pas ara + + + Podètz configurar Firefox per dobrir automaticament los ligams dins las aplicacions. Anar als paramètres @@ -321,6 +324,8 @@ Recercar dins l’istoric de navegacion Cercar dins los marcapaginas + + Cercar dins los onglets sincronizats Paramètres del compte @@ -470,6 +475,11 @@ Desfilar per amagar la barra d’aisinas + + Cambiar d’onglet en lisant la barra d’aisinas cap las costats + + Dobrir los ligams en lisant la barra d’aisina amont + Sessions @@ -811,6 +821,8 @@ Colleccions Menú de colleccion + + Amassatz çò important per vos\nGropatz las recèrcas, los sites e onglets similars per i tornar mai tard. Seleccionar d’onglets @@ -940,6 +952,8 @@ REFUSAR Volètz vertadièrament suprimir %1$s ? + + La supression d’aqueste onglet suprimirà tota la collecion. Podètz crear colleccions novèlas quand volgatz. Suprimir %1$s ? @@ -992,6 +1006,8 @@ Suprimir las donadas de navegacion en quitant Suprimís automaticament las donadas de navegacion quand seleccionatz « Quitar » al menú principal + + Suprimís automaticament las donadas de navegacion quand seleccionatz « Quitar » al menú principal Sortir diff --git a/app/src/main/res/values-rm/strings.xml b/app/src/main/res/values-rm/strings.xml index 632759601..2b6d9cc2c 100644 --- a/app/src/main/res/values-rm/strings.xml +++ b/app/src/main/res/values-rm/strings.xml @@ -1272,6 +1272,12 @@ The first parameter is the app name --> %s | Bibliotecas open source + + Fastizaders da renviament + + + Stizza cookies definids cun agid da renviaments a websites enconuschentas per fastizar. + Agid @@ -1317,6 +1323,9 @@ Num da la scursanida + + Ti pos agiuntar a moda simpla questa website al visur da partenza da tes apparat per avair access direct e navigar pli svelt, sco sch\'i fiss ina app. + Infurmaziuns d\'annunzia e pleds-clav @@ -1387,8 +1396,12 @@ Copià la pagina en l\'archiv provisoric Copiar il pled-clav + + Stizzar il pled-clav Copiar il num d\'utilisader + + Stizzar il num d\'utilisader Copiar la pagina @@ -1565,6 +1578,8 @@ Cuntanschì la limita da paginas preferidas + + Per pudair agiuntar ina nova pagina preferida stos ti l\'emprim allontanar in\'autra. Tegna smatgà la pagina e tscherna «Allontanar». OK, chapì diff --git a/app/src/main/res/values-tg/strings.xml b/app/src/main/res/values-tg/strings.xml index ffeb619d1..77fa83df4 100644 --- a/app/src/main/res/values-tg/strings.xml +++ b/app/src/main/res/values-tg/strings.xml @@ -246,8 +246,14 @@ Баррасии махфӣ Кушодани пайвандҳо дар варақаи махфӣ + + Илова кардани миёнбури тамошои махфӣ Қобилияти дастрасӣ + + Сервери ҳисоби фармоишии Firefox + + Сервери ҳамоҳангсозии фармоишӣ Ҳисоб @@ -276,6 +282,8 @@ Огоҳиномаи махфият Абзорҳои барномасозӣ + + Ислоҳкунии дурдасти хатоҳо тавассути USB Намоиш додани низомҳои ҷустуҷӯӣ @@ -696,6 +704,8 @@ Огоҳинома + + Дархости иҷозат Бояд манъ карда шавад @@ -721,6 +731,30 @@ Маҷмӯаҳо Менюи маҷмӯаҳо + + Варақаҳоро интихоб намоед + + Интихоб кардани маҷмӯа + + Номи маҷмӯа + + Илова кардани маҷмӯаи нав + + Ҳамаро интихоб кардан + + Варақаҳоро барои нигоҳ доштан интихоб намоед + + %d варақа интихоб карда шуд + + %d варақа интихоб карда шуд + + Варақаҳо нигоҳ дошта шудад! + + Маҷмӯа нигоҳ дошта шуд! + + Варақа нигоҳ дошта шуд! Пӯшидан @@ -753,9 +787,16 @@ Барои ҳамоҳангсозӣ аз нав пайваст шавед Офлайн + + Пайваст кардани дастгоҳи дигар Фаҳмо + + Фиристодан ба дастгоҳ + + Ягон дастгоҳ пайваст нашуд + Ҷаласаи баррасии махфӣ @@ -835,6 +876,9 @@ Нест кардани маълумоти баррасӣ ҳангоми баромад + + Баромадан + Бекор кардан @@ -844,14 +888,29 @@ Хуш омадед ба %s! + + Бо %s шинос шавед + + Бинед, ки чӣ нав аст Маълумоти бештар + + Ҳа, маро ворид кунед + + Ворид шуда истодааст… + + Ба Firefox ворид шавед Ҳамоҳангсозӣ фаъол аст Воридшавӣ иҷро нашуд Махфияти худкор + + Стандартӣ (пешфарз) + + Реҷаи тамошои махфӣ Кушодани танзимот @@ -884,6 +943,8 @@ Ирсол ғайриимкон аст АЗ НАВ КӮШИШ КАРДАН + + Ворид шудан ба воситаи камера Қатъ кардани пайваст @@ -926,6 +987,10 @@ Хонандаи изи ангушт Муҳтавои пайгирикунанда + + Муҳофизат барои ин сомона фаъол аст + + Муҳофизат барои ин сомона ғайрифаъол аст Ҳуқуқҳои шумо @@ -1054,6 +1119,9 @@ Ном (А-Я) + + Истифодашудаи охирин + Илова кардани низоми ҷустуҷӯӣ @@ -1076,6 +1144,8 @@ Пайванди «Маълумоти бештар» + + Сатри ҷустуҷӯро ворид кунед %s эҷод карда шуд @@ -1083,6 +1153,8 @@ %s нест карда шуд + + %s нав шуда истодааст... Оғоз кардани %s @@ -1103,10 +1175,21 @@ Пайвасти боэътимод Пайвасти беэътимод + + Шумо мутмаин ҳастед, ки мехоҳед ҳамаи иҷозатҳоро дар ҳамаи сомонаҳо тоза намоед? + + Шумо мутмаин ҳастед, ки мехоҳед ҳамаи иҷозатҳоро барои сомонаи ҷорӣ тоза намоед? + + Шумо мутмаин ҳастед, ки мехоҳед ин иҷозатҳоро барои сомонаи ҷорӣ тоза намоед? Мақолаҳои беҳтарин + + Шумо мутмаин ҳастед, ки мехоҳед ин хатбаракро нест намоед? Илова кардан ба сомонаҳои беҳтарин + + Тасдиқ аз ҷониби: %1$s Нест кардан @@ -1142,6 +1225,8 @@ Лутфан, санҷиши ҳаққониятро аз нав такрор кунед Лутфан ҳамоҳангсозии варақаҳоро фаъол кунед. + + Барои ҳамоҳангсозӣ ворид шавед Ягон варақаи кушодашуда нест diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index c4ec4faf4..344d7a3a2 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -653,6 +653,10 @@ Delete %1$d items + + Today + + Yesterday Last 24 hours 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 a2d1f92c5..1d31eaf56 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, strictMode) } + override val analytics by lazy { Analytics(context) } override val clipboardHandler by lazy { ClipboardHandler(context) } 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 deleted file mode 100644 index ea0e90302..000000000 --- a/app/src/test/java/org/mozilla/fenix/components/metrics/LeanplumMetricsServiceTest.kt +++ /dev/null @@ -1,51 +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.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 -import org.junit.Test -import org.junit.runner.RunWith -import org.mozilla.fenix.ext.application -import org.mozilla.fenix.helpers.FenixRobolectricTestRunner - -@RunWith(FenixRobolectricTestRunner::class) -class LeanplumMetricsServiceTest { - @Test - fun `deviceId is only generated on first run`() { - var callCount = 0 - val idGenerator = { - callCount++ - "TEST_DEVICE_ID" - } - - val sharedPreferences = testContext.application.getSharedPreferences( - "LEANPLUM_PREFERENCES", - MODE_PRIVATE - ) - - assertNull(sharedPreferences.getString("LP_DEVICE_ID", null)) - - val leanplumMetricService = LeanplumMetricsService( - testContext.application, - mockk(relaxed = true), - idGenerator - ) - assertEquals("TEST_DEVICE_ID", leanplumMetricService.deviceId) - - val leanplumMetricService2 = LeanplumMetricsService( - testContext.application, - mockk(relaxed = true), - idGenerator - ) - assertEquals("TEST_DEVICE_ID", leanplumMetricService2.deviceId) - assertEquals(1, callCount) - - assertEquals("TEST_DEVICE_ID", sharedPreferences.getString("LP_DEVICE_ID", "")) - } -} diff --git a/app/src/test/java/org/mozilla/fenix/settings/SettingsFragmentTest.kt b/app/src/test/java/org/mozilla/fenix/settings/SettingsFragmentTest.kt index 8a6d2ad64..fb6500a76 100644 --- a/app/src/test/java/org/mozilla/fenix/settings/SettingsFragmentTest.kt +++ b/app/src/test/java/org/mozilla/fenix/settings/SettingsFragmentTest.kt @@ -8,20 +8,47 @@ import androidx.fragment.app.FragmentActivity import androidx.preference.Preference import io.mockk.every import io.mockk.mockk +import io.mockk.mockkObject +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.TestCoroutineDispatcher +import mozilla.components.support.test.robolectric.testContext +import mozilla.components.support.test.rule.MainCoroutineRule import org.junit.Assert.assertFalse import org.junit.Assert.assertNotNull import org.junit.Assert.assertTrue +import org.junit.Before +import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith +import org.mozilla.fenix.Config import org.mozilla.fenix.R +import org.mozilla.fenix.ReleaseChannel +import org.mozilla.fenix.ext.components import org.mozilla.fenix.ext.getPreferenceKey import org.mozilla.fenix.helpers.FenixRobolectricTestRunner import org.mozilla.fenix.utils.Settings import org.robolectric.Robolectric +import java.io.IOException +@ExperimentalCoroutinesApi @RunWith(FenixRobolectricTestRunner::class) class SettingsFragmentTest { + private val testDispatcher = TestCoroutineDispatcher() + + @get:Rule + val coroutinesTestRule = MainCoroutineRule(testDispatcher) + + @Before + fun setup() { + // Mock client for fetching account avatar + val client = testContext.components.core.client + every { client.fetch(any()) } throws IOException("test") + + mockkObject(Config) + every { Config.channel } returns ReleaseChannel.Nightly + } + @Test fun `Add-on collection override pref is visible if debug menu active`() { val settingsFragment = SettingsFragment() @@ -31,6 +58,8 @@ class SettingsFragmentTest { .add(settingsFragment, "test") .commitNow() + testDispatcher.advanceUntilIdle() + val preferenceAmoCollectionOverride = settingsFragment.findPreference( settingsFragment.getPreferenceKey(R.string.pref_key_override_amo_collection) ) @@ -54,6 +83,8 @@ class SettingsFragmentTest { .add(settingsFragment, "test") .commitNow() + testDispatcher.advanceUntilIdle() + val preferenceAmoCollectionOverride = settingsFragment.findPreference( settingsFragment.getPreferenceKey(R.string.pref_key_override_amo_collection) ) diff --git a/app/src/test/java/org/mozilla/fenix/settings/logins/SavedLoginsStorageControllerTest.kt b/app/src/test/java/org/mozilla/fenix/settings/logins/SavedLoginsStorageControllerTest.kt index d29acb3ed..e06dc6641 100644 --- a/app/src/test/java/org/mozilla/fenix/settings/logins/SavedLoginsStorageControllerTest.kt +++ b/app/src/test/java/org/mozilla/fenix/settings/logins/SavedLoginsStorageControllerTest.kt @@ -54,7 +54,7 @@ class SavedLoginsStorageControllerTest { controller = SavedLoginsStorageController( passwordsStorage = passwordsStorage, - viewLifecycleScope = scope, + lifecycleScope = scope, navController = navController, loginsFragmentStore = loginsFragmentStore, ioDispatcher = ioDispatcher diff --git a/build.gradle b/build.gradle index 2a04c2b0b..1d4706748 100644 --- a/build.gradle +++ b/build.gradle @@ -179,7 +179,6 @@ tasks.register('ktlint', JavaExec) { tasks.withType(io.gitlab.arturbosch.detekt.Detekt.class).configureEach { exclude("**/resources/**") - exclude("**/test/**") exclude("**/tmp/**") } diff --git a/buildSrc/src/main/java/AndroidComponents.kt b/buildSrc/src/main/java/AndroidComponents.kt index 0d7401dd0..f1fa81fce 100644 --- a/buildSrc/src/main/java/AndroidComponents.kt +++ b/buildSrc/src/main/java/AndroidComponents.kt @@ -3,5 +3,5 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ object AndroidComponents { - const val VERSION = "61.0.20200925190057" + const val VERSION = "62.0.20201002143132" } diff --git a/buildSrc/src/main/java/Dependencies.kt b/buildSrc/src/main/java/Dependencies.kt index f2a55f5b4..286107756 100644 --- a/buildSrc/src/main/java/Dependencies.kt +++ b/buildSrc/src/main/java/Dependencies.kt @@ -13,7 +13,7 @@ object Versions { const val detekt = "1.9.1" const val androidx_appcompat = "1.2.0-rc01" - const val androidx_biometric = "1.1.0-alpha01" + const val androidx_biometric = "1.1.0-beta01" const val androidx_coordinator_layout = "1.1.0-rc01" const val androidx_constraint_layout = "2.0.0" const val androidx_preference = "1.1.0" @@ -22,8 +22,8 @@ object Versions { const val androidx_lifecycle = "2.2.0" const val androidx_fragment = "1.2.5" const val androidx_navigation = "2.3.0" - const val androidx_recyclerview = "1.2.0-alpha05" - const val androidx_core = "1.2.0" + const val androidx_recyclerview = "1.2.0-alpha06" + const val androidx_core = "1.3.2" const val androidx_paging = "2.1.0" const val androidx_transition = "1.3.0" const val androidx_work = "2.2.0" diff --git a/buildSrc/src/main/java/org/mozilla/fenix/gradle/tasks/LintUnitTestRunner.kt b/buildSrc/src/main/java/org/mozilla/fenix/gradle/tasks/LintUnitTestRunner.kt deleted file mode 100644 index 0082d361f..000000000 --- a/buildSrc/src/main/java/org/mozilla/fenix/gradle/tasks/LintUnitTestRunner.kt +++ /dev/null @@ -1,108 +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.gradle.tasks - -import org.gradle.api.DefaultTask -import org.gradle.api.GradleException -import org.gradle.api.tasks.TaskAction -import org.gradle.api.tasks.TaskExecutionException -import java.io.File - -private val INVALID_TEST_RUNNERS = setOf( - // When updating this list, also update the violation message. - "AndroidJUnit4", - "RobolectricTestRunner" -) - -/** - * A lint check that ensures unit tests do not specify an invalid test runner. See the error message - * and the suggested replacement class' kdoc for details. - * - * Performance note: AS indicates this task takes 50ms-100ms to run. This isn't too concerning because - * it's one task and it only runs for unit test compilation. However, if we add additional lint checks, - * we should considering aggregating them - e.g. this task reads the unit test file tree and we can - * combine all of our tasks such that the file tree only needs to be read once - or finding more - * optimal solutions - e.g. only running on changed files. - */ -open class LintUnitTestRunner : DefaultTask() { - init { - group = "Verification" - description = "Ensures unit tests do not specify an invalid test runner" - attachToCompilationTasks() - } - - private fun attachToCompilationTasks() { - project.gradle.projectsEvaluated { project.tasks.let { tasks -> - // We make compile tasks, rather than assemble tasks, depend on us because some tools, - // such as AS' test runner, call the compile task directly rather than going through assemble. - val compileUnitTestTasks = tasks.filter { - it.name.startsWith("compile") && it.name.contains("UnitTest") - } - compileUnitTestTasks.forEach { it.dependsOn(this@LintUnitTestRunner) } - - // To return feedback as early as possible, we run before all compile tasks including - // compiling the application. - val compileAllTasks = tasks.filter { it.name.startsWith("compile") } - compileAllTasks.forEach { it.mustRunAfter(this@LintUnitTestRunner) } - } } - } - - @TaskAction - fun lint() { - val unitTestDir = File(project.projectDir, "/src/test") - check(unitTestDir.exists()) { - "Error in task impl: expected test directory - ${unitTestDir.absolutePath} - to exist" - } - - val unitTestDirFileWalk = unitTestDir.walk().onEnter { true /* enter all dirs */ }.asSequence() - val kotlinFileWalk = unitTestDirFileWalk.filter { it.name.endsWith(".kt") && it.isFile } - check(kotlinFileWalk.count() > 0) { "Error in task impl: expected to walk > 0 test files" } - - val violatingFiles = kotlinFileWalk.filter { file -> - file.useLines { lines -> lines.any(::isLineInViolation) } - }.sorted().toList() - - if (violatingFiles.isNotEmpty()) { - throwViolation(violatingFiles) - } - } - - private fun isLineInViolation(line: String): Boolean { - val trimmed = line.trimStart() - return INVALID_TEST_RUNNERS.any { invalid -> - trimmed.startsWith("@RunWith($invalid::class)") - } - } - - private fun throwViolation(files: List) { - val failureHeader = """Lint failure: saw unexpected unit test runners. The following code blocks: - | - | @RunWith(AndroidJUnit4::class) - | @Config(application = TestApplication::class) - |OR - | @RunWith(RobolectricTestRunner::class) - | @Config(application = TestApplication::class) - | - |should be replaced with: - | - | @RunWith(FenixRobolectricTestRunner::class) - | - |To reduce redundancy of setting @Config. No @Config specification is necessary because - |the FenixRobolectricTestRunner sets it automatically. - | - |Relatedly, adding robolectric to a test increases its runtime non-trivially so please - |ensure Robolectric is necessary before adding it. - | - |The following files were found to be in violation: - """.trimMargin() - - val filesInViolation = files.map { - " ${it.relativeTo(project.rootDir)}" - } - - val errorMsg = (listOf(failureHeader) + filesInViolation).joinToString("\n") - throw TaskExecutionException(this, GradleException(errorMsg)) - } -} diff --git a/config/detekt.yml b/config/detekt.yml index 2568a929e..180847b99 100644 --- a/config/detekt.yml +++ b/config/detekt.yml @@ -112,6 +112,8 @@ mozilla-detekt-rules: # with the debuggable flag or not. Use a check for different build variants, # instead. bannedProperties: "BuildConfig.DEBUG" + MozillaCorrectUnitTestRunner: + active: true empty-blocks: active: true diff --git a/mozilla-detekt-rules/src/main/java/org/mozilla/fenix/detektrules/CustomRulesetProvider.kt b/mozilla-detekt-rules/src/main/java/org/mozilla/fenix/detektrules/CustomRulesetProvider.kt index 7899a60b3..1a4c0f67e 100644 --- a/mozilla-detekt-rules/src/main/java/org/mozilla/fenix/detektrules/CustomRulesetProvider.kt +++ b/mozilla-detekt-rules/src/main/java/org/mozilla/fenix/detektrules/CustomRulesetProvider.kt @@ -14,7 +14,8 @@ class CustomRulesetProvider : RuleSetProvider { override fun instance(config: Config): RuleSet = RuleSet( ruleSetId, listOf( - MozillaBannedPropertyAccess(config) + MozillaBannedPropertyAccess(config), + MozillaCorrectUnitTestRunner(config) ) ) } diff --git a/mozilla-detekt-rules/src/main/java/org/mozilla/fenix/detektrules/MozillaCorrectUnitTestRunner.kt b/mozilla-detekt-rules/src/main/java/org/mozilla/fenix/detektrules/MozillaCorrectUnitTestRunner.kt new file mode 100644 index 000000000..25834076b --- /dev/null +++ b/mozilla-detekt-rules/src/main/java/org/mozilla/fenix/detektrules/MozillaCorrectUnitTestRunner.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.detektrules + +import io.gitlab.arturbosch.detekt.api.CodeSmell +import io.gitlab.arturbosch.detekt.api.Config +import io.gitlab.arturbosch.detekt.api.Debt +import io.gitlab.arturbosch.detekt.api.Entity +import io.gitlab.arturbosch.detekt.api.Issue +import io.gitlab.arturbosch.detekt.api.Rule +import io.gitlab.arturbosch.detekt.api.Severity +import io.gitlab.arturbosch.detekt.api.internal.PathFilters +import org.jetbrains.kotlin.psi.KtAnnotationEntry + +private val BANNED_TEST_RUNNERS = setOf( + // When updating this list, also update the violation message. + "AndroidJUnit4", + "RobolectricTestRunner" +) + +// There is a change to how we output violations in a different PR so the formatting for message +// might be weird but it should still be readable. +private val VIOLATION_MSG = """The following code blocks: + | + | @RunWith(AndroidJUnit4::class) + | @Config(application = TestApplication::class) + | OR + | @RunWith(RobolectricTestRunner::class) + | @Config(application = TestApplication::class) + | + | should be replaced with: + | + | @RunWith(FenixRobolectricTestRunner::class) + | + | To reduce redundancy of setting @Config. No @Config specification is necessary because + | the FenixRobolectricTestRunner sets it automatically. + | + | Relatedly, adding robolectric to a test increases its runtime non-trivially so please + | ensure Robolectric is necessary before adding it.""".trimMargin() + +/** + * Ensures we're using the correct Robolectric unit test runner. + */ +class MozillaCorrectUnitTestRunner(config: Config) : Rule(config) { + override val issue = Issue( + MozillaCorrectUnitTestRunner::class.simpleName!!, + Severity.Defect, + "Verifies we're using the correct Robolectric unit test runner", + Debt.FIVE_MINS + ) + + override val filters: PathFilters? + get() = PathFilters.of( + includes = listOf("**/test/**"), // unit tests only. + excludes = emptyList() + ) + + override fun visitAnnotationEntry(annotationEntry: KtAnnotationEntry) { + super.visitAnnotationEntry(annotationEntry) + + // Example of an annotation: @RunWith(FenixRobolectricUnitTestRunner::class) + val annotationClassName = annotationEntry.shortName?.identifier + if (annotationClassName == "RunWith") { + // Example arg in parens: "(FenixRobolectricUnitTestRunner::class)" + val argInParens = annotationEntry.lastChild.node.chars + val argClassName = argInParens.substring(1 until argInParens.indexOf(":")) + + val isInViolation = BANNED_TEST_RUNNERS.any { it == argClassName } + if (isInViolation) { + report(CodeSmell(issue, Entity.from(annotationEntry), VIOLATION_MSG)) + } + } + } +} diff --git a/taskcluster/ci/github-release/kind.yml b/taskcluster/ci/github-release/kind.yml index 8d7df3941..262061c66 100644 --- a/taskcluster/ci/github-release/kind.yml +++ b/taskcluster/ci/github-release/kind.yml @@ -10,6 +10,9 @@ transforms: - taskgraph.transforms.task:transforms kind-dependencies: + # To work around a race condition where if this runs before + # push-apk, push-apk fails to verify chain of trust + - push-apk - signing primary-dependency: signing